Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.refactoring.core;
     18 
     19 import com.android.AndroidConstants;
     20 import com.android.ide.common.layout.LayoutConstants;
     21 import com.android.ide.eclipse.adt.AdtConstants;
     22 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
     23 import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChange;
     24 import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChangeDescription;
     25 import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutFileChanges;
     26 import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidTypeMoveChange;
     27 import com.android.sdklib.SdkConstants;
     28 import com.android.sdklib.xml.AndroidManifest;
     29 import com.android.sdklib.xml.ManifestData;
     30 
     31 import org.eclipse.core.filebuffers.FileBuffers;
     32 import org.eclipse.core.filebuffers.ITextFileBuffer;
     33 import org.eclipse.core.filebuffers.ITextFileBufferManager;
     34 import org.eclipse.core.filebuffers.LocationKind;
     35 import org.eclipse.core.resources.IFile;
     36 import org.eclipse.core.resources.IFolder;
     37 import org.eclipse.core.resources.IProject;
     38 import org.eclipse.core.resources.IResource;
     39 import org.eclipse.core.runtime.CoreException;
     40 import org.eclipse.core.runtime.IProgressMonitor;
     41 import org.eclipse.core.runtime.NullProgressMonitor;
     42 import org.eclipse.core.runtime.OperationCanceledException;
     43 import org.eclipse.jdt.core.IJavaElement;
     44 import org.eclipse.jdt.core.IJavaProject;
     45 import org.eclipse.jdt.core.IPackageFragment;
     46 import org.eclipse.jdt.core.IType;
     47 import org.eclipse.jdt.core.ITypeHierarchy;
     48 import org.eclipse.jdt.core.JavaModelException;
     49 import org.eclipse.jface.text.IDocument;
     50 import org.eclipse.ltk.core.refactoring.Change;
     51 import org.eclipse.ltk.core.refactoring.CompositeChange;
     52 import org.eclipse.ltk.core.refactoring.RefactoringStatus;
     53 import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
     54 import org.eclipse.ltk.core.refactoring.participants.MoveParticipant;
     55 import org.eclipse.wst.sse.core.StructuredModelManager;
     56 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
     57 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
     58 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
     59 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
     60 import org.w3c.dom.Attr;
     61 import org.w3c.dom.NamedNodeMap;
     62 import org.w3c.dom.Node;
     63 import org.w3c.dom.NodeList;
     64 
     65 import java.util.HashMap;
     66 import java.util.HashSet;
     67 import java.util.Map;
     68 import java.util.Set;
     69 
     70 /**
     71  * A participant to participate in refactorings that move a type in an Android project.
     72  * The class updates android manifest and the layout file
     73  * The user can suppress refactoring by disabling the "Update references" checkbox
     74  * <p>
     75  * Rename participants are registered via the extension point <code>
     76  * org.eclipse.ltk.core.refactoring.moveParticipants</code>.
     77  * Extensions to this extension point must therefore extend <code>org.eclipse.ltk.core.refactoring.participants.MoveParticipant</code>.
     78  * </p>
     79  */
     80 @SuppressWarnings("restriction")
     81 public class AndroidTypeMoveParticipant extends MoveParticipant {
     82 
     83     protected IFile mAndroidManifest;
     84 
     85     protected ITextFileBufferManager mManager;
     86 
     87     protected String mOldName;
     88 
     89     protected String mNewName;
     90 
     91     protected IDocument mDocument;
     92 
     93     protected String mJavaPackage;
     94 
     95     protected Map<String, String> mAndroidElements;
     96 
     97     private Set<AndroidLayoutFileChanges> mFileChanges = new HashSet<AndroidLayoutFileChanges>();
     98 
     99     @Override
    100     public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
    101             throws OperationCanceledException {
    102         return new RefactoringStatus();
    103     }
    104 
    105     @Override
    106     public Change createChange(IProgressMonitor pm) throws CoreException,
    107             OperationCanceledException {
    108         if (pm.isCanceled()) {
    109             return null;
    110         }
    111         if (!getArguments().getUpdateReferences())
    112             return null;
    113         CompositeChange result = new CompositeChange(getName());
    114         if (mAndroidManifest.exists()) {
    115             if (mAndroidElements.size() > 0) {
    116                 getDocument();
    117                 Change change = new AndroidTypeMoveChange(mAndroidManifest, mManager, mDocument,
    118                         mAndroidElements, mNewName, mOldName);
    119                 if (change != null) {
    120                     result.add(change);
    121                 }
    122             }
    123 
    124             for (AndroidLayoutFileChanges fileChange : mFileChanges) {
    125                 IFile file = fileChange.getFile();
    126                 ITextFileBufferManager lManager = FileBuffers.getTextFileBufferManager();
    127                 lManager.connect(file.getFullPath(), LocationKind.NORMALIZE,
    128                         new NullProgressMonitor());
    129                 ITextFileBuffer buffer = lManager.getTextFileBuffer(file.getFullPath(),
    130                         LocationKind.NORMALIZE);
    131                 IDocument lDocument = buffer.getDocument();
    132                 Change layoutChange = new AndroidLayoutChange(file, lDocument, lManager,
    133                         fileChange.getChanges());
    134                 if (layoutChange != null) {
    135                     result.add(layoutChange);
    136                 }
    137             }
    138         }
    139         return (result.getChildren().length == 0) ? null : result;
    140 
    141     }
    142 
    143     /**
    144      * @return the document
    145      * @throws CoreException
    146      */
    147     public IDocument getDocument() throws CoreException {
    148         if (mDocument == null) {
    149             mManager = FileBuffers.getTextFileBufferManager();
    150             mManager.connect(mAndroidManifest.getFullPath(), LocationKind.NORMALIZE,
    151                     new NullProgressMonitor());
    152             ITextFileBuffer buffer = mManager.getTextFileBuffer(mAndroidManifest.getFullPath(),
    153                     LocationKind.NORMALIZE);
    154             mDocument = buffer.getDocument();
    155         }
    156         return mDocument;
    157     }
    158 
    159     /**
    160      * @return the android manifest file
    161      */
    162     public IFile getAndroidManifest() {
    163         return mAndroidManifest;
    164     }
    165 
    166     @Override
    167     public String getName() {
    168         return "Android Type Move";
    169     }
    170 
    171     @Override
    172     protected boolean initialize(Object element) {
    173 
    174         if (element instanceof IType) {
    175             IType type = (IType) element;
    176             IJavaProject javaProject = (IJavaProject) type.getAncestor(IJavaElement.JAVA_PROJECT);
    177             IProject project = javaProject.getProject();
    178             IResource manifestResource = project.findMember(AdtConstants.WS_SEP
    179                     + SdkConstants.FN_ANDROID_MANIFEST_XML);
    180 
    181             if (manifestResource == null || !manifestResource.exists()
    182                     || !(manifestResource instanceof IFile)) {
    183                 RefactoringUtil.logInfo("Invalid or missing the "
    184                         + SdkConstants.FN_ANDROID_MANIFEST_XML + " in the " + project.getName()
    185                         + " project.");
    186                 return false;
    187             }
    188             mAndroidManifest = (IFile) manifestResource;
    189             ManifestData manifestData;
    190             manifestData = AndroidManifestHelper.parseForData(mAndroidManifest);
    191             if (manifestData == null) {
    192                 return false;
    193             }
    194             mJavaPackage = manifestData.getPackage();
    195             mOldName = type.getFullyQualifiedName();
    196             Object destination = getArguments().getDestination();
    197             if (destination instanceof IPackageFragment) {
    198                 IPackageFragment packageFragment = (IPackageFragment) destination;
    199                 mNewName = packageFragment.getElementName() + "." + type.getElementName();
    200             }
    201             if (mOldName == null || mNewName == null) {
    202                 return false;
    203             }
    204             mAndroidElements = addAndroidElements();
    205             try {
    206                 ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(null);
    207                 if (typeHierarchy == null) {
    208                     return false;
    209                 }
    210                 IType[] superTypes = typeHierarchy.getAllSuperclasses(type);
    211                 for (int i = 0; i < superTypes.length; i++) {
    212                     IType superType = superTypes[i];
    213                     String className = superType.getFullyQualifiedName();
    214                     if (className.equals(SdkConstants.CLASS_VIEW)) {
    215                         addLayoutChanges(project, type.getFullyQualifiedName());
    216                         break;
    217                     }
    218                 }
    219             } catch (JavaModelException ignore) {
    220             }
    221             return mAndroidElements.size() > 0 || mFileChanges.size() > 0;
    222         }
    223         return false;
    224     }
    225 
    226     /**
    227      * Adds layout changes for project
    228      *
    229      * @param project the Android project
    230      * @param className the layout classes
    231      *
    232      */
    233     private void addLayoutChanges(IProject project, String className) {
    234         try {
    235             IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
    236             IFolder layoutFolder = resFolder.getFolder(AndroidConstants.FD_RES_LAYOUT);
    237             IResource[] members = layoutFolder.members();
    238             for (int i = 0; i < members.length; i++) {
    239                 IResource member = members[i];
    240                 if ((member instanceof IFile) && member.exists()) {
    241                     IFile file = (IFile) member;
    242                     Set<AndroidLayoutChangeDescription> changes = parse(file, className);
    243                     if (changes.size() > 0) {
    244                         AndroidLayoutFileChanges fileChange = new AndroidLayoutFileChanges(file);
    245                         fileChange.getChanges().addAll(changes);
    246                         mFileChanges.add(fileChange);
    247                     }
    248                 }
    249             }
    250         } catch (CoreException e) {
    251             RefactoringUtil.log(e);
    252         }
    253     }
    254 
    255     /**
    256      * Searches the layout file for classes
    257      *
    258      * @param file the Android layout file
    259      * @param className the layout classes
    260      *
    261      */
    262     private Set<AndroidLayoutChangeDescription> parse(IFile file, String className) {
    263         Set<AndroidLayoutChangeDescription> changes = new HashSet<AndroidLayoutChangeDescription>();
    264         ITextFileBufferManager lManager = null;
    265         try {
    266             lManager = FileBuffers.getTextFileBufferManager();
    267             lManager.connect(file.getFullPath(), LocationKind.NORMALIZE, new NullProgressMonitor());
    268             ITextFileBuffer buffer = lManager.getTextFileBuffer(file.getFullPath(),
    269                     LocationKind.NORMALIZE);
    270             IDocument lDocument = buffer.getDocument();
    271             IStructuredModel model = null;
    272             try {
    273                 model = StructuredModelManager.getModelManager().getExistingModelForRead(lDocument);
    274                 if (model == null) {
    275                     if (lDocument instanceof IStructuredDocument) {
    276                         IStructuredDocument structuredDocument = (IStructuredDocument) lDocument;
    277                         model = StructuredModelManager.getModelManager().getModelForRead(
    278                                 structuredDocument);
    279                     }
    280                 }
    281                 if (model != null) {
    282                     IDOMModel xmlModel = (IDOMModel) model;
    283                     IDOMDocument xmlDoc = xmlModel.getDocument();
    284                     NodeList nodes = xmlDoc.getElementsByTagName(LayoutConstants.VIEW);
    285                     for (int i = 0; i < nodes.getLength(); i++) {
    286                         Node node = nodes.item(i);
    287                         NamedNodeMap attributes = node.getAttributes();
    288                         if (attributes != null) {
    289                             Node attributeNode =
    290                                 attributes.getNamedItem(LayoutConstants.ATTR_CLASS);
    291                             if (attributeNode instanceof Attr) {
    292                                 Attr attribute = (Attr) attributeNode;
    293                                 String value = attribute.getValue();
    294                                 if (value != null && value.equals(className)) {
    295                                     AndroidLayoutChangeDescription layoutChange =
    296                                         new AndroidLayoutChangeDescription(className, mNewName,
    297                                             AndroidLayoutChangeDescription.VIEW_TYPE);
    298                                     changes.add(layoutChange);
    299                                 }
    300                             }
    301                         }
    302                     }
    303                     nodes = xmlDoc.getElementsByTagName(className);
    304                     for (int i = 0; i < nodes.getLength(); i++) {
    305                         AndroidLayoutChangeDescription layoutChange =
    306                             new AndroidLayoutChangeDescription(className, mNewName,
    307                                     AndroidLayoutChangeDescription.STANDALONE_TYPE);
    308                         changes.add(layoutChange);
    309                     }
    310                 }
    311             } finally {
    312                 if (model != null) {
    313                     model.releaseFromRead();
    314                 }
    315             }
    316 
    317         } catch (CoreException ignore) {
    318         } finally {
    319             if (lManager != null) {
    320                 try {
    321                     lManager.disconnect(file.getFullPath(), LocationKind.NORMALIZE,
    322                             new NullProgressMonitor());
    323                 } catch (CoreException ignore) {
    324                 }
    325             }
    326         }
    327         return changes;
    328     }
    329 
    330     /**
    331      * Returns the elements (activity, receiver, service ...)
    332      * which have to be renamed
    333      *
    334      * @return the android elements
    335      */
    336     private Map<String, String> addAndroidElements() {
    337         Map<String, String> androidElements = new HashMap<String, String>();
    338 
    339         IDocument document;
    340         try {
    341             document = getDocument();
    342         } catch (CoreException e) {
    343             RefactoringUtil.log(e);
    344             if (mManager != null) {
    345                 try {
    346                     mManager.disconnect(mAndroidManifest.getFullPath(), LocationKind.NORMALIZE,
    347                             new NullProgressMonitor());
    348                 } catch (CoreException e1) {
    349                     RefactoringUtil.log(e1);
    350                 }
    351             }
    352             document = null;
    353             return androidElements;
    354         }
    355 
    356         IStructuredModel model = null;
    357         try {
    358             model = StructuredModelManager.getModelManager().getExistingModelForRead(document);
    359             if (model == null) {
    360                 if (document instanceof IStructuredDocument) {
    361                     IStructuredDocument structuredDocument = (IStructuredDocument) document;
    362                     model = StructuredModelManager.getModelManager().getModelForRead(
    363                             structuredDocument);
    364                 }
    365             }
    366             if (model != null) {
    367                 IDOMModel xmlModel = (IDOMModel) model;
    368                 IDOMDocument xmlDoc = xmlModel.getDocument();
    369                 add(xmlDoc, androidElements, AndroidManifest.NODE_ACTIVITY,
    370                         AndroidManifest.ATTRIBUTE_NAME);
    371                 add(xmlDoc, androidElements, AndroidManifest.NODE_APPLICATION,
    372                         AndroidManifest.ATTRIBUTE_NAME);
    373                 add(xmlDoc, androidElements, AndroidManifest.NODE_PROVIDER,
    374                         AndroidManifest.ATTRIBUTE_NAME);
    375                 add(xmlDoc, androidElements, AndroidManifest.NODE_RECEIVER,
    376                         AndroidManifest.ATTRIBUTE_NAME);
    377                 add(xmlDoc, androidElements, AndroidManifest.NODE_SERVICE,
    378                         AndroidManifest.ATTRIBUTE_NAME);
    379             }
    380         } finally {
    381             if (model != null) {
    382                 model.releaseFromRead();
    383             }
    384         }
    385 
    386         return androidElements;
    387     }
    388 
    389     /**
    390      * Adds the element  (activity, receiver, service ...) to the map
    391      *
    392      * @param xmlDoc the document
    393      * @param androidElements the map
    394      * @param element the element
    395      */
    396     private void add(IDOMDocument xmlDoc, Map<String, String> androidElements, String element,
    397             String argument) {
    398         NodeList nodes = xmlDoc.getElementsByTagName(element);
    399         for (int i = 0; i < nodes.getLength(); i++) {
    400             Node node = nodes.item(i);
    401             NamedNodeMap attributes = node.getAttributes();
    402             if (attributes != null) {
    403                 Attr attribute = RefactoringUtil.findAndroidAttributes(attributes, argument);
    404                 if (attribute != null) {
    405                     String value = attribute.getValue();
    406                     if (value != null) {
    407                         String fullName = AndroidManifest.combinePackageAndClassName(mJavaPackage,
    408                                 value);
    409                         if (fullName != null && fullName.equals(mOldName)) {
    410                             androidElements.put(element, value);
    411                         }
    412                     }
    413                 }
    414             }
    415         }
    416     }
    417 
    418 }
    419