Factored out some useful JsniRef manipulation methods into a helper class.
Patch by: spoon, scottb (pair prog)
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2219 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index 0956e3f..546409f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -90,6 +90,7 @@
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsSourceInfo;
+import com.google.gwt.dev.util.JsniRef;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.CompilationResult;
@@ -235,9 +236,14 @@
}
private HasEnclosingType parseJsniRef(SourceInfo info, String ident) {
- String[] parts = ident.substring(1).split("::");
- assert (parts.length == 2);
- String className = parts[0];
+ JsniRef parsed = JsniRef.parse(ident);
+ if (parsed == null) {
+ reportJsniError(info, methodDecl,
+ "Badly formatted native reference '" + ident + "'");
+ return null;
+ }
+
+ String className = parsed.className();
JReferenceType type = null;
if (!className.equals("null")) {
type = program.getFromTypeMap(className);
@@ -247,45 +253,47 @@
return null;
}
}
- String rhs = parts[1];
- int parenPos = rhs.indexOf('(');
- if (parenPos < 0) {
+
+ if (!parsed.isMethod()) {
// look for a field
+ String fieldName = parsed.memberName();
if (type == null) {
- if (rhs.equals("nullField")) {
+ if (fieldName.equals("nullField")) {
return program.getNullField();
}
} else {
for (int i = 0; i < type.fields.size(); ++i) {
JField field = type.fields.get(i);
- if (field.getName().equals(rhs)) {
+ if (field.getName().equals(fieldName)) {
return field;
}
}
}
reportJsniError(info, methodDecl,
- "Unresolvable native reference to field '" + rhs + "' in type '"
- + className + "'");
+ "Unresolvable native reference to field '" + fieldName
+ + "' in type '" + className + "'");
+ return null;
} else {
// look for a method
String almostMatches = null;
- String methodName = rhs.substring(0, parenPos);
+ String methodName = parsed.memberName();
+ String jsniSig = methodName + "(" + parsed.paramTypesString() + ")";
if (type == null) {
- if (rhs.equals("nullMethod()")) {
+ if (jsniSig.equals("nullMethod()")) {
return program.getNullMethod();
}
} else {
for (int i = 0; i < type.methods.size(); ++i) {
JMethod method = type.methods.get(i);
if (method.getName().equals(methodName)) {
- String jsniSig = JProgram.getJsniSig(method);
- if (jsniSig.equals(rhs)) {
+ String sig = JProgram.getJsniSig(method);
+ if (sig.equals(jsniSig)) {
return method;
} else if (almostMatches == null) {
- almostMatches = "'" + jsniSig + "'";
+ almostMatches = "'" + sig + "'";
} else {
- almostMatches += ", '" + jsniSig + "'";
+ almostMatches += ", '" + sig + "'";
}
}
}
@@ -295,14 +303,15 @@
reportJsniError(info, methodDecl,
"Unresolvable native reference to method '" + methodName
+ "' in type '" + className + "'");
+ return null;
} else {
reportJsniError(info, methodDecl,
"Unresolvable native reference to method '" + methodName
+ "' in type '" + className + "' (did you mean "
+ almostMatches + "?)");
+ return null;
}
}
- return null;
}
private void processField(JsNameRef nameRef, SourceInfo info,
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index 6c89d16..5475668 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -25,6 +25,7 @@
import com.google.gwt.dev.jdt.CacheManager;
import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter;
import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodMapper;
+import com.google.gwt.dev.util.JsniRef;
import com.google.gwt.util.tools.Utility;
import org.apache.commons.collections.map.ReferenceMap;
@@ -103,25 +104,20 @@
jsniMemberRef = "@java.lang.Object::toString()";
}
- // References are of the form "@class::field" or
- // "@class::method(typesigs)".
- int endClassName = jsniMemberRef.indexOf("::");
- if (endClassName == -1 || jsniMemberRef.length() < 1
- || jsniMemberRef.charAt(0) != '@') {
+ JsniRef parsed = JsniRef.parse(jsniMemberRef);
+ if (parsed == null) {
logger.log(TreeLogger.WARN, "Malformed JSNI reference '"
+ jsniMemberRef + "'; expect subsequent failures",
new NoSuchFieldError(jsniMemberRef));
return -1;
}
- String className = jsniMemberRef.substring(1, endClassName);
-
// Do the lookup by class name.
+ String className = parsed.className();
DispatchClassInfo dispClassInfo = getClassInfoFromClassName(className);
if (dispClassInfo != null) {
- String memberName = jsniMemberRef.substring(endClassName + 2);
+ String memberName = parsed.memberName();
int memberId = dispClassInfo.getMemberId(memberName);
-
if (memberId < 0) {
logger.log(TreeLogger.WARN, "Member '" + memberName
+ "' in JSNI reference '" + jsniMemberRef
diff --git a/dev/core/src/com/google/gwt/dev/util/JsniRef.java b/dev/core/src/com/google/gwt/dev/util/JsniRef.java
new file mode 100644
index 0000000..68f9a4c
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/JsniRef.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util;
+
+import com.google.gwt.core.ext.typeinfo.JniConstants;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A parsed Java reference from within a JSNI method.
+ */
+public class JsniRef {
+
+ /**
+ * A regex pattern for a Java reference in JSNI code. Its groups are:
+ * <ol>
+ * <li> the class name
+ * <li> the field or method name
+ * <li> the method parameter types, including the surrounding parentheses
+ * <li> the method parameter types, excluding the parentheses
+ * </ol>
+ */
+ private static Pattern JsniRefPattern = Pattern.compile("@?([^:]+)::([^(]+)(\\((.*)\\))?");
+
+ /**
+ * Parse a Java reference from JSNI code. This parser is forgiving; it does
+ * not always detect invalid references. If the refString is improperly
+ * formatted, returns null.
+ */
+ public static JsniRef parse(String refString) {
+ Matcher matcher = JsniRefPattern.matcher(refString);
+ if (!matcher.matches()) {
+ return null;
+ }
+
+ String className = matcher.group(1);
+ String memberName = matcher.group(2);
+ String paramTypesString = null;
+ String[] paramTypes = null;
+ if (matcher.group(3) != null) {
+ paramTypesString = matcher.group(4);
+ paramTypes = computeParamTypes(paramTypesString);
+ if (paramTypes == null) {
+ return null;
+ }
+ }
+ return new JsniRef(className, memberName, paramTypesString, paramTypes);
+ }
+
+ private static String[] computeParamTypes(String paramTypesString) {
+ ArrayList<String> types = new ArrayList<String>();
+ StringBuilder nextType = new StringBuilder();
+ boolean inRef = false;
+ for (char c : paramTypesString.toCharArray()) {
+ nextType.append(c);
+ if (inRef) {
+ if (c == JniConstants.DESC_REF_END) {
+ types.add(nextType.toString());
+ nextType.setLength(0);
+ inRef = false;
+ }
+ } else {
+ switch (c) {
+ case JniConstants.DESC_BOOLEAN:
+ case JniConstants.DESC_BYTE:
+ case JniConstants.DESC_CHAR:
+ case JniConstants.DESC_DOUBLE:
+ case JniConstants.DESC_FLOAT:
+ case JniConstants.DESC_INT:
+ case JniConstants.DESC_LONG:
+ case JniConstants.DESC_SHORT:
+ case JniConstants.DESC_VOID:
+ types.add(nextType.toString());
+ nextType.setLength(0);
+ break;
+
+ case JniConstants.DESC_ARRAY:
+ // Nothing special to do.
+ break;
+
+ case JniConstants.DESC_REF:
+ inRef = true;
+ break;
+
+ default:
+ // Bad input.
+ return null;
+ }
+ }
+ }
+
+ return types.toArray(Empty.STRINGS);
+ }
+
+ private final String className;
+ private final String memberName;
+ private final String[] paramTypes;
+ private final String paramTypesString;
+
+ private JsniRef(String className, String memberName, String paramTypesString,
+ String[] paramTypes) {
+ this.className = className;
+ this.memberName = memberName;
+ this.paramTypesString = paramTypesString;
+ this.paramTypes = paramTypes;
+ }
+
+ public String className() {
+ return className;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof JsniRef) {
+ return toString().equals(obj.toString());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ public final boolean isMethod() {
+ return paramTypesString() != null;
+ }
+
+ public String memberName() {
+ return memberName;
+ }
+
+ /**
+ * Return the list of parameter types for the method referred to by this
+ * reference.
+ */
+ public String[] paramTypes() {
+ return paramTypes;
+ }
+
+ public String paramTypesString() {
+ return paramTypesString;
+ }
+
+ @Override
+ public String toString() {
+ String ret = "@" + className + "::" + memberName;
+ if (isMethod()) {
+ ret += "(" + paramTypesString + ")";
+ }
+ return ret;
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/util/JsniRefTest.java b/dev/core/test/com/google/gwt/dev/util/JsniRefTest.java
new file mode 100644
index 0000000..47bc926
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/util/JsniRefTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the {@link JsniRef} class.
+ *
+ */
+public class JsniRefTest extends TestCase {
+ public static void testBasics() {
+ {
+ JsniRef ref = JsniRef.parse("@some.package.SomeClass::someField");
+ assertEquals("some.package.SomeClass", ref.className());
+ assertEquals("someField", ref.memberName());
+ assertFalse(ref.isMethod());
+ }
+
+ {
+ JsniRef ref = JsniRef.parse("@some.package.SomeClass::someMeth()");
+ assertEquals("some.package.SomeClass", ref.className());
+ assertEquals("someMeth", ref.memberName());
+ assertTrue(ref.isMethod());
+ assertEquals(0, ref.paramTypes().length);
+ }
+
+ {
+ // test with every JNI type included
+ JsniRef ref = JsniRef.parse("@some.package.SomeClass::someMeth("
+ + "[[ZBCDFIJLjava/lang/String;S)");
+ assertEquals("some.package.SomeClass", ref.className());
+ assertEquals("someMeth", ref.memberName());
+ assertTrue(ref.isMethod());
+ assertEquals(9, ref.paramTypes().length);
+ assertEquals("[[Z", ref.paramTypes()[0]);
+ assertEquals("B", ref.paramTypes()[1]);
+ assertEquals("C", ref.paramTypes()[2]);
+ assertEquals("D", ref.paramTypes()[3]);
+ assertEquals("F", ref.paramTypes()[4]);
+ assertEquals("I", ref.paramTypes()[5]);
+ assertEquals("J", ref.paramTypes()[6]);
+ assertEquals("Ljava/lang/String;", ref.paramTypes()[7]);
+ assertEquals("S", ref.paramTypes()[8]);
+ }
+
+ {
+ // test with no preceding at sign
+ JsniRef ref = JsniRef.parse("some.package.SomeClass::someField");
+ assertEquals("some.package.SomeClass", ref.className());
+ assertEquals("someField", ref.memberName());
+ assertFalse(ref.isMethod());
+ }
+ }
+
+ public void testEquals() {
+ String[] tests = new String[] {
+ "@some.package.SomeClass::someField",
+ "@some.package.SomeClass::someMeth()",
+ "@some.package.SomeClass::someMeth([[ZBCDFIJLjava/lang/String;S)"};
+
+ for (String test : tests) {
+ JsniRef ref1 = JsniRef.parse(test);
+ JsniRef ref2 = JsniRef.parse(test);
+ assertEquals(ref1, ref2);
+ }
+ }
+
+ public void testHashCode() {
+ String[] tests = new String[] {
+ "@some.package.SomeClass::someField",
+ "@some.package.SomeClass::someMeth()",
+ "@some.package.SomeClass::someMeth([[ZBCDFIJLjava/lang/String;S)"};
+
+ for (String test : tests) {
+ JsniRef ref1 = JsniRef.parse(test);
+ JsniRef ref2 = JsniRef.parse(test);
+ assertEquals(ref1.hashCode(), ref2.hashCode());
+ }
+ }
+
+ public void testToString() {
+ String[] tests = new String[] {
+ "@some.package.SomeClass::someField",
+ "@some.package.SomeClass::someMeth()",
+ "@some.package.SomeClass::someMeth([[ZBCDFIJLjava/lang/String;S)"};
+
+ for (String test : tests) {
+ JsniRef ref = JsniRef.parse(test);
+ assertEquals(test, ref.toString());
+ }
+ }
+}