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