1 /* 2 * Copyright (C) 2011 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 package com.android.ide.eclipse.adt.internal.editors.layout.refactoring; 17 18 import static com.android.SdkConstants.ANDROID_URI; 19 import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; 20 import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; 21 import static com.android.SdkConstants.EXT_XML; 22 23 import com.android.annotations.NonNull; 24 import com.android.annotations.VisibleForTesting; 25 import com.android.ide.common.xml.XmlFormatStyle; 26 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; 27 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; 28 29 import org.eclipse.core.resources.IFile; 30 import org.eclipse.core.runtime.CoreException; 31 import org.eclipse.core.runtime.IProgressMonitor; 32 import org.eclipse.core.runtime.OperationCanceledException; 33 import org.eclipse.jface.text.ITextSelection; 34 import org.eclipse.jface.viewers.ITreeSelection; 35 import org.eclipse.ltk.core.refactoring.Change; 36 import org.eclipse.ltk.core.refactoring.Refactoring; 37 import org.eclipse.ltk.core.refactoring.RefactoringStatus; 38 import org.eclipse.ltk.core.refactoring.TextFileChange; 39 import org.eclipse.text.edits.MultiTextEdit; 40 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; 41 import org.w3c.dom.Attr; 42 import org.w3c.dom.Document; 43 import org.w3c.dom.Element; 44 import org.w3c.dom.Node; 45 46 import java.util.ArrayList; 47 import java.util.Collections; 48 import java.util.List; 49 import java.util.Map; 50 51 /** 52 * Removes the layout surrounding the current selection (or if the current selection has 53 * children, removes the current layout), and migrates namespace and layout attributes. 54 */ 55 @SuppressWarnings("restriction") // XML model 56 public class UnwrapRefactoring extends VisualRefactoring { 57 private Element mContainer; 58 59 /** 60 * This constructor is solely used by {@link Descriptor}, 61 * to replay a previous refactoring. 62 * @param arguments argument map created by #createArgumentMap. 63 */ 64 UnwrapRefactoring(Map<String, String> arguments) { 65 super(arguments); 66 } 67 68 public UnwrapRefactoring( 69 IFile file, 70 LayoutEditorDelegate delegate, 71 ITextSelection selection, 72 ITreeSelection treeSelection) { 73 super(file, delegate, selection, treeSelection); 74 } 75 76 @VisibleForTesting 77 UnwrapRefactoring(List<Element> selectedElements, LayoutEditorDelegate editor) { 78 super(selectedElements, editor); 79 } 80 81 @Override 82 public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, 83 OperationCanceledException { 84 RefactoringStatus status = new RefactoringStatus(); 85 86 try { 87 pm.beginTask("Checking preconditions...", 6); 88 89 if (mSelectionStart == -1 || mSelectionEnd == -1) { 90 status.addFatalError("No selection to wrap"); 91 return status; 92 } 93 94 // Make sure that the selection all has the same parent? 95 if (mElements.size() == 0) { 96 status.addFatalError("Nothing to unwrap"); 97 return status; 98 } 99 100 Element first = mElements.get(0); 101 102 // Determine the element of the container to be removed. 103 // If you've selected a non-container, or you've selected multiple 104 // elements, then it's the parent which should be removed. Otherwise, 105 // it's the selection itself which represents the container. 106 boolean useParent = mElements.size() > 1; 107 if (!useParent) { 108 if (DomUtilities.getChildren(first).size() == 0) { 109 useParent = true; 110 } 111 } 112 Node parent = first.getParentNode(); 113 if (parent instanceof Document) { 114 mContainer = first; 115 List<Element> elements = DomUtilities.getChildren(mContainer); 116 if (elements.size() == 0) { 117 status.addFatalError( 118 "Cannot remove container when it has no children"); 119 return status; 120 } 121 } else if (useParent && (parent instanceof Element)) { 122 mContainer = (Element) parent; 123 } else { 124 mContainer = first; 125 } 126 127 for (Element element : mElements) { 128 if (element.getParentNode() != parent) { 129 status.addFatalError( 130 "All unwrapped elements must share the same parent element"); 131 return status; 132 } 133 } 134 135 // Ensure that if we are removing the root, that it has only one child 136 // such that there is a new single root 137 if (mContainer.getParentNode() instanceof Document) { 138 if (DomUtilities.getChildren(mContainer).size() > 1) { 139 status.addFatalError( 140 "Cannot remove root: it has more than one child " 141 + "which would result in multiple new roots"); 142 return status; 143 } 144 } 145 146 pm.worked(1); 147 return status; 148 149 } finally { 150 pm.done(); 151 } 152 } 153 154 @Override 155 protected VisualRefactoringDescriptor createDescriptor() { 156 String comment = getName(); 157 return new Descriptor( 158 mProject.getName(), //project 159 comment, //description 160 comment, //comment 161 createArgumentMap()); 162 } 163 164 @Override 165 public String getName() { 166 return "Remove Container"; 167 } 168 169 @Override 170 protected @NonNull List<Change> computeChanges(IProgressMonitor monitor) { 171 // (1) If the removed parent is the root container, transfer its 172 // namespace declarations 173 // (2) Remove the root element completely 174 // (3) Transfer layout attributes? 175 // (4) Check for Java R.file usages? 176 177 IFile file = mDelegate.getEditor().getInputFile(); 178 List<Change> changes = new ArrayList<Change>(); 179 if (file == null) { 180 return changes; 181 } 182 MultiTextEdit rootEdit = new MultiTextEdit(); 183 184 // Transfer namespace elements? 185 if (mContainer.getParentNode() instanceof Document) { 186 List<Element> elements = DomUtilities.getChildren(mContainer); 187 assert elements.size() == 1; 188 Element newRoot = elements.get(0); 189 190 List<Attr> declarations = findNamespaceAttributes(mContainer); 191 for (Attr attribute : declarations) { 192 if (attribute instanceof IndexedRegion) { 193 setAttribute(rootEdit, newRoot, attribute.getNamespaceURI(), 194 attribute.getPrefix(), attribute.getLocalName(), attribute.getValue()); 195 } 196 } 197 } 198 199 // Transfer layout_ attributes (other than width and height) 200 List<Element> children = DomUtilities.getChildren(mContainer); 201 if (children.size() == 1) { 202 List<Attr> layoutAttributes = findLayoutAttributes(mContainer); 203 for (Attr attribute : layoutAttributes) { 204 String name = attribute.getLocalName(); 205 if ((name.equals(ATTR_LAYOUT_WIDTH) || name.equals(ATTR_LAYOUT_HEIGHT)) 206 && ANDROID_URI.equals(attribute.getNamespaceURI())) { 207 // Already handled specially 208 continue; 209 } 210 } 211 } 212 213 // Remove the root 214 removeElementTags(rootEdit, mContainer, Collections.<Element>emptyList() /* skip */, 215 false /*changeIndentation*/); 216 217 MultiTextEdit formatted = reformat(rootEdit, XmlFormatStyle.LAYOUT); 218 if (formatted != null) { 219 rootEdit = formatted; 220 } 221 222 TextFileChange change = new TextFileChange(file.getName(), file); 223 change.setEdit(rootEdit); 224 change.setTextType(EXT_XML); 225 changes.add(change); 226 return changes; 227 } 228 229 @Override 230 public VisualRefactoringWizard createWizard() { 231 return new UnwrapWizard(this, mDelegate); 232 } 233 234 public static class Descriptor extends VisualRefactoringDescriptor { 235 public Descriptor(String project, String description, String comment, 236 Map<String, String> arguments) { 237 super("com.android.ide.eclipse.adt.refactoring.unwrap", //$NON-NLS-1$ 238 project, description, comment, arguments); 239 } 240 241 @Override 242 protected Refactoring createRefactoring(Map<String, String> args) { 243 return new UnwrapRefactoring(args); 244 } 245 } 246 } 247