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 17package com.android.ide.eclipse.adt.internal.refactorings.core; 18 19import static com.android.SdkConstants.ANDROID_URI; 20import static com.android.SdkConstants.ATTR_CLASS; 21import static com.android.SdkConstants.ATTR_CONTEXT; 22import static com.android.SdkConstants.ATTR_NAME; 23import static com.android.SdkConstants.DOT_XML; 24import static com.android.SdkConstants.EXT_XML; 25import static com.android.SdkConstants.TOOLS_URI; 26import static com.android.SdkConstants.VIEW_FRAGMENT; 27import static com.android.SdkConstants.VIEW_TAG; 28 29import com.android.SdkConstants; 30import com.android.annotations.NonNull; 31import com.android.ide.common.xml.ManifestData; 32import com.android.ide.eclipse.adt.AdtConstants; 33import com.android.ide.eclipse.adt.AdtPlugin; 34import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; 35import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 36import com.android.ide.eclipse.adt.internal.sdk.Sdk; 37import com.android.resources.ResourceFolderType; 38import com.android.utils.SdkUtils; 39 40import org.eclipse.core.resources.IFile; 41import org.eclipse.core.resources.IFolder; 42import org.eclipse.core.resources.IProject; 43import org.eclipse.core.resources.IResource; 44import org.eclipse.core.runtime.CoreException; 45import org.eclipse.core.runtime.IProgressMonitor; 46import org.eclipse.core.runtime.OperationCanceledException; 47import org.eclipse.jdt.core.IJavaElement; 48import org.eclipse.jdt.core.IJavaProject; 49import org.eclipse.jdt.core.IPackageFragment; 50import org.eclipse.jdt.core.IType; 51import org.eclipse.jdt.core.JavaModelException; 52import org.eclipse.ltk.core.refactoring.Change; 53import org.eclipse.ltk.core.refactoring.CompositeChange; 54import org.eclipse.ltk.core.refactoring.RefactoringStatus; 55import org.eclipse.ltk.core.refactoring.TextFileChange; 56import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; 57import org.eclipse.ltk.core.refactoring.participants.MoveParticipant; 58import org.eclipse.text.edits.MultiTextEdit; 59import org.eclipse.text.edits.ReplaceEdit; 60import org.eclipse.text.edits.TextEdit; 61import org.eclipse.wst.sse.core.StructuredModelManager; 62import org.eclipse.wst.sse.core.internal.provisional.IModelManager; 63import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; 64import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; 65import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; 66import org.w3c.dom.Attr; 67import org.w3c.dom.Element; 68import org.w3c.dom.NamedNodeMap; 69import org.w3c.dom.Node; 70import org.w3c.dom.NodeList; 71 72import java.io.IOException; 73import java.util.ArrayList; 74import java.util.Collection; 75import java.util.List; 76 77/** 78 * A participant to participate in refactorings that move a type in an Android project. 79 * The class updates android manifest and the layout file 80 * The user can suppress refactoring by disabling the "Update references" checkbox 81 * <p> 82 * Rename participants are registered via the extension point <code> 83 * org.eclipse.ltk.core.refactoring.moveParticipants</code>. 84 * Extensions to this extension point must therefore extend <code>org.eclipse.ltk.core.refactoring.participants.MoveParticipant</code>. 85 * </p> 86 */ 87@SuppressWarnings("restriction") 88public class AndroidTypeMoveParticipant extends MoveParticipant { 89 90 private IProject mProject; 91 protected IFile mManifestFile; 92 protected String mOldFqcn; 93 protected String mNewFqcn; 94 protected String mAppPackage; 95 96 @Override 97 public String getName() { 98 return "Android Type Move"; 99 } 100 101 @Override 102 public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) 103 throws OperationCanceledException { 104 return new RefactoringStatus(); 105 } 106 107 @Override 108 protected boolean initialize(Object element) { 109 if (element instanceof IType) { 110 IType type = (IType) element; 111 IJavaProject javaProject = (IJavaProject) type.getAncestor(IJavaElement.JAVA_PROJECT); 112 mProject = javaProject.getProject(); 113 IResource manifestResource = mProject.findMember(AdtConstants.WS_SEP 114 + SdkConstants.FN_ANDROID_MANIFEST_XML); 115 116 if (manifestResource == null || !manifestResource.exists() 117 || !(manifestResource instanceof IFile)) { 118 RefactoringUtil.logInfo("Invalid or missing the " 119 + SdkConstants.FN_ANDROID_MANIFEST_XML + " in the " + mProject.getName() 120 + " project."); 121 return false; 122 } 123 mManifestFile = (IFile) manifestResource; 124 ManifestData manifestData; 125 manifestData = AndroidManifestHelper.parseForData(mManifestFile); 126 if (manifestData == null) { 127 return false; 128 } 129 mAppPackage = manifestData.getPackage(); 130 mOldFqcn = type.getFullyQualifiedName(); 131 Object destination = getArguments().getDestination(); 132 if (destination instanceof IPackageFragment) { 133 IPackageFragment packageFragment = (IPackageFragment) destination; 134 mNewFqcn = packageFragment.getElementName() + "." + type.getElementName(); 135 } else if (destination instanceof IResource) { 136 try { 137 IPackageFragment[] fragments = javaProject.getPackageFragments(); 138 for (IPackageFragment fragment : fragments) { 139 IResource resource = fragment.getResource(); 140 if (resource.equals(destination)) { 141 mNewFqcn = fragment.getElementName() + '.' + type.getElementName(); 142 break; 143 } 144 } 145 } catch (JavaModelException e) { 146 // pass 147 } 148 } 149 return mOldFqcn != null && mNewFqcn != null; 150 } 151 152 return false; 153 } 154 155 @Override 156 public Change createChange(IProgressMonitor pm) throws CoreException, 157 OperationCanceledException { 158 if (pm.isCanceled()) { 159 return null; 160 } 161 if (!getArguments().getUpdateReferences()) { 162 return null; 163 } 164 CompositeChange result = new CompositeChange(getName()); 165 result.markAsSynthetic(); 166 167 addManifestFileChanges(result); 168 169 // Update layout files; we don't just need to react to custom view 170 // changes, we need to update fragment references and even tool:context activity 171 // references 172 addLayoutFileChanges(mProject, result); 173 174 // Also update in dependent projects 175 ProjectState projectState = Sdk.getProjectState(mProject); 176 if (projectState != null) { 177 Collection<ProjectState> parentProjects = projectState.getFullParentProjects(); 178 for (ProjectState parentProject : parentProjects) { 179 IProject project = parentProject.getProject(); 180 addLayoutFileChanges(project, result); 181 } 182 } 183 184 return (result.getChildren().length == 0) ? null : result; 185 } 186 187 private void addManifestFileChanges(CompositeChange result) { 188 addXmlFileChanges(mManifestFile, result, true); 189 } 190 191 private void addLayoutFileChanges(IProject project, CompositeChange result) { 192 try { 193 // Update references in XML resource files 194 IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES); 195 196 IResource[] folders = resFolder.members(); 197 for (IResource folder : folders) { 198 String folderName = folder.getName(); 199 ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName); 200 if (folderType != ResourceFolderType.LAYOUT) { 201 continue; 202 } 203 if (!(folder instanceof IFolder)) { 204 continue; 205 } 206 IResource[] files = ((IFolder) folder).members(); 207 for (int i = 0; i < files.length; i++) { 208 IResource member = files[i]; 209 if ((member instanceof IFile) && member.exists()) { 210 IFile file = (IFile) member; 211 String fileName = member.getName(); 212 213 if (SdkUtils.endsWith(fileName, DOT_XML)) { 214 addXmlFileChanges(file, result, false); 215 } 216 } 217 } 218 } 219 } catch (CoreException e) { 220 RefactoringUtil.log(e); 221 } 222 } 223 224 private boolean addXmlFileChanges(IFile file, CompositeChange changes, boolean isManifest) { 225 IModelManager modelManager = StructuredModelManager.getModelManager(); 226 IStructuredModel model = null; 227 try { 228 model = modelManager.getExistingModelForRead(file); 229 if (model == null) { 230 model = modelManager.getModelForRead(file); 231 } 232 if (model != null) { 233 IStructuredDocument document = model.getStructuredDocument(); 234 if (model instanceof IDOMModel) { 235 IDOMModel domModel = (IDOMModel) model; 236 Element root = domModel.getDocument().getDocumentElement(); 237 if (root != null) { 238 List<TextEdit> edits = new ArrayList<TextEdit>(); 239 if (isManifest) { 240 addManifestReplacements(edits, root, document); 241 } else { 242 addLayoutReplacements(edits, root, document); 243 } 244 if (!edits.isEmpty()) { 245 MultiTextEdit rootEdit = new MultiTextEdit(); 246 rootEdit.addChildren(edits.toArray(new TextEdit[edits.size()])); 247 TextFileChange change = new TextFileChange(file.getName(), file); 248 change.setTextType(EXT_XML); 249 change.setEdit(rootEdit); 250 changes.add(change); 251 } 252 } 253 } else { 254 return false; 255 } 256 } 257 258 return true; 259 } catch (IOException e) { 260 AdtPlugin.log(e, null); 261 } catch (CoreException e) { 262 AdtPlugin.log(e, null); 263 } finally { 264 if (model != null) { 265 model.releaseFromRead(); 266 } 267 } 268 269 return false; 270 } 271 272 private void addLayoutReplacements( 273 @NonNull List<TextEdit> edits, 274 @NonNull Element element, 275 @NonNull IStructuredDocument document) { 276 String tag = element.getTagName(); 277 if (tag.equals(mOldFqcn)) { 278 int start = RefactoringUtil.getTagNameRangeStart(element, document); 279 if (start != -1) { 280 int end = start + mOldFqcn.length(); 281 edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); 282 } 283 } else if (tag.equals(VIEW_TAG)) { 284 Attr classNode = element.getAttributeNode(ATTR_CLASS); 285 if (classNode != null && classNode.getValue().equals(mOldFqcn)) { 286 int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document); 287 if (start != -1) { 288 int end = start + mOldFqcn.length(); 289 edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); 290 } 291 } 292 } else if (tag.equals(VIEW_FRAGMENT)) { 293 Attr classNode = element.getAttributeNode(ATTR_CLASS); 294 if (classNode == null) { 295 classNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME); 296 } 297 if (classNode != null && classNode.getValue().equals(mOldFqcn)) { 298 int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document); 299 if (start != -1) { 300 int end = start + mOldFqcn.length(); 301 edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); 302 } 303 } 304 } else if (element.hasAttributeNS(TOOLS_URI, ATTR_CONTEXT)) { 305 Attr classNode = element.getAttributeNodeNS(TOOLS_URI, ATTR_CONTEXT); 306 if (classNode != null && classNode.getValue().equals(mOldFqcn)) { 307 int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document); 308 if (start != -1) { 309 int end = start + mOldFqcn.length(); 310 edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); 311 } 312 } 313 } 314 315 NodeList children = element.getChildNodes(); 316 for (int i = 0, n = children.getLength(); i < n; i++) { 317 Node child = children.item(i); 318 if (child.getNodeType() == Node.ELEMENT_NODE) { 319 addLayoutReplacements(edits, (Element) child, document); 320 } 321 } 322 } 323 324 private void addManifestReplacements( 325 @NonNull List<TextEdit> edits, 326 @NonNull Element element, 327 @NonNull IStructuredDocument document) { 328 NamedNodeMap attributes = element.getAttributes(); 329 for (int i = 0, n = attributes.getLength(); i < n; i++) { 330 Attr attr = (Attr) attributes.item(i); 331 if (!RefactoringUtil.isManifestClassAttribute(attr)) { 332 continue; 333 } 334 335 String value = attr.getValue(); 336 if (value.equals(mOldFqcn)) { 337 int start = RefactoringUtil.getAttributeValueRangeStart(attr, document); 338 if (start != -1) { 339 int end = start + mOldFqcn.length(); 340 edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); 341 } 342 } else if (value.startsWith(".")) { //$NON-NLS-1$ 343 String fqcn = mAppPackage + value; 344 if (fqcn.equals(mOldFqcn)) { 345 int start = RefactoringUtil.getAttributeValueRangeStart(attr, document); 346 if (start != -1) { 347 int end = start + value.length(); 348 edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); 349 } 350 } 351 } 352 } 353 354 NodeList children = element.getChildNodes(); 355 for (int i = 0, n = children.getLength(); i < n; i++) { 356 Node child = children.item(i); 357 if (child.getNodeType() == Node.ELEMENT_NODE) { 358 addManifestReplacements(edits, (Element) child, document); 359 } 360 } 361 } 362} 363