Add usage type support for class usages
diff --git a/smalidea/META-INF/plugin.xml b/smalidea/META-INF/plugin.xml
index 4f827bd..d14d8cc 100644
--- a/smalidea/META-INF/plugin.xml
+++ b/smalidea/META-INF/plugin.xml
@@ -30,6 +30,7 @@
                                implementationClass="org.jf.smalidea.findUsages.SmaliFindUsagesProvider"/>
       <referencesSearch implementation="org.jf.smalidea.findUsages.SmaliClassReferenceSearcher"/>
       <usageTargetProvider implementation="org.jf.smalidea.findUsages.SmaliUsageTargetProvider" />
+      <usageTypeProvider implementation="org.jf.smalidea.findUsages.SmaliUsageTypeProvider"/>
   </extensions>
 
   <application-components>
diff --git a/smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliUsageTypeProvider.java b/smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliUsageTypeProvider.java
new file mode 100644
index 0000000..a0069b8
--- /dev/null
+++ b/smalidea/src/main/java/org/jf/smalidea/findUsages/SmaliUsageTypeProvider.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.smalidea.findUsages;
+
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.usages.impl.rules.UsageType;
+import com.intellij.usages.impl.rules.UsageTypeProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jf.dexlib2.Opcode;
+import org.jf.smalidea.SmaliTokens;
+import org.jf.smalidea.psi.impl.*;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+public class SmaliUsageTypeProvider implements UsageTypeProvider {
+
+    static final UsageType CLASS_DECLARATION = new UsageType("Class declaration");
+    static final UsageType VERIFICATION_ERROR = new UsageType("Usage in verification error");
+    static final UsageType FIELD_TYPE_REFERENCE = new UsageType("Usage as field type in a field reference");
+    static final UsageType FIELD_DECLARING_TYPE_REFERENCE = new UsageType("Usage as a declaring type in a field reference");
+    static final UsageType METHOD_RETURN_TYPE_REFERENCE = new UsageType("Usage as return type in a method reference");
+    static final UsageType METHOD_PARAM_REFERENCE = new UsageType("Usage as parameter in a method reference");
+    static final UsageType METHOD_DECLARING_TYPE_REFERENCE = new UsageType("Usage as a declaring type in a method reference");
+    static final UsageType LITERAL = new UsageType("Usage as a literal");
+
+    @Nullable @Override public UsageType getUsageType(PsiElement element) {
+        if (element instanceof PsiReference) {
+            PsiElement referenced = ((PsiReference) element).resolve();
+            if (referenced != null) {
+                if (referenced instanceof PsiClass) {
+                    return findClassUsageType(element);
+                }
+            }
+        }
+        return UsageType.UNCLASSIFIED;
+    }
+
+    private final Set<Opcode> newArrayInstructions = EnumSet.of(Opcode.FILLED_NEW_ARRAY, Opcode.NEW_ARRAY,
+            Opcode.FILLED_NEW_ARRAY_RANGE);
+
+    @Nullable
+    private UsageType findClassUsageType(@NotNull PsiElement element) {
+        PsiElement originalElement = element;
+
+        while (element != null) {
+            if (element instanceof SmaliFieldReference) {
+                PsiElement prev = originalElement.getPrevSibling();
+                while (prev != null) {
+                    // if the element is to the right of a colon, then it is the field type, otherwise it is
+                    // the declaring class
+                    if (prev.getNode().getElementType() == SmaliTokens.COLON) {
+                        return FIELD_TYPE_REFERENCE;
+                    }
+                    prev = prev.getPrevSibling();
+                }
+                return FIELD_DECLARING_TYPE_REFERENCE;
+            } else if (element instanceof SmaliMethodReferenceParamList) {
+                return METHOD_PARAM_REFERENCE;
+            } else if (element instanceof SmaliMethodReference) {
+                PsiElement prev = originalElement.getPrevSibling();
+                while (prev != null) {
+                    IElementType elementType = prev.getNode().getElementType();
+                    // if the element is to the right of a close paren, then it is the return type,
+                    // otherwise it is the declaring class. Any parameter type will be taken care of by the previous
+                    // "if" for SmaliMethodReferenceParamList
+                    if (elementType == SmaliTokens.CLOSE_PAREN) {
+                        return METHOD_RETURN_TYPE_REFERENCE;
+                    }
+                    prev = prev.getPrevSibling();
+                }
+                return METHOD_DECLARING_TYPE_REFERENCE;
+            } else if (element instanceof SmaliInstruction) {
+                Opcode opcode = ((SmaliInstruction) element).getOpcode();
+                if (opcode == Opcode.INSTANCE_OF) {
+                    return UsageType.CLASS_INSTANCE_OF;
+                } else if (opcode == Opcode.CHECK_CAST) {
+                    return UsageType.CLASS_CAST_TO;
+                } else if (newArrayInstructions.contains(opcode)) {
+                    return UsageType.CLASS_NEW_ARRAY;
+                } else if (opcode == Opcode.NEW_INSTANCE) {
+                    return UsageType.CLASS_NEW_OPERATOR;
+                } else if (opcode == Opcode.CONST_CLASS) {
+                    return UsageType.CLASS_CLASS_OBJECT_ACCESS;
+                } else if (opcode == Opcode.THROW_VERIFICATION_ERROR) {
+                    return VERIFICATION_ERROR;
+                }
+            } else if (element instanceof SmaliSuperStatement || element instanceof SmaliImplementsStatement) {
+                return UsageType.CLASS_EXTENDS_IMPLEMENTS_LIST;
+            } else if (element instanceof SmaliClassStatement) {
+                return CLASS_DECLARATION;
+            } else if (element instanceof SmaliMethodParamList) {
+                return UsageType.CLASS_METHOD_PARAMETER_DECLARATION;
+            } else if (element instanceof SmaliMethodPrototype) {
+                return UsageType.CLASS_METHOD_RETURN_TYPE;
+            } else if (element instanceof SmaliField) {
+                return UsageType.CLASS_FIELD_DECLARATION;
+            } else if (element instanceof SmaliCatchStatement) {
+                return UsageType.CLASS_CATCH_CLAUSE_PARAMETER_DECLARATION;
+            } else if (element instanceof SmaliLocalDebugStatement) {
+                return UsageType.CLASS_LOCAL_VAR_DECLARATION;
+            } else if (element instanceof SmaliAnnotation) {
+                return UsageType.ANNOTATION;
+            } else if (element instanceof SmaliLiteral) {
+                return LITERAL;
+            }
+            element = element.getParent();
+        }
+        return UsageType.UNCLASSIFIED;
+    }
+}
diff --git a/smalidea/src/test/java/org/jf/smalidea/findUsages/ClassUsageTypeTest.java b/smalidea/src/test/java/org/jf/smalidea/findUsages/ClassUsageTypeTest.java
new file mode 100644
index 0000000..18a6194
--- /dev/null
+++ b/smalidea/src/test/java/org/jf/smalidea/findUsages/ClassUsageTypeTest.java
@@ -0,0 +1,74 @@
+package org.jf.smalidea.findUsages;
+
+import com.intellij.usages.impl.rules.UsageType;
+
+public class ClassUsageTypeTest extends UsageTypeTest {
+    public ClassUsageTypeTest() {
+        super(new SmaliUsageTypeProvider());
+    }
+
+    public void testClassUsageTypes() throws Exception {
+        doTest("blah.smali", "" +
+                        ".class public Lbl<ref:1>ah;\n" +
+                        ".super Lbl<ref:2>ah;\n" +
+                        ".implements Lbl<ref:3>ah;\n" +
+                        "\n" +
+                        ".annotation build Lbl<ref:22>ah;\n" +
+                        "    value = .subannotation Lbl<ref:23>ah;\n" +
+                        "                value = Lbl<ref:24>ah;\n" +
+                        "            .end subannotation\n" +
+                        ".end annotation\n" +
+                        "\n" +
+                        ".field static public blah:Lbl<ref:4>ah; = Lbl<ref:25>ah;\n" +
+                        "\n" +
+                        ".method public blah(Lbl<ref:5>ah;)Lbl<ref:6>ah;\n" +
+                        "    .registers 2\n" +
+                        "    .local p0, \"this\":Lbl<ref:7>ah;\n" +
+                        "\n" +
+                        "    :start\n" +
+                        "        iget-object v0, v0, Lbl<ref:8>ah;->blah:Lbl<ref:9>ah;\n" +
+                        "\n" +
+                        "        invoke-virtual {v0}, Lbl<ref:10>ah;->blah(Lbl<ref:11>ah;)Lbl<ref:12>ah;\n" +
+                        "\n" +
+                        "        instance-of v0, v0, Lbl<ref:13>ah;\n" +
+                        "        check-cast v0, Lbl<ref:14>ah;\n" +
+                        "        new-instance v0, Lbl<ref:15>ah;\n" +
+                        "        const-class v0, Lbl<ref:16>ah;\n" +
+                        "        throw-verification-error generic-error, Lbl<ref:17>ah;\n" +
+                        "\n" +
+                        "        filled-new-array {v0, v0, v0, v0, v0}, Lbl<ref:18>ah;\n" +
+                        "        new-array v0, v0, Lbl<ref:19>ah;\n" +
+                        "        filled-new-array/range {v0}, Lbl<ref:20>ah;\n" +
+                        "    :end\n" +
+                        "\n" +
+                        "    .catch Lbl<ref:21>ah; { :start .. :end } :handler\n" +
+                        "    :handler\n" +
+                        "    return-void\n" +
+                        ".end method",
+                1, SmaliUsageTypeProvider.CLASS_DECLARATION,
+                2, UsageType.CLASS_EXTENDS_IMPLEMENTS_LIST,
+                3, UsageType.CLASS_EXTENDS_IMPLEMENTS_LIST,
+                4, UsageType.CLASS_FIELD_DECLARATION,
+                5, UsageType.CLASS_METHOD_PARAMETER_DECLARATION,
+                6, UsageType.CLASS_METHOD_RETURN_TYPE,
+                7, UsageType.CLASS_LOCAL_VAR_DECLARATION,
+                8, SmaliUsageTypeProvider.FIELD_DECLARING_TYPE_REFERENCE,
+                9, SmaliUsageTypeProvider.FIELD_TYPE_REFERENCE,
+                10, SmaliUsageTypeProvider.METHOD_DECLARING_TYPE_REFERENCE,
+                11, SmaliUsageTypeProvider.METHOD_PARAM_REFERENCE,
+                12, SmaliUsageTypeProvider.METHOD_RETURN_TYPE_REFERENCE,
+                13, UsageType.CLASS_INSTANCE_OF,
+                14, UsageType.CLASS_CAST_TO,
+                15, UsageType.CLASS_NEW_OPERATOR,
+                16, UsageType.CLASS_CLASS_OBJECT_ACCESS,
+                17, SmaliUsageTypeProvider.VERIFICATION_ERROR,
+                18, UsageType.CLASS_NEW_ARRAY,
+                19, UsageType.CLASS_NEW_ARRAY,
+                20, UsageType.CLASS_NEW_ARRAY,
+                21, UsageType.CLASS_CATCH_CLAUSE_PARAMETER_DECLARATION,
+                22, UsageType.ANNOTATION,
+                23, UsageType.ANNOTATION,
+                24, SmaliUsageTypeProvider.LITERAL,
+                25, SmaliUsageTypeProvider.LITERAL);
+    }
+}
diff --git a/smalidea/src/test/java/org/jf/smalidea/findUsages/UsageTypeTest.java b/smalidea/src/test/java/org/jf/smalidea/findUsages/UsageTypeTest.java
new file mode 100644
index 0000000..44c2f49
--- /dev/null
+++ b/smalidea/src/test/java/org/jf/smalidea/findUsages/UsageTypeTest.java
@@ -0,0 +1,73 @@
+package org.jf.smalidea.findUsages;
+
+import com.google.common.collect.Maps;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
+import com.intellij.testFramework.PsiTestCase;
+import com.intellij.usages.impl.rules.UsageType;
+import com.intellij.usages.impl.rules.UsageTypeProvider;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class UsageTypeTest extends PsiTestCase {
+    // e.g. <ref:1>, <ref:1234>, etc.
+    private static final Pattern REF_PATTERN = Pattern.compile("(<ref:([0-9]+)>)");
+
+    @NotNull
+    private final UsageTypeProvider usageTypeProvider;
+
+    public UsageTypeTest(@NotNull UsageTypeProvider usageTypeProvider) {
+        this.usageTypeProvider = usageTypeProvider;
+    }
+
+    protected void doTest(@NotNull String fileName, @NotNull String text, @NotNull Object... expectedUsageTypes)
+            throws Exception {
+        Assert.assertTrue(expectedUsageTypes.length % 2 == 0);
+
+        Map<Integer, UsageType> expectedUsageTypesMap = Maps.newHashMap();
+        for (int i=0; i<expectedUsageTypes.length; i+=2) {
+            expectedUsageTypesMap.put((Integer) expectedUsageTypes[i], (UsageType) expectedUsageTypes[i + 1]);
+        }
+
+        PsiFile psiFile = createFile(fileName, REF_PATTERN.matcher(text).replaceAll(""));
+        Map<Integer, Integer> refIndexMap = getRefIndexes(text);
+
+        for (Map.Entry<Integer, Integer> entry: refIndexMap.entrySet()) {
+            int refId = entry.getKey();
+            int index = entry.getValue();
+
+            PsiReference reference = psiFile.getFirstChild().findReferenceAt(index);
+            Assert.assertNotNull(reference);
+            if (reference instanceof PsiMultiReference) {
+                // If there are multiple reference parents, the default seems to be the last one,
+                // i.e. the highest parent. We actually want the lowest one here.
+                reference = ((PsiMultiReference) reference).getReferences()[0];
+            }
+
+            UsageType usageType = usageTypeProvider.getUsageType(reference.getElement());
+            Assert.assertNotNull(usageType);
+            Assert.assertSame(expectedUsageTypesMap.get(refId), usageType);
+            expectedUsageTypesMap.remove(refId);
+        }
+        Assert.assertTrue(expectedUsageTypesMap.isEmpty());
+    }
+
+    @NotNull
+    private Map<Integer, Integer> getRefIndexes(@NotNull String text) {
+        Matcher m = REF_PATTERN.matcher(text);
+        int correction = 0;
+        Map<Integer, Integer> refIndexes = new HashMap<Integer, Integer>();
+        while (m.find()) {
+            int refId = Integer.parseInt(m.group(2));
+            refIndexes.put(refId, m.start() - correction);
+            correction += m.end() - m.start();
+        }
+        return refIndexes;
+    }
+}