Revision: 5859 Author: [email protected] Date: Fri Jul 31 13:43:46 2009 Log: Add Name classes that deal with various flavors of Java names, and some uses in GWT code.
This was split out from the IHM patch. Patch by: jat Review by: scottb. http://code.google.com/p/google-web-toolkit/source/detail?r=5859 Added: /trunk/dev/core/src/com/google/gwt/dev/util/Name.java /trunk/dev/core/test/com/google/gwt/dev/util/NameTest.java Modified: /trunk/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java /trunk/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java /trunk/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java /trunk/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java ======================================= --- /dev/null +++ /trunk/dev/core/src/com/google/gwt/dev/util/Name.java Fri Jul 31 13:43:46 2009 @@ -0,0 +1,308 @@ +/* + * Copyright 2009 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; + +/** + * Utility methods for dealing with the various types of Java names. + */ +public class Name { + + /** + * Represents a Java class name in binary form, for example: + * {...@code org.example.Foo$Bar}. + * + * See {...@link "http://java.sun.com/docs/books/jls/third_edition/html/binaryComp.html#59892"} + */ + public static class BinaryName { + + public static String getClassName(String binaryName) { + assert isBinaryName(binaryName); + int lastDot = binaryName.lastIndexOf('.'); + if (lastDot < 0) { + return binaryName; + } + return binaryName.substring(lastDot + 1); + } + + /** + * Construct the fully qualified name of an inner class. + * + * @param outerClassBinaryName binary name of outer class, ie + * {...@code org.test.Foo} + * @param innerClassShortName short name of inner class, ie {...@code Bar} + * @return fully qualified binary name of the inner class + */ + public static String getInnerClassName(String outerClassBinaryName, + String innerClassShortName) { + assert isBinaryName(outerClassBinaryName); + return outerClassBinaryName + '$' + innerClassShortName; + } + + public static String getOuterClassName(String binaryName) { + assert isBinaryName(binaryName); + int lastDollar = binaryName.lastIndexOf('$'); + if (lastDollar < 0) { + return null; + } + return binaryName.substring(0, lastDollar); + } + + public static String getPackageName(String binaryName) { + assert isBinaryName(binaryName); + int lastDot = binaryName.lastIndexOf('.'); + if (lastDot < 0) { + return ""; + } + return binaryName.substring(0, lastDot); + } + + public static String getShortClassName(String binaryName) { + assert isBinaryName(binaryName); + String className = getClassName(binaryName); + int lastDollar = className.lastIndexOf('$', className.length() - 2); + if (lastDollar < 0) { + return className; + } + return className.substring(lastDollar + 1); + } + + public static String toInternalName(String binaryName) { + assert isBinaryName(binaryName); + return binaryName.replace('.', '/'); + } + + public static String toSourceName(String binaryName) { + assert isBinaryName(binaryName); + // don't change a trailing $ to a . + return binaryName.replaceAll("[$](\\w)", ".$1"); + } + + private BinaryName() { + } + } + + /** + * Represents a Java class name in internal form, for example: + * {...@code org/example/Foo$Bar}. + * + * See {...@link "http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#14757"} + */ + public static class InternalName { + + public static String getClassName(String name) { + assert isInternalName(name); + int lastSlash = name.lastIndexOf('/'); + if (lastSlash < 0) { + return name; + } + return name.substring(lastSlash + 1); + } + + /** + * Construct the fully qualified name of an inner class. + * + * @param outerClassInternalName internal name of outer class, + * ie {...@code org.test.Foo} + * @param innerClassShortName short name of inner class, ie {...@code Bar} + * @return fully qualified internal name of the inner class + */ + public static String getInnerClassName(String outerClassInternalName, + String innerClassShortName) { + assert isInternalName(outerClassInternalName); + return outerClassInternalName + '$' + innerClassShortName; + } + + /** + * Return the outer class name of an inner class, or null if this is not + * an inner class. + * + * @param name internal name which might be an inner class + * @return an internal name of the enclosing class or null if none + */ + public static String getOuterClassName(String name) { + int lastDollar = name.lastIndexOf('$'); + if (lastDollar < 0) { + return null; + } + return name.substring(0, lastDollar); + } + + public static String getPackageName(String name) { + assert isInternalName(name); + int lastSlash = name.lastIndexOf('/'); + if (lastSlash < 0) { + return ""; + } + return name.substring(0, lastSlash); + } + + public static String getShortClassName(String internalName) { + assert isInternalName(internalName); + String className = getClassName(internalName); + int lastDollar = className.lastIndexOf('$', className.length() - 2); + if (lastDollar < 0) { + return className; + } + return className.substring(lastDollar + 1); + } + + public static String toBinaryName(String internalName) { + assert isInternalName(internalName); + return internalName.replace('/', '.'); + } + + public static String toSourceName(String internalName) { + assert isInternalName(internalName); + // don't change a trailing $ or slash to a . + return internalName.replaceAll("[$/](\\w)", ".$1"); + } + + private InternalName() { + } + } + + /** + * Represents a Java class name in source form, for example: + * {...@code org.example.Foo.Bar}. + * + * See {...@link "http://java.sun.com/docs/books/jvms/second_edition/html/Concepts.doc.html#20207"} + */ +public static class SourceName { + + /** + * Construct the fully qualified name of an inner class. + * + * @param outerClassSourceName source name of outer class, ie + * {...@code org.test.Foo} + * @param innerClassShortName short name of inner class, ie {...@code Bar} + * @return fully qualified source name of the inner class + */ + public static String getInnerClassName(String outerClassSourceName, + String innerClassShortName) { + assert isSourceName(outerClassSourceName); + return outerClassSourceName + '.' + innerClassShortName; + } + + public static String getShortClassName(String sourceName) { + assert isSourceName(sourceName); + int lastDollar = sourceName.lastIndexOf('.'); + if (lastDollar < 0) { + return sourceName; + } + return sourceName.substring(lastDollar + 1); + } + + private SourceName() { + } +} + + /** + * Represents a Java class name in either source or binary form, for example: + * {...@code org.example.Foo.Bar or org.example.Foo$Bar}. + * + * See {...@link "http://java.sun.com/docs/books/jls/third_edition/html/binaryComp.html#59892"} + */ + public static class SourceOrBinaryName { + + public static String toSourceName(String dottedName) { + // don't change a trailing $ to a . + return dottedName.replaceAll("[$](\\w)", ".$1"); + } + } + + /** + * Get the binary name for a Java class. + * + * @param clazz class literal + * @return binary name for the class + */ + public static String getBinaryNameForClass(Class<?> clazz) { + return clazz.getName(); + } + + /** + * Get the internal name for a Java class. + * + * @param clazz class literal + * @return internal name for the class + */ + public static String getInternalNameForClass(Class<?> clazz) { + return BinaryName.toInternalName(getBinaryNameForClass(clazz)); + } + + /** + * Get the source name for a Java class. + * + * @param clazz class literal + * @return source name for the class + */ + public static String getSourceNameForClass(Class<?> clazz) { + return clazz.getCanonicalName(); + } + + /** + * @return true if name could be a valid binary name. + * + * Note that many invalid names might pass this test -- in particular, source + * names cannot be verified to know they are not valid binary names without + * being able to tell the package name part of the name. + * + * @param name class name to test + */ + public static boolean isBinaryName(String name) { + return name == null || !name.contains("/"); + } + + /** + * @return true if name could be a valid internal name. + * + * Note that many invalid names might pass this test. + * + * @param name class name to test + */ + public static boolean isInternalName(String name) { + return name == null || !name.contains("."); + } + + /** + * @return true if name could be a valid source name. + * + * Note that many invalid names might pass this test. + * + * @param name class name to test + */ + public static boolean isSourceName(String name) { + if (name == null) { + return true; + } + int dollar = name.indexOf('$'); + return !name.contains("/") && (dollar < 0 || dollar == name.length() - 1); + } + + /** + * @return true if name could be a valid source or binary name. + * + * Note that many invalid names might pass this test. + * + * @param name class name to test + */ + public static boolean isSourceOrBinaryName(String name) { + return name == null || !name.contains("/"); + } + + private Name() { + } +} ======================================= --- /dev/null +++ /trunk/dev/core/test/com/google/gwt/dev/util/NameTest.java Fri Jul 31 13:43:46 2009 @@ -0,0 +1,188 @@ +/* + * Copyright 2009 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.dev.util.Name.BinaryName; +import com.google.gwt.dev.util.Name.SourceOrBinaryName; +import com.google.gwt.dev.util.Name.InternalName; +import com.google.gwt.dev.util.Name.SourceName; + +import junit.framework.TestCase; + +/** + * Tests for {...@link Name}. + */ +public class NameTest extends TestCase { + + /** + * Used to test getting names from a Class instance. + */ + private static class Inner { + } + + public void testBinaryName() { + assertEquals("org.test.Foo", BinaryName.toSourceName("org.test.Foo")); + assertEquals("org.test.Foo.Bar", + BinaryName.toSourceName("org.test.Foo$Bar")); + assertEquals("org.test.Foo.Bar.Baz", + BinaryName.toSourceName("org.test.Foo$Bar$Baz")); + assertEquals("org.test.Foo.Bar.Baz$", + BinaryName.toSourceName("org.test.Foo$Bar$Baz$")); + assertEquals("org/test/Foo", BinaryName.toInternalName("org.test.Foo")); + assertEquals("org/test/Foo$Bar", + BinaryName.toInternalName("org.test.Foo$Bar")); + assertEquals("org/test/Foo$Bar$Baz", + BinaryName.toInternalName("org.test.Foo$Bar$Baz")); + assertEquals("org/test/Foo$Bar$Baz$", + BinaryName.toInternalName("org.test.Foo$Bar$Baz$")); + assertEquals("org/test/Foo$Bar$Baz$1", + BinaryName.toInternalName("org.test.Foo$Bar$Baz$1")); + assertEquals("org.test.Foo$Bar", + BinaryName.getInnerClassName("org.test.Foo", "Bar")); + assertEquals("org.test.Foo", + BinaryName.getOuterClassName("org.test.Foo$Bar")); + assertEquals("org.test", + BinaryName.getPackageName("org.test.Foo$Bar")); + assertEquals("Foo$Bar", BinaryName.getClassName("org.test.Foo$Bar")); + assertEquals("Bar", BinaryName.getShortClassName("org.test.Foo$Bar")); + } + + public void testSourceOrBinaryName() { + assertEquals("org.test.Foo.Bar", + SourceOrBinaryName.toSourceName("org.test.Foo.Bar")); + assertEquals("org.test.Foo.Bar", + SourceOrBinaryName.toSourceName("org.test.Foo$Bar")); + assertEquals("org.test.Foo.Bar$", + SourceOrBinaryName.toSourceName("org.test.Foo.Bar$")); + assertEquals("org.test.Foo.Bar$", + SourceOrBinaryName.toSourceName("org.test.Foo$Bar$")); + } + + public void testGetBinaryNameForClass() { + assertEquals("com.google.gwt.dev.util.NameTest$Inner", + Name.getBinaryNameForClass(Inner.class)); + } + + public void testGetInternalNameForClass() { + assertEquals("com/google/gwt/dev/util/NameTest$Inner", + Name.getInternalNameForClass(Inner.class)); + } + + public void testGetSourceNameForClass() { + assertEquals("com.google.gwt.dev.util.NameTest.Inner", + Name.getSourceNameForClass(Inner.class)); + } + + public void testInternalName() { + assertEquals("org.test.Foo", InternalName.toSourceName("org/test/Foo")); + assertEquals("org.test.Foo.Bar", + InternalName.toSourceName("org/test/Foo$Bar")); + assertEquals("org.test.Foo.Bar.Baz", + InternalName.toSourceName("org/test/Foo$Bar$Baz")); + assertEquals("org.test.Foo.Bar.Baz$", + InternalName.toSourceName("org/test/Foo$Bar$Baz$")); + assertEquals("org.test.Foo", InternalName.toBinaryName("org/test/Foo")); + assertEquals("org.test.Foo$Bar", + InternalName.toBinaryName("org/test/Foo$Bar")); + assertEquals("org.test.Foo$Bar$Baz", + InternalName.toBinaryName("org/test/Foo$Bar$Baz")); + assertEquals("org.test.Foo$Bar$Baz$", + InternalName.toBinaryName("org/test/Foo$Bar$Baz$")); + assertEquals("org.test.Foo$Bar$Baz$1", + InternalName.toBinaryName("org/test/Foo$Bar$Baz$1")); + assertEquals("org/test/Foo$Bar", + InternalName.getInnerClassName("org/test/Foo", "Bar")); + assertEquals("org/test/Foo", + InternalName.getOuterClassName("org/test/Foo$Bar")); + assertEquals("org/test", + InternalName.getPackageName("org/test/Foo$Bar")); + assertEquals("Foo$Bar", InternalName.getClassName("org/test/Foo$Bar")); + assertEquals("Bar", InternalName.getShortClassName("org/test/Foo$Bar")); + } + + public void testIsBinaryName() { + assertTrue(Name.isBinaryName("org.test.Foo")); + assertTrue(Name.isBinaryName("org.test.Foo$Bar")); + assertTrue(Name.isBinaryName("org.test.Foo$Bar$Baz")); + assertTrue(Name.isBinaryName("org.test.Foo$Bar$Baz$")); + assertTrue(Name.isBinaryName("org.test.Foo$Bar$Baz$1")); + assertFalse(Name.isBinaryName("org/test/Foo")); + assertFalse(Name.isBinaryName("org/test/Foo$Bar")); + assertFalse(Name.isBinaryName("org/test/Foo$Bar$Baz")); + assertFalse(Name.isBinaryName("org/test/Foo$Bar$Baz$")); + assertFalse(Name.isBinaryName("org/test/Foo$Bar$Baz$1")); + assertTrue(Name.isBinaryName("org.test.Foo.Bar")); + // We can't tell these aren't binary names without being able to tell + // what the name of the top-level class is, but don't want to encode + // bad behavior in the test. + // assertTrue(Name.isBinaryName("org.test.Foo.Bar.Baz")); + // assertTrue(Name.isBinaryName("org.test.Foo.Bar.Baz$")); + } + + public void testIsInternalName() { + assertFalse(Name.isInternalName("org.test.Foo")); + assertFalse(Name.isInternalName("org.test.Foo$Bar")); + assertFalse(Name.isInternalName("org.test.Foo$Bar$Baz")); + assertFalse(Name.isInternalName("org.test.Foo$Bar$Baz$")); + assertFalse(Name.isInternalName("org.test.Foo$Bar$Baz$1")); + assertTrue(Name.isInternalName("org/test/Foo")); + assertTrue(Name.isInternalName("org/test/Foo$Bar")); + assertTrue(Name.isInternalName("org/test/Foo$Bar$Baz")); + assertTrue(Name.isInternalName("org/test/Foo$Bar$Baz$")); + assertTrue(Name.isInternalName("org/test/Foo$Bar$Baz$1")); + assertFalse(Name.isInternalName("org.test.Foo.Bar")); + assertFalse(Name.isInternalName("org.test.Foo.Bar.Baz")); + assertFalse(Name.isInternalName("org.test.Foo.Bar.Baz$")); + } + + public void testIsSourceName() { + assertTrue(Name.isSourceName("org.test.Foo")); + assertFalse(Name.isSourceName("org.test.Foo$Bar")); + assertFalse(Name.isSourceName("org.test.Foo$Bar$Baz")); + assertFalse(Name.isSourceName("org.test.Foo$Bar$Baz$")); + assertFalse(Name.isSourceName("org.test.Foo$Bar$Baz$1")); + assertFalse(Name.isSourceName("org/test/Foo")); + assertFalse(Name.isSourceName("org/test/Foo$Bar")); + assertFalse(Name.isSourceName("org/test/Foo$Bar$Baz")); + assertFalse(Name.isSourceName("org/test/Foo$Bar$Baz$")); + assertFalse(Name.isSourceName("org/test/Foo$Bar$Baz$1")); + assertTrue(Name.isSourceName("org.test.Foo.Bar")); + assertTrue(Name.isSourceName("org.test.Foo.Bar.Baz")); + assertTrue(Name.isSourceName("org.test.Foo.Bar.Baz$")); + } + + public void testIsSourceOrBinaryName() { + assertTrue(Name.isSourceOrBinaryName("org.test.Foo")); + assertTrue(Name.isSourceOrBinaryName("org.test.Foo$Bar")); + assertTrue(Name.isSourceOrBinaryName("org.test.Foo$Bar$Baz")); + assertTrue(Name.isSourceOrBinaryName("org.test.Foo$Bar$Baz$")); + assertTrue(Name.isSourceOrBinaryName("org.test.Foo$Bar$Baz$1")); + assertFalse(Name.isSourceOrBinaryName("org/test/Foo")); + assertFalse(Name.isSourceOrBinaryName("org/test/Foo$Bar")); + assertFalse(Name.isSourceOrBinaryName("org/test/Foo$Bar$Baz")); + assertFalse(Name.isSourceOrBinaryName("org/test/Foo$Bar$Baz$")); + assertFalse(Name.isSourceOrBinaryName("org/test/Foo$Bar$Baz$1")); + assertTrue(Name.isSourceOrBinaryName("org.test.Foo.Bar")); + assertTrue(Name.isSourceOrBinaryName("org.test.Foo.Bar.Baz")); + assertTrue(Name.isSourceOrBinaryName("org.test.Foo.Bar.Baz$")); + } + + public void testSourceName() { + assertEquals("org.test.Foo.Bar", + SourceName.getInnerClassName("org.test.Foo", "Bar")); + assertEquals("Bar", SourceName.getShortClassName("org.test.Foo.Bar")); + } +} ======================================= --- /trunk/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java Tue Jul 28 21:10:12 2009 +++ /trunk/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java Fri Jul 31 13:43:46 2009 @@ -17,6 +17,7 @@ import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType; import com.google.gwt.dev.jjs.InternalCompilerException; +import com.google.gwt.dev.util.Name; import com.google.gwt.dev.util.collect.HashMap; import com.google.gwt.dev.util.collect.IdentityHashMap; @@ -253,6 +254,7 @@ * @return <code>null</code> if the type is not found */ public JClassType findType(String name) { + assert Name.isSourceName(name); return allTypes.get(name); } @@ -264,6 +266,7 @@ * @return <code>null</code> if the type is not found */ public JClassType findType(String pkgName, String typeName) { + assert Name.isSourceName(typeName); JPackage pkg = findPackage(pkgName); if (pkg != null) { JClassType type = pkg.findType(typeName); @@ -471,6 +474,7 @@ * @return the specified type */ public JClassType getType(String name) throws NotFoundException { + assert Name.isSourceName(name); JClassType type = findType(name); if (type == null) { throw new NotFoundException(name); @@ -487,6 +491,7 @@ */ public JClassType getType(String pkgName, String topLevelTypeSimpleName) throws NotFoundException { + assert Name.isSourceName(topLevelTypeSimpleName); JClassType type = findType(pkgName, topLevelTypeSimpleName); if (type == null) { throw new NotFoundException(pkgName + "." + topLevelTypeSimpleName); ======================================= --- /trunk/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java Tue Jul 28 21:10:32 2009 +++ /trunk/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java Fri Jul 31 13:43:46 2009 @@ -36,6 +36,8 @@ import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter; import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle; import com.google.gwt.dev.util.JsniRef; +import com.google.gwt.dev.util.Name.SourceOrBinaryName; +import com.google.gwt.dev.util.Name.InternalName; import com.google.gwt.dev.util.Util; import com.google.gwt.util.tools.Utility; @@ -251,12 +253,12 @@ */ private Class<?> getClassFromBinaryOrSourceName(String className) { // Try the type oracle first - JClassType type = typeOracle.findType(className.replace('$', '.')); + JClassType type = typeOracle.findType(SourceOrBinaryName.toSourceName(className)); if (type != null) { // Use the type oracle to compute the exact binary name String jniSig = type.getJNISignature(); jniSig = jniSig.substring(1, jniSig.length() - 1); - className = jniSig.replace('/', '.'); + className = InternalName.toBinaryName(jniSig); } return getClassFromBinaryName(className); } ======================================= --- /trunk/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java Wed Mar 4 10:40:25 2009 +++ /trunk/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java Fri Jul 31 13:43:46 2009 @@ -17,6 +17,8 @@ import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.dev.util.Name; +import com.google.gwt.dev.util.Name.BinaryName; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -396,13 +398,14 @@ @SuppressWarnings("unchecked") public <T> T rebindAndCreate(String requestedClassName) throws UnableToCompleteException { + assert Name.isBinaryName(requestedClassName); Throwable caught = null; String msg = null; String resultName = null; try { // Rebind operates on source-level names. // - String sourceName = requestedClassName.replace('$', '.'); + String sourceName = BinaryName.toSourceName(requestedClassName); resultName = rebind(sourceName); Class<?> resolvedClass = loadClassFromSourceName(resultName); if (Modifier.isAbstract(resolvedClass.getModifiers())) { ======================================= --- /trunk/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java Fri Jan 16 11:50:20 2009 +++ /trunk/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java Fri Jul 31 13:43:46 2009 @@ -23,6 +23,7 @@ import com.google.gwt.dev.asm.commons.GeneratorAdapter; import com.google.gwt.dev.asm.commons.Method; import com.google.gwt.dev.shell.JavaScriptHost; +import com.google.gwt.dev.util.Name.InternalName; import java.lang.reflect.Modifier; import java.util.Map; @@ -234,6 +235,7 @@ * new Class[] {int.class,}, new Object[] {x,}); * </pre> */ + @Override public void visitCode() { super.visitCode(); @@ -319,7 +321,7 @@ private static final Type VOID_TYPE = Type.getObjectType("java/lang/Void"); /** - * The name of the class we're operating on. + * The internal name of the class we're operating on. */ private String classDesc; private Map<String, String> anonymousClassMap; @@ -370,11 +372,12 @@ + descriptor; String argsDescriptor = descriptor.substring(argsIndexBegin, argsIndexEnd + 1); - String classDescriptor = classDesc.replace('/', '.'); + String classDescriptor = InternalName.toBinaryName(classDesc); String newDescriptor = anonymousClassMap.get(classDesc); if (newDescriptor != null) { - classDescriptor = newDescriptor.replace('/', '.'); - } + classDescriptor = InternalName.toBinaryName(newDescriptor); + } + // Always use binary names for JSNI method names return "@" + classDescriptor + "::" + name + argsDescriptor; } } --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---
