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()); + } + } +}