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.AndroidConstants.FD_RES_VALUES;
     19 import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME;
     20 import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX;
     21 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
     22 import static com.android.ide.common.layout.LayoutConstants.ATTR_HINT;
     23 import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
     24 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN;
     25 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX;
     26 import static com.android.ide.common.layout.LayoutConstants.ATTR_ON_CLICK;
     27 import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC;
     28 import static com.android.ide.common.layout.LayoutConstants.ATTR_STYLE;
     29 import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT;
     30 import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML;
     31 import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
     32 import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_COLON;
     33 import static com.android.ide.eclipse.adt.internal.editors.values.descriptors.ValuesDescriptors.ITEM_TAG;
     34 import static com.android.ide.eclipse.adt.internal.editors.values.descriptors.ValuesDescriptors.NAME_ATTR;
     35 import static com.android.ide.eclipse.adt.internal.editors.values.descriptors.ValuesDescriptors.PARENT_ATTR;
     36 import static com.android.ide.eclipse.adt.internal.editors.values.descriptors.ValuesDescriptors.ROOT_ELEMENT;
     37 import static com.android.sdklib.SdkConstants.FD_RESOURCES;
     38 
     39 import com.android.annotations.VisibleForTesting;
     40 import com.android.ide.common.rendering.api.ResourceValue;
     41 import com.android.ide.common.resources.ResourceResolver;
     42 import com.android.ide.eclipse.adt.AdtPlugin;
     43 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
     44 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
     45 import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
     46 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
     47 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     48 import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard;
     49 import com.android.util.Pair;
     50 
     51 import org.eclipse.core.resources.IFile;
     52 import org.eclipse.core.resources.IProject;
     53 import org.eclipse.core.runtime.CoreException;
     54 import org.eclipse.core.runtime.IProgressMonitor;
     55 import org.eclipse.core.runtime.OperationCanceledException;
     56 import org.eclipse.core.runtime.Path;
     57 import org.eclipse.jface.text.ITextSelection;
     58 import org.eclipse.jface.viewers.ITreeSelection;
     59 import org.eclipse.ltk.core.refactoring.Change;
     60 import org.eclipse.ltk.core.refactoring.Refactoring;
     61 import org.eclipse.ltk.core.refactoring.RefactoringStatus;
     62 import org.eclipse.ltk.core.refactoring.TextFileChange;
     63 import org.eclipse.text.edits.InsertEdit;
     64 import org.eclipse.text.edits.MultiTextEdit;
     65 import org.eclipse.wst.sse.core.StructuredModelManager;
     66 import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
     67 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
     68 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
     69 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
     70 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
     71 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
     72 import org.w3c.dom.Attr;
     73 import org.w3c.dom.Element;
     74 import org.w3c.dom.NamedNodeMap;
     75 import org.w3c.dom.Node;
     76 
     77 import java.io.IOException;
     78 import java.util.ArrayList;
     79 import java.util.HashSet;
     80 import java.util.List;
     81 import java.util.Map;
     82 import java.util.Set;
     83 import java.util.TreeMap;
     84 
     85 /**
     86  * Extracts the selection and writes it out as a separate layout file, then adds an
     87  * include to that new layout file. Interactively asks the user for a new name for the
     88  * layout.
     89  * <p>
     90  * Remaining work to do / Possible enhancements:
     91  * <ul>
     92  * <li>Optionally look in other files in the project and attempt to set style attributes
     93  * in other cases where the style attributes match?
     94  * <li>If the elements we are extracting from already contain a style attribute, set that
     95  * style as the parent style of the current style?
     96  * <li>Add a parent-style picker to the wizard (initialized with the above if applicable)
     97  * <li>Pick up indentation settings from the XML module
     98  * <li>Integrate with themes somehow -- make an option to have the extracted style go into
     99  *    the theme instead
    100  * </ul>
    101  */
    102 @SuppressWarnings("restriction") // XML model
    103 public class ExtractStyleRefactoring extends VisualRefactoring {
    104     private static final String KEY_NAME = "name";                        //$NON-NLS-1$
    105     private static final String KEY_REMOVE_EXTRACTED = "removeextracted"; //$NON-NLS-1$
    106     private static final String KEY_REMOVE_ALL = "removeall";             //$NON-NLS-1$
    107     private static final String KEY_APPLY_STYLE = "applystyle";           //$NON-NLS-1$
    108     private static final String KEY_PARENT = "parent";           //$NON-NLS-1$
    109     private String mStyleName;
    110     /** The name of the file in res/values/ that the style will be added to. Normally
    111      * res/values/styles.xml - but unit tests pick other names */
    112     private String mStyleFileName = "styles.xml";
    113     /** Set a style reference on the extracted elements? */
    114     private boolean mApplyStyle;
    115     /** Remove the attributes that were extracted? */
    116     private boolean mRemoveExtracted;
    117     /** List of attributes chosen by the user to be extracted */
    118     private List<Attr> mChosenAttributes = new ArrayList<Attr>();
    119     /** Remove all attributes that match the extracted attributes names, regardless of value */
    120     private boolean mRemoveAll;
    121     /** The parent style to extend */
    122     private String mParent;
    123     /** The full list of available attributes in the refactoring */
    124     private Map<String, List<Attr>> mAvailableAttributes;
    125 
    126     /**
    127      * This constructor is solely used by {@link Descriptor},
    128      * to replay a previous refactoring.
    129      * @param arguments argument map created by #createArgumentMap.
    130      */
    131     ExtractStyleRefactoring(Map<String, String> arguments) {
    132         super(arguments);
    133         mStyleName = arguments.get(KEY_NAME);
    134         mRemoveExtracted = Boolean.parseBoolean(arguments.get(KEY_REMOVE_EXTRACTED));
    135         mRemoveAll = Boolean.parseBoolean(arguments.get(KEY_REMOVE_ALL));
    136         mApplyStyle = Boolean.parseBoolean(arguments.get(KEY_APPLY_STYLE));
    137         mParent = arguments.get(KEY_PARENT);
    138         if (mParent != null && mParent.length() == 0) {
    139             mParent = null;
    140         }
    141     }
    142 
    143     public ExtractStyleRefactoring(
    144             IFile file,
    145             LayoutEditorDelegate delegate,
    146             ITextSelection selection,
    147             ITreeSelection treeSelection) {
    148         super(file, delegate, selection, treeSelection);
    149     }
    150 
    151     @VisibleForTesting
    152     ExtractStyleRefactoring(List<Element> selectedElements, LayoutEditorDelegate editor) {
    153         super(selectedElements, editor);
    154     }
    155 
    156     @Override
    157     public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException,
    158             OperationCanceledException {
    159         RefactoringStatus status = new RefactoringStatus();
    160 
    161         try {
    162             pm.beginTask("Checking preconditions...", 6);
    163 
    164             if (mSelectionStart == -1 || mSelectionEnd == -1) {
    165                 status.addFatalError("No selection to extract");
    166                 return status;
    167             }
    168 
    169             // This also ensures that we have a valid DOM model:
    170             if (mElements.size() == 0) {
    171                 status.addFatalError("Nothing to extract");
    172                 return status;
    173             }
    174 
    175             pm.worked(1);
    176             return status;
    177 
    178         } finally {
    179             pm.done();
    180         }
    181     }
    182 
    183     @Override
    184     protected VisualRefactoringDescriptor createDescriptor() {
    185         String comment = getName();
    186         return new Descriptor(
    187                 mProject.getName(), //project
    188                 comment,            //description
    189                 comment,            //comment
    190                 createArgumentMap());
    191     }
    192 
    193     @Override
    194     protected Map<String, String> createArgumentMap() {
    195         Map<String, String> args = super.createArgumentMap();
    196         args.put(KEY_NAME, mStyleName);
    197         args.put(KEY_REMOVE_EXTRACTED, Boolean.toString(mRemoveExtracted));
    198         args.put(KEY_REMOVE_ALL, Boolean.toString(mRemoveAll));
    199         args.put(KEY_APPLY_STYLE, Boolean.toString(mApplyStyle));
    200         args.put(KEY_PARENT, mParent != null ? mParent : "");
    201 
    202         return args;
    203     }
    204 
    205     @Override
    206     public String getName() {
    207         return "Extract Style";
    208     }
    209 
    210     void setStyleName(String styleName) {
    211         mStyleName = styleName;
    212     }
    213 
    214     void setStyleFileName(String styleFileName) {
    215         mStyleFileName = styleFileName;
    216     }
    217 
    218     void setChosenAttributes(List<Attr> attributes) {
    219         mChosenAttributes = attributes;
    220     }
    221 
    222     void setRemoveExtracted(boolean removeExtracted) {
    223         mRemoveExtracted = removeExtracted;
    224     }
    225 
    226     void setApplyStyle(boolean applyStyle) {
    227         mApplyStyle = applyStyle;
    228     }
    229 
    230     void setRemoveAll(boolean removeAll) {
    231         mRemoveAll = removeAll;
    232     }
    233 
    234     void setParent(String parent) {
    235         mParent = parent;
    236     }
    237 
    238     // ---- Actual implementation of Extract Style modification computation ----
    239 
    240     /**
    241      * Returns two items: a map from attribute name to a list of attribute nodes of that
    242      * name, and a subset of these attributes that fall within the text selection
    243      * (used to drive initial selection in the wizard)
    244      */
    245     Pair<Map<String, List<Attr>>, Set<Attr>> getAvailableAttributes() {
    246         mAvailableAttributes = new TreeMap<String, List<Attr>>();
    247         Set<Attr> withinSelection = new HashSet<Attr>();
    248         for (Element element : getElements()) {
    249             IndexedRegion elementRegion = getRegion(element);
    250             boolean allIncluded =
    251                 (mOriginalSelectionStart <= elementRegion.getStartOffset() &&
    252                  mOriginalSelectionEnd >= elementRegion.getEndOffset());
    253 
    254             NamedNodeMap attributeMap = element.getAttributes();
    255             for (int i = 0, n = attributeMap.getLength(); i < n; i++) {
    256                 Attr attribute = (Attr) attributeMap.item(i);
    257 
    258                 String name = attribute.getLocalName();
    259                 if (!isStylableAttribute(name)) {
    260                     // Don't offer to extract attributes that don't make sense in
    261                     // styles (like "id" or "style"), or attributes that the user
    262                     // probably does not want to define in styles (like layout
    263                     // attributes such as layout_width, or the label of a button etc).
    264                     // This makes the options offered listed in the wizard simpler.
    265                     // In special cases where the user *does* want to set one of these
    266                     // attributes, they can always do it manually so optimize for
    267                     // the common case here.
    268                     continue;
    269                 }
    270 
    271                 // Skip attributes that are in a namespace other than the Android one
    272                 String namespace = attribute.getNamespaceURI();
    273                 if (namespace != null && !ANDROID_URI.equals(namespace)) {
    274                     continue;
    275                 }
    276 
    277                 if (!allIncluded) {
    278                     IndexedRegion region = getRegion(attribute);
    279                     boolean attributeIncluded = mOriginalSelectionStart < region.getEndOffset() &&
    280                         mOriginalSelectionEnd >= region.getStartOffset();
    281                     if (attributeIncluded) {
    282                         withinSelection.add(attribute);
    283                     }
    284                 } else {
    285                     withinSelection.add(attribute);
    286                 }
    287 
    288                 List<Attr> list = mAvailableAttributes.get(name);
    289                 if (list == null) {
    290                     list = new ArrayList<Attr>();
    291                     mAvailableAttributes.put(name, list);
    292                 }
    293                 list.add(attribute);
    294             }
    295         }
    296 
    297         return Pair.of(mAvailableAttributes, withinSelection);
    298     }
    299 
    300     /**
    301      * Returns whether the given local attribute name is one the style wizard
    302      * should present as a selectable attribute to be extracted.
    303      *
    304      * @param name the attribute name, not including a namespace prefix
    305      * @return true if the name is one that the user can extract
    306      */
    307     public static boolean isStylableAttribute(String name) {
    308         return !(name == null
    309                 || name.equals(ATTR_ID)
    310                 || name.startsWith(ATTR_STYLE)
    311                 || (name.startsWith(ATTR_LAYOUT_PREFIX) &&
    312                         !name.startsWith(ATTR_LAYOUT_MARGIN))
    313                 || name.equals(ATTR_TEXT)
    314                 || name.equals(ATTR_HINT)
    315                 || name.equals(ATTR_SRC)
    316                 || name.equals(ATTR_ON_CLICK));
    317     }
    318 
    319     IFile getStyleFile(IProject project) {
    320         return project.getFile(new Path(FD_RESOURCES + WS_SEP + FD_RES_VALUES + WS_SEP
    321                 + mStyleFileName));
    322     }
    323 
    324     @Override
    325     protected List<Change> computeChanges(IProgressMonitor monitor) {
    326         List<Change> changes = new ArrayList<Change>();
    327         if (mChosenAttributes.size() == 0) {
    328             return changes;
    329         }
    330 
    331         IFile file = getStyleFile(mDelegate.getEditor().getProject());
    332         boolean createFile = !file.exists();
    333         int insertAtIndex;
    334         String initialIndent = null;
    335         if (!createFile) {
    336             Pair<Integer, String> context = computeInsertContext(file);
    337             insertAtIndex = context.getFirst();
    338             initialIndent = context.getSecond();
    339         } else {
    340             insertAtIndex = 0;
    341         }
    342 
    343         TextFileChange addFile = new TextFileChange("Create new separate style declaration", file);
    344         addFile.setTextType(EXT_XML);
    345         changes.add(addFile);
    346         String styleString = computeStyleDeclaration(createFile, initialIndent);
    347         addFile.setEdit(new InsertEdit(insertAtIndex, styleString));
    348 
    349         // Remove extracted attributes?
    350         MultiTextEdit rootEdit = new MultiTextEdit();
    351         if (mRemoveExtracted || mRemoveAll) {
    352             for (Attr attribute : mChosenAttributes) {
    353                 List<Attr> list = mAvailableAttributes.get(attribute.getLocalName());
    354                 for (Attr attr : list) {
    355                     if (mRemoveAll || attr.getValue().equals(attribute.getValue())) {
    356                         removeAttribute(rootEdit, attr);
    357                     }
    358                 }
    359             }
    360         }
    361 
    362         // Set the style attribute?
    363         if (mApplyStyle) {
    364             for (Element element : getElements()) {
    365                 String value = ResourceResolver.PREFIX_RESOURCE_REF +
    366                    ResourceResolver.REFERENCE_STYLE + mStyleName;
    367                 setAttribute(rootEdit, element, null, null, ATTR_STYLE, value);
    368             }
    369         }
    370 
    371         if (rootEdit.hasChildren()) {
    372             IFile sourceFile = mDelegate.getEditor().getInputFile();
    373             if (sourceFile == null) {
    374                 return changes;
    375             }
    376             TextFileChange change = new TextFileChange(sourceFile.getName(), sourceFile);
    377             change.setTextType(EXT_XML);
    378             changes.add(change);
    379 
    380             if (AdtPrefs.getPrefs().getFormatGuiXml()) {
    381                 MultiTextEdit formatted = reformat(rootEdit, XmlFormatStyle.LAYOUT);
    382                 if (formatted != null) {
    383                     rootEdit = formatted;
    384                 }
    385             }
    386 
    387             change.setEdit(rootEdit);
    388         }
    389 
    390         return changes;
    391     }
    392 
    393     private String computeStyleDeclaration(boolean createFile, String initialIndent) {
    394         StringBuilder sb = new StringBuilder();
    395         if (createFile) {
    396             sb.append(NewXmlFileWizard.XML_HEADER_LINE);
    397             sb.append('<').append(ROOT_ELEMENT).append(' ');
    398             sb.append(XMLNS_COLON).append(ANDROID_NS_NAME).append('=').append('"');
    399             sb.append(ANDROID_URI);
    400             sb.append('"').append('>').append('\n');
    401         }
    402 
    403         // Indent. Use the existing indent found for previous <style> elements in
    404         // the resource file - but if that indent was 0 (e.g. <style> elements are
    405         // at the left margin) only use it to indent the style elements and use a real
    406         // nonzero indent for its children.
    407         String indent = "    "; //$NON-NLS-1$
    408         if (initialIndent == null) {
    409             initialIndent = indent;
    410         } else if (initialIndent.length() > 0) {
    411             indent = initialIndent;
    412         }
    413         sb.append(initialIndent);
    414         String styleTag = "style"; //$NON-NLS-1$ // TODO - use constant in parallel changeset
    415         sb.append('<').append(styleTag).append(' ').append(NAME_ATTR).append('=').append('"');
    416         sb.append(mStyleName);
    417         sb.append('"');
    418         if (mParent != null) {
    419             sb.append(' ').append(PARENT_ATTR).append('=').append('"');
    420             sb.append(mParent);
    421             sb.append('"');
    422         }
    423         sb.append('>').append('\n');
    424 
    425         for (Attr attribute : mChosenAttributes) {
    426             sb.append(initialIndent).append(indent);
    427             sb.append('<').append(ITEM_TAG).append(' ').append(NAME_ATTR).append('=').append('"');
    428             // We've already enforced that regardless of prefix, only attributes with
    429             // an Android namespace can be in the set of chosen attributes. Rewrite the
    430             // prefix to android here.
    431             if (attribute.getPrefix() != null) {
    432                 sb.append(ANDROID_NS_NAME_PREFIX);
    433             }
    434             sb.append(attribute.getLocalName());
    435             sb.append('"').append('>');
    436             sb.append(attribute.getValue());
    437             sb.append('<').append('/').append(ITEM_TAG).append('>').append('\n');
    438         }
    439         sb.append(initialIndent).append('<').append('/').append(styleTag).append('>').append('\n');
    440 
    441         if (createFile) {
    442             sb.append('<').append('/').append(ROOT_ELEMENT).append('>').append('\n');
    443         }
    444         String styleString = sb.toString();
    445         return styleString;
    446     }
    447 
    448     /** Computes the location in the file to insert the new style element at, as well as
    449      * the exact indent string to use to indent the {@code <style>} element.
    450      * @param file the styles.xml file to insert into
    451      * @return a pair of an insert offset and an indent string
    452      */
    453     private Pair<Integer, String> computeInsertContext(final IFile file) {
    454         int insertAtIndex = -1;
    455         // Find the insert of the final </resources> item where we will insert
    456         // the new style elements.
    457         String indent = null;
    458         IModelManager modelManager = StructuredModelManager.getModelManager();
    459         IStructuredModel model = null;
    460         try {
    461             model = modelManager.getModelForRead(file);
    462             if (model instanceof IDOMModel) {
    463                 IDOMModel domModel = (IDOMModel) model;
    464                 IDOMDocument otherDocument = domModel.getDocument();
    465                 Element root = otherDocument.getDocumentElement();
    466                 Node lastChild = root.getLastChild();
    467                 if (lastChild != null) {
    468                     if (lastChild instanceof IndexedRegion) {
    469                         IndexedRegion region = (IndexedRegion) lastChild;
    470                         insertAtIndex = region.getStartOffset() + region.getLength();
    471                     }
    472 
    473                     // Compute indent
    474                     while (lastChild != null) {
    475                         if (lastChild.getNodeType() == Node.ELEMENT_NODE) {
    476                             IStructuredDocument document = model.getStructuredDocument();
    477                             indent = AndroidXmlEditor.getIndent(document, lastChild);
    478                             break;
    479                         }
    480                         lastChild = lastChild.getPreviousSibling();
    481                     }
    482                 }
    483             }
    484         } catch (IOException e) {
    485             AdtPlugin.log(e, null);
    486         } catch (CoreException e) {
    487             AdtPlugin.log(e, null);
    488         } finally {
    489             if (model != null) {
    490                 model.releaseFromRead();
    491             }
    492         }
    493 
    494         if (insertAtIndex == -1) {
    495             String contents = AdtPlugin.readFile(file);
    496             insertAtIndex = contents.indexOf("</" + ROOT_ELEMENT + ">"); //$NON-NLS-1$
    497             if (insertAtIndex == -1) {
    498                 insertAtIndex = contents.length();
    499             }
    500         }
    501 
    502         return Pair.of(insertAtIndex, indent);
    503     }
    504 
    505     @Override
    506     VisualRefactoringWizard createWizard() {
    507         return new ExtractStyleWizard(this, mDelegate);
    508     }
    509 
    510     public static class Descriptor extends VisualRefactoringDescriptor {
    511         public Descriptor(String project, String description, String comment,
    512                 Map<String, String> arguments) {
    513             super("com.android.ide.eclipse.adt.refactoring.extract.style", //$NON-NLS-1$
    514                     project, description, comment, arguments);
    515         }
    516 
    517         @Override
    518         protected Refactoring createRefactoring(Map<String, String> args) {
    519             return new ExtractStyleRefactoring(args);
    520         }
    521     }
    522 
    523     /**
    524      * Determines the parent style to be used for this refactoring
    525      *
    526      * @return the parent style to be used for this refactoring
    527      */
    528     public String getParentStyle() {
    529         Set<String> styles = new HashSet<String>();
    530         for (Element element : getElements()) {
    531             // Includes "" for elements not setting the style
    532             styles.add(element.getAttribute(ATTR_STYLE));
    533         }
    534 
    535         if (styles.size() > 1) {
    536             // The elements differ in what style attributes they are set to
    537             return null;
    538         }
    539 
    540         String style = styles.iterator().next();
    541         if (style != null && style.length() > 0) {
    542             return style;
    543         }
    544 
    545         // None of the elements set the style -- see if they have the same widget types
    546         // and if so offer to extend the theme style for that widget type
    547 
    548         Set<String> types = new HashSet<String>();
    549         for (Element element : getElements()) {
    550             types.add(element.getTagName());
    551         }
    552 
    553         if (types.size() == 1) {
    554             String view = DescriptorsUtils.getBasename(types.iterator().next());
    555 
    556             ResourceResolver resolver = mDelegate.getGraphicalEditor().getResourceResolver();
    557             // Look up the theme item name, which for a Button would be "buttonStyle", and so on.
    558             String n = Character.toLowerCase(view.charAt(0)) + view.substring(1)
    559                 + "Style"; //$NON-NLS-1$
    560             ResourceValue value = resolver.findItemInTheme(n);
    561             if (value != null) {
    562                 ResourceValue resolvedValue = resolver.resolveResValue(value);
    563                 String name = resolvedValue.getName();
    564                 if (name != null) {
    565                     if (resolvedValue.isFramework()) {
    566                         return ResourceResolver.PREFIX_ANDROID + name;
    567                     } else {
    568                         return name;
    569                     }
    570                 }
    571             }
    572         }
    573 
    574         return null;
    575     }
    576 }
    577