Home | History | Annotate | Download | only in refactoring
      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