OSDN Git Service

ADT XML String Refactoring: fix refusing to edit @+id/blah.
authorRaphael <raphael@google.com>
Tue, 29 Sep 2009 23:02:33 +0000 (16:02 -0700)
committerRaphael <raphael@google.com>
Tue, 29 Sep 2009 23:43:53 +0000 (16:43 -0700)
It correctly only refuses to edit @string/blah now.

This CL also does a bit of refactoring; I extracted some methods
and a class to make it a bit easier to read.

BUG 2066460

Change-Id: I95a34d28d6390ab0cc075f05ea83ceec04993ae9

eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java [new file with mode: 0755]

index c37fb6b..20ef355 100644 (file)
@@ -50,26 +50,7 @@ import org.eclipse.jdt.core.compiler.InvalidInputException;
 import org.eclipse.jdt.core.dom.AST;
 import org.eclipse.jdt.core.dom.ASTNode;
 import org.eclipse.jdt.core.dom.ASTParser;
-import org.eclipse.jdt.core.dom.ASTVisitor;
-import org.eclipse.jdt.core.dom.ClassInstanceCreation;
 import org.eclipse.jdt.core.dom.CompilationUnit;
-import org.eclipse.jdt.core.dom.Expression;
-import org.eclipse.jdt.core.dom.IMethodBinding;
-import org.eclipse.jdt.core.dom.ITypeBinding;
-import org.eclipse.jdt.core.dom.IVariableBinding;
-import org.eclipse.jdt.core.dom.MethodDeclaration;
-import org.eclipse.jdt.core.dom.MethodInvocation;
-import org.eclipse.jdt.core.dom.Modifier;
-import org.eclipse.jdt.core.dom.Name;
-import org.eclipse.jdt.core.dom.SimpleName;
-import org.eclipse.jdt.core.dom.SimpleType;
-import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
-import org.eclipse.jdt.core.dom.StringLiteral;
-import org.eclipse.jdt.core.dom.Type;
-import org.eclipse.jdt.core.dom.TypeDeclaration;
-import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
-import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
-import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
 import org.eclipse.jface.text.ITextSelection;
@@ -106,7 +87,6 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.TreeMap;
 
 /**
  * This refactoring extracts a string from a file and replaces it by an Android resource ID
@@ -402,7 +382,8 @@ public class ExtractStringRefactoring extends Refactoring {
             }
 
             if (!status.isOK()) {
-                status.addFatalError("Selection must be inside a Java source or an Android Layout XML file.");
+                status.addFatalError(
+                        "Selection must be inside a Java source or an Android Layout XML file.");
             }
 
         } finally {
@@ -521,7 +502,8 @@ public class ExtractStringRefactoring extends Refactoring {
                     }
 
                     if (node == null) {
-                        status.addFatalError("The selection does not match any element in the XML document.");
+                        status.addFatalError(
+                                "The selection does not match any element in the XML document.");
                         return status.isOK();
                     }
 
@@ -542,79 +524,10 @@ public class ExtractStringRefactoring extends Refactoring {
                             sdoc.getRegionAtCharacterOffset(selStart);
                         if (region != null &&
                                 DOMRegionContext.XML_TAG_NAME.equals(region.getType())) {
-                            // The region gives us the textual representation of the XML element
-                            // where the selection starts, split using sub-regions. We now just
-                            // need to iterate through the sub-regions to find which one
-                            // contains the actual selection. We're interested in an attribute
-                            // value however when we find one we want to memorize the attribute
-                            // name that was defined just before.
-
-                            int startInRegion = selStart - region.getStartOffset();
-
-                            int nb = region.getNumberOfRegions();
-                            ITextRegionList list = region.getRegions();
-                            String currAttrValue = null;
-
-                            for (int i = 0; i < nb; i++) {
-                                ITextRegion subRegion = list.get(i);
-                                String type = subRegion.getType();
-
-                                if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) {
-                                    currAttrName = region.getText(subRegion);
-
-                                    // I like to select the attribute definition and invoke
-                                    // the extract string wizard. So if the selection is on
-                                    // the attribute name part, find the value that is just
-                                    // after and use it as if it were the selection.
-
-                                    if (subRegion.getStart() <= startInRegion &&
-                                            startInRegion < subRegion.getTextEnd()) {
-                                        // A well-formed attribute is composed of a name,
-                                        // an equal sign and the value. There can't be any space
-                                        // in between, which makes the parsing a lot easier.
-                                        if (i <= nb - 3 &&
-                                                DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS.equals(
-                                                                       list.get(i + 1).getType())) {
-                                            subRegion = list.get(i + 2);
-                                            type = subRegion.getType();
-                                            if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(
-                                                    type)) {
-                                                currAttrValue = region.getText(subRegion);
-                                            }
-                                        }
-                                    }
-
-                                } else if (subRegion.getStart() <= startInRegion &&
-                                        startInRegion < subRegion.getTextEnd() &&
-                                        DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) {
-                                    currAttrValue = region.getText(subRegion);
-                                }
-
-                                if (currAttrValue != null) {
-                                    // We found the value. Only accept it if not empty
-                                    // and if we found an attribute name before.
-                                    String text = currAttrValue;
-
-                                    // The attribute value will contain the XML quotes. Remove them.
-                                    int len = text.length();
-                                    if (len >= 2 &&
-                                            text.charAt(0) == '"' &&
-                                            text.charAt(len - 1) == '"') {
-                                        text = text.substring(1, len - 1);
-                                    } else if (len >= 2 &&
-                                            text.charAt(0) == '\'' &&
-                                            text.charAt(len - 1) == '\'') {
-                                        text = text.substring(1, len - 1);
-                                    }
-                                    if (text.length() > 0 && currAttrName != null) {
-                                        // Setting mTokenString to non-null marks the fact we
-                                        // accept this attribute.
-                                        mTokenString = text;
-                                    }
-
-                                    break;
-                                }
-                            }
+                            // Find if any sub-region representing an attribute contains the
+                            // selection. If it does, returns the name of the attribute in
+                            // currAttrName and returns the value in the field mTokenString.
+                            currAttrName = findSelectionInRegion(region, selStart);
 
                             if (mTokenString == null) {
                                 status.addFatalError(
@@ -625,67 +538,10 @@ public class ExtractStringRefactoring extends Refactoring {
 
                     if (mTokenString != null && node != null && currAttrName != null) {
 
-                        UiElementNode rootUiNode = editor.getUiRootNode();
-                        UiElementNode currentUiNode =
-                            rootUiNode == null ? null : rootUiNode.findXmlNode(node);
-                        ReferenceAttributeDescriptor attrDesc = null;
-
-                        if (currentUiNode != null) {
-                            // remove any namespace prefix from the attribute name
-                            String name = currAttrName;
-                            int pos = name.indexOf(':');
-                            if (pos > 0 && pos < name.length() - 1) {
-                                name = name.substring(pos + 1);
-                            }
-
-                            for (UiAttributeNode attrNode : currentUiNode.getUiAttributes()) {
-                                if (attrNode.getDescriptor().getXmlLocalName().equals(name)) {
-                                    AttributeDescriptor desc = attrNode.getDescriptor();
-                                    if (desc instanceof ReferenceAttributeDescriptor) {
-                                        attrDesc = (ReferenceAttributeDescriptor) desc;
-                                    }
-                                    break;
-                                }
-                            }
-                        }
-
-                        // The attribute descriptor is a resource reference. It must either accept
-                        // of any resource type or specifically accept string types.
-                        if (attrDesc != null &&
-                                (attrDesc.getResourceType() == null ||
-                                 attrDesc.getResourceType() == ResourceType.STRING)) {
-                            // We have one more check to do: is the current string value already
-                            // an Android XML string reference? If so, we can't edit it.
-                            if (mTokenString.startsWith("@")) { //$NON-NLS-1$
-                                int pos1 = 0;
-                                if (mTokenString.length() > 1 && mTokenString.charAt(1) == '+') {
-                                    pos1++;
-                                }
-                                int pos2 = mTokenString.indexOf('/');
-                                if (pos2 > pos1) {
-                                    String kind = mTokenString.substring(pos1 + 1, pos2);
-                                    mTokenString = null;
-                                    status.addFatalError(String.format(
-                                            "The attribute %1$s already contains a %2$s reference.",
-                                            currAttrName,
-                                            kind));
-                                }
-                            }
-
-                            if (mTokenString != null) {
-                                // We're done with all our checks. mTokenString contains the
-                                // current attribute value. We don't memorize the region nor the
-                                // attribute, however we memorize the textual attribute name so
-                                // that we can offer replacement for all its occurrences.
-                                mXmlAttributeName = currAttrName;
-                            }
-
-                        } else {
-                            mTokenString = null;
-                            status.addFatalError(String.format(
-                                    "The attribute %1$s does not accept a string reference.",
-                                    currAttrName));
-                        }
+                        // Validate that the attribute accepts a string reference.
+                        // This sets mTokenString to null by side-effect when it fails and
+                        // adds a fatal error to the status as needed.
+                        validateSelectedAttribute(editor, node, currAttrName, status);
 
                     } else {
                         // We shouldn't get here: we're missing one of the token string, the node
@@ -708,6 +564,163 @@ public class ExtractStringRefactoring extends Refactoring {
     }
 
     /**
+     * The region gives us the textual representation of the XML element
+     * where the selection starts, split using sub-regions. We now just
+     * need to iterate through the sub-regions to find which one
+     * contains the actual selection. We're interested in an attribute
+     * value however when we find one we want to memorize the attribute
+     * name that was defined just before.
+     *
+     * @return When the cursor is on a valid attribute name or value, returns the string of
+     * attribute name. As a side-effect, returns the value of the attribute in {@link #mTokenString}
+     */
+    private String findSelectionInRegion(IStructuredDocumentRegion region, int selStart) {
+
+        String currAttrName = null;
+
+        int startInRegion = selStart - region.getStartOffset();
+
+        int nb = region.getNumberOfRegions();
+        ITextRegionList list = region.getRegions();
+        String currAttrValue = null;
+
+        for (int i = 0; i < nb; i++) {
+            ITextRegion subRegion = list.get(i);
+            String type = subRegion.getType();
+
+            if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) {
+                currAttrName = region.getText(subRegion);
+
+                // I like to select the attribute definition and invoke
+                // the extract string wizard. So if the selection is on
+                // the attribute name part, find the value that is just
+                // after and use it as if it were the selection.
+
+                if (subRegion.getStart() <= startInRegion &&
+                        startInRegion < subRegion.getTextEnd()) {
+                    // A well-formed attribute is composed of a name,
+                    // an equal sign and the value. There can't be any space
+                    // in between, which makes the parsing a lot easier.
+                    if (i <= nb - 3 &&
+                            DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS.equals(
+                                                   list.get(i + 1).getType())) {
+                        subRegion = list.get(i + 2);
+                        type = subRegion.getType();
+                        if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(
+                                type)) {
+                            currAttrValue = region.getText(subRegion);
+                        }
+                    }
+                }
+
+            } else if (subRegion.getStart() <= startInRegion &&
+                    startInRegion < subRegion.getTextEnd() &&
+                    DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) {
+                currAttrValue = region.getText(subRegion);
+            }
+
+            if (currAttrValue != null) {
+                // We found the value. Only accept it if not empty
+                // and if we found an attribute name before.
+                String text = currAttrValue;
+
+                // The attribute value will contain the XML quotes. Remove them.
+                int len = text.length();
+                if (len >= 2 &&
+                        text.charAt(0) == '"' &&
+                        text.charAt(len - 1) == '"') {
+                    text = text.substring(1, len - 1);
+                } else if (len >= 2 &&
+                        text.charAt(0) == '\'' &&
+                        text.charAt(len - 1) == '\'') {
+                    text = text.substring(1, len - 1);
+                }
+                if (text.length() > 0 && currAttrName != null) {
+                    // Setting mTokenString to non-null marks the fact we
+                    // accept this attribute.
+                    mTokenString = text;
+                }
+
+                break;
+            }
+        }
+
+        return currAttrName;
+    }
+
+    /**
+     * Validates that the attribute accepts a string reference.
+     * This sets mTokenString to null by side-effect when it fails and
+     * adds a fatal error to the status as needed.
+     */
+    private void validateSelectedAttribute(AndroidEditor editor, Node node,
+            String attrName, RefactoringStatus status) {
+        UiElementNode rootUiNode = editor.getUiRootNode();
+        UiElementNode currentUiNode =
+            rootUiNode == null ? null : rootUiNode.findXmlNode(node);
+        ReferenceAttributeDescriptor attrDesc = null;
+
+        if (currentUiNode != null) {
+            // remove any namespace prefix from the attribute name
+            String name = attrName;
+            int pos = name.indexOf(':');
+            if (pos > 0 && pos < name.length() - 1) {
+                name = name.substring(pos + 1);
+            }
+
+            for (UiAttributeNode attrNode : currentUiNode.getUiAttributes()) {
+                if (attrNode.getDescriptor().getXmlLocalName().equals(name)) {
+                    AttributeDescriptor desc = attrNode.getDescriptor();
+                    if (desc instanceof ReferenceAttributeDescriptor) {
+                        attrDesc = (ReferenceAttributeDescriptor) desc;
+                    }
+                    break;
+                }
+            }
+        }
+
+        // The attribute descriptor is a resource reference. It must either accept
+        // of any resource type or specifically accept string types.
+        if (attrDesc != null &&
+                (attrDesc.getResourceType() == null ||
+                 attrDesc.getResourceType() == ResourceType.STRING)) {
+            // We have one more check to do: is the current string value already
+            // an Android XML string reference? If so, we can't edit it.
+            if (mTokenString.startsWith("@")) {                             //$NON-NLS-1$
+                int pos1 = 0;
+                if (mTokenString.length() > 1 && mTokenString.charAt(1) == '+') {
+                    pos1++;
+                }
+                int pos2 = mTokenString.indexOf('/');
+                if (pos2 > pos1) {
+                    String kind = mTokenString.substring(pos1 + 1, pos2);
+                    if (ResourceType.STRING.getName().equals(kind)) {                            //$NON-NLS-1$
+                        mTokenString = null;
+                        status.addFatalError(String.format(
+                                "The attribute %1$s already contains a %2$s reference.",
+                                attrName,
+                                kind));
+                    }
+                }
+            }
+
+            if (mTokenString != null) {
+                // We're done with all our checks. mTokenString contains the
+                // current attribute value. We don't memorize the region nor the
+                // attribute, however we memorize the textual attribute name so
+                // that we can offer replacement for all its occurrences.
+                mXmlAttributeName = attrName;
+            }
+
+        } else {
+            mTokenString = null;
+            status.addFatalError(String.format(
+                    "The attribute %1$s does not accept a string reference.",
+                    attrName));
+        }
+    }
+
+    /**
      * Tests from org.eclipse.jdt.internal.corext.refactoringChecks#validateEdit()
      * Might not be useful.
      *
@@ -1373,412 +1386,6 @@ public class ExtractStringRefactoring extends Refactoring {
         return null;
     }
 
-    public class ReplaceStringsVisitor extends ASTVisitor {
-
-        private static final String CLASS_ANDROID_CONTEXT    = "android.content.Context"; //$NON-NLS-1$
-        private static final String CLASS_JAVA_CHAR_SEQUENCE = "java.lang.CharSequence";  //$NON-NLS-1$
-        private static final String CLASS_JAVA_STRING        = "java.lang.String";        //$NON-NLS-1$
-
-
-        private final AST mAst;
-        private final ASTRewrite mRewriter;
-        private final String mOldString;
-        private final String mRQualifier;
-        private final String mXmlId;
-        private final ArrayList<TextEditGroup> mEditGroups;
-
-        public ReplaceStringsVisitor(AST ast,
-                ASTRewrite astRewrite,
-                ArrayList<TextEditGroup> editGroups,
-                String oldString,
-                String rQualifier,
-                String xmlId) {
-            mAst = ast;
-            mRewriter = astRewrite;
-            mEditGroups = editGroups;
-            mOldString = oldString;
-            mRQualifier = rQualifier;
-            mXmlId = xmlId;
-        }
-
-        @SuppressWarnings("unchecked")    //$NON-NLS-1$
-        @Override
-        public boolean visit(StringLiteral node) {
-            if (node.getLiteralValue().equals(mOldString)) {
-
-                // We want to analyze the calling context to understand whether we can
-                // just replace the string literal by the named int constant (R.id.foo)
-                // or if we should generate a Context.getString() call.
-                boolean useGetResource = false;
-                useGetResource = examineVariableDeclaration(node) ||
-                                    examineMethodInvocation(node);
-
-                Name qualifierName = mAst.newName(mRQualifier + ".string");     //$NON-NLS-1$
-                SimpleName idName = mAst.newSimpleName(mXmlId);
-                ASTNode newNode = mAst.newQualifiedName(qualifierName, idName);
-                String title = "Replace string by ID";
-
-                if (useGetResource) {
-
-                    Expression context = methodHasContextArgument(node);
-                    if (context == null && !isClassDerivedFromContext(node)) {
-                        // if we don't have a class that derives from Context and
-                        // we don't have a Context method argument, then try a bit harder:
-                        // can we find a method or a field that will give us a context?
-                        context = findContextFieldOrMethod(node);
-
-                        if (context == null) {
-                            // If not, let's  write Context.getString(), which is technically
-                            // invalid but makes it a good clue on how to fix it.
-                            context = mAst.newSimpleName("Context");            //$NON-NLS-1$
-                        }
-                    }
-
-                    MethodInvocation mi2 = mAst.newMethodInvocation();
-                    mi2.setName(mAst.newSimpleName("getString"));               //$NON-NLS-1$
-                    mi2.setExpression(context);
-                    mi2.arguments().add(newNode);
-
-                    newNode = mi2;
-                    title = "Replace string by Context.getString(R.string...)";
-                }
-
-                TextEditGroup editGroup = new TextEditGroup(title);
-                mEditGroups.add(editGroup);
-                mRewriter.replace(node, newNode, editGroup);
-            }
-            return super.visit(node);
-        }
-
-        /**
-         * Examines if the StringLiteral is part of of an assignment to a string,
-         * e.g. String foo = id.
-         *
-         * The parent fragment is of syntax "var = expr" or "var[] = expr".
-         * We want the type of the variable, which is either held by a
-         * VariableDeclarationStatement ("type [fragment]") or by a
-         * VariableDeclarationExpression. In either case, the type can be an array
-         * but for us all that matters is to know whether the type is an int or
-         * a string.
-         */
-        private boolean examineVariableDeclaration(StringLiteral node) {
-            VariableDeclarationFragment fragment = findParentClass(node,
-                    VariableDeclarationFragment.class);
-
-            if (fragment != null) {
-                ASTNode parent = fragment.getParent();
-
-                Type type = null;
-                if (parent instanceof VariableDeclarationStatement) {
-                    type = ((VariableDeclarationStatement) parent).getType();
-                } else if (parent instanceof VariableDeclarationExpression) {
-                    type = ((VariableDeclarationExpression) parent).getType();
-                }
-
-                if (type instanceof SimpleType) {
-                    return isJavaString(type.resolveBinding());
-                }
-            }
-
-            return false;
-        }
-
-        /**
-         * If the expression is part of a method invocation (aka a function call) or a
-         * class instance creation (aka a "new SomeClass" constructor call), we try to
-         * find the type of the argument being used. If it is a String (most likely), we
-         * want to return true (to generate a getString() call). However if there might
-         * be a similar method that takes an int, in which case we don't want to do that.
-         *
-         * This covers the case of Activity.setTitle(int resId) vs setTitle(String str).
-         */
-        @SuppressWarnings("unchecked")  //$NON-NLS-1$
-        private boolean examineMethodInvocation(StringLiteral node) {
-
-            ASTNode parent = null;
-            List arguments = null;
-            IMethodBinding methodBinding = null;
-
-            MethodInvocation invoke = findParentClass(node, MethodInvocation.class);
-            if (invoke != null) {
-                parent = invoke;
-                arguments = invoke.arguments();
-                methodBinding = invoke.resolveMethodBinding();
-            } else {
-                ClassInstanceCreation newclass = findParentClass(node, ClassInstanceCreation.class);
-                if (newclass != null) {
-                    parent = newclass;
-                    arguments = newclass.arguments();
-                    methodBinding = newclass.resolveConstructorBinding();
-                }
-            }
-
-            if (parent != null && arguments != null && methodBinding != null) {
-                // We want to know which argument this is.
-                // Walk up the hierarchy again to find the immediate child of the parent,
-                // which should turn out to be one of the invocation arguments.
-                ASTNode child = null;
-                for (ASTNode n = node; n != parent; ) {
-                    ASTNode p = n.getParent();
-                    if (p == parent) {
-                        child = n;
-                        break;
-                    }
-                    n = p;
-                }
-                if (child == null) {
-                    // This can't happen: a parent of 'node' must be the child of 'parent'.
-                    return false;
-                }
-
-                // Find the index
-                int index = 0;
-                for (Object arg : arguments) {
-                    if (arg == child) {
-                        break;
-                    }
-                    index++;
-                }
-
-                if (index == arguments.size()) {
-                    // This can't happen: one of the arguments of 'invoke' must be 'child'.
-                    return false;
-                }
-
-                // Eventually we want to determine if the parameter is a string type,
-                // in which case a Context.getString() call must be generated.
-                boolean useStringType = false;
-
-                // Find the type of that argument
-                ITypeBinding[] types = methodBinding.getParameterTypes();
-                if (index < types.length) {
-                    ITypeBinding type = types[index];
-                    useStringType = isJavaString(type);
-                }
-
-                // Now that we know that this method takes a String parameter, can we find
-                // a variant that would accept an int for the same parameter position?
-                if (useStringType) {
-                    String name = methodBinding.getName();
-                    ITypeBinding clazz = methodBinding.getDeclaringClass();
-                    nextMethod: for (IMethodBinding mb2 : clazz.getDeclaredMethods()) {
-                        if (methodBinding == mb2 || !mb2.getName().equals(name)) {
-                            continue;
-                        }
-                        // We found a method with the same name. We want the same parameters
-                        // except that the one at 'index' must be an int type.
-                        ITypeBinding[] types2 = mb2.getParameterTypes();
-                        int len2 = types2.length;
-                        if (types.length == len2) {
-                            for (int i = 0; i < len2; i++) {
-                                if (i == index) {
-                                    ITypeBinding type2 = types2[i];
-                                    if (!("int".equals(type2.getQualifiedName()))) {   //$NON-NLS-1$
-                                        // The argument at 'index' is not an int.
-                                        continue nextMethod;
-                                    }
-                                } else if (!types[i].equals(types2[i])) {
-                                    // One of the other arguments do not match our original method
-                                    continue nextMethod;
-                                }
-                            }
-                            // If we got here, we found a perfect match: a method with the same
-                            // arguments except the one at 'index' is an int. In this case we
-                            // don't need to convert our R.id into a string.
-                            useStringType = false;
-                            break;
-                        }
-                    }
-                }
-
-                return useStringType;
-            }
-            return false;
-        }
-
-        /**
-         * Examines if the StringLiteral is part of a method declaration (a.k.a. a function
-         * definition) which takes a Context argument.
-         * If such, it returns the name of the variable as a {@link SimpleName}.
-         * Otherwise it returns null.
-         */
-        private SimpleName methodHasContextArgument(StringLiteral node) {
-            MethodDeclaration decl = findParentClass(node, MethodDeclaration.class);
-            if (decl != null) {
-                for (Object obj : decl.parameters()) {
-                    if (obj instanceof SingleVariableDeclaration) {
-                        SingleVariableDeclaration var = (SingleVariableDeclaration) obj;
-                        if (isAndroidContext(var.getType())) {
-                            return mAst.newSimpleName(var.getName().getIdentifier());
-                        }
-                    }
-                }
-            }
-            return null;
-        }
-
-        /**
-         * Walks up the node hierarchy to find the class (aka type) where this statement
-         * is used and returns true if this class derives from android.content.Context.
-         */
-        private boolean isClassDerivedFromContext(StringLiteral node) {
-            TypeDeclaration clazz = findParentClass(node, TypeDeclaration.class);
-            if (clazz != null) {
-                // This is the class that the user is currently writing, so it can't be
-                // a Context by itself, it has to be derived from it.
-                return isAndroidContext(clazz.getSuperclassType());
-            }
-            return false;
-        }
-
-        private Expression findContextFieldOrMethod(StringLiteral node) {
-            TypeDeclaration clazz = findParentClass(node, TypeDeclaration.class);
-            ITypeBinding clazzType = clazz == null ? null : clazz.resolveBinding();
-            return findContextFieldOrMethod(clazzType);
-        }
-
-        private Expression findContextFieldOrMethod(ITypeBinding clazzType) {
-            TreeMap<Integer, Expression> results = new TreeMap<Integer, Expression>();
-            findContextCandidates(results, clazzType, 0 /*superType*/);
-            if (results.size() > 0) {
-                Integer bestRating = results.keySet().iterator().next();
-                return results.get(bestRating);
-            }
-            return null;
-        }
-
-        /**
-         * Find all method or fields that are candidates for providing a Context.
-         * There can be various choices amongst this class or its super classes.
-         * Sort them by rating in the results map.
-         *
-         * The best ever choice is to find a method with no argument that returns a Context.
-         * The second suitable choice is to find a Context field.
-         * The least desirable choice is to find a method with arguments. It's not really
-         * desirable since we can't generate these arguments automatically.
-         *
-         * Methods and fields from supertypes are ignored if they are private.
-         *
-         * The rating is reversed: the lowest rating integer is used for the best candidate.
-         * Because the superType argument is actually a recursion index, this makes the most
-         * immediate classes more desirable.
-         *
-         * @param results The map that accumulates the rating=>expression results. The lower
-         *                rating number is the best candidate.
-         * @param clazzType The class examined.
-         * @param superType The recursion index.
-         *                  0 for the immediate class, 1 for its super class, etc.
-         */
-        private void findContextCandidates(TreeMap<Integer, Expression> results,
-                ITypeBinding clazzType,
-                int superType) {
-            for (IMethodBinding mb : clazzType.getDeclaredMethods()) {
-                // If we're looking at supertypes, we can't use private methods.
-                if (superType != 0 && Modifier.isPrivate(mb.getModifiers())) {
-                    continue;
-                }
-
-                if (isAndroidContext(mb.getReturnType())) {
-                    // We found a method that returns something derived from Context.
-
-                    int argsLen = mb.getParameterTypes().length;
-                    if (argsLen == 0) {
-                        // We'll favor any method that takes no argument,
-                        // That would be the best candidate ever, so we can stop here.
-                        MethodInvocation mi = mAst.newMethodInvocation();
-                        mi.setName(mAst.newSimpleName(mb.getName()));
-                        results.put(Integer.MIN_VALUE, mi);
-                        return;
-                    } else {
-                        // A method with arguments isn't as interesting since we wouldn't
-                        // know how to populate such arguments. We'll use it if there are
-                        // no other alternatives. We'll favor the one with the less arguments.
-                        Integer rating = Integer.valueOf(10000 + 1000 * superType + argsLen);
-                        if (!results.containsKey(rating)) {
-                            MethodInvocation mi = mAst.newMethodInvocation();
-                            mi.setName(mAst.newSimpleName(mb.getName()));
-                            results.put(rating, mi);
-                        }
-                    }
-                }
-            }
-
-            // A direct Context field would be more interesting than a method with
-            // arguments. Try to find one.
-            for (IVariableBinding var : clazzType.getDeclaredFields()) {
-                // If we're looking at supertypes, we can't use private field.
-                if (superType != 0 && Modifier.isPrivate(var.getModifiers())) {
-                    continue;
-                }
-
-                if (isAndroidContext(var.getType())) {
-                    // We found such a field. Let's use it.
-                    Integer rating = Integer.valueOf(superType);
-                    results.put(rating, mAst.newSimpleName(var.getName()));
-                    break;
-                }
-            }
-
-            // Examine the super class to see if we can locate a better match
-            clazzType = clazzType.getSuperclass();
-            if (clazzType != null) {
-                findContextCandidates(results, clazzType, superType + 1);
-            }
-        }
-
-        /**
-         * Walks up the node hierarchy and returns the first ASTNode of the requested class.
-         * Only look at parents.
-         *
-         * Implementation note: this is a generic method so that it returns the node already
-         * casted to the requested type.
-         */
-        @SuppressWarnings("unchecked")
-        private <T extends ASTNode> T findParentClass(ASTNode node, Class<T> clazz) {
-            for (node = node.getParent(); node != null; node = node.getParent()) {
-                if (node.getClass().equals(clazz)) {
-                    return (T) node;
-                }
-            }
-            return null;
-        }
-
-        /**
-         * Returns true if the given type is or derives from android.content.Context.
-         */
-        private boolean isAndroidContext(Type type) {
-            if (type != null) {
-                return isAndroidContext(type.resolveBinding());
-            }
-            return false;
-        }
-
-        /**
-         * Returns true if the given type is or derives from android.content.Context.
-         */
-        private boolean isAndroidContext(ITypeBinding type) {
-            for (; type != null; type = type.getSuperclass()) {
-                if (CLASS_ANDROID_CONTEXT.equals(type.getQualifiedName())) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        /**
-         * Returns true if this type binding represents a String or CharSequence type.
-         */
-        private boolean isJavaString(ITypeBinding type) {
-            for (; type != null; type = type.getSuperclass()) {
-                if (CLASS_JAVA_STRING.equals(type.getQualifiedName()) ||
-                    CLASS_JAVA_CHAR_SEQUENCE.equals(type.getQualifiedName())) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
     /**
      * Step 3 of 3 of the refactoring: returns the {@link Change} that will be able to do the
      * work and creates a descriptor that can be used to replay that refactoring later.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java
new file mode 100755 (executable)
index 0000000..dd0f9f4
--- /dev/null
@@ -0,0 +1,457 @@
+/*\r
+ * Copyright (C) 2009 The Android Open Source Project\r
+ *\r
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.eclipse.org/org/documents/epl-v10.php\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.android.ide.eclipse.adt.internal.refactorings.extractstring;\r
+\r
+import org.eclipse.jdt.core.dom.AST;\r
+import org.eclipse.jdt.core.dom.ASTNode;\r
+import org.eclipse.jdt.core.dom.ASTVisitor;\r
+import org.eclipse.jdt.core.dom.ClassInstanceCreation;\r
+import org.eclipse.jdt.core.dom.Expression;\r
+import org.eclipse.jdt.core.dom.IMethodBinding;\r
+import org.eclipse.jdt.core.dom.ITypeBinding;\r
+import org.eclipse.jdt.core.dom.IVariableBinding;\r
+import org.eclipse.jdt.core.dom.MethodDeclaration;\r
+import org.eclipse.jdt.core.dom.MethodInvocation;\r
+import org.eclipse.jdt.core.dom.Modifier;\r
+import org.eclipse.jdt.core.dom.Name;\r
+import org.eclipse.jdt.core.dom.SimpleName;\r
+import org.eclipse.jdt.core.dom.SimpleType;\r
+import org.eclipse.jdt.core.dom.SingleVariableDeclaration;\r
+import org.eclipse.jdt.core.dom.StringLiteral;\r
+import org.eclipse.jdt.core.dom.Type;\r
+import org.eclipse.jdt.core.dom.TypeDeclaration;\r
+import org.eclipse.jdt.core.dom.VariableDeclarationExpression;\r
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;\r
+import org.eclipse.jdt.core.dom.VariableDeclarationStatement;\r
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;\r
+import org.eclipse.text.edits.TextEditGroup;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.TreeMap;\r
+\r
+/**\r
+ * Visitor used by {@link ExtractStringRefactoring} to extract a string from an existing\r
+ * Java source and replace it by an Android XML string reference.\r
+ *\r
+ * @see ExtractStringRefactoring#computeJavaChanges\r
+ */\r
+class ReplaceStringsVisitor extends ASTVisitor {\r
+\r
+    private static final String CLASS_ANDROID_CONTEXT    = "android.content.Context"; //$NON-NLS-1$\r
+    private static final String CLASS_JAVA_CHAR_SEQUENCE = "java.lang.CharSequence";  //$NON-NLS-1$\r
+    private static final String CLASS_JAVA_STRING        = "java.lang.String";        //$NON-NLS-1$\r
+\r
+\r
+    private final AST mAst;\r
+    private final ASTRewrite mRewriter;\r
+    private final String mOldString;\r
+    private final String mRQualifier;\r
+    private final String mXmlId;\r
+    private final ArrayList<TextEditGroup> mEditGroups;\r
+\r
+    public ReplaceStringsVisitor(AST ast,\r
+            ASTRewrite astRewrite,\r
+            ArrayList<TextEditGroup> editGroups,\r
+            String oldString,\r
+            String rQualifier,\r
+            String xmlId) {\r
+        mAst = ast;\r
+        mRewriter = astRewrite;\r
+        mEditGroups = editGroups;\r
+        mOldString = oldString;\r
+        mRQualifier = rQualifier;\r
+        mXmlId = xmlId;\r
+    }\r
+\r
+    @SuppressWarnings("unchecked")    //$NON-NLS-1$\r
+    @Override\r
+    public boolean visit(StringLiteral node) {\r
+        if (node.getLiteralValue().equals(mOldString)) {\r
+\r
+            // We want to analyze the calling context to understand whether we can\r
+            // just replace the string literal by the named int constant (R.id.foo)\r
+            // or if we should generate a Context.getString() call.\r
+            boolean useGetResource = false;\r
+            useGetResource = examineVariableDeclaration(node) ||\r
+                                examineMethodInvocation(node);\r
+\r
+            Name qualifierName = mAst.newName(mRQualifier + ".string");     //$NON-NLS-1$\r
+            SimpleName idName = mAst.newSimpleName(mXmlId);\r
+            ASTNode newNode = mAst.newQualifiedName(qualifierName, idName);\r
+            String title = "Replace string by ID";\r
+\r
+            if (useGetResource) {\r
+\r
+                Expression context = methodHasContextArgument(node);\r
+                if (context == null && !isClassDerivedFromContext(node)) {\r
+                    // if we don't have a class that derives from Context and\r
+                    // we don't have a Context method argument, then try a bit harder:\r
+                    // can we find a method or a field that will give us a context?\r
+                    context = findContextFieldOrMethod(node);\r
+\r
+                    if (context == null) {\r
+                        // If not, let's  write Context.getString(), which is technically\r
+                        // invalid but makes it a good clue on how to fix it.\r
+                        context = mAst.newSimpleName("Context");            //$NON-NLS-1$\r
+                    }\r
+                }\r
+\r
+                MethodInvocation mi2 = mAst.newMethodInvocation();\r
+                mi2.setName(mAst.newSimpleName("getString"));               //$NON-NLS-1$\r
+                mi2.setExpression(context);\r
+                mi2.arguments().add(newNode);\r
+\r
+                newNode = mi2;\r
+                title = "Replace string by Context.getString(R.string...)";\r
+            }\r
+\r
+            TextEditGroup editGroup = new TextEditGroup(title);\r
+            mEditGroups.add(editGroup);\r
+            mRewriter.replace(node, newNode, editGroup);\r
+        }\r
+        return super.visit(node);\r
+    }\r
+\r
+    /**\r
+     * Examines if the StringLiteral is part of of an assignment to a string,\r
+     * e.g. String foo = id.\r
+     *\r
+     * The parent fragment is of syntax "var = expr" or "var[] = expr".\r
+     * We want the type of the variable, which is either held by a\r
+     * VariableDeclarationStatement ("type [fragment]") or by a\r
+     * VariableDeclarationExpression. In either case, the type can be an array\r
+     * but for us all that matters is to know whether the type is an int or\r
+     * a string.\r
+     */\r
+    private boolean examineVariableDeclaration(StringLiteral node) {\r
+        VariableDeclarationFragment fragment = findParentClass(node,\r
+                VariableDeclarationFragment.class);\r
+\r
+        if (fragment != null) {\r
+            ASTNode parent = fragment.getParent();\r
+\r
+            Type type = null;\r
+            if (parent instanceof VariableDeclarationStatement) {\r
+                type = ((VariableDeclarationStatement) parent).getType();\r
+            } else if (parent instanceof VariableDeclarationExpression) {\r
+                type = ((VariableDeclarationExpression) parent).getType();\r
+            }\r
+\r
+            if (type instanceof SimpleType) {\r
+                return isJavaString(type.resolveBinding());\r
+            }\r
+        }\r
+\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * If the expression is part of a method invocation (aka a function call) or a\r
+     * class instance creation (aka a "new SomeClass" constructor call), we try to\r
+     * find the type of the argument being used. If it is a String (most likely), we\r
+     * want to return true (to generate a getString() call). However if there might\r
+     * be a similar method that takes an int, in which case we don't want to do that.\r
+     *\r
+     * This covers the case of Activity.setTitle(int resId) vs setTitle(String str).\r
+     */\r
+    @SuppressWarnings("unchecked")  //$NON-NLS-1$\r
+    private boolean examineMethodInvocation(StringLiteral node) {\r
+\r
+        ASTNode parent = null;\r
+        List arguments = null;\r
+        IMethodBinding methodBinding = null;\r
+\r
+        MethodInvocation invoke = findParentClass(node, MethodInvocation.class);\r
+        if (invoke != null) {\r
+            parent = invoke;\r
+            arguments = invoke.arguments();\r
+            methodBinding = invoke.resolveMethodBinding();\r
+        } else {\r
+            ClassInstanceCreation newclass = findParentClass(node, ClassInstanceCreation.class);\r
+            if (newclass != null) {\r
+                parent = newclass;\r
+                arguments = newclass.arguments();\r
+                methodBinding = newclass.resolveConstructorBinding();\r
+            }\r
+        }\r
+\r
+        if (parent != null && arguments != null && methodBinding != null) {\r
+            // We want to know which argument this is.\r
+            // Walk up the hierarchy again to find the immediate child of the parent,\r
+            // which should turn out to be one of the invocation arguments.\r
+            ASTNode child = null;\r
+            for (ASTNode n = node; n != parent; ) {\r
+                ASTNode p = n.getParent();\r
+                if (p == parent) {\r
+                    child = n;\r
+                    break;\r
+                }\r
+                n = p;\r
+            }\r
+            if (child == null) {\r
+                // This can't happen: a parent of 'node' must be the child of 'parent'.\r
+                return false;\r
+            }\r
+\r
+            // Find the index\r
+            int index = 0;\r
+            for (Object arg : arguments) {\r
+                if (arg == child) {\r
+                    break;\r
+                }\r
+                index++;\r
+            }\r
+\r
+            if (index == arguments.size()) {\r
+                // This can't happen: one of the arguments of 'invoke' must be 'child'.\r
+                return false;\r
+            }\r
+\r
+            // Eventually we want to determine if the parameter is a string type,\r
+            // in which case a Context.getString() call must be generated.\r
+            boolean useStringType = false;\r
+\r
+            // Find the type of that argument\r
+            ITypeBinding[] types = methodBinding.getParameterTypes();\r
+            if (index < types.length) {\r
+                ITypeBinding type = types[index];\r
+                useStringType = isJavaString(type);\r
+            }\r
+\r
+            // Now that we know that this method takes a String parameter, can we find\r
+            // a variant that would accept an int for the same parameter position?\r
+            if (useStringType) {\r
+                String name = methodBinding.getName();\r
+                ITypeBinding clazz = methodBinding.getDeclaringClass();\r
+                nextMethod: for (IMethodBinding mb2 : clazz.getDeclaredMethods()) {\r
+                    if (methodBinding == mb2 || !mb2.getName().equals(name)) {\r
+                        continue;\r
+                    }\r
+                    // We found a method with the same name. We want the same parameters\r
+                    // except that the one at 'index' must be an int type.\r
+                    ITypeBinding[] types2 = mb2.getParameterTypes();\r
+                    int len2 = types2.length;\r
+                    if (types.length == len2) {\r
+                        for (int i = 0; i < len2; i++) {\r
+                            if (i == index) {\r
+                                ITypeBinding type2 = types2[i];\r
+                                if (!("int".equals(type2.getQualifiedName()))) {   //$NON-NLS-1$\r
+                                    // The argument at 'index' is not an int.\r
+                                    continue nextMethod;\r
+                                }\r
+                            } else if (!types[i].equals(types2[i])) {\r
+                                // One of the other arguments do not match our original method\r
+                                continue nextMethod;\r
+                            }\r
+                        }\r
+                        // If we got here, we found a perfect match: a method with the same\r
+                        // arguments except the one at 'index' is an int. In this case we\r
+                        // don't need to convert our R.id into a string.\r
+                        useStringType = false;\r
+                        break;\r
+                    }\r
+                }\r
+            }\r
+\r
+            return useStringType;\r
+        }\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Examines if the StringLiteral is part of a method declaration (a.k.a. a function\r
+     * definition) which takes a Context argument.\r
+     * If such, it returns the name of the variable as a {@link SimpleName}.\r
+     * Otherwise it returns null.\r
+     */\r
+    private SimpleName methodHasContextArgument(StringLiteral node) {\r
+        MethodDeclaration decl = findParentClass(node, MethodDeclaration.class);\r
+        if (decl != null) {\r
+            for (Object obj : decl.parameters()) {\r
+                if (obj instanceof SingleVariableDeclaration) {\r
+                    SingleVariableDeclaration var = (SingleVariableDeclaration) obj;\r
+                    if (isAndroidContext(var.getType())) {\r
+                        return mAst.newSimpleName(var.getName().getIdentifier());\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Walks up the node hierarchy to find the class (aka type) where this statement\r
+     * is used and returns true if this class derives from android.content.Context.\r
+     */\r
+    private boolean isClassDerivedFromContext(StringLiteral node) {\r
+        TypeDeclaration clazz = findParentClass(node, TypeDeclaration.class);\r
+        if (clazz != null) {\r
+            // This is the class that the user is currently writing, so it can't be\r
+            // a Context by itself, it has to be derived from it.\r
+            return isAndroidContext(clazz.getSuperclassType());\r
+        }\r
+        return false;\r
+    }\r
+\r
+    private Expression findContextFieldOrMethod(StringLiteral node) {\r
+        TypeDeclaration clazz = findParentClass(node, TypeDeclaration.class);\r
+        ITypeBinding clazzType = clazz == null ? null : clazz.resolveBinding();\r
+        return findContextFieldOrMethod(clazzType);\r
+    }\r
+\r
+    private Expression findContextFieldOrMethod(ITypeBinding clazzType) {\r
+        TreeMap<Integer, Expression> results = new TreeMap<Integer, Expression>();\r
+        findContextCandidates(results, clazzType, 0 /*superType*/);\r
+        if (results.size() > 0) {\r
+            Integer bestRating = results.keySet().iterator().next();\r
+            return results.get(bestRating);\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Find all method or fields that are candidates for providing a Context.\r
+     * There can be various choices amongst this class or its super classes.\r
+     * Sort them by rating in the results map.\r
+     *\r
+     * The best ever choice is to find a method with no argument that returns a Context.\r
+     * The second suitable choice is to find a Context field.\r
+     * The least desirable choice is to find a method with arguments. It's not really\r
+     * desirable since we can't generate these arguments automatically.\r
+     *\r
+     * Methods and fields from supertypes are ignored if they are private.\r
+     *\r
+     * The rating is reversed: the lowest rating integer is used for the best candidate.\r
+     * Because the superType argument is actually a recursion index, this makes the most\r
+     * immediate classes more desirable.\r
+     *\r
+     * @param results The map that accumulates the rating=>expression results. The lower\r
+     *                rating number is the best candidate.\r
+     * @param clazzType The class examined.\r
+     * @param superType The recursion index.\r
+     *                  0 for the immediate class, 1 for its super class, etc.\r
+     */\r
+    private void findContextCandidates(TreeMap<Integer, Expression> results,\r
+            ITypeBinding clazzType,\r
+            int superType) {\r
+        for (IMethodBinding mb : clazzType.getDeclaredMethods()) {\r
+            // If we're looking at supertypes, we can't use private methods.\r
+            if (superType != 0 && Modifier.isPrivate(mb.getModifiers())) {\r
+                continue;\r
+            }\r
+\r
+            if (isAndroidContext(mb.getReturnType())) {\r
+                // We found a method that returns something derived from Context.\r
+\r
+                int argsLen = mb.getParameterTypes().length;\r
+                if (argsLen == 0) {\r
+                    // We'll favor any method that takes no argument,\r
+                    // That would be the best candidate ever, so we can stop here.\r
+                    MethodInvocation mi = mAst.newMethodInvocation();\r
+                    mi.setName(mAst.newSimpleName(mb.getName()));\r
+                    results.put(Integer.MIN_VALUE, mi);\r
+                    return;\r
+                } else {\r
+                    // A method with arguments isn't as interesting since we wouldn't\r
+                    // know how to populate such arguments. We'll use it if there are\r
+                    // no other alternatives. We'll favor the one with the less arguments.\r
+                    Integer rating = Integer.valueOf(10000 + 1000 * superType + argsLen);\r
+                    if (!results.containsKey(rating)) {\r
+                        MethodInvocation mi = mAst.newMethodInvocation();\r
+                        mi.setName(mAst.newSimpleName(mb.getName()));\r
+                        results.put(rating, mi);\r
+                    }\r
+                }\r
+            }\r
+        }\r
+\r
+        // A direct Context field would be more interesting than a method with\r
+        // arguments. Try to find one.\r
+        for (IVariableBinding var : clazzType.getDeclaredFields()) {\r
+            // If we're looking at supertypes, we can't use private field.\r
+            if (superType != 0 && Modifier.isPrivate(var.getModifiers())) {\r
+                continue;\r
+            }\r
+\r
+            if (isAndroidContext(var.getType())) {\r
+                // We found such a field. Let's use it.\r
+                Integer rating = Integer.valueOf(superType);\r
+                results.put(rating, mAst.newSimpleName(var.getName()));\r
+                break;\r
+            }\r
+        }\r
+\r
+        // Examine the super class to see if we can locate a better match\r
+        clazzType = clazzType.getSuperclass();\r
+        if (clazzType != null) {\r
+            findContextCandidates(results, clazzType, superType + 1);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Walks up the node hierarchy and returns the first ASTNode of the requested class.\r
+     * Only look at parents.\r
+     *\r
+     * Implementation note: this is a generic method so that it returns the node already\r
+     * casted to the requested type.\r
+     */\r
+    @SuppressWarnings("unchecked")\r
+    private <T extends ASTNode> T findParentClass(ASTNode node, Class<T> clazz) {\r
+        for (node = node.getParent(); node != null; node = node.getParent()) {\r
+            if (node.getClass().equals(clazz)) {\r
+                return (T) node;\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Returns true if the given type is or derives from android.content.Context.\r
+     */\r
+    private boolean isAndroidContext(Type type) {\r
+        if (type != null) {\r
+            return isAndroidContext(type.resolveBinding());\r
+        }\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Returns true if the given type is or derives from android.content.Context.\r
+     */\r
+    private boolean isAndroidContext(ITypeBinding type) {\r
+        for (; type != null; type = type.getSuperclass()) {\r
+            if (CLASS_ANDROID_CONTEXT.equals(type.getQualifiedName())) {\r
+                return true;\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Returns true if this type binding represents a String or CharSequence type.\r
+     */\r
+    private boolean isJavaString(ITypeBinding type) {\r
+        for (; type != null; type = type.getSuperclass()) {\r
+            if (CLASS_JAVA_STRING.equals(type.getQualifiedName()) ||\r
+                CLASS_JAVA_CHAR_SEQUENCE.equals(type.getQualifiedName())) {\r
+                return true;\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+}\r