Add support for reporting the use of deprecated types, methods, and fields from within JSNI methods.
Support the @SuppressWarnings annotation on native methods.

Patch by: bobv
Review by: scottb


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4958 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java b/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
index eef261f..a34680a 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.client.UnsafeNativeLong;
 import com.google.gwt.dev.jdt.FindJsniRefVisitor;
+import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.util.InstalledHelpInfo;
 import com.google.gwt.dev.util.JsniRef;
 
@@ -25,8 +26,12 @@
 import org.eclipse.jdt.internal.compiler.ast.ASTNode;
 import org.eclipse.jdt.internal.compiler.ast.Annotation;
 import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
 import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
 import org.eclipse.jdt.internal.compiler.ast.TypeReference;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
 import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
@@ -40,6 +45,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
 import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
 
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
@@ -85,7 +91,7 @@
     }
 
     private void checkFieldRef(ReferenceBinding clazz, JsniRef jsniRef,
-        Set<String> errors) {
+        Set<String> errors, Map<String, Set<String>> warnings) {
       assert jsniRef.isField();
       FieldBinding target = getField(clazz, jsniRef);
       if (target == null) {
@@ -96,10 +102,14 @@
             + jsniRef.memberName() + "': type '" + typeString(target.type)
             + "' is not safe to access in JSNI code");
       }
+      if (target.isDeprecated()) {
+        add(warnings, "deprecation", "Referencing deprecated field '"
+            + jsniRef.className() + "." + jsniRef.memberName() + "'");
+      }
     }
 
     private void checkMethodRef(ReferenceBinding clazz, JsniRef jsniRef,
-        Set<String> errors) {
+        Set<String> errors, Map<String, Set<String>> warnings) {
       assert jsniRef.isMethod();
       MethodBinding target = getMethod(clazz, jsniRef);
       if (target == null) {
@@ -125,6 +135,11 @@
           }
         }
       }
+
+      if (target.isDeprecated()) {
+        add(warnings, "deprecation", "Referencing deprecated method '"
+            + jsniRef.className() + "." + jsniRef.memberName() + "'");
+      }
     }
 
     private void checkRefs(MethodDeclaration meth, ClassScope scope) {
@@ -138,20 +153,27 @@
 
       for (String jsniRefString : sloppyRefsVisitor.getJsniRefs()) {
         JsniRef jsniRef = JsniRef.parse(jsniRefString);
+        Map<String, Set<String>> warnings = new LinkedHashMap<String, Set<String>>();
+
         if (jsniRef != null) {
           ReferenceBinding clazz = findClass(jsniRef);
           if (looksLikeAnonymousClass(jsniRef)
               || (clazz != null && clazz.isAnonymousType())) {
-            GWTProblem.recordInCud(ProblemSeverities.Warning, meth, cud,
-                "Referencing class '" + jsniRef.className()
-                    + ": JSNI references to anonymous classes are deprecated",
-                null);
+            add(warnings, "deprecation", "Referencing class '"
+                + jsniRef.className()
+                + ": JSNI references to anonymous classes are deprecated");
+
           } else if (clazz != null) {
+            if (clazz.isDeprecated()) {
+              add(warnings, "deprecation", "Referencing deprecated class '"
+                  + jsniRef.className() + "'");
+            }
+
             Set<String> refErrors = new LinkedHashSet<String>();
             if (jsniRef.isMethod()) {
-              checkMethodRef(clazz, jsniRef, refErrors);
+              checkMethodRef(clazz, jsniRef, refErrors, warnings);
             } else {
-              checkFieldRef(clazz, jsniRef, refErrors);
+              checkFieldRef(clazz, jsniRef, refErrors, warnings);
             }
             if (!refErrors.isEmpty()) {
               errors.put(jsniRefString, refErrors);
@@ -163,6 +185,14 @@
                 null);
           }
         }
+
+        filterWarnings(meth, warnings);
+        for (Set<String> set : warnings.values()) {
+          for (String warning : set) {
+            GWTProblem.recordInCud(ProblemSeverities.Warning, meth, cud,
+                warning, null);
+          }
+        }
       }
 
       if (!errors.isEmpty()) {
@@ -180,8 +210,8 @@
     }
 
     /**
-     * Check whether the argument type is the <code>long</code> primitive
-     * type. If the argument is <code>null</code>, returns <code>false</code>.
+     * Check whether the argument type is the <code>long</code> primitive type.
+     * If the argument is <code>null</code>, returns <code>false</code>.
      */
     private boolean containsLong(TypeBinding type) {
       if (type instanceof BaseTypeBinding) {
@@ -199,6 +229,55 @@
       return returnType != null && containsLong(returnType.resolveType(scope));
     }
 
+    /**
+     * Given a MethodDeclaration and a map of warnings, filter those warning
+     * messages that are keyed with a suppressed type.
+     */
+    private void filterWarnings(MethodDeclaration method,
+        Map<String, Set<String>> warnings) {
+      Annotation[] annotations = method.annotations;
+      if (annotations == null) {
+        return;
+      }
+
+      for (Annotation a : annotations) {
+        if (SuppressWarnings.class.getName().equals(
+            CharOperation.toString(((ReferenceBinding) a.resolvedType).compoundName))) {
+          for (MemberValuePair pair : a.memberValuePairs()) {
+            if (String.valueOf(pair.name).equals("value")) {
+              String[] values;
+              Expression valueExpr = pair.value;
+
+              if (valueExpr instanceof StringLiteral) {
+                // @SuppressWarnings("Foo")
+                values = new String[] {((StringLiteral) valueExpr).constant.stringValue()};
+
+              } else if (valueExpr instanceof ArrayInitializer) {
+                // @SuppressWarnings({ "Foo", "Bar"})
+                ArrayInitializer ai = (ArrayInitializer) valueExpr;
+                values = new String[ai.expressions.length];
+                for (int i = 0, j = values.length; i < j; i++) {
+                  values[i] = ((StringLiteral) ai.expressions[i]).constant.stringValue();
+                }
+              } else {
+                throw new InternalCompilerException(
+                    "Unable to analyze SuppressWarnings annotation");
+              }
+
+              for (String value : values) {
+                for (Iterator<String> it = warnings.keySet().iterator(); it.hasNext();) {
+                  if (it.next().toLowerCase().equals(value.toLowerCase())) {
+                    it.remove();
+                  }
+                }
+              }
+              return;
+            }
+          }
+        }
+      }
+    }
+
     private ReferenceBinding findClass(JsniRef jsniRef) {
       char[][] compoundName = getCompoundName(jsniRef);
       TypeBinding binding = cud.scope.getType(compoundName, compoundName.length);
@@ -319,6 +398,18 @@
     checker.check();
   }
 
+  /**
+   * Adds an entry to a map of sets.
+   */
+  private static <K, V> void add(Map<K, Set<V>> map, K key, V value) {
+    Set<V> set = map.get(key);
+    if (set == null) {
+      set = new LinkedHashSet<V>();
+      map.put(key, set);
+    }
+    set.add(value);
+  }
+
   private final CompilationUnitDeclaration cud;
 
   private JsniChecker(CompilationUnitDeclaration cud) {
diff --git a/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java b/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
index 5c2fa62..5c8c0f7 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
@@ -118,6 +118,69 @@
     }
   }
 
+  public void testDeprecationField() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  @Deprecated int bar;\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::bar;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateWarning(code, 3, "Referencing deprecated field 'Buggy.bar'");
+  }
+
+  public void testDeprecationMethod() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  @Deprecated void foo(){}\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::foo();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateWarning(code, 3, "Referencing deprecated method 'Buggy.foo'");
+  }
+
+  public void testDeprecationSuppression() {
+    StringBuffer code = new StringBuffer();
+    code.append("@Deprecated class D {\n");
+    code.append("  int bar;\n");
+    code.append("}\n");
+    code.append("class Buggy {\n");
+    code.append("  @Deprecated void foo(){}\n");
+    code.append("  @Deprecated int bar;\n");
+    code.append("  @SuppressWarnings(\"deprecation\")\n");
+    code.append("  native void jsniMethod1() /*-{\n");
+    code.append("    @Buggy::foo();\n");
+    code.append("    @Buggy::bar;\n");
+    code.append("    @D::bar;\n");
+    code.append("  }-*/;\n");
+    code.append("  @SuppressWarnings({\"deprecation\", \"other\"})\n");
+    code.append("  native void jsniMethod2() /*-{\n");
+    code.append("    @Buggy::foo();\n");
+    code.append("    @Buggy::bar;\n");
+    code.append("    @D::bar;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testDeprecationType() {
+    StringBuffer code = new StringBuffer();
+    code.append("@Deprecated class D {\n");
+    code.append("  int bar;\n");
+    code.append("}\n");
+    code.append("class Buggy {\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @D::bar;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateWarning(code, 5, "Referencing deprecated class 'D'");
+  }
+
   public void testFieldAccess() {
     StringBuffer code = new StringBuffer();
     code.append("class Buggy {\n");