/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
import static com.android.SdkConstants.ANDROID_NS_NAME;
import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_HINT;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN;
import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_ON_CLICK;
import static com.android.SdkConstants.ATTR_PARENT;
import static com.android.SdkConstants.ATTR_SRC;
import static com.android.SdkConstants.ATTR_STYLE;
import static com.android.SdkConstants.ATTR_TEXT;
import static com.android.SdkConstants.EXT_XML;
import static com.android.SdkConstants.FD_RESOURCES;
import static com.android.SdkConstants.FD_RES_VALUES;
import static com.android.SdkConstants.PREFIX_ANDROID;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.REFERENCE_STYLE;
import static com.android.SdkConstants.TAG_ITEM;
import static com.android.SdkConstants.TAG_RESOURCES;
import static com.android.SdkConstants.XMLNS_PREFIX;
import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard;
import com.android.utils.Pair;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MultiTextEdit;
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.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Extracts the selection and writes it out as a separate layout file, then adds an
* include to that new layout file. Interactively asks the user for a new name for the
* layout.
*
* Remaining work to do / Possible enhancements:
*
* - Optionally look in other files in the project and attempt to set style attributes
* in other cases where the style attributes match?
*
- If the elements we are extracting from already contain a style attribute, set that
* style as the parent style of the current style?
*
- Add a parent-style picker to the wizard (initialized with the above if applicable)
*
- Pick up indentation settings from the XML module
*
- Integrate with themes somehow -- make an option to have the extracted style go into
* the theme instead
*
*/
@SuppressWarnings("restriction") // XML model
public class ExtractStyleRefactoring extends VisualRefactoring {
private static final String KEY_NAME = "name"; //$NON-NLS-1$
private static final String KEY_REMOVE_EXTRACTED = "removeextracted"; //$NON-NLS-1$
private static final String KEY_REMOVE_ALL = "removeall"; //$NON-NLS-1$
private static final String KEY_APPLY_STYLE = "applystyle"; //$NON-NLS-1$
private static final String KEY_PARENT = "parent"; //$NON-NLS-1$
private String mStyleName;
/** The name of the file in res/values/ that the style will be added to. Normally
* res/values/styles.xml - but unit tests pick other names */
private String mStyleFileName = "styles.xml";
/** Set a style reference on the extracted elements? */
private boolean mApplyStyle;
/** Remove the attributes that were extracted? */
private boolean mRemoveExtracted;
/** List of attributes chosen by the user to be extracted */
private List mChosenAttributes = new ArrayList();
/** Remove all attributes that match the extracted attributes names, regardless of value */
private boolean mRemoveAll;
/** The parent style to extend */
private String mParent;
/** The full list of available attributes in the refactoring */
private Map> mAvailableAttributes;
/**
* This constructor is solely used by {@link Descriptor},
* to replay a previous refactoring.
* @param arguments argument map created by #createArgumentMap.
*/
ExtractStyleRefactoring(Map arguments) {
super(arguments);
mStyleName = arguments.get(KEY_NAME);
mRemoveExtracted = Boolean.parseBoolean(arguments.get(KEY_REMOVE_EXTRACTED));
mRemoveAll = Boolean.parseBoolean(arguments.get(KEY_REMOVE_ALL));
mApplyStyle = Boolean.parseBoolean(arguments.get(KEY_APPLY_STYLE));
mParent = arguments.get(KEY_PARENT);
if (mParent != null && mParent.length() == 0) {
mParent = null;
}
}
public ExtractStyleRefactoring(
IFile file,
LayoutEditorDelegate delegate,
ITextSelection selection,
ITreeSelection treeSelection) {
super(file, delegate, selection, treeSelection);
}
@VisibleForTesting
ExtractStyleRefactoring(List selectedElements, LayoutEditorDelegate editor) {
super(selectedElements, editor);
}
@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;
}
// This also 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();
}
}
@Override
protected VisualRefactoringDescriptor createDescriptor() {
String comment = getName();
return new Descriptor(
mProject.getName(), //project
comment, //description
comment, //comment
createArgumentMap());
}
@Override
protected Map createArgumentMap() {
Map args = super.createArgumentMap();
args.put(KEY_NAME, mStyleName);
args.put(KEY_REMOVE_EXTRACTED, Boolean.toString(mRemoveExtracted));
args.put(KEY_REMOVE_ALL, Boolean.toString(mRemoveAll));
args.put(KEY_APPLY_STYLE, Boolean.toString(mApplyStyle));
args.put(KEY_PARENT, mParent != null ? mParent : "");
return args;
}
@Override
public String getName() {
return "Extract Style";
}
void setStyleName(String styleName) {
mStyleName = styleName;
}
void setStyleFileName(String styleFileName) {
mStyleFileName = styleFileName;
}
void setChosenAttributes(List attributes) {
mChosenAttributes = attributes;
}
void setRemoveExtracted(boolean removeExtracted) {
mRemoveExtracted = removeExtracted;
}
void setApplyStyle(boolean applyStyle) {
mApplyStyle = applyStyle;
}
void setRemoveAll(boolean removeAll) {
mRemoveAll = removeAll;
}
void setParent(String parent) {
mParent = parent;
}
// ---- Actual implementation of Extract Style modification computation ----
/**
* Returns two items: a map from attribute name to a list of attribute nodes of that
* name, and a subset of these attributes that fall within the text selection
* (used to drive initial selection in the wizard)
*/
Pair