import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
@SuppressWarnings("restriction") // No replacement for restricted XML model yet
return generated;
}
+ /**
+ * Returns the element children of the given element
+ *
+ * @param element the parent element
+ * @return a list of child elements, possibly empty but never null
+ */
+ public static List<Element> getChildren(Element element) {
+ // Convenience to avoid lots of ugly DOM access casting
+ NodeList children = element.getChildNodes();
+ // An iterator would have been more natural (to directly drive the child list
+ // iteration) but iterators can't be used in enhanced for loops...
+ List<Element> result = new ArrayList<Element>(children.getLength());
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node node = children.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element child = (Element) node;
+ result.add(child);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns true iff the given elements are contiguous siblings
+ *
+ * @param elements the elements to be tested
+ * @return true if the elements are contiguous siblings with no gaps
+ */
+ public static boolean isContiguous(List<Element> elements) {
+ if (elements.size() > 1) {
+ // All elements must be siblings (e.g. same parent)
+ Node parent = elements.get(0).getParentNode();
+ if (!(parent instanceof Element)) {
+ return false;
+ }
+ for (Element node : elements) {
+ if (parent != node.getParentNode()) {
+ return false;
+ }
+ }
+
+ // Ensure that the siblings are contiguous; no gaps.
+ // If we've selected all the children of the parent then we don't need
+ // to look.
+ List<Element> siblings = DomUtilities.getChildren((Element) parent);
+ if (siblings.size() != elements.size()) {
+ Set<Element> nodeSet = new HashSet<Element>(elements);
+ boolean inRange = false;
+ int remaining = elements.size();
+ for (Element node : siblings) {
+ boolean in = nodeSet.contains(node);
+ if (in) {
+ remaining--;
+ if (remaining == 0) {
+ break;
+ }
+ inRange = true;
+ } else if (inRange) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines whether two element trees are equivalent. Two element trees are
+ * equivalent if they represent the same DOM structure (elements, attributes, and
+ * children in order). This is almost the same as simply checking whether the String
+ * representations of the two nodes are identical, but this allows for minor
+ * variations that are not semantically significant, such as variations in formatting
+ * or ordering of the element attribute declarations, and the text children are
+ * ignored (this is such that in for example layout where content is only used for
+ * indentation the indentation differences are ignored). Null trees are never equal.
+ *
+ * @param element1 the first element to compare
+ * @param element2 the second element to compare
+ * @return true if the two element hierarchies are logically equal
+ */
+ public static boolean isEquivalent(Element element1, Element element2) {
+ if (element1 == null || element2 == null) {
+ return false;
+ }
+
+ if (!element1.getTagName().equals(element2.getTagName())) {
+ return false;
+ }
+
+ // Check attribute map
+ NamedNodeMap attributes1 = element1.getAttributes();
+ NamedNodeMap attributes2 = element2.getAttributes();
+ if (attributes1.getLength() != attributes2.getLength()) {
+ return false;
+ }
+ if (attributes1.getLength() > 0) {
+ List<Attr> attributeNodes1 = new ArrayList<Attr>();
+ for (int i = 0, n = attributes1.getLength(); i < n; i++) {
+ attributeNodes1.add((Attr) attributes1.item(i));
+ }
+ List<Attr> attributeNodes2 = new ArrayList<Attr>();
+ for (int i = 0, n = attributes2.getLength(); i < n; i++) {
+ attributeNodes2.add((Attr) attributes2.item(i));
+ }
+ Collections.sort(attributeNodes1, ATTRIBUTE_COMPARATOR);
+ Collections.sort(attributeNodes2, ATTRIBUTE_COMPARATOR);
+ for (int i = 0; i < attributeNodes1.size(); i++) {
+ Attr attr1 = attributeNodes1.get(i);
+ Attr attr2 = attributeNodes2.get(i);
+ if (attr1.getLocalName() == null || attr2.getLocalName() == null) {
+ if (!attr1.getName().equals(attr2.getName())) {
+ return false;
+ }
+ } else if (!attr1.getLocalName().equals(attr2.getLocalName())) {
+ return false;
+ }
+ if (!attr1.getValue().equals(attr2.getValue())) {
+ return false;
+ }
+ if (attr1.getNamespaceURI() == null) {
+ if (attr2.getNamespaceURI() != null) {
+ return false;
+ }
+ } else if (attr2.getNamespaceURI() == null) {
+ return false;
+ } else if (!attr1.getNamespaceURI().equals(attr2.getNamespaceURI())) {
+ return false;
+ }
+ }
+ }
+
+ NodeList children1 = element1.getChildNodes();
+ NodeList children2 = element2.getChildNodes();
+ int nextIndex1 = 0;
+ int nextIndex2 = 0;
+ while (true) {
+ while (nextIndex1 < children1.getLength() &&
+ children1.item(nextIndex1).getNodeType() != Node.ELEMENT_NODE) {
+ nextIndex1++;
+ }
+
+ while (nextIndex2 < children2.getLength() &&
+ children2.item(nextIndex2).getNodeType() != Node.ELEMENT_NODE) {
+ nextIndex2++;
+ }
+
+ Element nextElement1 = (Element) (nextIndex1 < children1.getLength()
+ ? children1.item(nextIndex1) : null);
+ Element nextElement2 = (Element) (nextIndex2 < children2.getLength()
+ ? children2.item(nextIndex2) : null);
+ if (nextElement1 == null) {
+ if (nextElement2 == null) {
+ return true;
+ } else {
+ return false;
+ }
+ } else if (nextElement2 == null) {
+ return false;
+ } else if (!isEquivalent(nextElement1, nextElement2)) {
+ return false;
+ }
+ nextIndex1++;
+ nextIndex2++;
+ }
+ }
+
+ /**
+ * Finds the corresponding element in a document to a given element in another
+ * document. Note that this does <b>not</b> do any kind of equivalence check
+ * (see {@link #isEquivalent(Element, Element)}), and currently the search
+ * is only by id; there is no structural search.
+ *
+ * @param element the element to find an equivalent for
+ * @param document the document to search for an equivalent element in
+ * @return an equivalent element, or null
+ */
+ public static Element findCorresponding(Element element, Document document) {
+ // Make sure the method is called correctly -- the element is for a different
+ // document than the one we are searching
+ assert element.getOwnerDocument() != document;
+
+ // First search by id. This allows us to find the corresponding
+ String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if (id != null && id.length() > 0) {
+ if (id.startsWith(ID_PREFIX)) {
+ id = NEW_ID_PREFIX + id.substring(ID_PREFIX.length());
+ }
+
+ return findCorresponding(document.getDocumentElement(), id);
+ }
+
+ // TODO: Search by structure - look in the document and
+ // find a corresponding element in the same location in the structure,
+ // e.g. 4th child of root, 3rd child, 6th child, then pick node with tag "foo".
+
+ return null;
+ }
+
+ /** Helper method for {@link #findCorresponding(Element, Document)} */
+ private static Element findCorresponding(Element element, String targetId) {
+ String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if (id != null) { // Work around DOM bug
+ if (id.equals(targetId)) {
+ return element;
+ } else if (id.startsWith(ID_PREFIX)) {
+ id = NEW_ID_PREFIX + id.substring(ID_PREFIX.length());
+ if (id.equals(targetId)) {
+ return element;
+ }
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node node = children.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element child = (Element) node;
+ Element match = findCorresponding(child, targetId);
+ if (match != null) {
+ return match;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /** Can be used to sort attributes by name */
+ private static final Comparator<Attr> ATTRIBUTE_COMPARATOR = new Comparator<Attr>() {
+ public int compare(Attr a1, Attr a2) {
+ return a1.getName().compareTo(a2.getName());
+ }
+ };
}
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
}
}
- ensureIdMatchesType(layout, mTypeFqcn, rootEdit);
+ String oldId = getId(layout);
+ String newId = ensureIdMatchesType(layout, mTypeFqcn, rootEdit);
+ // Update any layout references to the old id with the new id
+ if (oldId != null && newId != null) {
+ IStructuredModel model = mEditor.getModelForRead();
+ try {
+ IStructuredDocument doc = model.getStructuredDocument();
+ if (doc != null) {
+ List<TextEdit> replaceIds = replaceIds(getAndroidNamespacePrefix(), doc,
+ mSelectionStart,
+ mSelectionEnd, oldId, newId);
+ for (TextEdit edit : replaceIds) {
+ rootEdit.addChild(edit);
+ }
+ }
+ } finally {
+ model.releaseFromRead();
+ }
+ }
String oldType = getOldType();
String newType = mTypeFqcn;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.util.Pair;
import org.eclipse.core.resources.IProject;
-import org.eclipse.ltk.ui.refactoring.UserInputWizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
}
/** Wizard page which inputs parameters for the {@link ChangeLayoutRefactoring} operation */
- private static class InputPage extends UserInputWizardPage {
+ private static class InputPage extends VisualRefactoringInputPage {
private final IProject mProject;
private final String mOldType;
private Combo mTypeCombo;
private Button mFlatten;
- private List<String> mClassNames;
+ private List<Pair<String, ViewElementDescriptor>> mClassNames;
public InputPage(IProject project, String oldType) {
super("ChangeLayoutInputPage"); //$NON-NLS-1$
}
};
mTypeCombo.addSelectionListener(selectionListener);
+ mTypeCombo.addSelectionListener(mSelectionValidateListener);
mFlatten = new Button(composite, SWT.CHECK);
mFlatten.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER,
mFlatten.addSelectionListener(selectionListener);
// Should flattening be selected by default?
mFlatten.setSelection(true);
+ mFlatten.addSelectionListener(mSelectionValidateListener);
// We don't exclude RelativeLayout even if the current layout is RelativeLayout,
// in case you are trying to flatten the hierarchy for a hierarchy that has a
validatePage();
}
- private boolean validatePage() {
+ @Override
+ protected boolean validatePage() {
boolean ok = true;
int selectionIndex = mTypeCombo.getSelectionIndex();
- String type = selectionIndex != -1 ? mClassNames.get(selectionIndex) : null;
+ String type = selectionIndex != -1 ? mClassNames.get(selectionIndex).getFirst() : null;
if (type == null) {
setErrorMessage("Select a layout type");
ok = false; // The user has chosen a separator
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
}
// Change tag type
- ensureIdMatchesType(element, mTypeFqcn, rootEdit);
+ String oldId = getId(element);
+ String newId = ensureIdMatchesType(element, mTypeFqcn, rootEdit);
+ // Update any layout references to the old id with the new id
+ if (oldId != null && newId != null) {
+ IStructuredModel model = mEditor.getModelForRead();
+ try {
+ IStructuredDocument doc = model.getStructuredDocument();
+ if (doc != null) {
+ IndexedRegion range = getRegion(element);
+ int skipStart = range.getStartOffset();
+ int skipEnd = range.getEndOffset();
+ List<TextEdit> replaceIds = replaceIds(getAndroidNamespacePrefix(), doc,
+ skipStart, skipEnd,
+ oldId, newId);
+ for (TextEdit edit : replaceIds) {
+ rootEdit.addChild(edit);
+ }
+ }
+ } finally {
+ model.releaseFromRead();
+ }
+ }
// Strip out attributes that no longer make sense
removeUndefinedAttrs(rootEdit, element);
import com.android.util.Pair;
import org.eclipse.core.resources.IProject;
-import org.eclipse.ltk.ui.refactoring.UserInputWizardPage;
import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
}
/** Wizard page which inputs parameters for the {@link ChangeViewRefactoring} operation */
- private static class InputPage extends UserInputWizardPage {
+ private static class InputPage extends VisualRefactoringInputPage {
private final IProject mProject;
private Combo mTypeCombo;
private final String mOldType;
mTypeCombo = new Combo(composite, SWT.READ_ONLY);
mTypeCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
- SelectionAdapter selectionListener = new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- validatePage();
- }
- };
- mTypeCombo.addSelectionListener(selectionListener);
+ mTypeCombo.addSelectionListener(mSelectionValidateListener);
mClassNames = getWidgetTypes(mOldType, mTypeCombo);
mTypeCombo.select(0);
return classNames;
}
- private boolean validatePage() {
+ @Override
+ protected boolean validatePage() {
boolean ok = true;
int selectionIndex = mTypeCombo.getSelectionIndex();
String type = selectionIndex != -1 ? mClassNames.get(selectionIndex) : null;
*/
package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
+import static com.android.AndroidConstants.FD_RES_LAYOUT;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS;
import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_COLON;
import static com.android.resources.ResourceType.LAYOUT;
+import static com.android.sdklib.SdkConstants.FD_RES;
import com.android.annotations.VisibleForTesting;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
-import com.android.util.Pair;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ExtractIncludeRefactoring extends VisualRefactoring {
private static final String KEY_NAME = "name"; //$NON-NLS-1$
private static final String KEY_OCCURRENCES = "all-occurrences"; //$NON-NLS-1$
- private static final String KEY_UPDATE_REFS = "update-refs"; //$NON-NLS-1$
private String mLayoutName;
private boolean mReplaceOccurrences;
- private boolean mUpdateReferences;
/**
* This constructor is solely used by {@link Descriptor},
ExtractIncludeRefactoring(Map<String, String> arguments) {
super(arguments);
mLayoutName = arguments.get(KEY_NAME);
- mUpdateReferences = Boolean.parseBoolean(arguments.get(KEY_UPDATE_REFS));
mReplaceOccurrences = Boolean.parseBoolean(arguments.get(KEY_OCCURRENCES));
}
protected Map<String, String> createArgumentMap() {
Map<String, String> args = super.createArgumentMap();
args.put(KEY_NAME, mLayoutName);
- args.put(KEY_UPDATE_REFS, Boolean.toString(mUpdateReferences));
args.put(KEY_OCCURRENCES, Boolean.toString(mReplaceOccurrences));
return args;
mLayoutName = layoutName;
}
- void setUpdateReferences(boolean selection) {
- mUpdateReferences = selection;
- }
-
void setReplaceOccurrences(boolean selection) {
mReplaceOccurrences = selection;
}
protected List<Change> computeChanges() {
String extractedText = getExtractedText();
- Pair<String, String> namespace = computeNamespaces();
- String androidNsPrefix = namespace.getFirst();
- String namespaceDeclarations = namespace.getSecond();
+ String namespaceDeclarations = computeNamespaceDeclarations();
// Insert namespace:
extractedText = insertNamespace(extractedText, namespaceDeclarations);
IProject project = mEditor.getProject();
IFile sourceFile = mEditor.getInputFile();
+ // Replace extracted elements by <include> tag
+ handleIncludingFile(changes, sourceFile, mSelectionStart, mSelectionEnd,
+ getDomDocument(), getPrimaryElement());
+
+ // Also extract in other variations of the same file (landscape/portrait, etc)
+ boolean haveVariations = false;
+ if (mReplaceOccurrences) {
+ //String id = primary.getAttributeNS(ANDROID_URI, ATTR_ID);
+ List<IFile> variations = getConfigurationVariations(sourceFile);
+ for (IFile variation : variations) {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ IStructuredModel model = null;
+ try {
+ model = modelManager.getModelForRead(variation);
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ IDOMDocument otherDocument = domModel.getDocument();
+ List<Element> otherElements = new ArrayList<Element>();
+ Element otherPrimary = null;
+
+ for (Element element : getElements()) {
+ Element other = DomUtilities.findCorresponding(element,
+ otherDocument);
+ if (other != null) {
+ // See if the structure is similar to what we have in this
+ // document
+ if (DomUtilities.isEquivalent(element, other)) {
+ otherElements.add(other);
+ if (element == getPrimaryElement()) {
+ otherPrimary = other;
+ }
+ }
+ }
+ }
+
+ // Only perform extract in the other file if we find a match for
+ // ALL of elements being extracted, and if they too are contiguous
+ if (otherElements.size() == getElements().size() &&
+ DomUtilities.isContiguous(otherElements)) {
+ // Find the range
+ int begin = Integer.MAX_VALUE;
+ int end = Integer.MIN_VALUE;
+ for (Element element : otherElements) {
+ // Yes!! Extract this one as well!
+ IndexedRegion region = getRegion(element);
+ end = Math.max(end, region.getEndOffset());
+ begin = Math.min(begin, region.getStartOffset());
+ }
+ handleIncludingFile(changes, variation, begin,
+ end, otherDocument, otherPrimary);
+ haveVariations = true;
+ }
+ }
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ if (model != null) {
+ model.releaseFromRead();
+ }
+ }
+ }
+ }
+
+ // Add change to create the new file
+ IContainer parent = sourceFile.getParent();
+ if (haveVariations) {
+ // If we're extracting from multiple configuration folders, then we need to
+ // place the extracted include in the base layout folder (if not it goes next to
+ // the including file)
+ parent = mProject.getFolder(FD_RES).getFolder(FD_RES_LAYOUT);
+ }
+ IPath parentPath = parent.getProjectRelativePath();
+ final IFile file = project.getFile(new Path(parentPath + WS_SEP + newFileName));
+ TextFileChange addFile = new TextFileChange("Create new separate layout", file);
+ addFile.setTextType(AdtConstants.EXT_XML);
+ changes.add(addFile);
+ addFile.setEdit(new InsertEdit(0, sb.toString()));
+
+ Change finishHook = createFinishHook(file);
+ changes.add(finishHook);
+
+ return changes;
+ }
+
+ private void handleIncludingFile(List<Change> changes,
+ IFile sourceFile, int begin, int end, Document document, Element primary) {
TextFileChange change = new TextFileChange(sourceFile.getName(), sourceFile);
MultiTextEdit rootEdit = new MultiTextEdit();
change.setEdit(rootEdit);
String referenceId = getReferenceId();
// Replace existing elements in the source file and insert <include>
- String include = computeIncludeString(mLayoutName, androidNsPrefix, referenceId);
- int length = mSelectionEnd - mSelectionStart;
- ReplaceEdit replace = new ReplaceEdit(mSelectionStart, length, include);
+ String androidNsPrefix = getAndroidNamespacePrefix(document);
+ String include = computeIncludeString(primary, mLayoutName, androidNsPrefix, referenceId);
+ int length = end - begin;
+ ReplaceEdit replace = new ReplaceEdit(begin, length, include);
rootEdit.addChild(replace);
// Update any layout references to the old id with the new id
- if (mUpdateReferences && referenceId != null) {
- String rootId = getRootId();
- IStructuredModel model = mEditor.getModelForRead();
+ if (referenceId != null && primary != null) {
+ String rootId = getId(primary);
+ IStructuredModel model = null;
try {
+ model = StructuredModelManager.getModelManager().getModelForRead(sourceFile);
IStructuredDocument doc = model.getStructuredDocument();
- if (doc != null) {
- List<TextEdit> replaceIds = replaceIds(doc, mSelectionStart,
- mSelectionEnd, rootId, referenceId);
+ if (doc != null && rootId != null) {
+ List<TextEdit> replaceIds = replaceIds(androidNsPrefix, doc, begin,
+ end, rootId, referenceId);
for (TextEdit edit : replaceIds) {
rootEdit.addChild(edit);
}
}
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
} finally {
- model.releaseFromRead();
+ if (model != null) {
+ model.releaseFromRead();
+ }
}
}
+ }
- // Add change to create the new file
- IContainer parent = sourceFile.getParent();
- IPath parentPath = parent.getProjectRelativePath();
- final IFile file = project.getFile(new Path(parentPath + WS_SEP + newFileName));
- TextFileChange addFile = new TextFileChange("Create new separate layout", file);
- addFile.setTextType(AdtConstants.EXT_XML);
- changes.add(addFile);
- addFile.setEdit(new InsertEdit(0, sb.toString()));
+ /**
+ * This method returns all the configuration variations of the given layout. For
+ * example, if you have both layout/foo.xml and layout-land/foo.xml and
+ * layout-xlarge/foo.xml, then calling this method on any of the three files will
+ * return the other two.
+ *
+ * @param file the file to find configuration variations of
+ * @return the other layout variations of the file
+ */
+ public static List<IFile> getConfigurationVariations(IFile file) {
+ List<IFile> variations = new ArrayList<IFile>();
- Change finishHook = createFinishHook(file);
- changes.add(finishHook);
+ // This currently just searches the layout folders in the project for an exact
+ // resource name match. This could later use ProjectResources instead, but
+ // currently that's not done since it doesn't work from the tests.
- return changes;
+ String name = file.getName();
+ IContainer resFolder = file.getParent().getParent();
+ try {
+ for (IResource member : resFolder.members()) {
+ if (member.getName().startsWith(FD_RES_LAYOUT)) {
+ if (member instanceof IContainer) {
+ IContainer container = (IContainer) member;
+ IResource alternative = container.findMember(name);
+ IPath relPath = file.getProjectRelativePath();
+ if (alternative instanceof IFile
+ && !alternative.getProjectRelativePath().equals(relPath)) {
+ variations.add((IFile) alternative);
+ }
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return variations;
}
String getInitialName() {
return defaultName;
}
+ IFile getSourceFile() {
+ return mFile;
+ }
+
private Change createFinishHook(final IFile file) {
return new NullChange("Open extracted layout and refresh resources") {
@Override
};
}
- private Pair<String, String> computeNamespaces() {
+ private String computeNamespaceDeclarations() {
String androidNsPrefix = null;
String namespaceDeclarations = null;
namespaceDeclarations = sb.toString();
}
- return Pair.of(androidNsPrefix, namespaceDeclarations);
+ return namespaceDeclarations;
}
/** Returns the id to be used for the include tag itself (may be null) */
* Compute the actual {@code <include>} string to be inserted in place of the old
* selection
*/
- private String computeIncludeString(String newName, String androidNsPrefix,
- String referenceId) {
+ private static String computeIncludeString(Element primaryNode, String newName,
+ String androidNsPrefix, String referenceId) {
StringBuilder sb = new StringBuilder();
sb.append("<include layout=\"@layout/"); //$NON-NLS-1$
sb.append(newName);
// HACK: see issue 13494: We must duplicate the width/height attributes on the
// <include> statement for designtime rendering only
- Element primaryNode = getPrimaryElement();
String width = null;
String height = null;
if (primaryNode == null) {
package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
-import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
import com.android.resources.ResourceType;
+import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
-import org.eclipse.ltk.ui.refactoring.UserInputWizardPage;
import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
+import java.util.List;
+
class ExtractIncludeWizard extends VisualRefactoringWizard {
public ExtractIncludeWizard(ExtractIncludeRefactoring ref, LayoutEditor editor) {
super(ref, editor);
protected void addUserInputPages() {
ExtractIncludeRefactoring ref = (ExtractIncludeRefactoring) getRefactoring();
String initialName = ref.getInitialName();
- addPage(new InputPage(mEditor.getProject(), initialName));
+ IFile sourceFile = ref.getSourceFile();
+ addPage(new InputPage(mEditor.getProject(), sourceFile, initialName));
}
/** Wizard page which inputs parameters for the {@link ExtractIncludeRefactoring} operation */
- private static class InputPage extends UserInputWizardPage {
+ private static class InputPage extends VisualRefactoringInputPage {
private final IProject mProject;
+ private final IFile mSourceFile;
private final String mSuggestedName;
private Text mNameText;
- private Button mUpdateReferences;
private Button mReplaceAllOccurrences;
- public InputPage(IProject project, String suggestedName) {
- super("ExtractIncludeInputPage"); //$NON-NLS-1$
+ public InputPage(IProject project, IFile sourceFile, String suggestedName) {
+ super("ExtractIncludeInputPage");
mProject = project;
+ mSourceFile = sourceFile;
mSuggestedName = suggestedName;
}
mNameText = new Text(composite, SWT.BORDER);
mNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
- mNameText.addModifyListener(new ModifyListener() {
- public void modifyText(ModifyEvent e) {
- validatePage();
- }
- });
-
- mUpdateReferences = new Button(composite, SWT.CHECK);
- mUpdateReferences.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER,
- false, false, 2, 1));
- mUpdateReferences.setSelection(true);
- mUpdateReferences.setText("Update layout references");
+ mNameText.addModifyListener(mModifyValidateListener);
mReplaceAllOccurrences = new Button(composite, SWT.CHECK);
mReplaceAllOccurrences.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER,
false, false, 2, 1));
- mReplaceAllOccurrences.setText("Replace all occurrences with include to new layout");
- mReplaceAllOccurrences.setEnabled(false);
+ mReplaceAllOccurrences.setText(
+ "Replace occurrences in all layouts with include to new layout");
+ List<IFile> variations =
+ ExtractIncludeRefactoring.getConfigurationVariations(mSourceFile);
+ boolean enabled = variations.size() > 0;
+ mReplaceAllOccurrences.setEnabled(enabled);
+ mReplaceAllOccurrences.setSelection(enabled);
+ mReplaceAllOccurrences.addSelectionListener(mSelectionValidateListener);
// Initialize UI:
if (mSuggestedName != null) {
validatePage();
}
- private boolean validatePage() {
+ @Override
+ protected boolean validatePage() {
boolean ok = true;
String text = mNameText.getText().trim();
(ExtractIncludeRefactoring) getRefactoring();
refactoring.setLayoutName(text);
refactoring.setReplaceOccurrences(mReplaceAllOccurrences.getSelection());
- refactoring.setUpdateReferences(mUpdateReferences.getSelection());
}
setPageComplete(ok);
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.util.Pair;
import org.eclipse.core.runtime.IStatus;
}
/**
- * Returns the element children of the given element
- *
- * @param element the parent element
- * @return a list of child elements, possibly empty but never null
- */
- public static List<Element> getChildren(Element element) {
- // Convenience to avoid lots of ugly DOM access casting
- NodeList children = element.getChildNodes();
- // An iterator would have been more natural (to directly drive the child list
- // iteration) but iterators can't be used in enhanced for loops...
- List<Element> result = new ArrayList<Element>(children.getLength());
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node node = children.item(i);
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- Element child = (Element) node;
- result.add(child);
- }
- }
-
- return result;
- }
-
- /**
* Returns the layout weight of of the given child of a LinearLayout, or 0.0 if it
* does not define a weight
*/
*/
private float getWeightSum(Element linearLayout) {
float sum = 0;
- for (Element child : getChildren(linearLayout)) {
+ for (Element child : DomUtilities.getChildren(linearLayout)) {
sum += getWeight(child);
}
// Baseline alignment. Find the tallest child and set it as the baseline reference.
int tallestHeight = 0;
View tallest = null;
- for (Element child : getChildren(layout)) {
+ for (Element child : DomUtilities.getChildren(layout)) {
View view = edgeList.getView(child);
if (view != null && view.getHeight() > tallestHeight) {
tallestHeight = view.getHeight();
float weightSum = getWeightSum(layout);
float cumulativeWeight = 0;
- List<Element> children = getChildren(layout);
+ List<Element> children = DomUtilities.getChildren(layout);
String prevId = null;
boolean isFirstChild = true;
boolean linkBackwards = true;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
-import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
mElements = initElements();
}
- @Override
- public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException,
- OperationCanceledException {
- RefactoringStatus status = new RefactoringStatus();
-
- try {
- pm.beginTask("Checking preconditions...", 6);
-
- if (mSelectionStart == -1 || mSelectionEnd == -1) {
- status.addFatalError("No selection to extract");
- return status;
- }
-
- // Make sure the selection is contiguous
- if (mTreeSelection != null) {
- // TODO - don't do this if we based the selection on text. In this case,
- // make sure we're -balanced-.
-
- List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>();
- for (TreePath path : mTreeSelection.getPaths()) {
- Object lastSegment = path.getLastSegment();
- if (lastSegment instanceof CanvasViewInfo) {
- infos.add((CanvasViewInfo) lastSegment);
- }
- }
-
- if (infos.size() == 0) {
- status.addFatalError("No selection to extract");
- return status;
- }
-
- // Can't extract the root -- wouldn't that be pointless? (or maybe not
- // always)
- for (CanvasViewInfo info : infos) {
- if (info.isRoot()) {
- status.addFatalError("Cannot refactor the root");
- return status;
- }
- }
-
- // Disable if you've selected a single include tag
- if (infos.size() == 1) {
- UiViewElementNode uiNode = infos.get(0).getUiViewNode();
- if (uiNode != null) {
- Node xmlNode = uiNode.getXmlNode();
- if (xmlNode.getLocalName().equals(LayoutDescriptors.VIEW_INCLUDE)) {
- status.addWarning("No point in refactoring a single include tag");
- }
- }
- }
-
- // Enforce that the selection is -contiguous-
- if (infos.size() > 1) {
- // All elements must be siblings (e.g. same parent)
- List<UiViewElementNode> nodes = new ArrayList<UiViewElementNode>(infos
- .size());
- for (CanvasViewInfo info : infos) {
- UiViewElementNode node = info.getUiViewNode();
- if (node != null) {
- nodes.add(node);
- }
- }
- if (nodes.size() == 0) {
- status.addFatalError("No selected views");
- return status;
- }
-
- UiElementNode parent = nodes.get(0).getUiParent();
- for (UiViewElementNode node : nodes) {
- if (parent != node.getUiParent()) {
- status.addFatalError("The selected elements must be adjacent");
- return status;
- }
- }
- // Ensure that the siblings are contiguous; no gaps.
- // If we've selected all the children of the parent then we don't need
- // to look.
- List<UiElementNode> siblings = parent.getUiChildren();
- if (siblings.size() != nodes.size()) {
- Set<UiViewElementNode> nodeSet = new HashSet<UiViewElementNode>(nodes);
- boolean inRange = false;
- int remaining = nodes.size();
- for (UiElementNode node : siblings) {
- boolean in = nodeSet.contains(node);
- if (in) {
- remaining--;
- if (remaining == 0) {
- break;
- }
- inRange = true;
- } else if (inRange) {
- status.addFatalError("The selected elements must be adjacent");
- return status;
- }
- }
- }
- }
- }
-
- // Ensures that we have a valid DOM model:
- if (mElements.size() == 0) {
- status.addFatalError("Nothing to extract");
- return status;
- }
-
- pm.worked(1);
- return status;
-
- } finally {
- pm.done();
- }
- }
-
protected abstract List<Change> computeChanges();
@Override
/** Produce a list of edits to replace references to the given id with the given new id */
- protected List<TextEdit> replaceIds(IStructuredDocument doc, int skipStart, int skipEnd,
+ protected static List<TextEdit> replaceIds(String androidNamePrefix,
+ IStructuredDocument doc, int skipStart, int skipEnd,
String rootId, String referenceId) {
if (rootId == null) {
return Collections.emptyList();
return Collections.emptyList();
}
- String namePrefix = getAndroidNamespacePrefix() + ':' + ATTR_LAYOUT_PREFIX;
+ String namePrefix = androidNamePrefix + ':' + ATTR_LAYOUT_PREFIX;
List<TextEdit> edits = new ArrayList<TextEdit>();
IStructuredDocumentRegion region = doc.getFirstStructuredDocumentRegion();
return mAndroidNamespacePrefix;
}
+ protected static String getAndroidNamespacePrefix(Document document) {
+ String nsPrefix = null;
+ List<Attr> attributeNodes = findNamespaceAttributes(document);
+ for (Node attributeNode : attributeNodes) {
+ String prefix = attributeNode.getPrefix();
+ if (XMLNS.equals(prefix)) {
+ String name = attributeNode.getNodeName();
+ String value = attributeNode.getNodeValue();
+ if (value.equals(ANDROID_URI)) {
+ nsPrefix = name;
+ if (nsPrefix.startsWith(XMLNS_COLON)) {
+ nsPrefix =
+ nsPrefix.substring(XMLNS_COLON.length());
+ }
+ }
+ }
+ }
+
+ if (nsPrefix == null) {
+ nsPrefix = ANDROID_NS_NAME;
+ }
+
+ return nsPrefix;
+ }
+
protected List<Attr> findNamespaceAttributes() {
Document document = getDomDocument();
+ return findNamespaceAttributes(document);
+ }
+
+ protected static List<Attr> findNamespaceAttributes(Document document) {
if (document != null) {
Element root = document.getDocumentElement();
return findNamespaceAttributes(root);
return Collections.emptyList();
}
- protected List<Attr> findNamespaceAttributes(Node root) {
+ protected static List<Attr> findNamespaceAttributes(Node root) {
List<Attr> result = new ArrayList<Attr>();
NamedNodeMap attributes = root.getAttributes();
for (int i = 0, n = attributes.getLength(); i < n; i++) {
return true;
}
- protected void ensureIdMatchesType(Element element, String newType, MultiTextEdit rootEdit) {
+ /**
+ * Updates the given element with a new name if the current id reflects the old
+ * element type. If the name was changed, it will return the new name.
+ */
+ protected String ensureIdMatchesType(Element element, String newType, MultiTextEdit rootEdit) {
String oldType = element.getTagName();
if (oldType.indexOf('.') == -1) {
oldType = ANDROID_WIDGET_PREFIX + oldType;
String id = getId(element);
if (id == null || id.toLowerCase().contains(oldTypeBase.toLowerCase())) {
String newTypeBase = newType.substring(newType.lastIndexOf('.') + 1);
- ensureHasId(rootEdit, element, newTypeBase);
+ return ensureHasId(rootEdit, element, newTypeBase);
}
+
+ return null;
}
protected static IndexedRegion getRegion(Node node) {
*/
package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
-import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
/**
* Examine the selection to determine if the action should be enabled or not.
* <p/>
- * Keep a link to the relevant selection structure (i.e. a part of the Java AST).
+ * Keep a link to the relevant selection structure
*/
public void selectionChanged(IAction action, ISelection selection) {
// Look for selections in XML and in the layout UI editor
if (selection instanceof ITextSelection) {
mTextSelection = (ITextSelection) selection;
- if (mTextSelection.getLength() > 0) {
- editor = getActiveEditor();
- mFile = getSelectedFile(editor);
- }
+ editor = getActiveEditor();
+ mFile = getSelectedFile(editor);
} else if (selection instanceof ITreeSelection) {
Object firstElement = ((ITreeSelection)selection).getFirstElement();
if (firstElement instanceof CanvasViewInfo) {
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
+import org.eclipse.ltk.ui.refactoring.UserInputWizardPage;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
public abstract class VisualRefactoringWizard extends RefactoringWizard {
protected final LayoutEditor mEditor;
mEditor.refreshXmlModel();
}
}
+
+ protected abstract static class VisualRefactoringInputPage extends UserInputWizardPage {
+ public VisualRefactoringInputPage(String name) {
+ super(name);
+ }
+
+ /**
+ * Listener which can be attached on any widget in the wizard page to force
+ * modifications of the associated widget to validate the page again
+ */
+ protected ModifyListener mModifyValidateListener = new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ validatePage();
+ }
+ };
+
+ /**
+ * Listener which can be attached on any widget in the wizard page to force
+ * selection changes of the associated widget to validate the page again
+ */
+ protected SelectionAdapter mSelectionValidateListener = new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ validatePage();
+ }
+ };
+
+ protected abstract boolean validatePage();
+ }
}
*/
package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
public class WrapInRefactoring extends VisualRefactoring {
private static final String KEY_ID = "name"; //$NON-NLS-1$
private static final String KEY_TYPE = "type"; //$NON-NLS-1$
- private static final String KEY_UPDATE_REFS = "update-refs"; //$NON-NLS-1$
private String mId;
private String mTypeFqcn;
- private boolean mUpdateReferences;
+ private String mInitializedAttributes;
/**
* This constructor is solely used by {@link Descriptor},
super(arguments);
mId = arguments.get(KEY_ID);
mTypeFqcn = arguments.get(KEY_TYPE);
- mUpdateReferences = Boolean.parseBoolean(arguments.get(KEY_UPDATE_REFS));
}
public WrapInRefactoring(IFile file, LayoutEditor editor, ITextSelection selection,
Map<String, String> args = super.createArgumentMap();
args.put(KEY_TYPE, mTypeFqcn);
args.put(KEY_ID, mId);
- args.put(KEY_UPDATE_REFS, Boolean.toString(mUpdateReferences));
return args;
}
mTypeFqcn = typeFqcn;
}
- void setUpdateReferences(boolean selection) {
- mUpdateReferences = selection;
+ void setInitializedAttributes(String initializedAttributes) {
+ mInitializedAttributes = initializedAttributes;
}
@Override
String startIndent = AndroidXmlEditor.getIndentAtOffset(document, mSelectionStart);
String viewClass = getViewClass(mTypeFqcn);
+ String androidNsPrefix = getAndroidNamespacePrefix();
+
IFile file = mEditor.getInputFile();
List<Change> changes = new ArrayList<Change>();
String id = ensureNewId(mId);
// Update any layout references to the old id with the new id
- if (mUpdateReferences && id != null) {
+ if (id != null) {
String rootId = getRootId();
IStructuredModel model = mEditor.getModelForRead();
try {
IStructuredDocument doc = model.getStructuredDocument();
if (doc != null) {
- List<TextEdit> replaceIds = replaceIds(doc, mSelectionStart,
- mSelectionEnd, rootId, id);
+ List<TextEdit> replaceIds = replaceIds(androidNsPrefix,
+ doc, mSelectionStart, mSelectionEnd, rootId, id);
for (TextEdit edit : replaceIds) {
rootEdit.addChild(edit);
}
sb.append(namespace);
}
- String androidNsPrefix = getAndroidNamespacePrefix();
-
// Set the ID if any
if (id != null) {
if (separateAttributes) {
sb.append(androidNsPrefix).append(':');
sb.append(ATTR_LAYOUT_HEIGHT).append('=').append('"').append(height).append('"');
+ if (mInitializedAttributes != null && mInitializedAttributes.length() > 0) {
+ for (String s : mInitializedAttributes.split(",")) { //$NON-NLS-1$
+ sb.append(' ');
+ String[] nameValue = s.split("="); //$NON-NLS-1$
+ String name = nameValue[0];
+ String value = nameValue[1];
+ if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
+ name = name.substring(ANDROID_NS_NAME_PREFIX.length());
+ sb.append(androidNsPrefix).append(':');
+ }
+ sb.append(name).append('=').append('"').append(value).append('"');
+ }
+ }
+
// Transfer layout_ attributes (other than width and height)
- if (mUpdateReferences) {
+ if (primary != null) {
List<Attr> layoutAttributes = findLayoutAttributes(primary);
for (Attr attribute : layoutAttributes) {
String name = attribute.getLocalName();
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.PaletteMetadataDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.core.ResolvedBinaryType;
import org.eclipse.jdt.internal.core.ResolvedSourceType;
-import org.eclipse.ltk.ui.refactoring.UserInputWizardPage;
import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
}
/** Wizard page which inputs parameters for the {@link WrapInRefactoring} operation */
- private static class InputPage extends UserInputWizardPage {
+ private static class InputPage extends VisualRefactoringInputPage {
private final IProject mProject;
private final String mOldType;
private Text mIdText;
private Combo mTypeCombo;
- private Button mUpdateReferences;
- private List<String> mClassNames;
+ private List<Pair<String, ViewElementDescriptor>> mClassNames;
public InputPage(IProject project, String oldType) {
super("WrapInInputPage"); //$NON-NLS-1$
mTypeCombo = new Combo(composite, SWT.READ_ONLY);
mTypeCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
- SelectionAdapter selectionListener = new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- validatePage();
- }
- };
- mTypeCombo.addSelectionListener(selectionListener);
+ mTypeCombo.addSelectionListener(mSelectionValidateListener);
Label idLabel = new Label(composite, SWT.NONE);
idLabel.setText("New Layout Id:");
mIdText = new Text(composite, SWT.BORDER);
mIdText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
- mIdText.addModifyListener(new ModifyListener() {
- public void modifyText(ModifyEvent e) {
- validatePage();
- }
- });
-
- mUpdateReferences = new Button(composite, SWT.CHECK);
- mUpdateReferences.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER,
- false, false, 2, 1));
- mUpdateReferences.setSelection(true);
- mUpdateReferences.setText("Update layout references");
- mUpdateReferences.addSelectionListener(selectionListener);
+ mIdText.addModifyListener(mModifyValidateListener);
Set<String> exclude = Collections.singleton(VIEW_INCLUDE);
mClassNames = addLayouts(mProject, mOldType, mTypeCombo, exclude, true);
mTypeCombo.setFocus();
}
- private boolean validatePage() {
+ @Override
+ protected boolean validatePage() {
boolean ok = true;
String id = mIdText.getText().trim();
if (id.length() == 0) {
- // It's okay to not define a title...
- // ...unless you want to update references
- if (mUpdateReferences.getSelection()) {
- setErrorMessage("ID required when updating layout references");
- ok = false;
- }
+ setErrorMessage("ID required");
+ ok = false;
} else {
// ...but if you do, it has to be valid!
ResourceNameValidator validator = ResourceNameValidator.create(false, mProject,
}
int selectionIndex = mTypeCombo.getSelectionIndex();
- String type = selectionIndex != -1 ? mClassNames.get(selectionIndex) : null;
+ String type = selectionIndex != -1 ? mClassNames.get(selectionIndex).getFirst() : null;
if (type == null) {
setErrorMessage("Select a container type");
ok = false; // The user has chosen a separator
(WrapInRefactoring) getRefactoring();
refactoring.setId(id);
refactoring.setType(type);
- refactoring.setUpdateReferences(mUpdateReferences.getSelection());
+
+ ViewElementDescriptor descriptor = mClassNames.get(selectionIndex).getSecond();
+ if (descriptor instanceof PaletteMetadataDescriptor) {
+ PaletteMetadataDescriptor paletteDescriptor =
+ (PaletteMetadataDescriptor) descriptor;
+ String initializedAttributes = paletteDescriptor.getInitializedAttributes();
+ refactoring.setInitializedAttributes(initializedAttributes);
+ } else {
+ refactoring.setInitializedAttributes(null);
+ }
}
setPageComplete(ok);
}
}
- static List<String> addLayouts(IProject project, String oldType, Combo combo,
+ static List<Pair<String, ViewElementDescriptor>> addLayouts(IProject project,
+ String oldType, Combo combo,
Set<String> exclude, boolean addGestureOverlay) {
- List<String> classNames = new ArrayList<String>();
+ List<Pair<String, ViewElementDescriptor>> classNames =
+ new ArrayList<Pair<String, ViewElementDescriptor>>();
- if (oldType.equals(FQCN_RADIO_BUTTON)) {
+ if (oldType != null && oldType.equals(FQCN_RADIO_BUTTON)) {
combo.add(RADIO_GROUP);
// NOT a fully qualified name since android widgets do not include the package
- classNames.add(RADIO_GROUP);
+ classNames.add(Pair.of(RADIO_GROUP, (ViewElementDescriptor) null));
combo.add(SEPARATOR_LABEL);
- classNames.add(null);
+ classNames.add(Pair.<String,ViewElementDescriptor>of(null, null));
}
Pair<List<String>,List<String>> result = findViews(project, true);
if (customViews.size() > 0) {
for (String view : customViews) {
combo.add(view);
- classNames.add(view);
+ classNames.add(Pair.of(view, (ViewElementDescriptor) null));
}
combo.add(SEPARATOR_LABEL);
- classNames.add(null);
+ classNames.add(Pair.<String,ViewElementDescriptor>of(null, null));
}
// Populate type combo
String className = d.getFullClassName();
if (exclude == null || !exclude.contains(className)) {
combo.add(d.getUiName());
- classNames.add(className);
+ classNames.add(Pair.of(className, d));
}
}
if (thirdPartyViews.size() > 0) {
for (String view : thirdPartyViews) {
combo.add(view);
- classNames.add(view);
+ classNames.add(Pair.of(view, (ViewElementDescriptor) null));
}
combo.add(SEPARATOR_LABEL);
classNames.add(null);
if (addGestureOverlay) {
combo.add(GESTURE_OVERLAY_VIEW);
- classNames.add(FQCN_GESTURE_OVERLAY_VIEW);
+ classNames.add(Pair.<String, ViewElementDescriptor> of(
+ FQCN_GESTURE_OVERLAY_VIEW, null));
combo.add(SEPARATOR_LABEL);
- classNames.add(null);
+ classNames.add(Pair.<String,ViewElementDescriptor>of(null, null));
}
}
String className = d.getFullClassName();
if (exclude == null || !exclude.equals(className)) {
combo.add(d.getUiName());
- classNames.add(className);
+ classNames.add(Pair.of(className, d));
}
}
}
}
} else {
combo.add("SDK not initialized");
- classNames.add(null);
+ classNames.add(Pair.<String,ViewElementDescriptor>of(null, null));
}
return classNames;
*/
package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
-import static com.android.AndroidConstants.FD_RES_LAYOUT;
-import static com.android.sdklib.SdkConstants.FD_RES;
-
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
return sProject;
}
- protected IFile getTestFile(IProject project, String name) throws Exception {
- IFolder resFolder = project.getFolder(FD_RES);
+ protected IFile getTestDataFile(IProject project, String name) throws Exception {
+ return getTestDataFile(project, name, name);
+ }
+
+ protected IFile getTestDataFile(IProject project, String sourceName,
+ String destPath) throws Exception {
+ String[] split = destPath.split("/"); //$NON-NLS-1$
+ assertTrue(split.length > 1);
+ IFolder folder = project.getFolder(split[0]);
NullProgressMonitor monitor = new NullProgressMonitor();
- if (!resFolder.exists()) {
- resFolder.create(true /* force */, true /* local */, monitor);
+ if (!folder.exists()) {
+ folder.create(true /* force */, true /* local */, monitor);
}
- IFolder layoutfolder = resFolder.getFolder(FD_RES_LAYOUT);
- if (!layoutfolder.exists()) {
- layoutfolder.create(true /* force */, true /* local */, monitor);
+ for (int i = 1, n = split.length; i < n -1; i++) {
+ IFolder subFolder = folder.getFolder(split[i]);
+ if (!subFolder.exists()) {
+ subFolder.create(true /* force */, true /* local */, monitor);
+ }
+ folder = subFolder;
}
- IFile file = layoutfolder.getFile(name);
+
+ String name = split[split.length - 1];
+ IFile file = folder.getFile(name);
if (!file.exists()) {
- String xml = readTestFile(name, true);
+ String xml = readTestFile(sourceName, true);
InputStream bstream = new ByteArrayInputStream(xml.getBytes("UTF-8")); //$NON-NLS-1$
file.create(bstream, false /* force */, monitor);
}
}
private void checkRefactoring(String basename, boolean flatten) throws Exception {
- IFile file = getTestFile(sProject, basename);
+ IFile file = getLayoutFile(sProject, basename);
TestContext info = setupTestContext(file, basename);
TestLayoutEditor layoutEditor = info.mLayoutEditor;
CanvasViewInfo rootView = info.mRootView;
public class ChangeViewRefactoringTest extends RefactoringTest {
public void testChangeView1() throws Exception {
- checkRefactoring("sample1a.xml", "@+id/button1", "@+id/button6");
+ checkRefactoring("sample1a.xml", FQCN_RADIO_BUTTON, "@+id/button1", "@+id/button6");
}
- private void checkRefactoring(String basename, String... ids) throws Exception {
+ public void testChangeView2() throws Exception {
+ // Tests (1) updating references to the renamed id of the changed widgets
+ // (e.g. button3 is renamed to imageButton1 and layout references to button3
+ // must be updated), and (2) removal of attributes not available in the new type
+ // (the text property is removed since it is not available on the new widget
+ // type ImageButton)
+ checkRefactoring("sample2.xml", "android.widget.ImageButton",
+ "@+id/button3", "@+id/button5");
+ }
+
+ private void checkRefactoring(String basename, String newType,
+ String... ids) throws Exception {
assertTrue(ids.length > 0);
- IFile file = getTestFile(sProject, basename);
+ IFile file = getLayoutFile(sProject, basename);
TestContext info = setupTestContext(file, basename);
TestLayoutEditor layoutEditor = info.mLayoutEditor;
List<Element> selectedElements = getElements(info.mElement, ids);
ChangeViewRefactoring refactoring = new ChangeViewRefactoring(selectedElements,
layoutEditor);
- refactoring.setType(FQCN_RADIO_BUTTON);
+ refactoring.setType(newType);
List<Change> changes = refactoring.computeChanges();
checkEdits(basename, changes);
import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IPath;
import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.w3c.dom.Element;
-import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class ExtractIncludeRefactoringTest extends RefactoringTest {
public void testExtract1() throws Exception {
// Basic: Extract a single button
- checkRefactoring("sample3.xml", "newlayout1", true, true, "@+id/button2");
+ checkRefactoring("sample3.xml", "newlayout1", false, null, 2, "@+id/button2");
}
public void testExtract2() throws Exception {
// Extract a couple of elements
- checkRefactoring("sample3.xml", "newlayout2", true, true,
+ checkRefactoring("sample3.xml", "newlayout2", false, null, 2,
"@+id/button2", "@+id/android_logo");
}
+ public void testExtract3() throws Exception {
+ // Test to make sure layout attributes are updated
+ checkRefactoring("sample2.xml", "newlayout3", false, null, 2,
+ "@+id/button3");
+ }
+
+ public void testExtract4() throws Exception {
+ // Tests extracting from -multiple- files (as well as with custom android namespace
+ // prefix)
+
+ // Make sure the variation-files exist
+ Map<IPath, String> extraFiles = new HashMap<IPath, String>();
+ extraFiles.put(getTestDataFile(getProject(), "sample3-variation1.xml",
+ "res/layout-land/sample3.xml").getProjectRelativePath(),
+ "sample3-variation1.xml");
+ extraFiles.put(getTestDataFile(getProject(), "sample3-variation2.xml",
+ "res/layout-xlarge-land/sample3.xml").getProjectRelativePath(),
+ "sample3-variation2.xml");
+
+ checkRefactoring("sample3.xml", "newlayout3", true, extraFiles, 4,
+ "@+id/android_logo");
+ }
+
+ public void testExtract5() throws Exception {
+ // Tests extracting from multiple files with -contiguous regions-.
+
+ // Make sure the variation-files exist
+ Map<IPath, String> extraFiles = new HashMap<IPath, String>();
+ extraFiles.put(getTestDataFile(getProject(), "sample3-variation1.xml",
+ "res/layout-land/sample3.xml").getProjectRelativePath(),
+ "sample3-variation1.xml");
+ extraFiles.put(getTestDataFile(getProject(), "sample3-variation2.xml",
+ "res/layout-xlarge-land/sample3.xml").getProjectRelativePath(),
+ "sample3-variation2.xml");
+
+ checkRefactoring("sample3.xml", "newlayout3", true, extraFiles, 4,
+ "@+id/android_logo", "@+id/button1");
+ }
+
private void checkRefactoring(String basename, String layoutName,
- boolean updateRefs, boolean replaceOccurrences, String... ids) throws Exception {
+ boolean replaceOccurrences, Map<IPath,String> extraFiles,
+ int expectedModifiedFileCount, String... ids) throws Exception {
assertTrue(ids.length > 0);
- IFile file = getTestFile(sProject, basename);
+ IFile file = getLayoutFile(sProject, basename);
TestContext info = setupTestContext(file, basename);
TestLayoutEditor layoutEditor = info.mLayoutEditor;
List<Element> selectedElements = getElements(info.mElement, ids);
ExtractIncludeRefactoring refactoring = new ExtractIncludeRefactoring(selectedElements,
layoutEditor);
refactoring.setLayoutName(layoutName);
- refactoring.setUpdateReferences(updateRefs);
refactoring.setReplaceOccurrences(replaceOccurrences);
List<Change> changes = refactoring.computeChanges();
- assertEquals(3, changes.size());
+ assertTrue(changes.size() >= 3);
+
+ Map<IPath,String> fileToGolden = new HashMap<IPath,String>();
+ IPath sourcePath = file.getProjectRelativePath();
+ fileToGolden.put(sourcePath, basename);
+ IPath newPath = sourcePath.removeLastSegments(1).append(layoutName + DOT_XML);
+ fileToGolden.put(newPath, layoutName + DOT_XML);
+ if (extraFiles != null) {
+ fileToGolden.putAll(extraFiles);
+ }
- // The first change is to update the current file:
- checkEdits(basename, Collections.singletonList(changes.get(0)));
+ checkEdits(changes, fileToGolden);
- // The second change is to create the new extracted file
- checkEdits(layoutName + DOT_XML, Collections.singletonList(changes.get(1)));
+ int modifiedFileCount = 0;
+ for (Change change : changes) {
+ if (change instanceof TextFileChange) {
+ modifiedFileCount++;
+ }
+ }
+ assertEquals(expectedModifiedFileCount, modifiedFileCount);
}
}
*/
package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
+import static com.android.AndroidConstants.FD_RES_LAYOUT;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX;
import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
+import static com.android.sdklib.SdkConstants.FD_RES;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@SuppressWarnings("restriction")
public class RefactoringTest extends AdtProjectTest {
+
+ protected IFile getLayoutFile(IProject project, String name) throws Exception {
+ return getTestDataFile(project, name, FD_RES + "/" + FD_RES_LAYOUT + "/" + name);
+ }
+
protected static Element findElementById(Element root, String id) {
if (id.equals(VisualRefactoring.getId(root))) {
return root;
}
- for (Element child : RelativeLayoutConversionHelper.getChildren(root)) {
+ for (Element child : DomUtilities.getChildren(root)) {
Element result = findElementById(child, id);
if (result != null) {
return result;
assertEqualsGolden(basename, actual);
}
+ protected void checkEdits(List<Change> changes,
+ Map<IPath, String> fileToGoldenName) throws BadLocationException,
+ IOException {
+ for (Change change : changes) {
+ if (change instanceof TextFileChange) {
+ TextFileChange tf = (TextFileChange) change;
+ IFile file = tf.getFile();
+ assertNotNull(file);
+ IPath path = file.getProjectRelativePath();
+ String goldenName = fileToGoldenName.get(path);
+ assertNotNull(goldenName);
+
+ String xml = readTestFile(goldenName, false);
+ if (xml == null) { // New file
+ xml = ""; //$NON-NLS-1$
+ }
+ IDocument document = new Document();
+ document.set(xml);
+
+ TextEdit edit = tf.getEdit();
+ if (edit instanceof MultiTextEdit) {
+ MultiTextEdit edits = (MultiTextEdit) edit;
+ edits.apply(document);
+ } else {
+ edit.apply(document);
+ }
+
+ String actual = document.get();
+ assertEqualsGolden(goldenName, actual);
+ } else {
+ System.out.println("Ignoring non-textfilechange in refactoring result");
+ assertNull(change.getAffectedObjects());
+ }
+ }
+ }
+
protected void assertEqualsGolden(String basename, String actual) {
String testName = getName();
if (testName.startsWith("test")) {
}
protected UiViewElementNode createModel(UiViewElementNode parent, Element element) {
- List<Element> children = RelativeLayoutConversionHelper.getChildren(element);
+ List<Element> children = DomUtilities.getChildren(element);
String fqcn = ANDROID_WIDGET_PREFIX + element.getTagName();
boolean hasChildren = children.size() > 0;
UiViewElementNode node = createNode(parent, fqcn, hasChildren);
private void checkRefactoring(String basename, String fqcn, String... ids) throws Exception {
assertTrue(ids.length > 0);
- IFile file = getTestFile(sProject, basename);
+ IFile file = getLayoutFile(sProject, basename);
TestContext info = setupTestContext(file, basename);
TestLayoutEditor layoutEditor = info.mLayoutEditor;
List<Element> selectedElements = getElements(info.mElement, ids);
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:id="@+id/button3" android:layout_height="wrap_content" android:text="Button" ></Button>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" />
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" />
+</merge>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
+ <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <RelativeLayout android:layout_height="match_parent" android:id="@+id/relativeLayout1" android:layout_width="match_parent">
+ <Button android:layout_width="wrap_content" android:layout_alignParentLeft="true" android:id="@+id/button2" android:layout_height="wrap_content" android:text="Button"></Button>
+ <ImageButton android:layout_width="wrap_content" android:id="@+id/ImageButton1" android:layout_below="@+id/button2" android:layout_height="wrap_content" android:layout_toRightOf="@+id/button2"></ImageButton>
+ <Button android:layout_width="wrap_content" android:id="@+id/button4" android:layout_below="@+id/ImageButton1" android:layout_height="wrap_content" android:text="Button" android:layout_toRightOf="@+id/ImageButton1"></Button>
+ <CheckBox android:layout_width="wrap_content" android:layout_below="@+id/button4" android:id="@+id/checkBox1" android:layout_height="wrap_content" android:text="CheckBox" android:layout_toLeftOf="@+id/button4"></CheckBox>
+ <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/ImageButton2" android:layout_alignParentRight="true"></ImageButton>
+ </RelativeLayout>
+</LinearLayout>
+
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
+ <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <RelativeLayout android:layout_height="match_parent" android:id="@+id/relativeLayout1" android:layout_width="match_parent">
+ <Button android:layout_width="wrap_content" android:layout_alignParentLeft="true" android:id="@+id/button2" android:layout_height="wrap_content" android:text="Button"></Button>
+ <include layout="@layout/newlayout3" android:id="@+id/button3_ref" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/button2" android:layout_toRightOf="@+id/button2"/>
+ <Button android:layout_width="wrap_content" android:id="@+id/button4" android:layout_below="@+id/button3_ref" android:layout_height="wrap_content" android:text="Button" android:layout_toRightOf="@+id/button3_ref"></Button>
+ <CheckBox android:layout_width="wrap_content" android:layout_below="@+id/button4" android:id="@+id/checkBox1" android:layout_height="wrap_content" android:text="CheckBox" android:layout_toLeftOf="@+id/button4"></CheckBox>
+ <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" android:id="@+id/button5" android:layout_alignParentRight="true"></Button>
+ </RelativeLayout>
+</LinearLayout>
+
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <include layout="@layout/newlayout3" android:id="@+id/android_logo_ref" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1.0"/>
+ <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <include layout="@layout/newlayout3" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
+ <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <LinearLayout android:id="@+id/newlinear2" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <include layout="@layout/newlayout3" android:id="@+id/android_logo_ref" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1.0"/>
+ </LinearLayout>
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <LinearLayout android:id="@+id/newlinear2" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <include layout="@layout/newlayout3" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
+ </LinearLayout>
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <LinearLayout android:id="@+id/newlinear2" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button"
+ android:focusable="false" android:clickable="false" android:layout_weight="1.0" android:id="@+id/android_logo" />
+ </LinearLayout>
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:customprefix="http://schemas.android.com/apk/res/android" customprefix:id="@+id/newlinear" customprefix:orientation="vertical" customprefix:layout_width="match_parent" customprefix:layout_height="match_parent">
+ <include layout="@layout/newlayout3" customprefix:id="@+id/android_logo_ref" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content" customprefix:layout_weight="1.0"/>
+ <Button customprefix:text="Button" customprefix:id="@+id/button1" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"></Button>
+ <Button customprefix:text="Button" customprefix:id="@+id/button2" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"></Button>
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:customprefix="http://schemas.android.com/apk/res/android" customprefix:id="@+id/newlinear" customprefix:orientation="vertical" customprefix:layout_width="match_parent" customprefix:layout_height="match_parent">
+ <include layout="@layout/newlayout3" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"/>
+ <Button customprefix:text="Button" customprefix:id="@+id/button2" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"></Button>
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:customprefix="http://schemas.android.com/apk/res/android" customprefix:id="@+id/newlinear" customprefix:orientation="vertical" customprefix:layout_width="match_parent" customprefix:layout_height="match_parent">
+ <ImageView customprefix:id="@+id/android_logo" customprefix:layout_width="wrap_content"
+ customprefix:layout_height="wrap_content" customprefix:src="@drawable/android_button" customprefix:focusable="false" customprefix:clickable="false" customprefix:layout_weight="1.0" />
+ <Button customprefix:text="Button" customprefix:id="@+id/button1" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"></Button>
+ <Button customprefix:text="Button" customprefix:id="@+id/button2" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"></Button>
+</LinearLayout>
+
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
import junit.framework.TestCase;
public class DomUtilitiesTest extends TestCase {
assertEquals("foo"b''ar",
DomUtilities.toXmlAttributeValue("foo\"b''ar"));
}
+
+ public void testIsEquivalent() throws Exception {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document document1 = builder.newDocument();
+ Document document2 = builder.newDocument();
+ document1.appendChild(document1.createElement("root"));
+ document2.appendChild(document2.createElement("root"));
+
+ assertFalse(DomUtilities.isEquivalent(null, null));
+ Element root1 = document1.getDocumentElement();
+ assertFalse(DomUtilities.isEquivalent(null, root1));
+ Element root2 = document2.getDocumentElement();
+ assertFalse(DomUtilities.isEquivalent(root2, null));
+ assertTrue(DomUtilities.isEquivalent(root1, root2));
+
+ root1.appendChild(document1.createTextNode(" "));
+ // Differences in text are NOT significant!
+ assertTrue(DomUtilities.isEquivalent(root1, root2));
+ root2.appendChild(document2.createTextNode(" "));
+ assertTrue(DomUtilities.isEquivalent(root1, root2));
+
+ Element foo1 = document1.createElement("foo");
+ Element foo2 = document2.createElement("foo");
+ root1.appendChild(foo1);
+ assertFalse(DomUtilities.isEquivalent(root1, root2));
+ root2.appendChild(foo2);
+ assertTrue(DomUtilities.isEquivalent(root1, root2));
+
+ root1.appendChild(document1.createElement("bar"));
+ assertFalse(DomUtilities.isEquivalent(root1, root2));
+ root2.appendChild(document2.createElement("bar"));
+ assertTrue(DomUtilities.isEquivalent(root1, root2));
+
+ // Add attributes in opposite order
+ foo1.setAttribute("attribute1", "value1");
+ foo1.setAttribute("attribute2", "value2");
+ assertFalse(DomUtilities.isEquivalent(root1, root2));
+ foo2.setAttribute("attribute2", "value2");
+ foo2.setAttribute("attribute1", "valueWrong");
+ assertFalse(DomUtilities.isEquivalent(root1, root2));
+ foo2.setAttribute("attribute1", "value1");
+ assertTrue(DomUtilities.isEquivalent(root1, root2));
+
+ // TODO - test different tag names
+ // TODO - test different name spaces!
+ }
+
+ public void testIsContiguous() throws Exception {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document document = builder.newDocument();
+ document.appendChild(document.createElement("root"));
+ Element root = document.getDocumentElement();
+ root.appendChild(document.createTextNode(" "));
+ Element foo = document.createElement("foo");
+ root.appendChild(foo);
+ root.appendChild(document.createTextNode(" "));
+ Element bar = document.createElement("bar");
+ root.appendChild(bar);
+ Element baz = document.createElement("baz");
+ root.appendChild(baz);
+
+ assertTrue(DomUtilities.isContiguous(Arrays.asList(foo)));
+ assertTrue(DomUtilities.isContiguous(Arrays.asList(foo, bar)));
+ assertTrue(DomUtilities.isContiguous(Arrays.asList(foo, bar, baz)));
+ assertTrue(DomUtilities.isContiguous(Arrays.asList(foo, bar, baz)));
+ assertTrue(DomUtilities.isContiguous(Arrays.asList(bar, baz, foo)));
+ assertTrue(DomUtilities.isContiguous(Arrays.asList(baz, bar, foo)));
+ assertTrue(DomUtilities.isContiguous(Arrays.asList(baz, foo, bar)));
+
+ assertFalse(DomUtilities.isContiguous(Arrays.asList(foo, baz)));
+ assertFalse(DomUtilities.isContiguous(Arrays.asList(root, baz)));
+ }
+
}