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.AndroidPackageRenameChange;
     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.IPath;
     41 import org.eclipse.core.runtime.IProgressMonitor;
     42 import org.eclipse.core.runtime.NullProgressMonitor;
     43 import org.eclipse.core.runtime.OperationCanceledException;
     44 import org.eclipse.jdt.core.IJavaElement;
     45 import org.eclipse.jdt.core.IJavaProject;
     46 import org.eclipse.jdt.core.IPackageFragment;
     47 import org.eclipse.jdt.core.IType;
     48 import org.eclipse.jdt.core.JavaModelException;
     49 import org.eclipse.jdt.core.search.IJavaSearchConstants;
     50 import org.eclipse.jdt.core.search.IJavaSearchScope;
     51 import org.eclipse.jdt.core.search.SearchEngine;
     52 import org.eclipse.jdt.core.search.SearchMatch;
     53 import org.eclipse.jdt.core.search.SearchParticipant;
     54 import org.eclipse.jdt.core.search.SearchPattern;
     55 import org.eclipse.jdt.core.search.SearchRequestor;
     56 import org.eclipse.jdt.internal.corext.refactoring.changes.RenamePackageChange;
     57 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
     58 import org.eclipse.jface.text.IDocument;
     59 import org.eclipse.ltk.core.refactoring.Change;
     60 import org.eclipse.ltk.core.refactoring.CompositeChange;
     61 import org.eclipse.wst.sse.core.StructuredModelManager;
     62 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
     63 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
     64 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
     65 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
     66 import org.w3c.dom.Attr;
     67 import org.w3c.dom.NamedNodeMap;
     68 import org.w3c.dom.Node;
     69 import org.w3c.dom.NodeList;
     70 
     71 import java.util.ArrayList;
     72 import java.util.HashMap;
     73 import java.util.HashSet;
     74 import java.util.List;
     75 import java.util.Map;
     76 import java.util.Set;
     77 
     78 /**
     79  * A participant to participate in refactorings that rename a package in an Android project.
     80  * The class updates android manifest and the layout file
     81  * The user can suppress refactoring by disabling the "Update references" checkbox
     82  * <p>
     83  * Rename participants are registered via the extension point <code>
     84  * org.eclipse.ltk.core.refactoring.renameParticipants</code>.
     85  * Extensions to this extension point must therefore extend
     86  * <code>org.eclipse.ltk.core.refactoring.participants.RenameParticipant</code>.
     87  * </p>
     88  */
     89 @SuppressWarnings("restriction")
     90 public class AndroidPackageRenameParticipant extends AndroidRenameParticipant {
     91 
     92     private IPackageFragment mPackageFragment;
     93 
     94     private boolean mIsPackage;
     95 
     96     private Set<AndroidLayoutFileChanges> mFileChanges = new HashSet<AndroidLayoutFileChanges>();
     97 
     98     @Override
     99     public Change createChange(IProgressMonitor pm) throws CoreException,
    100             OperationCanceledException {
    101         if (pm.isCanceled()) {
    102             return null;
    103         }
    104         if (!getArguments().getUpdateReferences())
    105             return null;
    106         IPath pkgPath = mPackageFragment.getPath();
    107         IJavaProject javaProject = (IJavaProject) mPackageFragment
    108                 .getAncestor(IJavaElement.JAVA_PROJECT);
    109         IProject project = javaProject.getProject();
    110         IPath genPath = project.getFullPath().append(SdkConstants.FD_GEN_SOURCES);
    111         if (genPath.isPrefixOf(pkgPath)) {
    112             RefactoringUtil.logInfo(getName() + ": Cannot rename generated package.");
    113             return null;
    114         }
    115         CompositeChange result = new CompositeChange(getName());
    116         if (mAndroidManifest.exists()) {
    117             if (mAndroidElements.size() > 0 || mIsPackage) {
    118                 getDocument();
    119                 Change change = new AndroidPackageRenameChange(mAndroidManifest, mManager,
    120                         mDocument, mAndroidElements, mNewName, mOldName, mIsPackage);
    121                 if (change != null) {
    122                     result.add(change);
    123                 }
    124             }
    125             if (mIsPackage) {
    126                 Change genChange = getGenPackageChange(pm);
    127                 if (genChange != null) {
    128                     result.add(genChange);
    129                 }
    130             }
    131             // add layoutChange
    132             for (AndroidLayoutFileChanges fileChange : mFileChanges) {
    133                 IFile file = fileChange.getFile();
    134                 ITextFileBufferManager lManager = FileBuffers.getTextFileBufferManager();
    135                 lManager.connect(file.getFullPath(), LocationKind.NORMALIZE,
    136                         new NullProgressMonitor());
    137                 ITextFileBuffer buffer = lManager.getTextFileBuffer(file.getFullPath(),
    138                         LocationKind.NORMALIZE);
    139                 IDocument lDocument = buffer.getDocument();
    140                 Change layoutChange = new AndroidLayoutChange(file, lDocument, lManager,
    141                         fileChange.getChanges());
    142                 if (layoutChange != null) {
    143                     result.add(layoutChange);
    144                 }
    145             }
    146         }
    147         return (result.getChildren().length == 0) ? null : result;
    148     }
    149 
    150     /**
    151      * Returns Android gen package text change
    152      *
    153      * @param pm the progress monitor
    154      *
    155      * @return Android gen package text change
    156      * @throws CoreException
    157      * @throws OperationCanceledException
    158      */
    159     public Change getGenPackageChange(IProgressMonitor pm) throws CoreException,
    160             OperationCanceledException {
    161         if (mIsPackage) {
    162             IPackageFragment genJavaPackageFragment = getGenPackageFragment();
    163             if (genJavaPackageFragment != null && genJavaPackageFragment.exists()) {
    164                 return new RenamePackageChange(genJavaPackageFragment, mNewName, true);
    165             }
    166         }
    167         return null;
    168     }
    169 
    170     /**
    171      * Return the gen package fragment
    172      *
    173      */
    174     private IPackageFragment getGenPackageFragment() throws JavaModelException {
    175         IJavaProject javaProject = (IJavaProject) mPackageFragment
    176                 .getAncestor(IJavaElement.JAVA_PROJECT);
    177         if (javaProject != null && javaProject.isOpen()) {
    178             IProject project = javaProject.getProject();
    179             IFolder genFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES);
    180             if (genFolder.exists()) {
    181                 String javaPackagePath = mAppPackage.replace(".", "/");
    182                 IPath genJavaPackagePath = genFolder.getFullPath().append(javaPackagePath);
    183                 IPackageFragment genPackageFragment = javaProject
    184                         .findPackageFragment(genJavaPackagePath);
    185                 return genPackageFragment;
    186             }
    187         }
    188         return null;
    189     }
    190 
    191     @Override
    192     public String getName() {
    193         return "Android Package Rename";
    194     }
    195 
    196     @Override
    197     protected boolean initialize(final Object element) {
    198         mIsPackage = false;
    199         try {
    200             if (element instanceof IPackageFragment) {
    201                 mPackageFragment = (IPackageFragment) element;
    202                 if (!mPackageFragment.containsJavaResources())
    203                     return false;
    204                 IJavaProject javaProject = (IJavaProject) mPackageFragment
    205                         .getAncestor(IJavaElement.JAVA_PROJECT);
    206                 IProject project = javaProject.getProject();
    207                 IResource manifestResource = project.findMember(AdtConstants.WS_SEP
    208                         + SdkConstants.FN_ANDROID_MANIFEST_XML);
    209 
    210                 if (manifestResource == null || !manifestResource.exists()
    211                         || !(manifestResource instanceof IFile)) {
    212                     RefactoringUtil.logInfo("Invalid or missing the "
    213                             + SdkConstants.FN_ANDROID_MANIFEST_XML + " in the " + project.getName()
    214                             + " project.");
    215                     return false;
    216                 }
    217                 mAndroidManifest = (IFile) manifestResource;
    218                 String packageName = mPackageFragment.getElementName();
    219                 ManifestData manifestData;
    220                 manifestData = AndroidManifestHelper.parseForData(mAndroidManifest);
    221                 if (manifestData == null) {
    222                     return false;
    223                 }
    224                 mAppPackage = manifestData.getPackage();
    225                 mOldName = packageName;
    226                 mNewName = getArguments().getNewName();
    227                 if (mOldName == null || mNewName == null) {
    228                     return false;
    229                 }
    230 
    231                 if (RefactoringUtil.isRefactorAppPackage()
    232                         && mAppPackage != null
    233                         && mAppPackage.equals(packageName)) {
    234                     mIsPackage = true;
    235                 }
    236                 mAndroidElements = addAndroidElements();
    237                 try {
    238                     final IType type = javaProject.findType(SdkConstants.CLASS_VIEW);
    239                     SearchPattern pattern = SearchPattern.createPattern("*",
    240                             IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS,
    241                             SearchPattern.R_REGEXP_MATCH);
    242                     IJavaSearchScope scope =SearchEngine.createJavaSearchScope(
    243                             new IJavaElement[] { mPackageFragment });
    244                     final HashSet<IType> elements = new HashSet<IType>();
    245                     SearchRequestor requestor = new SearchRequestor() {
    246 
    247                         @Override
    248                         public void acceptSearchMatch(SearchMatch match) throws CoreException {
    249                             Object elem = match.getElement();
    250                             if (elem instanceof IType) {
    251                                 IType eType = (IType) elem;
    252                                 IType[] superTypes = JavaModelUtil.getAllSuperTypes(eType,
    253                                         new NullProgressMonitor());
    254                                 for (int i = 0; i < superTypes.length; i++) {
    255                                     if (superTypes[i].equals(type)) {
    256                                         elements.add(eType);
    257                                         break;
    258                                     }
    259                                 }
    260                             }
    261 
    262                         }
    263                     };
    264                     SearchEngine searchEngine = new SearchEngine();
    265                     searchEngine.search(pattern, new SearchParticipant[] {
    266                         SearchEngine.getDefaultSearchParticipant()
    267                     }, scope, requestor, null);
    268                     List<String> views = new ArrayList<String>();
    269                     for (IType elem : elements) {
    270                         views.add(elem.getFullyQualifiedName());
    271                     }
    272                     if (views.size() > 0) {
    273                         String[] classNames = views.toArray(new String[0]);
    274                         addLayoutChanges(project, classNames);
    275                     }
    276                 } catch (CoreException e) {
    277                     RefactoringUtil.log(e);
    278                 }
    279 
    280                 return mIsPackage || mAndroidElements.size() > 0 || mFileChanges.size() > 0;
    281             }
    282         } catch (JavaModelException ignore) {
    283         }
    284         return false;
    285     }
    286 
    287     /**
    288      * Adds layout changes for project
    289      *
    290      * @param project the Android project
    291      * @param classNames the layout classes
    292      */
    293     private void addLayoutChanges(IProject project, String[] classNames) {
    294         try {
    295             IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
    296             IResource[] layoutMembers = resFolder.members();
    297             for (int j = 0; j < layoutMembers.length; j++) {
    298                 IResource resource = layoutMembers[j];
    299                 if (resource instanceof IFolder
    300                         && resource.exists()
    301                         && resource.getName().startsWith(AndroidConstants.FD_RES_LAYOUT)) {
    302                     IFolder layoutFolder = (IFolder) resource;
    303                     IResource[] members = layoutFolder.members();
    304                     for (int i = 0; i < members.length; i++) {
    305                            IResource member = members[i];
    306                            if ((member instanceof IFile)
    307                                    && member.exists()
    308                                    && member.getName().endsWith(".xml")) { //$NON-NLS-1$
    309                                IFile file = (IFile) member;
    310                                Set<AndroidLayoutChangeDescription> changes =
    311                                    parse(file, classNames);
    312                                if (changes.size() > 0) {
    313                                    AndroidLayoutFileChanges fileChange =
    314                                        new AndroidLayoutFileChanges(file);
    315                                    fileChange.getChanges().addAll(changes);
    316                                    mFileChanges.add(fileChange);
    317                                }
    318                            }
    319                     }
    320                 }
    321             }
    322         } catch (CoreException e) {
    323             RefactoringUtil.log(e);
    324         }
    325     }
    326 
    327     /**
    328      * Searches the layout file for classes
    329      *
    330      * @param file the Android layout file
    331      * @param classNames the layout classes
    332      */
    333     private Set<AndroidLayoutChangeDescription> parse(IFile file, String[] classNames) {
    334         Set<AndroidLayoutChangeDescription> changes =
    335             new HashSet<AndroidLayoutChangeDescription>();
    336         ITextFileBufferManager lManager = null;
    337         try {
    338             lManager = FileBuffers.getTextFileBufferManager();
    339             lManager.connect(file.getFullPath(),
    340                     LocationKind.NORMALIZE, new NullProgressMonitor());
    341             ITextFileBuffer buffer = lManager.getTextFileBuffer(file.getFullPath(),
    342                     LocationKind.NORMALIZE);
    343             IDocument lDocument = buffer.getDocument();
    344             IStructuredModel model = null;
    345             try {
    346                 model = StructuredModelManager.getModelManager().
    347                     getExistingModelForRead(lDocument);
    348                 if (model == null) {
    349                     if (lDocument instanceof IStructuredDocument) {
    350                         IStructuredDocument structuredDocument = (IStructuredDocument) lDocument;
    351                         model = StructuredModelManager.getModelManager().getModelForRead(
    352                                 structuredDocument);
    353                     }
    354                 }
    355                 if (model != null) {
    356                     IDOMModel xmlModel = (IDOMModel) model;
    357                     IDOMDocument xmlDoc = xmlModel.getDocument();
    358                     NodeList nodes = xmlDoc.getElementsByTagName(LayoutConstants.VIEW);
    359                     for (int i = 0; i < nodes.getLength(); i++) {
    360                         Node node = nodes.item(i);
    361                         NamedNodeMap attributes = node.getAttributes();
    362                         if (attributes != null) {
    363                             Node attributeNode = attributes
    364                                     .getNamedItem(LayoutConstants.ATTR_CLASS);
    365                             if (attributeNode instanceof Attr) {
    366                                 Attr attribute = (Attr) attributeNode;
    367                                 String value = attribute.getValue();
    368                                 if (value != null) {
    369                                     for (int j = 0; j < classNames.length; j++) {
    370                                         String className = classNames[j];
    371                                         if (value.equals(className)) {
    372                                             String newClassName = getNewClassName(className);
    373                                             AndroidLayoutChangeDescription layoutChange =
    374                                                 new AndroidLayoutChangeDescription(
    375                                                     className, newClassName,
    376                                                     AndroidLayoutChangeDescription.VIEW_TYPE);
    377                                             changes.add(layoutChange);
    378                                         }
    379                                     }
    380                                 }
    381                             }
    382                         }
    383                     }
    384                     for (int j = 0; j < classNames.length; j++) {
    385                         String className = classNames[j];
    386                         nodes = xmlDoc.getElementsByTagName(className);
    387                         for (int i = 0; i < nodes.getLength(); i++) {
    388                             String newClassName = getNewClassName(className);
    389                             AndroidLayoutChangeDescription layoutChange =
    390                                 new AndroidLayoutChangeDescription(
    391                                     className, newClassName,
    392                                     AndroidLayoutChangeDescription.STANDALONE_TYPE);
    393                             changes.add(layoutChange);
    394                         }
    395                     }
    396                 }
    397             } finally {
    398                 if (model != null) {
    399                     model.releaseFromRead();
    400                 }
    401             }
    402 
    403         } catch (CoreException ignore) {
    404         } finally {
    405             if (lManager != null) {
    406                 try {
    407                     lManager.disconnect(file.getFullPath(), LocationKind.NORMALIZE,
    408                             new NullProgressMonitor());
    409                 } catch (CoreException ignore) {
    410                 }
    411             }
    412         }
    413         return changes;
    414     }
    415 
    416     /**
    417      * Returns the new class name
    418      *
    419      * @param className the class name
    420      * @return the new class name
    421      */
    422     private String getNewClassName(String className) {
    423         int lastDot = className.lastIndexOf("."); //$NON-NLS-1$
    424         if (lastDot < 0) {
    425             return mNewName;
    426         }
    427         String name = className.substring(lastDot, className.length());
    428         String newClassName = mNewName + name;
    429         return newClassName;
    430     }
    431 
    432     /**
    433      * Returns the elements (activity, receiver, service ...)
    434      * which have to be renamed
    435      *
    436      * @return the android elements
    437      */
    438     private Map<String, String> addAndroidElements() {
    439         Map<String, String> androidElements = new HashMap<String, String>();
    440 
    441         IDocument document;
    442         try {
    443             document = getDocument();
    444         } catch (CoreException e) {
    445             RefactoringUtil.log(e);
    446             if (mManager != null) {
    447                 try {
    448                     mManager.disconnect(mAndroidManifest.getFullPath(), LocationKind.NORMALIZE,
    449                             new NullProgressMonitor());
    450                 } catch (CoreException e1) {
    451                     RefactoringUtil.log(e1);
    452                 }
    453             }
    454             document = null;
    455             return androidElements;
    456         }
    457 
    458         IStructuredModel model = null;
    459         try {
    460             model = StructuredModelManager.getModelManager().getExistingModelForRead(document);
    461             if (model == null) {
    462                 if (document instanceof IStructuredDocument) {
    463                     IStructuredDocument structuredDocument = (IStructuredDocument) document;
    464                     model = StructuredModelManager.getModelManager().getModelForRead(
    465                             structuredDocument);
    466                 }
    467             }
    468             if (model != null) {
    469                 IDOMModel xmlModel = (IDOMModel) model;
    470                 IDOMDocument xmlDoc = xmlModel.getDocument();
    471                 add(xmlDoc, androidElements, AndroidManifest.NODE_ACTIVITY,
    472                         AndroidManifest.ATTRIBUTE_NAME);
    473                 add(xmlDoc, androidElements, AndroidManifest.NODE_APPLICATION,
    474                         AndroidManifest.ATTRIBUTE_NAME);
    475                 add(xmlDoc, androidElements, AndroidManifest.NODE_PROVIDER,
    476                         AndroidManifest.ATTRIBUTE_NAME);
    477                 add(xmlDoc, androidElements, AndroidManifest.NODE_RECEIVER,
    478                         AndroidManifest.ATTRIBUTE_NAME);
    479                 add(xmlDoc, androidElements, AndroidManifest.NODE_SERVICE,
    480                         AndroidManifest.ATTRIBUTE_NAME);
    481             }
    482         } finally {
    483             if (model != null) {
    484                 model.releaseFromRead();
    485             }
    486         }
    487 
    488         return androidElements;
    489     }
    490 
    491     /**
    492      * Adds the element  (activity, receiver, service ...) to the map
    493      *
    494      * @param xmlDoc the document
    495      * @param androidElements the map
    496      * @param element the element
    497      */
    498     private void add(IDOMDocument xmlDoc, Map<String, String> androidElements, String element,
    499             String argument) {
    500         NodeList nodes = xmlDoc.getElementsByTagName(element);
    501         for (int i = 0; i < nodes.getLength(); i++) {
    502             Node node = nodes.item(i);
    503             NamedNodeMap attributes = node.getAttributes();
    504             if (attributes != null) {
    505                 Attr attribute = RefactoringUtil.findAndroidAttributes(attributes, argument);
    506                 if (attribute != null) {
    507                     String value = attribute.getValue();
    508                     if (value != null) {
    509                         String fullName = AndroidManifest.combinePackageAndClassName(mAppPackage,
    510                                 value);
    511                         if (RefactoringUtil.isRefactorAppPackage()) {
    512                             if (fullName != null && fullName.startsWith(mAppPackage)) {
    513                                 boolean startWithDot = (value.charAt(0) == '.');
    514                                 boolean hasDot = (value.indexOf('.') != -1);
    515                                 if (!startWithDot && hasDot) {
    516                                     androidElements.put(element, value);
    517                                 }
    518                             }
    519                         } else {
    520                             if (fullName != null) {
    521                                 androidElements.put(element, value);
    522                             }
    523                         }
    524                     }
    525                 }
    526             }
    527         }
    528     }
    529 
    530 }
    531