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