Merging releases/1.5@r3078:r3125 into trunk.
svn merge -r3078:3125 https://google-web-toolkit.googlecode.com/svn/releases/1.5 .
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3126 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeTemplate.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeTemplate.js
index 8f2c3b9..e60fd20 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeTemplate.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeTemplate.js
@@ -20,7 +20,6 @@
// Cache symbols locally for good obfuscation
var $wnd = window
,$doc = document
- ,external = $wnd.external
,$stats = $wnd.__gwtStatsEvent ? function(a) {return $wnd.__gwtStatsEvent(a);} : null
// These variables gate calling gwtOnLoad; all must be true to start
@@ -71,7 +70,7 @@
function isHostedMode() {
try {
- return (external && external.gwtOnLoad &&
+ return ($wnd.external && $wnd.external.gwtOnLoad &&
($wnd.location.search.indexOf('gwt.hybrid') == -1));
} catch (e) {
// Defensive: some versions of IE7 reportedly can throw an exception
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
index ecbfd30..6b7d755 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
@@ -332,6 +332,11 @@
if (isParameterized != null && isParameterized.getBaseType() == type) {
return isParameterized;
}
+
+ JRawType isRaw = supertype.isRawType();
+ if (isRaw != null && isRaw.getBaseType() == type) {
+ return isRaw.asParameterizedByWildcards();
+ }
}
return null;
@@ -355,6 +360,7 @@
public abstract JClassType getEnclosingType();
+ @Override
public abstract JClassType getErasedType();
public abstract JField getField(String name);
@@ -471,6 +477,7 @@
public abstract boolean isFinal();
+ @Override
public abstract JGenericType isGenericType();
@Override
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JGenericType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JGenericType.java
index 68d3f1f..b914c76 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JGenericType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JGenericType.java
@@ -15,6 +15,8 @@
*/
package com.google.gwt.core.ext.typeinfo;
+import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType;
+
import java.util.ArrayList;
import java.util.List;
@@ -40,6 +42,15 @@
}
}
+ public JParameterizedType asParameterizedByWildcards() {
+ JClassType[] typeArgs = new JClassType[typeParams.size()];
+ for (int i = 0; i < typeArgs.length; ++i) {
+ typeArgs[i] = getOracle().getWildcardType(BoundType.EXTENDS,
+ typeParams.get(i).getFirstBound());
+ }
+ return getOracle().getParameterizedType(this, typeArgs);
+ }
+
@Override
public JClassType getErasedType() {
return getRawType();
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JRawType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JRawType.java
index b35075d..9b6ea87 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JRawType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JRawType.java
@@ -37,6 +37,10 @@
members = new DelegateMembers(this, getBaseType(), ERASURE_SUBSTITUTION);
}
+ public JParameterizedType asParameterizedByWildcards() {
+ return getBaseType().asParameterizedByWildcards();
+ }
+
@Override
public JConstructor findConstructor(JType[] paramTypes) {
return members.findConstructor(paramTypes);
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JWildcardType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JWildcardType.java
index bb5a33e..7681004 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JWildcardType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JWildcardType.java
@@ -39,9 +39,9 @@
UNBOUND
}
+ private final BoundType boundType;
private JClassType[] lazyLowerBounds;
private JClassType[] lazyUpperBounds;
- private final BoundType boundType;
public JWildcardType(BoundType boundType, JClassType typeBound) {
this.boundType = boundType;
@@ -58,15 +58,13 @@
return getBaseType().findMethod(name, paramTypes);
}
+ public BoundType getBoundType() {
+ return boundType;
+ }
+
@Override
public JClassType getErasedType() {
- if (isUpperBound()) {
- // ? extends T erases to T
- return getFirstBound().getErasedType();
- }
-
- // ? super T erases to Object
- return getOracle().getJavaLangObject();
+ return getUpperBound().getErasedType();
}
@Override
@@ -145,6 +143,14 @@
return getOracle().getJavaLangObject();
}
+ public JClassType getUpperBound() {
+ if (isUpperBound()) {
+ return getFirstBound();
+ }
+
+ return getOracle().getJavaLangObject();
+ }
+
/**
* Returns the upper bounds of this wildcard type. If no upper bounds were
* declared, an array containing {@link Object} is returned.
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
index d3dcd7a..e217ce2 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
@@ -167,6 +167,8 @@
}
}
+ private Set<JClassType> allTypes = null;
+
private final Map<JType, JArrayType> arrayTypes = new IdentityHashMap<JType, JArrayType>();
private final Set<JRealClassType> invalidatedTypes = new HashSet<JRealClassType>();
@@ -455,14 +457,16 @@
* @return an array of types, possibly of zero length
*/
public JClassType[] getTypes() {
- Set<JClassType> allTypes = new HashSet<JClassType>();
- JPackage[] pkgs = getPackages();
- for (int i = 0; i < pkgs.length; i++) {
- JPackage pkg = pkgs[i];
- JClassType[] types = pkg.getTypes();
- for (int j = 0; j < types.length; j++) {
- JRealClassType type = (JRealClassType) types[j];
- buildAllTypesImpl(allTypes, type);
+ if (allTypes == null) {
+ allTypes = new HashSet<JClassType>();
+ JPackage[] pkgs = getPackages();
+ for (int i = 0; i < pkgs.length; i++) {
+ JPackage pkg = pkgs[i];
+ JClassType[] types = pkg.getTypes();
+ for (int j = 0; j < types.length; j++) {
+ JRealClassType type = (JRealClassType) types[j];
+ buildAllTypesImpl(allTypes, type);
+ }
}
}
return allTypes.toArray(NO_JCLASSES);
@@ -533,6 +537,7 @@
* TODO: make this not public.
*/
public void refresh(TreeLogger logger) throws NotFoundException {
+ allTypes = null;
if (javaLangObject == null) {
javaLangObject = findType("java.lang.Object");
if (javaLangObject == null) {
@@ -633,9 +638,9 @@
// For each type, walk up its hierarchy chain and tell each supertype
// about its subtype.
//
- JClassType[] allTypes = getTypes();
- for (int i = 0; i < allTypes.length; i++) {
- JClassType type = allTypes[i];
+ JClassType[] types = getTypes();
+ for (int i = 0; i < types.length; i++) {
+ JClassType type = types[i];
type.notifySuperTypes();
}
}
diff --git a/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js b/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
index 29d93f1..177eaa9 100644
--- a/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
@@ -20,7 +20,6 @@
// Cache symbols locally for good obfuscation
var $wnd = window
,$doc = document
- ,external = $wnd.external
,$stats = $wnd.__gwtStatsEvent ? function(a) {return $wnd.__gwtStatsEvent(a);} : null
// These variables gate calling gwtOnLoad; all must be true to start
@@ -67,7 +66,7 @@
function isHostedMode() {
try {
- return (external && external.gwtOnLoad &&
+ return ($wnd.external && $wnd.external.gwtOnLoad &&
($wnd.location.search.indexOf('gwt.hybrid') == -1));
} catch (e) {
// Defensive: some versions of IE7 reportedly can throw an exception
diff --git a/dev/core/src/com/google/gwt/core/linker/SingleScriptTemplate.js b/dev/core/src/com/google/gwt/core/linker/SingleScriptTemplate.js
index d4b02f5..b2aed9b 100644
--- a/dev/core/src/com/google/gwt/core/linker/SingleScriptTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/SingleScriptTemplate.js
@@ -20,7 +20,6 @@
// Cache symbols locally for good obfuscation
var $wnd = window
,$doc = document
- ,external = $wnd.external
// These variables gate calling gwtOnLoad; all must be true to start
,gwtOnLoad, bodyDone
@@ -58,7 +57,7 @@
function isHostedMode() {
try {
- return (external && external.gwtOnLoad &&
+ return ($wnd.external && $wnd.external.gwtOnLoad &&
($wnd.location.search.indexOf('gwt.hybrid') == -1));
} catch (e) {
// Defensive: some versions of IE7 reportedly can throw an exception
diff --git a/dev/core/src/com/google/gwt/core/linker/XSTemplate.js b/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
index 0e4e4cc..826d553 100644
--- a/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
@@ -20,7 +20,6 @@
// Cache symbols locally for good obfuscation
var $wnd = window
,$doc = document
- ,external = $wnd.external
,$stats = $wnd.__gwtStatsEvent ? function(a) {return $wnd.__gwtStatsEvent(a);} : null
// These variables gate calling gwtOnLoad; all must be true to start
@@ -67,7 +66,7 @@
function isHostedMode() {
try {
- return (external && external.gwtOnLoad &&
+ return ($wnd.external && $wnd.external.gwtOnLoad &&
($wnd.location.search.indexOf('gwt.hybrid') == -1));
} catch (e) {
// Defensive: some versions of IE7 reportedly can throw an exception
diff --git a/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java b/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
index a221bcb..4462ec7 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
@@ -20,6 +20,7 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.shell.StandardGeneratorContext;
+import com.google.gwt.dev.util.PerfLogger;
/**
* A rule to replace the type being rebound with a class whose name is
@@ -45,7 +46,10 @@
}
long before = System.currentTimeMillis();
+ PerfLogger.start("Generator " + generator.getClass().getCanonicalName()
+ + " for " + typeName);
String className = generator.generate(logger, context, typeName);
+ PerfLogger.end();
long after = System.currentTimeMillis();
if (className == null) {
msg = "Generator returned null, so the requested type will be used as is";
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationState.java b/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
index e374a06..3b69128 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
@@ -21,6 +21,7 @@
import com.google.gwt.dev.javac.CompilationUnit.State;
import com.google.gwt.dev.javac.impl.SourceFileCompilationUnit;
import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.util.PerfLogger;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ClassFile;
@@ -95,31 +96,28 @@
* compile errors.
*/
public void compile(TreeLogger logger) throws UnableToCompleteException {
+ PerfLogger.start("CompilationState.compile");
Set<CompilationUnit> units = getCompilationUnits();
- JdtCompiler.compile(units);
- Set<String> validBinaryTypeNames = getValidBinaryTypeNames(units);
+ if (JdtCompiler.compile(units)) {
+ Set<String> validBinaryTypeNames = getValidBinaryTypeNames(units);
- // Dump all units with direct errors; we cannot safely check them.
- boolean anyErrors = CompilationUnitInvalidator.invalidateUnitsWithErrors(
- logger, units);
+ // Dump all units with direct errors; we cannot safely check them.
+ boolean anyErrors = CompilationUnitInvalidator.invalidateUnitsWithErrors(
+ logger, units);
- // Check all units using our custom checks.
- CompilationUnitInvalidator.validateCompilationUnits(units,
- validBinaryTypeNames);
+ // Check all units using our custom checks.
+ CompilationUnitInvalidator.validateCompilationUnits(units,
+ validBinaryTypeNames);
- // More units may have errors now.
- anyErrors |= CompilationUnitInvalidator.invalidateUnitsWithErrors(logger,
- units);
+ // More units may have errors now.
+ anyErrors |= CompilationUnitInvalidator.invalidateUnitsWithErrors(logger,
+ units);
- if (anyErrors) {
- CompilationUnitInvalidator.invalidateUnitsWithInvalidRefs(logger, units);
- }
+ if (anyErrors) {
+ CompilationUnitInvalidator.invalidateUnitsWithInvalidRefs(logger, units);
+ }
- JsniCollector.collectJsniMethods(logger, units, new JsProgram());
-
- // JSNI collection can generate additional errors.
- if (CompilationUnitInvalidator.invalidateUnitsWithErrors(logger, units)) {
- CompilationUnitInvalidator.invalidateUnitsWithInvalidRefs(logger, units);
+ JsniCollector.collectJsniMethods(logger, units, new JsProgram());
}
mediator.refresh(logger, units);
@@ -132,6 +130,7 @@
}
updateExposedUnits();
+ PerfLogger.end();
}
/**
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
index 5a6748a..3d303f7 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
@@ -39,8 +39,37 @@
*/
public abstract class CompilationUnit {
+ /**
+ * Tracks the state of a compilation unit through the compile and recompile
+ * process.
+ */
enum State {
- COMPILED, CHECKED, ERROR, FRESH
+ /**
+ * All internal state is cleared; the unit's source has not yet been
+ * compiled by JDT.
+ */
+ FRESH,
+ /**
+ * In this intermediate state, the unit's source has been compiled by JDT.
+ * The unit will contain a set of CompiledClasses.
+ */
+ COMPILED,
+ /**
+ * In this final state, the unit was compiled, but contained one or more
+ * errors. Those errors are cached inside the unit, but all other internal
+ * state is cleared.
+ */
+ ERROR,
+ /**
+ * In this final state, the unit has been compiled and is error free.
+ * Additionally, all other units this unit depends on (transitively) are
+ * also error free. The unit contains a set of checked CompiledClasses. The
+ * unit and each contained CompiledClass releases all references to the JDT
+ * AST. Each class contains a reference to a valid JRealClassType, which has
+ * been added to the module's TypeOracle, as well as byte code, JSNI
+ * methods, and all other final state.
+ */
+ CHECKED
}
private class FindTypesInCud extends ASTVisitor {
@@ -209,6 +238,7 @@
* Sets the compiled JDT AST for this unit.
*/
void setJdtCud(CompilationUnitDeclaration cud) {
+ assert (state == State.FRESH || state == State.ERROR);
this.cud = cud;
state = State.COMPILED;
}
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
index 831b46a..79bd184 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
@@ -189,10 +189,8 @@
* Compiles the given set of units. The units will be internally modified to
* reflect the results of compilation.
*/
- public static void compile(Collection<CompilationUnit> units) {
- PerfLogger.start("JdtCompiler.compile");
- new JdtCompiler().doCompile(units);
- PerfLogger.end();
+ public static boolean compile(Collection<CompilationUnit> units) {
+ return new JdtCompiler().doCompile(units);
}
private static CompilerOptions getCompilerOptions() {
@@ -255,7 +253,7 @@
}
}
- private void doCompile(Collection<CompilationUnit> units) {
+ private boolean doCompile(Collection<CompilationUnit> units) {
List<ICompilationUnit> icus = new ArrayList<ICompilationUnit>();
for (CompilationUnit unit : units) {
String packageName = Shared.getPackageName(unit.getTypeName());
@@ -271,9 +269,14 @@
}
}
}
- if (!icus.isEmpty()) {
- compiler.compile(icus.toArray(new ICompilationUnit[icus.size()]));
+ if (icus.isEmpty()) {
+ return false;
}
+
+ PerfLogger.start("JdtCompiler.compile");
+ compiler.compile(icus.toArray(new ICompilationUnit[icus.size()]));
+ PerfLogger.end();
+ return true;
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java
index 4987620..fc3bc02 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java
@@ -70,9 +70,9 @@
case SHL:
case SHR:
case SHRU:
- if (rhsType != program.getTypePrimitiveInt()) {
+ if (rhsType == longType) {
throw new InternalCompilerException(
- "Expected right operand to be of type int");
+ "Expected right operand not to be of type long");
}
break;
default:
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java b/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
index d2d4d36..2f1a8e0 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
@@ -42,17 +42,54 @@
}
private void ctorAddKnownGlobalSymbols() {
+ // Section references are from Ecma-262
+ // (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf)
String[] commonBuiltins = new String[] {
- "ActiveXObject", "Array", "Boolean", "Date", "Debug", "Enumerator",
- "Error", "Function", "Global", "Image", "Math", "Number", "Object",
- "RegExp", "String", "VBArray", "window", "document", "event",
- "arguments", "call", "toString", "$wnd", "$doc", "$moduleName",
- "$moduleBase", "undefined", "getClass", "$gwt_version"};
+ // 15.1.1 Value Properties of the Global Object
+ "NaN",
+ "Infinity",
+ "undefined",
+
+ // 15.1.2 Function Properties of the Global Object
+ "eval", "parseInt", "parseFloat",
+ "isNan",
+ "isFinite",
+
+ // 15.1.3 URI Handling Function Properties
+ "decodeURI", "decodeURIComponent",
+ "encodeURI",
+ "encodeURIComponent",
+
+ // 15.1.4 Constructor Properties of the Global Object
+ "Object", "Function", "Array", "String", "Boolean", "Number", "Date",
+ "RegExp", "Error", "EvalError", "RangeError", "ReferenceError",
+ "SyntaxError", "TypeError", "URIError",
+
+ // 15.1.5 Other Properties of the Global Object
+ "Math",
+
+ // 10.1.6 Activation Object
+ "arguments",
+
+ // B.2 Additional Properties (non-normative)
+ "escape", "unescape",
+
+ // Common browser-defined identifiers not defined in ECMAScript
+ "window", "document", "event", "location", "history", "external",
+ "Debug", "Enumerator", "Global", "Image", "ActiveXObject", "VBArray",
+
+ // Functions commonly defined on Object
+ "toString", "getClass",
+
+ // GWT-defined identifiers
+ "$wnd", "$doc", "$moduleName", "$moduleBase", "$gwt_version",
+
+ // TODO: prove why this is necessary or remove it
+ "call",};
for (int i = 0; i < commonBuiltins.length; i++) {
String ident = commonBuiltins[i];
this.doCreateName(ident, ident);
}
}
-
}
diff --git a/dev/core/src/com/google/gwt/dev/util/PerfLogger.java b/dev/core/src/com/google/gwt/dev/util/PerfLogger.java
index 31f5965..5404539 100644
--- a/dev/core/src/com/google/gwt/dev/util/PerfLogger.java
+++ b/dev/core/src/com/google/gwt/dev/util/PerfLogger.java
@@ -142,7 +142,7 @@
msg.append(t.message);
if (!t.messageOnly) {
msg.append(" ");
- msg.append(t.totalTimeNanos / 1000000.0);
+ msg.append(t.totalTimeNanos / 1000000);
msg.append("ms");
}
System.out.println(msg);
diff --git a/dev/core/super/com/google/gwt/lang/LongLib.java b/dev/core/super/com/google/gwt/lang/LongLib.java
index 4d86ba6..db43c0f 100644
--- a/dev/core/super/com/google/gwt/lang/LongLib.java
+++ b/dev/core/super/com/google/gwt/lang/LongLib.java
@@ -45,6 +45,10 @@
* implementation of the long type for web mode, any place it uses a long is
* not usable in web mode. There are currently two such methods:
* {@link #toLong(double[])} and {@link #make(long)}.
+ *
+ * The GWT RPC serialization code is dependent on the internal format of the
+ * long type; any changes made to this class should be reflected in the
+ * implementations of SerializationStreamReader and Writer.
*/
/**
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java
index 46a22be..e950b20 100644
--- a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java
@@ -28,6 +28,7 @@
addIssue(new Issue1245());
addIssue(new Issue1772());
addIssue(new Issue1897());
+ addIssue(new Issue1932());
addIssue(new Issue2261());
addIssue(new Issue2290());
addIssue(new Issue2307());
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue1932.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue1932.java
new file mode 100644
index 0000000..7233d75
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue1932.java
@@ -0,0 +1,160 @@
+/*
+ * 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.museum.client.defaultmuseum;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.museum.client.common.AbstractIssue;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.EventPreview;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * DOM.eventGetClientX/Y incorrect with HTML margin/borders Firefox 2/Safari 3.
+ */
+public class Issue1932 extends AbstractIssue {
+ /**
+ * A set of options used to set the page margins and borders
+ */
+ private class ControlPanel extends Composite {
+ private final Grid grid = new Grid(2, 3);
+
+ private final TextBox borderBox = new TextBox();
+
+ private final TextBox marginBox = new TextBox();
+
+ public ControlPanel() {
+ initWidget(grid);
+
+ // Add an option to set the margin
+ marginBox.setText("10px");
+ grid.setHTML(0, 0, "<b>Margin:</b>");
+ grid.setWidget(0, 1, marginBox);
+ grid.setWidget(0, 2, new Button("Set", new ClickListener() {
+ public void onClick(Widget sender) {
+ updateMargin();
+ }
+ }));
+
+ // Add an option to set the border
+ borderBox.setText("5px solid #DDDDDD");
+ grid.setHTML(1, 0, "<b>Border:</b>");
+ grid.setWidget(1, 1, borderBox);
+ grid.setWidget(1, 2, new Button("Set", new ClickListener() {
+ public void onClick(Widget sender) {
+ updateBorder();
+ }
+ }));
+ }
+
+ /**
+ * Update the border on the HTML element.
+ */
+ public void updateBorder() {
+ htmlElement.getStyle().setProperty("border", borderBox.getText());
+ }
+
+ /**
+ * Update the margin on the HTML element.
+ */
+ public void updateMargin() {
+ htmlElement.getStyle().setProperty("margin", marginBox.getText());
+ }
+ }
+
+ /**
+ * The HTML element of the page.
+ */
+ private Element htmlElement = null;
+
+ @Override
+ public Widget createIssue() {
+ // Setup the page size and cursor
+ Element bodyElement = RootPanel.getBodyElement();
+ htmlElement = DOM.getParent(RootPanel.getBodyElement());
+
+ // Create a crosshair to show the current position
+ final SimplePanel positioner = new SimplePanel();
+ positioner.setPixelSize(30, 30);
+ positioner.getElement().getStyle().setProperty("borderLeft",
+ "1px solid red");
+ positioner.getElement().getStyle().setProperty("borderTop", "1px solid red");
+ positioner.getElement().getStyle().setProperty("cursor", "crosshair");
+
+ // Create a target box to test inside
+ final Label sandbox = new Label();
+ sandbox.sinkEvents(Event.ONMOUSEMOVE);
+ sandbox.setPixelSize(300, 300);
+ sandbox.getElement().getStyle().setProperty("border", "3px solid blue");
+ sandbox.getElement().getStyle().setProperty("cursor", "crosshair");
+
+ // Keep the crosshair under the cursor
+ DOM.addEventPreview(new EventPreview() {
+ public boolean onEventPreview(Event event) {
+ // Ignore events outside of the sandbox
+ Element target = DOM.eventGetTarget(event);
+ if (!sandbox.getElement().isOrHasChild(target)
+ && !positioner.getElement().isOrHasChild(target)) {
+ positioner.removeFromParent();
+ return true;
+ }
+
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEMOVE:
+ RootPanel.get().add(positioner, event.getClientX(),
+ event.getClientY());
+ break;
+ }
+ return true;
+ }
+ });
+
+ // Combine the control panel and return
+ VerticalPanel vPanel = new VerticalPanel();
+ vPanel.add(new ControlPanel());
+ vPanel.add(sandbox);
+ return vPanel;
+ }
+
+ @Override
+ public String getInstructions() {
+ return "Move the cursor inside the blue box below and verify that the "
+ + "point of the red positioner lines up directly beneath the center of "
+ + "the cursor (crosshair). The buttons may not work on Safari 2 "
+ + "because Safari 2 has issues when you attempt to modify the HTML "
+ + "element programatically.";
+ }
+
+ @Override
+ public String getSummary() {
+ return "DOM.eventGetClientX/Y incorrect with HTML margin/border in Firefox "
+ + "2 and Safari 2";
+ }
+
+ @Override
+ public boolean hasCSS() {
+ return true;
+ }
+}
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/TestFireEvents.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/TestFireEvents.java
index 9f5bdca..f67db1c 100644
--- a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/TestFireEvents.java
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/TestFireEvents.java
@@ -58,6 +58,30 @@
Button button = new Button("Double-click me") {
@Override
public void onBrowserEvent(Event event) {
+ // Verify that values associated with events are defined. For some
+ // values, we just want to make sure we can get them without any
+ // errrors.
+ assert event.getClientX() > 0;
+ assert event.getClientY() > 0;
+ assert event.getScreenX() > 0;
+ assert event.getScreenY() > 0;
+ event.getAltKey();
+ event.getCtrlKey();
+ event.getShiftKey();
+ event.getMetaKey();
+ int eventType = event.getTypeInt();
+ switch (eventType) {
+ case Event.ONMOUSEDOWN:
+ case Event.ONMOUSEUP:
+ event.getButton();
+ break;
+ case Event.ONMOUSEOVER:
+ case Event.ONMOUSEOUT:
+ assert event.getFromElement() != null;
+ assert event.getToElement() != null;
+ break;
+ }
+
passTest(event);
}
};
@@ -72,8 +96,30 @@
// Keyboard events
TextBox textBox = new TextBox() {
+ @SuppressWarnings("fallthrough")
@Override
public void onBrowserEvent(Event event) {
+ // Verify that values associated with events are defined
+ int eventType = event.getTypeInt();
+ switch (eventType) {
+ case Event.ONFOCUS:
+ case Event.ONBLUR:
+ break;
+ case Event.ONKEYDOWN:
+ event.getRepeat();
+ // Intentional fall through
+ case Event.ONKEYUP:
+ case Event.ONKEYPRESS:
+ event.getAltKey();
+ event.getCtrlKey();
+ event.getShiftKey();
+ event.getMetaKey();
+ assert event.getKeyCode() > 0;
+ break;
+ case Event.ONCHANGE:
+ break;
+ }
+
passTest(event);
}
};
@@ -96,6 +142,20 @@
ScrollPanel scrollable = new ScrollPanel(scrollableContents) {
@Override
public void onBrowserEvent(Event event) {
+ // Verify that values associated with events are defined
+ int eventType = event.getTypeInt();
+ switch (eventType) {
+ case Event.ONMOUSEWHEEL:
+ event.getClientX();
+ event.getClientY();
+ event.getScreenX();
+ event.getScreenY();
+ event.getMouseWheelVelocityY();
+ break;
+ case Event.ONSCROLL:
+ break;
+ }
+
passTest(event);
}
};
@@ -110,13 +170,29 @@
@Override
public void onBrowserEvent(Event event) {
passTest(event);
- if (DOM.eventGetType(event) == Event.ONERROR) {
- setUrl("issues/images/gwtLogo.png");
+
+ int eventType = event.getTypeInt();
+ switch (eventType) {
+ case Event.ONERROR:
+ setUrl("issues/images/gwtLogo.png");
+ break;
+ case Event.ONCONTEXTMENU:
+ assert event.getClientX() > 0;
+ assert event.getClientY() > 0;
+ assert event.getScreenX() > 0;
+ assert event.getScreenY() > 0;
+ event.getAltKey();
+ event.getShiftKey();
+ event.getCtrlKey();
+ event.getMetaKey();
+ break;
}
}
};
+ loadable.sinkEvents(Event.ONCONTEXTMENU);
addTest(Event.ONERROR, "error", loadable);
addDependentTest(Event.ONLOAD, "load");
+ addDependentTest(Event.ONCONTEXTMENU, "contextMenu");
loadable.setUrl("imageDoesNotExist.abc");
// The following are not testable or not supported in all browsers
diff --git a/reference/code-museum/src/com/google/gwt/museum/public/issues/Issue1932.css b/reference/code-museum/src/com/google/gwt/museum/public/issues/Issue1932.css
new file mode 100644
index 0000000..129ffe9
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/public/issues/Issue1932.css
@@ -0,0 +1,6 @@
+@import url("MasterIssue.css");
+
+HTML {
+ margin: 10px;
+ border: 5px solid #DDDDDD;
+}
diff --git a/user/src/com/google/gwt/benchmarks/BenchmarkShell.java b/user/src/com/google/gwt/benchmarks/BenchmarkShell.java
index d556134..c916dc6 100644
--- a/user/src/com/google/gwt/benchmarks/BenchmarkShell.java
+++ b/user/src/com/google/gwt/benchmarks/BenchmarkShell.java
@@ -45,7 +45,9 @@
}
public void processResult(TestCase testCase, JUnitResult result) {
- report.addBenchmarkResults(testCase, (BenchmarkResults) result);
+ if (result instanceof BenchmarkResults) {
+ report.addBenchmarkResults(testCase, (BenchmarkResults) result);
+ }
}
}
diff --git a/user/src/com/google/gwt/core/client/GWT.java b/user/src/com/google/gwt/core/client/GWT.java
index 5e8d86d..4811501 100644
--- a/user/src/com/google/gwt/core/client/GWT.java
+++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -92,8 +92,7 @@
"ERROR: GWT.create() is only usable in client code! It cannot be called, "
+ "for example, from server code. If you are running a unit test, "
+ "check that your test case extends GWTTestCase and that GWT.create() "
- + "is not called from within an initializer, constructor, or "
- + "setUp()/tearDown().");
+ + "is not called from within an initializer or constructor.");
} else {
return sGWTBridge.<T> create(classLiteral);
}
diff --git a/user/src/com/google/gwt/core/client/JsArray.java b/user/src/com/google/gwt/core/client/JsArray.java
new file mode 100644
index 0000000..4008079
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/JsArray.java
@@ -0,0 +1,74 @@
+/*
+ * 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.core.client;
+
+/**
+ * A simple wrapper around a homogeneous native array of
+ * {@link JavaScriptObject} values.
+ *
+ * This class may not be directly instantiated, and can only be returned from a
+ * native method. For example,
+ *
+ * <code>
+ * native JsArray<JavaScriptObject> getNativeArray() /*-{
+ * return [
+ * { x: 0, y: 1},
+ * { x: 2, y: 3},
+ * { x: 4, y: 5},
+ * ];
+ * }-* /;
+ * </code>
+ *
+ * @param <T> the concrete type of object contained in this array
+ */
+public class JsArray<T extends JavaScriptObject> extends JavaScriptObject {
+
+ protected JsArray() {
+ }
+
+ /**
+ * Gets the object at a given index.
+ *
+ * @param index the index to be retrieved
+ * @return the object at the given index, or <code>null</code> if none
+ * exists
+ */
+ public final native T get(int index) /*-{
+ return this[index];
+ }-*/;
+
+ /**
+ * Gets the length of the array.
+ *
+ * @return the array length
+ */
+ public final native int length() /*-{
+ return this.length;
+ }-*/;
+
+ /**
+ * Sets the object value at a given index.
+ *
+ * If the index is out of bounds, the value will still be set. The array's
+ * length will be updated to encompass the bounds implied by the added object.
+ *
+ * @param index the index to be set
+ * @param value the object to be stored
+ */
+ public final native void set(int index, T value) /*-{
+ this[index] = value;
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/core/client/JsArrayBoolean.java b/user/src/com/google/gwt/core/client/JsArrayBoolean.java
new file mode 100644
index 0000000..80bb05e
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/JsArrayBoolean.java
@@ -0,0 +1,70 @@
+/*
+ * 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.core.client;
+
+/**
+ * A simple wrapper around a homogeneous native array of boolean values.
+ *
+ * This class may not be directly instantiated, and can only be returned from a
+ * native method. For example,
+ *
+ * <code>
+ * native JsArrayBoolean getNativeArray() /*-{
+ * return [true, false, true];
+ * }-* /;
+ * </code>
+ */
+public class JsArrayBoolean extends JavaScriptObject {
+
+ protected JsArrayBoolean() {
+ }
+
+ /**
+ * Gets the value at a given index.
+ *
+ * If an undefined or non-boolean value exists at the given index, a
+ * type-conversion error will occur in hosted mode and unpredictable behavior
+ * may occur in web mode.
+ *
+ * @param index the index to be retrieved
+ * @return the value at the given index
+ */
+ public final native boolean get(int index) /*-{
+ return this[index];
+ }-*/;
+
+ /**
+ * Gets the length of the array.
+ *
+ * @return the array length
+ */
+ public final native int length() /*-{
+ return this.length;
+ }-*/;
+
+ /**
+ * Sets the value value at a given index.
+ *
+ * If the index is out of bounds, the value will still be set. The array's
+ * length will be updated to encompass the bounds implied by the added value.
+ *
+ * @param index the index to be set
+ * @param value the value to be stored
+ */
+ public final native void set(int index, boolean value) /*-{
+ this[index] = value;
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/core/client/JsArrayInteger.java b/user/src/com/google/gwt/core/client/JsArrayInteger.java
new file mode 100644
index 0000000..eef3150
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/JsArrayInteger.java
@@ -0,0 +1,71 @@
+/*
+ * 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.core.client;
+
+/**
+ * A simple wrapper around a homogeneous native array of integer values.
+ *
+ * This class may not be directly instantiated, and can only be returned from a
+ * native method. For example,
+ *
+ * <code>
+ * native JsArrayInteger getNativeArray() /*-{
+ * return [1, 2, 3];
+ * }-* /;
+ * </code>
+ */
+public class JsArrayInteger extends JavaScriptObject {
+
+ protected JsArrayInteger() {
+ }
+
+ /**
+ * Gets the value at a given index.
+ *
+ * If no value exists at the given index, a type-conversion error will occur
+ * in hosted mode and unpredictable behavior may occur in web mode. If the
+ * numeric value returned is non-integral, it will cause a warning in hosted
+ * mode, and may affect the results of mathematical expressions.
+ *
+ * @param index the index to be retrieved
+ * @return the value at the given index
+ */
+ public final native int get(int index) /*-{
+ return this[index];
+ }-*/;
+
+ /**
+ * Gets the length of the array.
+ *
+ * @return the array length
+ */
+ public final native int length() /*-{
+ return this.length;
+ }-*/;
+
+ /**
+ * Sets the value value at a given index.
+ *
+ * If the index is out of bounds, the value will still be set. The array's
+ * length will be updated to encompass the bounds implied by the added value.
+ *
+ * @param index the index to be set
+ * @param value the value to be stored
+ */
+ public final native void set(int index, int value) /*-{
+ this[index] = value;
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/core/client/JsArrayNumber.java b/user/src/com/google/gwt/core/client/JsArrayNumber.java
new file mode 100644
index 0000000..c6822d3
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/JsArrayNumber.java
@@ -0,0 +1,73 @@
+/*
+ * 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.core.client;
+
+/**
+ * A simple wrapper around a homogeneous native array of numeric values.
+ *
+ * All native JavaScript numeric values are implicitly double-precision, so only
+ * double values may be set and retrieved.
+ *
+ * This class may not be directly instantiated, and can only be returned from a
+ * native method. For example,
+ *
+ * <code>
+ * native JsArrayNumber getNativeArray() /*-{
+ * return [1.1, 2.2, 3.3];
+ * }-* /;
+ * </code>
+ */
+public class JsArrayNumber extends JavaScriptObject {
+
+ protected JsArrayNumber() {
+ }
+
+ /**
+ * Gets the value at a given index.
+ *
+ * If an undefined or non-numeric value exists at the given index, a
+ * type-conversion error will occur in hosted mode and unpredictable behavior
+ * may occur in web mode.
+ *
+ * @param index the index to be retrieved
+ * @return the value at the given index
+ */
+ public final native double get(int index) /*-{
+ return this[index];
+ }-*/;
+
+ /**
+ * Gets the length of the array.
+ *
+ * @return the array length
+ */
+ public final native int length() /*-{
+ return this.length;
+ }-*/;
+
+ /**
+ * Sets the value value at a given index.
+ *
+ * If the index is out of bounds, the value will still be set. The array's
+ * length will be updated to encompass the bounds implied by the added value.
+ *
+ * @param index the index to be set
+ * @param value the value to be stored
+ */
+ public final native void set(int index, double value) /*-{
+ this[index] = value;
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/core/client/JsArrayString.java b/user/src/com/google/gwt/core/client/JsArrayString.java
new file mode 100644
index 0000000..d2b1de3
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/JsArrayString.java
@@ -0,0 +1,66 @@
+/*
+ * 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.core.client;
+
+/**
+ * A simple wrapper around a homogeneous native array of string values.
+ *
+ * This class may not be directly instantiated, and can only be returned from a
+ * native method. For example,
+ *
+ * <code>
+ * native JsArrayString getNativeArray() /*-{
+ * return ['foo', 'bar', 'baz'];
+ * }-* /;
+ * </code>
+ */
+public class JsArrayString extends JavaScriptObject {
+
+ protected JsArrayString() {
+ }
+
+ /**
+ * Gets the value at a given index.
+ *
+ * @param index the index to be retrieved
+ * @return the value at the given index, or <code>null</code> if none exists
+ */
+ public final native String get(int index) /*-{
+ return this[index];
+ }-*/;
+
+ /**
+ * Gets the length of the array.
+ *
+ * @return the array length
+ */
+ public final native int length() /*-{
+ return this.length;
+ }-*/;
+
+ /**
+ * Sets the value value at a given index.
+ *
+ * If the index is out of bounds, the value will still be set. The array's
+ * length will be updated to encompass the bounds implied by the added value.
+ *
+ * @param index the index to be set
+ * @param value the value to be stored
+ */
+ public final native void set(int index, String value) /*-{
+ this[index] = value;
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/dom/client/DOMImplSafari.java b/user/src/com/google/gwt/dom/client/DOMImplSafari.java
index 6d34d26..098d70c 100644
--- a/user/src/com/google/gwt/dom/client/DOMImplSafari.java
+++ b/user/src/com/google/gwt/dom/client/DOMImplSafari.java
@@ -41,9 +41,15 @@
while (elem) {
left += elem.offsetLeft;
+ // Safari 3 does not include borders with offsetLeft, so we need to add
+ // the borders of the parent manually.
+ var parent = elem.offsetParent;
+ if (parent && $wnd.devicePixelRatio) {
+ left += parseInt($doc.defaultView.getComputedStyle(parent, '').getPropertyValue('border-left-width'));
+ }
+
// Safari bug: a top-level absolutely positioned element includes the
// body's offset position already.
- var parent = elem.offsetParent;
if (parent && (parent.tagName == 'BODY') &&
(elem.style.position == 'absolute')) {
break;
@@ -75,9 +81,15 @@
while (elem) {
top += elem.offsetTop;
+ // Safari 3 does not include borders with offsetTop, so we need to add the
+ // borders of the parent manually.
+ var parent = elem.offsetParent;
+ if (parent && $wnd.devicePixelRatio) {
+ top += parseInt($doc.defaultView.getComputedStyle(parent, '').getPropertyValue('border-top-width'));
+ }
+
// Safari bug: a top-level absolutely positioned element includes the
// body's offset position already.
- var parent = elem.offsetParent;
if (parent && (parent.tagName == 'BODY') &&
(elem.style.position == 'absolute')) {
break;
diff --git a/user/src/com/google/gwt/junit/GWTDummyBridge.java b/user/src/com/google/gwt/junit/GWTDummyBridge.java
new file mode 100644
index 0000000..efcef3d
--- /dev/null
+++ b/user/src/com/google/gwt/junit/GWTDummyBridge.java
@@ -0,0 +1,60 @@
+/*
+ * 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.junit;
+
+import com.google.gwt.core.client.GWTBridge;
+import com.google.gwt.dev.About;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A dummy implementation of {@link GWTBridge}, which instantiates nothing.
+ *
+ * @see GWTMockUtilities
+ */
+class GWTDummyBridge extends GWTBridge {
+ private static final Logger logger = Logger.getLogger(GWTDummyBridge.class.getName());
+
+ /**
+ * @return null
+ */
+ public <T> T create(Class<?> classLiteral) {
+ return null;
+ }
+
+ /**
+ * @return the current version of GWT ({@link About#GWT_VERSION_NUM})
+ */
+ public String getVersion() {
+ return About.GWT_VERSION_NUM;
+ }
+
+ /**
+ * @return false
+ */
+ public boolean isClient() {
+ return false;
+ }
+
+ /**
+ * Logs the message and throwable to the standard logger, with level {@link
+ * Level#SEVERE}.
+ */
+ public void log(String message, Throwable e) {
+ logger.log(Level.SEVERE, message, e);
+ }
+}
diff --git a/user/src/com/google/gwt/junit/GWTMockUtilities.java b/user/src/com/google/gwt/junit/GWTMockUtilities.java
new file mode 100644
index 0000000..b9fe106
--- /dev/null
+++ b/user/src/com/google/gwt/junit/GWTMockUtilities.java
@@ -0,0 +1,99 @@
+/*
+ * 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.junit;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.GWTBridge;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Defangs {@link GWT#create(Class)} to allow unit tests to mock out Widgets and
+ * other UIObjects.
+ */
+public class GWTMockUtilities {
+
+ /**
+ * Replace the normal GWT.create() behavior with a method that returns null
+ * instead of throwing a runtime exception. This is to allow JUnit tests to
+ * mock classes that make GWT.create() calls in their static initializers.
+ * This is not for use with GWTTestCase, and is not for use testing widgets
+ * themselves. Rather, it is to allow pure java unit tests of classes that
+ * need to manipulate widgets.
+ *
+ * <p>
+ * <b>NOTE:</b> Be sure to call {@link #restore} in your tearDown
+ * method, to avoid confusing downstream tests.
+ *
+ * <p>
+ * Sample use:
+ *
+ * <pre>@Override
+ * public void setUp() throws Exception {
+ * super.setUp();
+ * GWTMockUtilities.disarm();
+ * }
+ *
+ * @Override
+ * public void tearDown() {
+ * GWTMockUtilities.restore();
+ * }
+ *
+ * public void testSomething() {
+ * MyWidget mock = EasyMock.createMock(MyWidget.class);
+ * mock.setText("expected text");
+ * mock.replay();
+ *
+ * StatusController controller = new StatusController(mock);
+ * controller.setStatus("expected text");
+ *
+ * EasyMock.verify(mock);
+ * }
+ * </pre>
+ */
+ public static void disarm(/* TearDownAccepter test */) {
+ GWTBridge bridge = new GWTDummyBridge();
+ setGwtBridge(bridge);
+ }
+
+ public static void restore() {
+ setGwtBridge(null);
+ }
+
+ /**
+ * Install the given instance of {@link GWTBridge}, allowing it to override
+ * the behavior of calls to {@link GWT#create(Class)}.
+ */
+ private static void setGwtBridge(GWTBridge bridge) {
+ Class<GWT> gwtClass = GWT.class;
+ Class<?>[] paramTypes = new Class[] {GWTBridge.class};
+ Method setBridgeMethod = null;
+ try {
+ setBridgeMethod = gwtClass.getDeclaredMethod("setBridge", paramTypes);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ setBridgeMethod.setAccessible(true);
+ try {
+ setBridgeMethod.invoke(gwtClass, new Object[] {bridge});
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/junit/JUnitMessageQueue.java b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
index 7466b3c..373d004 100644
--- a/user/src/com/google/gwt/junit/JUnitMessageQueue.java
+++ b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
@@ -149,6 +149,14 @@
}
assert (results != null);
ClientStatus clientStatus = clientStatuses.get(clientId);
+ /*
+ * Unknown client, but valid testInfo; this can happen if the client's
+ * module fails to load.
+ */
+ if (clientStatus == null) {
+ clientStatus = new ClientStatus(clientId);
+ clientStatuses.put(clientId, clientStatus);
+ }
clientStatus.currentTestResults = results;
clientStatusesLock.notifyAll();
}
diff --git a/user/src/com/google/gwt/junit/public/junit.html b/user/src/com/google/gwt/junit/public/junit.html
index ebefbe4..dd33eec 100644
--- a/user/src/com/google/gwt/junit/public/junit.html
+++ b/user/src/com/google/gwt/junit/public/junit.html
@@ -40,7 +40,7 @@
function junitError(msg) {
var xmlHttpRequest = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
xmlHttpRequest.open('POST', 'junithost/loadError', true);
- xmlHttpRequest.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');
+ xmlHttpRequest.setRequestHeader('Content-Type', 'text/x-gwt-rpc; charset=utf-8');
xmlHttpRequest.send(msg);
}
diff --git a/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java b/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java
index a523d85..19a2d59 100644
--- a/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java
+++ b/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java
@@ -24,7 +24,6 @@
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.util.PerfLogger;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.junit.client.impl.GWTRunner;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
@@ -90,22 +89,18 @@
generatedClass, GWT_RUNNER_NAME);
if (sourceWriter != null) {
- PerfLogger.start("GWTRunnerGenerator");
JClassType[] allTestTypes = getAllTestTypes(context.getTypeOracle());
Set<String> testClasses = getTestTypesForModule(logger, moduleName,
allTestTypes);
writeCreateNewTestCaseMethod(testClasses, sourceWriter);
sourceWriter.commit(logger);
- PerfLogger.end();
}
return qualifiedStubClassName;
}
private JClassType[] getAllTestTypes(TypeOracle typeOracle) {
- PerfLogger.start("GWTRunnerGenerator.getAllTestTypes");
JClassType gwtTestType = typeOracle.findType(GWTTestCase.class.getName());
- PerfLogger.end();
if (gwtTestType != null) {
return gwtTestType.getSubtypes();
} else {
@@ -130,7 +125,6 @@
private Set<String> getTestTypesForModule(TreeLogger logger,
String moduleName, JClassType[] allTestTypes) {
- PerfLogger.start("GWTRunnerGenerator.getTestTypesForModule");
// Must use sorted set to prevent nondeterminism.
Set<String> testClasses = new TreeSet<String>();
for (JClassType classType : allTestTypes) {
@@ -158,13 +152,11 @@
}
testClasses.add(className);
}
- PerfLogger.end();
return testClasses;
}
private void writeCreateNewTestCaseMethod(Set<String> testClasses,
SourceWriter sw) {
- PerfLogger.start("GWTRunnerGenerator.writeCreateNewTestCaseMethod");
sw.println();
sw.println("protected final GWTTestCase createNewTestCase(String testClass) {");
sw.indent();
@@ -183,6 +175,5 @@
sw.println("return null;");
sw.outdent();
sw.println("}");
- PerfLogger.end();
}
}
diff --git a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
index 5bc0b1b..eb7dbbd 100644
--- a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
+++ b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
@@ -81,7 +81,8 @@
}
public TestInfo getFirstMethod() throws TimeoutException {
- return getHost().getNextTestInfo(getClientId(), TIME_TO_WAIT_FOR_TESTNAME);
+ return getHost().getNextTestInfo(getClientId(getThreadLocalRequest()),
+ TIME_TO_WAIT_FOR_TESTNAME);
}
public TestInfo reportResultsAndGetNextMethod(TestInfo testInfo,
@@ -90,7 +91,7 @@
ExceptionWrapper ew = result.getExceptionWrapper();
result.setException(deserialize(ew));
JUnitMessageQueue host = getHost();
- String clientId = getClientId();
+ String clientId = getClientId(getThreadLocalRequest());
host.reportResults(clientId, testInfo, result);
return host.getNextTestInfo(clientId, TIME_TO_WAIT_FOR_TESTNAME);
}
@@ -104,7 +105,7 @@
JUnitResult result = new JUnitResult();
initResult(request, result);
result.setException(new JUnitFatalLaunchException(requestPayload));
- getHost().reportResults(getClientId(), null, result);
+ getHost().reportResults(getClientId(request), null, result);
} else {
super.service(request, response);
}
@@ -219,8 +220,7 @@
/**
* Returns a "client id" for the current request.
*/
- private String getClientId() {
- HttpServletRequest request = getThreadLocalRequest();
+ private String getClientId(HttpServletRequest request) {
String machine = request.getRemoteHost();
String agent = request.getHeader("User-Agent");
return machine + " / " + agent;
diff --git a/user/src/com/google/gwt/user/Focus.gwt.xml b/user/src/com/google/gwt/user/Focus.gwt.xml
index af75aa3..9c591a8 100644
--- a/user/src/com/google/gwt/user/Focus.gwt.xml
+++ b/user/src/com/google/gwt/user/Focus.gwt.xml
@@ -20,12 +20,11 @@
<inherits name="com.google.gwt.core.Core"/>
<inherits name="com.google.gwt.user.UserAgent"/>
- <!-- old Mozilla, and Opera need a different implementation -->
+ <!-- old Mozilla needs a different implementation -->
<replace-with class="com.google.gwt.user.client.ui.impl.FocusImplOld">
<when-type-is class="com.google.gwt.user.client.ui.impl.FocusImpl"/>
<any>
- <when-property-is name="user.agent" value="gecko"/>
- <when-property-is name="user.agent" value="opera"/>
+ <when-property-is name="user.agent" value="gecko"/>
</any>
</replace-with>
diff --git a/user/src/com/google/gwt/user/History.gwt.xml b/user/src/com/google/gwt/user/History.gwt.xml
index 7f662cc..3637cad 100644
--- a/user/src/com/google/gwt/user/History.gwt.xml
+++ b/user/src/com/google/gwt/user/History.gwt.xml
@@ -46,4 +46,10 @@
<when-type-is class="com.google.gwt.user.client.impl.HistoryImpl"/>
<when-property-is name="user.agent" value="safari"/>
</replace-with>
+
+ <!-- Opera has yet another history implementation. -->
+ <replace-with class="com.google.gwt.user.client.impl.HistoryImplOpera">
+ <when-type-is class="com.google.gwt.user.client.impl.HistoryImpl"/>
+ <when-property-is name="user.agent" value="opera"/>
+ </replace-with>
</module>
diff --git a/user/src/com/google/gwt/user/client/DOM.java b/user/src/com/google/gwt/user/client/DOM.java
index 2d8e3fc..59e351d 100644
--- a/user/src/com/google/gwt/user/client/DOM.java
+++ b/user/src/com/google/gwt/user/client/DOM.java
@@ -403,6 +403,8 @@
* @return <code>true</code> if ALT was depressed when the event occurred
*/
public static boolean eventGetAltKey(Event evt) {
+ assertEventType(evt, Event.MOUSEEVENTS | Event.ONCLICK | Event.ONDBLCLICK
+ | Event.KEYEVENTS | Event.ONCONTEXTMENU, "altKey");
return impl.eventGetAltKey(evt);
}
@@ -414,6 +416,7 @@
* {@link Event#BUTTON_MIDDLE}, and {@link Event#BUTTON_RIGHT}
*/
public static int eventGetButton(Event evt) {
+ assertEventType(evt, Event.ONMOUSEDOWN | Event.ONMOUSEUP, "button");
return impl.eventGetButton(evt);
}
@@ -424,6 +427,8 @@
* @return the mouse x-position
*/
public static int eventGetClientX(Event evt) {
+ assertEventType(evt, Event.MOUSEEVENTS | Event.ONCLICK | Event.ONDBLCLICK
+ | Event.ONMOUSEWHEEL | Event.ONCONTEXTMENU, "clientX");
return impl.eventGetClientX(evt);
}
@@ -434,6 +439,8 @@
* @return the mouse y-position
*/
public static int eventGetClientY(Event evt) {
+ assertEventType(evt, Event.MOUSEEVENTS | Event.ONCLICK | Event.ONDBLCLICK
+ | Event.ONMOUSEWHEEL | Event.ONCONTEXTMENU, "clientY");
return impl.eventGetClientY(evt);
}
@@ -444,6 +451,8 @@
* @return <code>true</code> if CTRL was depressed when the event occurred
*/
public static boolean eventGetCtrlKey(Event evt) {
+ assertEventType(evt, Event.MOUSEEVENTS | Event.ONCLICK | Event.ONDBLCLICK
+ | Event.KEYEVENTS | Event.ONCONTEXTMENU, "ctrlKey");
return impl.eventGetCtrlKey(evt);
}
@@ -478,6 +487,7 @@
* @return the element from which the mouse pointer was moved
*/
public static Element eventGetFromElement(Event evt) {
+ assertEventType(evt, Event.ONMOUSEOVER | Event.ONMOUSEOUT, "fromElement");
return impl.eventGetFromElement(evt);
}
@@ -495,6 +505,7 @@
* @see com.google.gwt.user.client.ui.KeyboardListener
*/
public static int eventGetKeyCode(Event evt) {
+ assertEventType(evt, Event.KEYEVENTS, "keyCode");
return impl.eventGetKeyCode(evt);
}
@@ -505,6 +516,8 @@
* @return <code>true</code> if META was depressed when the event occurred
*/
public static boolean eventGetMetaKey(Event evt) {
+ assertEventType(evt, Event.MOUSEEVENTS | Event.ONCLICK | Event.ONDBLCLICK
+ | Event.KEYEVENTS | Event.ONCONTEXTMENU, "metaKey");
return impl.eventGetMetaKey(evt);
}
@@ -524,16 +537,18 @@
* @return The velocity of the mouse wheel.
*/
public static int eventGetMouseWheelVelocityY(Event evt) {
+ assertEventType(evt, Event.ONMOUSEWHEEL, "mouseWheelVelocityY");
return impl.eventGetMouseWheelVelocityY(evt);
}
/**
- * Gets the key-repeat state of this event.
+ * Gets the key-repeat state of this event. Only IE supports this attribute.
*
* @param evt the event to be tested
* @return <code>true</code> if this key event was an auto-repeat
*/
public static boolean eventGetRepeat(Event evt) {
+ assertEventType(evt, Event.ONKEYDOWN, "repeat");
return impl.eventGetRepeat(evt);
}
@@ -544,6 +559,8 @@
* @return the mouse x-position
*/
public static int eventGetScreenX(Event evt) {
+ assertEventType(evt, Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.ONCLICK
+ | Event.ONDBLCLICK | Event.ONCONTEXTMENU, "screenX");
return impl.eventGetScreenX(evt);
}
@@ -554,6 +571,8 @@
* @return the mouse y-position
*/
public static int eventGetScreenY(Event evt) {
+ assertEventType(evt, Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.ONCLICK
+ | Event.ONDBLCLICK | Event.ONCONTEXTMENU, "screenY");
return impl.eventGetScreenY(evt);
}
@@ -564,6 +583,8 @@
* @return <code>true</code> if shift was depressed when the event occurred
*/
public static boolean eventGetShiftKey(Event evt) {
+ assertEventType(evt, Event.MOUSEEVENTS | Event.ONCLICK | Event.ONDBLCLICK
+ | Event.KEYEVENTS | Event.ONCONTEXTMENU, "shiftKey");
return impl.eventGetShiftKey(evt);
}
@@ -585,6 +606,7 @@
* @return the element to which the mouse pointer was moved
*/
public static Element eventGetToElement(Event evt) {
+ assertEventType(evt, Event.ONMOUSEOVER | Event.ONMOUSEOUT, "toElement");
return impl.eventGetToElement(evt);
}
@@ -624,6 +646,7 @@
* @param key the new key code
*/
public static void eventSetKeyCode(Event evt, char key) {
+ assertEventType(evt, Event.KEYEVENTS, "keyCode");
impl.eventSetKeyCode(evt, key);
}
@@ -1281,6 +1304,20 @@
return ret;
}
+ /**
+ * Assert that the event is of the given event type, as defined by the
+ * {@link Event} class.
+ *
+ * @param evt the {@link Event} to check
+ * @param evtType the event type that is expected
+ * @param attr the attribute that should be defined by the event
+ */
+ private static void assertEventType(Event evt, int evtType, String attr) {
+ assert (eventGetType(evt) & evtType) != 0 :
+ "attribute '" + attr + "' not defined for event type '"
+ + eventGetTypeString(evt) + "'";
+ }
+
private static void dispatchEventAndCatch(Event evt, Element elem,
EventListener listener, UncaughtExceptionHandler handler) {
try {
diff --git a/user/src/com/google/gwt/user/client/Element.java b/user/src/com/google/gwt/user/client/Element.java
index 54a5d33..a8b8ca0 100644
--- a/user/src/com/google/gwt/user/client/Element.java
+++ b/user/src/com/google/gwt/user/client/Element.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -24,7 +24,7 @@
* typically done by calling methods in the
* {@link com.google.gwt.user.client.DOM} class.
*/
-public final class Element extends com.google.gwt.dom.client.Element {
+public class Element extends com.google.gwt.dom.client.Element {
/**
* Not directly instantiable. Subclasses should also define a protected no-arg
diff --git a/user/src/com/google/gwt/user/client/Event.java b/user/src/com/google/gwt/user/client/Event.java
index f387190..c3c388a 100644
--- a/user/src/com/google/gwt/user/client/Event.java
+++ b/user/src/com/google/gwt/user/client/Event.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -26,7 +26,7 @@
* from, and can be accessed in JavaScript code as expected. This is typically
* done by calling methods in the {@link com.google.gwt.user.client.DOM} class.
*/
-public final class Event extends JavaScriptObject {
+public class Event extends JavaScriptObject {
/**
* The left mouse button (used in {@link DOM#eventGetButton(Event)}).
@@ -135,6 +135,13 @@
public static final int ONSCROLL = 0x04000;
/**
+ * Fired when the user requests an element's context menu (usually by right-clicking).
+ *
+ * Note that not all browsers will fire this event (notably Opera, as of 9.5).
+ */
+ public static final int ONCONTEXTMENU = 0x40000;
+
+ /**
* A bit-mask covering both focus events (focus and blur).
*/
public static final int FOCUSEVENTS = ONFOCUS | ONBLUR;
@@ -152,14 +159,27 @@
| ONMOUSEOVER | ONMOUSEOUT;
/**
- * Error code returned by DOM.getEventXXX methods when the actual integer
- * value is undefined. For example, DOM.getEventKeyCode returns UNDEFINED for
- * some non-keyboard events.
+ * Value returned by DOM.getEventXXX methods when the actual integer value is
+ * undefined. For example, DOM.getEventKeyCode returns UNDEFINED for
+ * non-keyboard events.
*
- * For some events, some browsers return undefined while others return data
- * for certain events.
+ * If assertions are enabled in hosted mode (using the -ea or
+ * -enableassertion compiler flag), DOM.getEventXXX will throw assertion
+ * errors instead of returning UNDEFINED. The assertions in each
+ * DOM.getEventXXX ensure that the attribute you are retrieving is defined for
+ * all supported browsers.
+ *
+ * If you disable assertions, some events in some browsers will return
+ * UNDEFINED. However, other browsers may return meaningless data instead of
+ * UNDEFINED if the attribute does not have meaning in the context of the
+ * Event. In addition, some methods might return the value 0, which equals
+ * UNDEFINED, when in fact 0 is a valid return type (for example, clientX
+ * could be 0). As a result, it is NOT safe to rely on the return type of
+ * DOM.getEventXXX methods when assertions are disabled, because the return
+ * values may not have meaning in the context of the event.
*/
- public static final int UNDEFINED = -1;
+ @Deprecated
+ public static final int UNDEFINED = 0;
/**
* Gets the current event that is being fired. The current event is only
diff --git a/user/src/com/google/gwt/user/client/Window.java b/user/src/com/google/gwt/user/client/Window.java
index 160b633..aadbc63 100644
--- a/user/src/com/google/gwt/user/client/Window.java
+++ b/user/src/com/google/gwt/user/client/Window.java
@@ -21,6 +21,7 @@
import com.google.gwt.user.client.impl.WindowImpl;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -33,12 +34,13 @@
/**
* This class provides access to the browser's location's object. The location
* object contains information about the current URL and methods to manipulate
- * it. <code>Location</code> is a very simple wrapper, so not all browser quirks are hidden from
- * the user.
+ * it. <code>Location</code> is a very simple wrapper, so not all browser
+ * quirks are hidden from the user.
*
*/
public static class Location {
private static Map<String, String> paramMap;
+ private static Map<String, String> unmodifiableParamMap;
/**
* Assigns the window to a new URL. All GWT state will be lost.
@@ -93,7 +95,24 @@
* @return the value of the URL's parameter
*/
public static String getParameter(String name) {
- return ensureParameterMap().get(name);
+ ensureParameterMap();
+ return paramMap.get(name);
+ }
+
+ /**
+ * Returns a Map of the URL query parameters for the host page; since
+ * changing the map would not change the window's location, the map returned
+ * is immutable.
+ *
+ * @return a map from URL query parameter names to values
+ */
+ public static Map<String, String> getParameterMap() {
+ ensureParameterMap();
+
+ if (unmodifiableParamMap == null) {
+ unmodifiableParamMap = Collections.unmodifiableMap(paramMap);
+ }
+ return unmodifiableParamMap;
}
/**
@@ -149,7 +168,7 @@
$wnd.location.replace(newURL);
}-*/;
- private static Map<String, String> ensureParameterMap() {
+ private static void ensureParameterMap() {
if (paramMap == null) {
paramMap = new HashMap<String, String>();
String queryString = getQueryString();
@@ -165,7 +184,6 @@
}
}
}
- return paramMap;
}
private Location() {
@@ -470,7 +488,7 @@
}-*/;
private static void maybeInitializeHandlers() {
- if (!handlersAreInitialized) {
+ if (GWT.isClient() && !handlersAreInitialized) {
init();
handlersAreInitialized = true;
}
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImpl.java b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
index 20c7f68..b9d1a5b 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImpl.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
@@ -54,15 +54,15 @@
}-*/;
public native int eventGetButton(Event evt) /*-{
- return evt.button || -1;
+ return evt.button || 0;
}-*/;
public native int eventGetClientX(Event evt) /*-{
- return evt.clientX || -1;
+ return evt.clientX || 0;
}-*/;
public native int eventGetClientY(Event evt) /*-{
- return evt.clientY || -1;
+ return evt.clientY || 0;
}-*/;
public native boolean eventGetCtrlKey(Event evt) /*-{
@@ -79,7 +79,7 @@
// 'which' gives the right key value, except when it doesn't -- in which
// case, keyCode gives the right value on all browsers.
// If all else fails, return an error code
- return evt.which || evt.keyCode || -1;
+ return evt.which || evt.keyCode || 0;
}-*/;
public native boolean eventGetMetaKey(Event evt) /*-{
@@ -93,11 +93,11 @@
}-*/;
public native int eventGetScreenX(Event evt) /*-{
- return evt.screenX || -1;
+ return evt.screenX || 0;
}-*/;
public native int eventGetScreenY(Event evt) /*-{
- return evt.screenY || -1;
+ return evt.screenY || 0;
}-*/;
public native boolean eventGetShiftKey(Event evt) /*-{
@@ -133,6 +133,7 @@
case "error": return 0x10000;
case "mousewheel": return 0x20000;
case "DOMMouseScroll": return 0x20000;
+ case "contextmenu": return 0x40000;
}
}-*/;
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplIE6.java b/user/src/com/google/gwt/user/client/impl/DOMImplIE6.java
index 80f038d..f5df1e6 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplIE6.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplIE6.java
@@ -58,7 +58,7 @@
@Override
public native int eventGetMouseWheelVelocityY(Event evt) /*-{
- return Math.round(-evt.wheelDelta / 40) || -1;
+ return Math.round(-evt.wheelDelta / 40) || 0;
}-*/;
@Override
@@ -160,6 +160,7 @@
$doc.body.attachEvent('onfocus', bodyDispatcher);
$doc.body.attachEvent('onblur', bodyDispatcher);
$doc.body.attachEvent('ondblclick', bodyDblClickDispatcher);
+ $doc.body.attachEvent('oncontextmenu', bodyDispatcher);
}-*/;
@Override
@@ -239,5 +240,7 @@
@com.google.gwt.user.client.impl.DOMImplIE6::dispatchEvent : null;
if (chMask & 0x20000) elem.onmousewheel = (bits & 0x20000) ?
@com.google.gwt.user.client.impl.DOMImplIE6::dispatchEvent : null;
+ if (chMask & 0x40000) elem.oncontextmenu = (bits & 0x40000) ?
+ @com.google.gwt.user.client.impl.DOMImplIE6::dispatchEvent : null;
}-*/;
}
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplMozilla.java b/user/src/com/google/gwt/user/client/impl/DOMImplMozilla.java
index c7b7734..6c5c0df 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplMozilla.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplMozilla.java
@@ -24,8 +24,18 @@
class DOMImplMozilla extends DOMImplStandard {
@Override
+ public native int eventGetClientX(Event evt) /*-{
+ return evt.clientX - $doc.getBoxObjectFor($doc.getElementsByTagName('html')[0]).x || 0;
+ }-*/;
+
+ @Override
+ public native int eventGetClientY(Event evt) /*-{
+ return evt.clientY - $doc.getBoxObjectFor($doc.getElementsByTagName('html')[0]).y || 0;
+ }-*/;
+
+ @Override
public native int eventGetMouseWheelVelocityY(Event evt) /*-{
- return evt.detail || -1;
+ return evt.detail || 0;
}-*/;
@Override
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplOpera.java b/user/src/com/google/gwt/user/client/impl/DOMImplOpera.java
index 9cb970a..d94dbc8 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplOpera.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplOpera.java
@@ -25,7 +25,7 @@
@Override
public native int eventGetMouseWheelVelocityY(Event evt) /*-{
- return evt.detail * 4 || -1;
+ return evt.detail * 4 || 0;
}-*/;
/**
@@ -71,5 +71,7 @@
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
elem.onmousewheel = (bits & 0x20000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
+ elem.oncontextmenu = (bits & 0x40000) ?
+ @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
}-*/;
}
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplSafari.java b/user/src/com/google/gwt/user/client/impl/DOMImplSafari.java
index bbaf894..bdd5f10 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplSafari.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplSafari.java
@@ -25,17 +25,37 @@
@Override
public native int eventGetClientX(Event evt) /*-{
// In Safari2: clientX is wrong and pageX is returned instead.
- return evt.pageX - $doc.body.scrollLeft || -1;
+ if ($wnd.devicePixelRatio) {
+ // $wnd.devicePixelRatio identifies Safari 3 from Safari 2
+ return evt.pageX - $doc.body.scrollLeft || 0;
+ } else {
+ // Subtract the margin and border of the HTML element in Safari 2
+ // TODO: Remove this code when we drop Safari 2 support
+ var style = document.defaultView.getComputedStyle($doc.getElementsByTagName('html')[0], '');
+ return evt.pageX - $doc.body.scrollLeft
+ - parseInt(style.getPropertyValue('margin-left'))
+ - parseInt(style.getPropertyValue('border-left-width')) || 0;
+ }
}-*/;
@Override
public native int eventGetClientY(Event evt) /*-{
// In Safari2: clientY is wrong and pageY is returned instead.
- return evt.pageY - $doc.body.scrollTop || -1;
+ if ($wnd.devicePixelRatio) {
+ // $wnd.devicePixelRatio identifies Safari 3 from Safari 2
+ return evt.pageY - $doc.body.scrollTop || 0;
+ } else {
+ // Subtract the margin and border of the HTML element in Safari 2
+ // TODO: Remove this code when we drop Safari 2 support
+ var style = document.defaultView.getComputedStyle($doc.getElementsByTagName('html')[0], '');
+ return evt.pageY - $doc.body.scrollTop
+ - parseInt(style.getPropertyValue('margin-top'))
+ - parseInt(style.getPropertyValue('border-top-width')) || 0;
+ }
}-*/;
@Override
public native int eventGetMouseWheelVelocityY(Event evt) /*-{
- return Math.round(-evt.wheelDelta / 40) || -1;
+ return Math.round(-evt.wheelDelta / 40) || 0;
}-*/;
}
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java b/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java
index 31d7a4d..ba0cf6c 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java
@@ -48,7 +48,7 @@
} else if (button == 3) {
return 2;
}
- return button || -1;
+ return button || 0;
}-*/;
@Override
@@ -256,6 +256,8 @@
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
if (chMask & 0x20000) elem.onmousewheel = (bits & 0x20000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
+ if (chMask & 0x40000) elem.oncontextmenu = (bits & 0x40000) ?
+ @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
}-*/;
private native void releaseCaptureImpl(Element elem) /*-{
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplOpera.java b/user/src/com/google/gwt/user/client/impl/HistoryImplOpera.java
new file mode 100644
index 0000000..57ddb0b
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImplOpera.java
@@ -0,0 +1,34 @@
+/*
+ * 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.user.client.impl;
+
+/**
+ * History implementation for Opera.
+ */
+class HistoryImplOpera extends HistoryImplStandard {
+
+ @Override
+ public native void newItem(String historyToken) /*-{
+ historyToken = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(historyToken || "");
+
+ // Opera adds a new stack item each time location.hash is set. All other
+ // browsers check whether the value actually changes, which we duplicate.
+ var prevHistoryToken = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)($wnd.__gwt_historyToken || "");
+ if (prevHistoryToken != historyToken) {
+ $wnd.location.hash = historyToken;
+ }
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java b/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java
index e5fa7e6..7278862 100644
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java
@@ -42,7 +42,7 @@
@com.google.gwt.user.client.impl.HistoryImpl::onHistoryChanged(Ljava/lang/String;)(token);
}
- $wnd.setTimeout('__checkHistory()', 250);
+ $wnd.setTimeout($wnd.__checkHistory, 250);
};
// Kick off the timer.
diff --git a/user/src/com/google/gwt/user/client/impl/WindowImplOpera.java b/user/src/com/google/gwt/user/client/impl/WindowImplOpera.java
index b828e18..a6e8d0e 100644
--- a/user/src/com/google/gwt/user/client/impl/WindowImplOpera.java
+++ b/user/src/com/google/gwt/user/client/impl/WindowImplOpera.java
@@ -15,17 +15,36 @@
*/
package com.google.gwt.user.client.impl;
+import com.google.gwt.user.client.Element;
+
/**
* Opera implementation of {@link com.google.gwt.user.client.impl.WindowImpl}.
*/
public class WindowImplOpera extends WindowImpl {
- @Override
- public native int getClientHeight() /*-{
- return $doc.body.clientHeight;
+ @SuppressWarnings("unused")
+ private static Element body;
+
+ /**
+ * In standards mode, on Opera 9.5 (and presumably, above), the clientHeight
+ * and clientWidth are defined on doc.documentElement instead of doc.body.
+ */
+ @SuppressWarnings("unused")
+ private static native Element getBodyElement() /*-{
+ if (@com.google.gwt.user.client.impl.WindowImplOpera::body == null) {
+ @com.google.gwt.user.client.impl.WindowImplOpera::body =
+ ($doc.compatMode == 'CSS1Compat' && opera.version() >= 9.5) ?
+ $doc.documentElement : $doc.body;
+ }
+ return @com.google.gwt.user.client.impl.WindowImplOpera::body;
}-*/;
@Override
+ public native int getClientHeight() /*-{
+ return @com.google.gwt.user.client.impl.WindowImplOpera::getBodyElement()().clientHeight;
+ }-*/;
+
+ @Override
public native int getClientWidth() /*-{
- return $doc.body.clientWidth;
- }-*/;
+ return @com.google.gwt.user.client.impl.WindowImplOpera::getBodyElement()().clientWidth;
+ }-*/;
}
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java
index 60c2e0c..c8aaf0a 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java
@@ -33,6 +33,30 @@
public abstract class AbstractSerializationStreamWriter extends
AbstractSerializationStream implements SerializationStreamWriter {
+ // Keep synchronized with LongLib
+ private static final double TWO_PWR_16_DBL = 0x10000;
+
+ // Keep synchronized with LongLib
+ private static final double TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL;
+
+ /**
+ * Make an instance equivalent to stringing highBits next to lowBits, where
+ * highBits and lowBits are assumed to be in 32-bit twos-complement notation.
+ * As a result, for any double[] l, the following identity holds:
+ *
+ * <blockquote> <code>l == makeFromBits(l.highBits(), l.lowBits())</code>
+ * </blockquote>
+ */
+ // Keep synchronized with LongLib
+ protected static double[] makeLongComponents(int highBits, int lowBits) {
+ double high = highBits * TWO_PWR_32_DBL;
+ double low = lowBits;
+ if (lowBits < 0) {
+ low += TWO_PWR_32_DBL;
+ }
+ return new double[] {low, high};
+ }
+
private int objectCount;
private Map<Object, Integer> objectMap = new IdentityHashMap<Object, Integer>();
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java
index 4146c5d..f9a7a82 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -15,7 +15,9 @@
*/
package com.google.gwt.user.client.rpc.impl;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.UnsafeNativeLong;
import com.google.gwt.user.client.rpc.SerializationException;
/**
@@ -32,6 +34,11 @@
return array.length;
}-*/;
+ @UnsafeNativeLong
+ private static native long readLong0(double low, double high) /*-{
+ return [low, high];
+ }-*/;
+
int index;
JavaScriptObject results;
@@ -77,11 +84,11 @@
}-*/;
public long readLong() {
- /*
- * Sent as a string; if we tried to marshal as a number the JSON eval would
- * lose precision. We use hex due to specially optimized code paths in Long.
- */
- return Long.parseLong(readString(), 16);
+ if (GWT.isScript()) {
+ return readLong0(readDouble(), readDouble());
+ } else {
+ return (long) readDouble() + (long) readDouble();
+ }
}
public native short readShort() /*-{
@@ -103,7 +110,7 @@
}
@Override
- protected native String getString(int index) /*-{
+ protected native String getString(int index) /*-{
// index is 1-based
return index > 0 ? this.@com.google.gwt.user.client.rpc.impl.ClientSerializationStreamReader::stringTable[index - 1] : null;
}-*/;
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
index 85dfc22..df00dc1 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
@@ -15,6 +15,8 @@
*/
package com.google.gwt.user.client.rpc.impl;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.UnsafeNativeLong;
import com.google.gwt.user.client.rpc.SerializationException;
import java.util.List;
@@ -31,6 +33,12 @@
sb.append('\uffff');
}
+ @UnsafeNativeLong
+ // Keep synchronized with LongLib
+ private static native double[] makeLongComponents0(long value) /*-{
+ return value;
+ }-*/;
+
private StringBuffer encodeBuffer;
private final String moduleBaseURL;
@@ -92,8 +100,22 @@
return buffer.toString();
}
+ @Override
public void writeLong(long fieldValue) {
- append(Long.toString(fieldValue, 16));
+ /*
+ * Client code represents longs internally as an array of two Numbers. In
+ * order to make serialization of longs faster, we'll send the component
+ * parts so that the value can be directly reconstituted on the server.
+ */
+ double[] parts;
+ if (GWT.isScript()) {
+ parts = makeLongComponents0(fieldValue);
+ } else {
+ parts = makeLongComponents((int) (fieldValue >> 32), (int) fieldValue);
+ }
+ assert parts.length == 2;
+ writeDouble(parts[0]);
+ writeDouble(parts[1]);
}
/**
diff --git a/user/src/com/google/gwt/user/client/ui/Anchor.java b/user/src/com/google/gwt/user/client/ui/Anchor.java
new file mode 100644
index 0000000..f7669f5
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/Anchor.java
@@ -0,0 +1,265 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.dom.client.AnchorElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.i18n.client.BidiUtils;
+import com.google.gwt.i18n.client.HasDirection;
+
+/**
+ * A widget that represents a simple <a> element.
+ *
+ * <h3>CSS Style Rules</h3>
+ * <ul class='css'>
+ * <li>.gwt-Anchor { }</li>
+ * </ul>
+ */
+public class Anchor extends FocusWidget implements HasHorizontalAlignment,
+ HasName, HasText, HasHTML, HasWordWrap, HasDirection {
+
+ /**
+ * Creates an Anchor widget that wraps an existing <div> or <span>
+ * element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static Anchor wrap(Element element) {
+ // Assert that the element is of the correct type and is attached.
+ AnchorElement.as(element);
+ assert Document.get().getBody().isOrHasChild(element);
+
+ Anchor anchor = new Anchor(element);
+
+ // Mark it attached and remember it for cleanup.
+ anchor.onAttach();
+ RootPanel.detachOnWindowClose(anchor);
+
+ return anchor;
+ }
+
+ private HorizontalAlignmentConstant horzAlign;
+
+ /**
+ * Creates an empty anchor.
+ */
+ public Anchor() {
+ setElement(Document.get().createAnchorElement());
+ setStyleName("gwt-Anchor");
+ }
+
+ /**
+ * Creates an anchor for scripting.
+ *
+ * The anchor's href is set to <code>javascript:</code>, based on the
+ * expectation that listeners will be added to the anchor.
+ *
+ * @param text the anchor's text
+ */
+ public Anchor(String text) {
+ this(text, "javascript:");
+ }
+
+ /**
+ * Creates an anchor for scripting.
+ *
+ * The anchor's href is set to <code>javascript:</code>, based on the
+ * expectation that listeners will be added to the anchor.
+ *
+ * @param text the anchor's text
+ * @param asHtml <code>true</code> to treat the specified text as html
+ */
+ public Anchor(String text, boolean asHtml) {
+ this(text, asHtml, "javascript:");
+ }
+
+ /**
+ * Creates an anchor with its text and href (target URL) specified.
+ *
+ * @param text the anchor's text
+ * @param asHTML <code>true</code> to treat the specified text as html
+ * @param href the url to which it will link
+ */
+ public Anchor(String text, boolean asHTML, String href) {
+ this();
+ if (asHTML) {
+ setHTML(text);
+ } else {
+ setText(text);
+ }
+ setHref(href);
+ }
+
+ /**
+ * Creates a source anchor (link to URI).
+ *
+ * That is, an anchor with an href attribute specifying the destination URI.
+ *
+ * @param text the anchor's text
+ * @param asHtml asHTML <code>true</code> to treat the specified text as
+ * html
+ * @param href the url to which it will link
+ * @param target the target frame (e.g. "_blank" to open the link in a new
+ * window)
+ */
+ public Anchor(String text, boolean asHtml, String href, String target) {
+ this(text, asHtml, href);
+ setTarget(target);
+ }
+
+ /**
+ * Creates an anchor with its text and href (target URL) specified.
+ *
+ * @param text the anchor's text
+ * @param href the url to which it will link
+ */
+ public Anchor(String text, String href) {
+ this();
+ setText(text);
+ setHref(href);
+ }
+
+ /**
+ * Creates a source anchor with a frame target.
+ *
+ * @param text the anchor's text
+ * @param href the url to which it will link
+ * @param target the target frame (e.g. "_blank" to open the link in a new
+ * window)
+ */
+ public Anchor(String text, String href, String target) {
+ this(text, false, href, target);
+ }
+
+ private Anchor(Element element) {
+ setElement(element);
+ }
+
+ public Direction getDirection() {
+ return BidiUtils.getDirectionOnElement(getElement());
+ }
+
+ public HorizontalAlignmentConstant getHorizontalAlignment() {
+ return horzAlign;
+ }
+
+ /**
+ * Gets the anchor's href (the url to which it links).
+ *
+ * @return the anchor's href
+ */
+ public String getHref() {
+ return getAnchorElement().getHref();
+ }
+
+ public String getHTML() {
+ return getElement().getInnerHTML();
+ }
+
+ public String getName() {
+ return getAnchorElement().getName();
+ }
+
+ public int getTabIndex() {
+ return getAnchorElement().getTabIndex();
+ }
+
+ /**
+ * Gets the anchor's target frame (the frame in which navigation will occur
+ * when the link is selected).
+ *
+ * @return the target frame
+ */
+ public String getTarget() {
+ return getAnchorElement().getTarget();
+ }
+
+ public String getText() {
+ return getElement().getInnerText();
+ }
+
+ public boolean getWordWrap() {
+ return !getElement().getStyle().getProperty("whiteSpace").equals("nowrap");
+ }
+
+ public void setAccessKey(char key) {
+ getAnchorElement().setAccessKey(Character.toString(key));
+ }
+
+ public void setDirection(Direction direction) {
+ BidiUtils.setDirectionOnElement(getElement(), direction);
+ }
+
+ public void setFocus(boolean focused) {
+ if (focused) {
+ getAnchorElement().focus();
+ } else {
+ getAnchorElement().blur();
+ }
+ }
+
+ public void setHorizontalAlignment(HorizontalAlignmentConstant align) {
+ horzAlign = align;
+ getElement().getStyle().setProperty("textAlign", align.getTextAlignString());
+ }
+
+ /**
+ * Sets the anchor's href (the url to which it links).
+ *
+ * @param href the anchor's href
+ */
+ public void setHref(String href) {
+ getAnchorElement().setHref(href);
+ }
+
+ public void setHTML(String html) {
+ getElement().setInnerHTML(html);
+ }
+
+ public void setName(String name) {
+ getAnchorElement().setName(name);
+ }
+
+ public void setTabIndex(int index) {
+ getAnchorElement().setTabIndex(index);
+ }
+
+ /**
+ * Sets the anchor's target frame (the frame in which navigation will occur
+ * when the link is selected).
+ *
+ * @param target the target frame
+ */
+ public void setTarget(String target) {
+ getAnchorElement().setTarget(target);
+ }
+
+ public void setText(String text) {
+ getElement().setInnerText(text);
+ }
+
+ public void setWordWrap(boolean wrap) {
+ getElement().getStyle().setProperty("whiteSpace",
+ wrap ? "normal" : "nowrap");
+ }
+
+ private AnchorElement getAnchorElement() {
+ return AnchorElement.as(getElement());
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/Button.java b/user/src/com/google/gwt/user/client/ui/Button.java
index 94e41d8..a29a69b 100644
--- a/user/src/com/google/gwt/user/client/ui/Button.java
+++ b/user/src/com/google/gwt/user/client/ui/Button.java
@@ -15,6 +15,8 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.ButtonElement;
+import com.google.gwt.dom.client.Document;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
@@ -36,9 +38,26 @@
*/
public class Button extends ButtonBase {
- static native void click(Element button) /*-{
- button.click();
- }-*/;
+ /**
+ * Creates a Button widget that wraps an existing <button> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static Button wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ ButtonElement.as(element);
+ assert Document.get().getBody().isOrHasChild(element);
+
+ Button button = new Button((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ button.onAttach();
+ RootPanel.detachOnWindowClose(button);
+
+ return button;
+ }
static native void adjustType(Element button) /*-{
// Check before setting this attribute, as not all browsers define it.
@@ -50,6 +69,10 @@
}
}-*/;
+ static native void click(Element button) /*-{
+ button.click();
+ }-*/;
+
/**
* Creates a button with no caption.
*/
@@ -80,6 +103,10 @@
addClickListener(listener);
}
+ private Button(Element element) {
+ super(element);
+ }
+
/**
* Programmatic equivalent of the user clicking the button.
*/
diff --git a/user/src/com/google/gwt/user/client/ui/CheckBox.java b/user/src/com/google/gwt/user/client/ui/CheckBox.java
index 14bc01c..5d09e5d 100644
--- a/user/src/com/google/gwt/user/client/ui/CheckBox.java
+++ b/user/src/com/google/gwt/user/client/ui/CheckBox.java
@@ -19,16 +19,21 @@
import com.google.gwt.user.client.Element;
/**
- * A standard check box widget (also serves as a base class for
+ * A standard check box widget.
+ *
+ * This class also serves as a base class for
* {@link com.google.gwt.user.client.ui.RadioButton}.
+ *
* <p>
* <img class='gallery' src='CheckBox.png'/>
* </p>
+ *
* <h3>CSS Style Rules</h3>
* <ul class='css'>
* <li>.gwt-CheckBox { }</li>
* <li>.gwt-CheckBox-disabled { Applied when Checkbox is disabled }</li>
* </ul>
+ *
* <p>
* <h3>Example</h3>
* {@example com.google.gwt.examples.CheckBoxExample}
@@ -237,7 +242,6 @@
* @param elem the new input element
*/
protected void replaceInputElement(Element elem) {
-
// Collect information we need to set
int tabIndex = getTabIndex();
boolean checked = isChecked();
diff --git a/user/src/com/google/gwt/user/client/ui/FileUpload.java b/user/src/com/google/gwt/user/client/ui/FileUpload.java
index 2ea794b..966fbf3 100644
--- a/user/src/com/google/gwt/user/client/ui/FileUpload.java
+++ b/user/src/com/google/gwt/user/client/ui/FileUpload.java
@@ -15,20 +15,46 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.InputElement;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
/**
* A widget that wraps the HTML <input type='file'> element. This widget
- * must be used with {@link com.google.gwt.user.client.ui.FormPanel} if it is
- * to be submitted to a server.
+ * must be used with {@link com.google.gwt.user.client.ui.FormPanel} if it is to
+ * be submitted to a server.
*
* <p>
- * <h3>Example</h3> {@example com.google.gwt.examples.FormPanelExample}
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.FormPanelExample}
* </p>
*/
public class FileUpload extends Widget implements HasName {
/**
+ * Creates a FileUpload widget that wraps an existing <input
+ * type='file'> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static FileUpload wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the right type and is attached.
+ assert InputElement.as(element).getType().equalsIgnoreCase("file");
+ assert Document.get().getBody().isOrHasChild(element);
+
+ FileUpload fileUpload = new FileUpload((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ fileUpload.onAttach();
+ RootPanel.detachOnWindowClose(fileUpload);
+
+ return fileUpload;
+ }
+
+ /**
* Constructs a new file upload widget.
*/
public FileUpload() {
@@ -37,6 +63,10 @@
setStyleName("gwt-FileUpload");
}
+ private FileUpload(Element element) {
+ setElement(element);
+ }
+
/**
* Gets the filename selected by the user. This property has no mutator, as
* browser security restrictions preclude setting it.
diff --git a/user/src/com/google/gwt/user/client/ui/FormPanel.java b/user/src/com/google/gwt/user/client/ui/FormPanel.java
index d335822..d873e14 100644
--- a/user/src/com/google/gwt/user/client/ui/FormPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/FormPanel.java
@@ -16,8 +16,10 @@
package com.google.gwt.user.client.ui;
import com.google.gwt.core.client.GWT;
-import com.google.gwt.user.client.Command;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.FormElement;
+import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
@@ -85,6 +87,27 @@
private static int formId = 0;
private static FormPanelImpl impl = GWT.create(FormPanelImpl.class);
+ /**
+ * Creates a FormPanel that wraps an existing <form> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static FormPanel wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ FormElement.as(element);
+ assert Document.get().getBody().isOrHasChild(element);
+
+ FormPanel formPanel = new FormPanel((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ formPanel.onAttach();
+ RootPanel.detachOnWindowClose(formPanel);
+
+ return formPanel;
+ }
+
private FormHandlerCollection formHandlers;
private String frameName;
private Element iframe;
@@ -152,6 +175,10 @@
setTarget(target);
}
+ private FormPanel(Element elem) {
+ super(elem);
+ }
+
public void addFormHandler(FormHandler handler) {
if (formHandlers == null) {
formHandlers = new FormHandlerCollection();
diff --git a/user/src/com/google/gwt/user/client/ui/Frame.java b/user/src/com/google/gwt/user/client/ui/Frame.java
index e58bbe7..c8a6069 100644
--- a/user/src/com/google/gwt/user/client/ui/Frame.java
+++ b/user/src/com/google/gwt/user/client/ui/Frame.java
@@ -15,7 +15,10 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.IFrameElement;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
/**
* A widget that wraps an IFRAME element, which can contain an arbitrary web
@@ -35,8 +38,30 @@
* </p>
*/
public class Frame extends Widget {
+
static final String DEFAULT_STYLENAME = "gwt-Frame";
-
+
+ /**
+ * Creates a Frame widget that wraps an existing <frame> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static Frame wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ IFrameElement.as(element);
+ assert Document.get().getBody().isOrHasChild(element);
+
+ Frame frame = new Frame((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ frame.onAttach();
+ RootPanel.detachOnWindowClose(frame);
+
+ return frame;
+ }
+
/**
* Creates an empty frame.
*/
@@ -55,6 +80,10 @@
setUrl(url);
}
+ private Frame(Element element) {
+ setElement(element);
+ }
+
/**
* Gets the URL of the frame's resource.
*
diff --git a/user/src/com/google/gwt/user/client/ui/HTML.java b/user/src/com/google/gwt/user/client/ui/HTML.java
index 80edbc2..5cebe0a 100644
--- a/user/src/com/google/gwt/user/client/ui/HTML.java
+++ b/user/src/com/google/gwt/user/client/ui/HTML.java
@@ -15,11 +15,16 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.Document;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
/**
* A widget that can contain arbitrary HTML.
*
+ * This widget uses a <div> element, causing it to be displayed with block
+ * layout.
+ *
* <p>
* If you only need a simple label (text, but not HTML), then the
* {@link com.google.gwt.user.client.ui.Label} widget is more appropriate, as it
@@ -40,6 +45,29 @@
public class HTML extends Label implements HasHTML {
/**
+ * Creates an HTML widget that wraps an existing <div> or <span>
+ * element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static HTML wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ assert element.getTagName().equalsIgnoreCase("div")
+ || element.getTagName().equalsIgnoreCase("span");
+ assert Document.get().getBody().isOrHasChild(element);
+
+ HTML html = new HTML((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ html.onAttach();
+ RootPanel.detachOnWindowClose(html);
+
+ return html;
+ }
+
+ /**
* Creates an empty HTML widget.
*/
public HTML() {
@@ -69,6 +97,10 @@
setWordWrap(wordWrap);
}
+ HTML(Element element) {
+ super(element);
+ }
+
public String getHTML() {
return DOM.getInnerHTML(getElement());
}
diff --git a/user/src/com/google/gwt/user/client/ui/HTMLPanel.java b/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
index b2e9d03..ca7fa68 100644
--- a/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
@@ -89,7 +89,11 @@
}
/**
- * Finds an {@link Element element} within this panel by its id.
+ * Finds an {@link Element element} within this panel by its id.
+ *
+ * This method uses
+ * {@link com.google.gwt.dom.client.Document#getElementById(String)}, so the
+ * id must still be unique within the document.
*
* @param id the id of the element to be found
* @return the element with the given id, or <code>null</code> if none is found
diff --git a/user/src/com/google/gwt/user/client/ui/HasHorizontalAlignment.java b/user/src/com/google/gwt/user/client/ui/HasHorizontalAlignment.java
index ab187aa..7518d12 100644
--- a/user/src/com/google/gwt/user/client/ui/HasHorizontalAlignment.java
+++ b/user/src/com/google/gwt/user/client/ui/HasHorizontalAlignment.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.LocaleInfo;
/**
@@ -67,7 +68,8 @@
* should be aligned to the left.
*/
HorizontalAlignmentConstant ALIGN_DEFAULT =
- (LocaleInfo.getCurrentLocale().isRTL()) ? ALIGN_RIGHT : ALIGN_LEFT;
+ (GWT.isClient() && (LocaleInfo.getCurrentLocale().isRTL()))
+ ? ALIGN_RIGHT : ALIGN_LEFT;
/**
* Gets the horizontal alignment.
@@ -86,4 +88,4 @@
* {@link HasHorizontalAlignment#ALIGN_DEFAULT}).
*/
void setHorizontalAlignment(HorizontalAlignmentConstant align);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/user/client/ui/Hidden.java b/user/src/com/google/gwt/user/client/ui/Hidden.java
index 2c48c71..b667197 100644
--- a/user/src/com/google/gwt/user/client/ui/Hidden.java
+++ b/user/src/com/google/gwt/user/client/ui/Hidden.java
@@ -16,6 +16,8 @@
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.InputElement;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
@@ -25,6 +27,28 @@
public class Hidden extends Widget implements HasName {
/**
+ * Creates a Hidden widget that wraps an existing <input type='hidden'>
+ * element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static Hidden wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ assert InputElement.as(element).getType().equalsIgnoreCase("hidden");
+ assert Document.get().getBody().isOrHasChild(element);
+
+ Hidden hidden = new Hidden((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ hidden.onAttach();
+ RootPanel.detachOnWindowClose(hidden);
+
+ return hidden;
+ }
+
+ /**
* Constructor for <code>Hidden</code>.
*/
public Hidden() {
@@ -54,6 +78,10 @@
setValue(value);
}
+ private Hidden(Element element) {
+ setElement(element);
+ }
+
/**
* Gets the default value of the hidden field.
*
diff --git a/user/src/com/google/gwt/user/client/ui/Image.java b/user/src/com/google/gwt/user/client/ui/Image.java
index 0031214..a6feb7e 100644
--- a/user/src/com/google/gwt/user/client/ui/Image.java
+++ b/user/src/com/google/gwt/user/client/ui/Image.java
@@ -15,13 +15,15 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.ImageElement;
+import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
-import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.impl.ClippedImageImpl;
-import com.google.gwt.core.client.GWT;
import java.util.HashMap;
@@ -221,6 +223,11 @@
*/
private static class UnclippedState extends State {
+ UnclippedState(Element element) {
+ DOM.sinkEvents(element, Event.ONCLICK | Event.MOUSEEVENTS | Event.ONLOAD
+ | Event.ONERROR | Event.ONMOUSEWHEEL);
+ }
+
UnclippedState(Image image) {
image.replaceElement(DOM.createImg());
image.sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.ONLOAD
@@ -300,6 +307,28 @@
prefetchImages.put(url, img);
}
+ /**
+ * Creates a Image widget that wraps an existing <img> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static Image wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ ImageElement.as(element);
+ assert Document.get().getBody().isOrHasChild(element);
+
+ Image image = new Image((Element) element);
+ image.changeState(new UnclippedState((Element) element));
+
+ // Mark it attached and remember it for cleanup.
+ image.onAttach();
+ RootPanel.detachOnWindowClose(image);
+
+ return image;
+ }
+
private ClickListenerCollection clickListeners;
private LoadListenerCollection loadListeners;
private MouseListenerCollection mouseListeners;
@@ -349,6 +378,10 @@
setStyleName("gwt-Image");
}
+ private Image(Element element) {
+ setElement(element);
+ }
+
public void addClickListener(ClickListener listener) {
if (clickListeners == null) {
clickListeners = new ClickListenerCollection();
diff --git a/user/src/com/google/gwt/user/client/ui/InlineHTML.java b/user/src/com/google/gwt/user/client/ui/InlineHTML.java
new file mode 100644
index 0000000..36c885a
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/InlineHTML.java
@@ -0,0 +1,86 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+
+/**
+ * A widget that can contain arbitrary HTML.
+ *
+ * This widget uses a <span> element, causing it to be displayed with
+ * inline layout.
+ *
+ * <p>
+ * If you only need a simple label (text, but not HTML), then the
+ * {@link com.google.gwt.user.client.ui.Label} widget is more appropriate, as it
+ * disallows the use of HTML, which can lead to potential security issues if not
+ * used properly.
+ * </p>
+ *
+ * <h3>CSS Style Rules</h3>
+ * <ul class='css'>
+ * <li>.gwt-InlineHTML { }</li>
+ * </ul>
+ */
+public class InlineHTML extends HTML {
+
+ /**
+ * Creates an InlineHTML widget that wraps an existing <div> or
+ * <span> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static InlineHTML wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ assert element.getTagName().equalsIgnoreCase("div")
+ || element.getTagName().equalsIgnoreCase("span");
+ assert Document.get().getBody().isOrHasChild(element);
+
+ InlineHTML html = new InlineHTML((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ html.onAttach();
+ RootPanel.detachOnWindowClose(html);
+
+ return html;
+ }
+
+ /**
+ * Creates an empty HTML widget.
+ */
+ public InlineHTML() {
+ super(DOM.createSpan());
+ setStyleName("gwt-InlineHTML");
+ }
+
+ /**
+ * Creates an HTML widget with the specified HTML contents.
+ *
+ * @param html the new widget's HTML contents
+ */
+ public InlineHTML(String html) {
+ this();
+ setHTML(html);
+ }
+
+ private InlineHTML(Element element) {
+ super(element);
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/InlineLabel.java b/user/src/com/google/gwt/user/client/ui/InlineLabel.java
new file mode 100644
index 0000000..d69affa
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/InlineLabel.java
@@ -0,0 +1,79 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+
+/**
+ * A widget that contains arbitrary text, <i>not</i> interpreted as HTML.
+ *
+ * This widget uses a <span> element, causing it to be displayed with
+ * inline layout.
+ *
+ * <h3>CSS Style Rules</h3>
+ * <ul class='css'>
+ * <li>.gwt-InlineLabel { }</li>
+ * </ul>
+ */
+public class InlineLabel extends Label {
+
+ /**
+ * Creates a InlineLabel widget that wraps an existing <div> or
+ * <span> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static InlineLabel wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ assert element.getTagName().equalsIgnoreCase("div")
+ || element.getTagName().equalsIgnoreCase("span");
+ assert Document.get().getBody().isOrHasChild(element);
+
+ InlineLabel label = new InlineLabel((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ label.onAttach();
+ RootPanel.detachOnWindowClose(label);
+
+ return label;
+ }
+
+ /**
+ * Creates an empty label.
+ */
+ public InlineLabel() {
+ super(DOM.createSpan());
+ setStyleName("gwt-InlineLabel");
+ }
+
+ /**
+ * Creates a label with the specified text.
+ *
+ * @param text the new label's text
+ */
+ public InlineLabel(String text) {
+ this();
+ setText(text);
+ }
+
+ private InlineLabel(Element element) {
+ super(element);
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/Label.java b/user/src/com/google/gwt/user/client/ui/Label.java
index 386ab0a..c985697 100644
--- a/user/src/com/google/gwt/user/client/ui/Label.java
+++ b/user/src/com/google/gwt/user/client/ui/Label.java
@@ -15,15 +15,19 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.i18n.client.BidiUtils;
+import com.google.gwt.i18n.client.HasDirection;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
-import com.google.gwt.i18n.client.HasDirection;
-import com.google.gwt.i18n.client.BidiUtils;
/**
* A widget that contains arbitrary text, <i>not</i> interpreted as HTML.
*
+ * This widget uses a <div> element, causing it to be displayed with
+ * block layout.
+ *
* <h3>CSS Style Rules</h3>
* <ul class='css'>
* <li>.gwt-Label { }</li>
@@ -38,6 +42,29 @@
SourcesMouseEvents, SourcesMouseWheelEvents, HasHorizontalAlignment,
HasText, HasWordWrap, HasDirection {
+ /**
+ * Creates a Label widget that wraps an existing <div> or <span>
+ * element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static Label wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ assert element.getTagName().equalsIgnoreCase("div")
+ || element.getTagName().equalsIgnoreCase("span");
+ assert Document.get().getBody().isOrHasChild(element);
+
+ Label label = new Label((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ label.onAttach();
+ RootPanel.detachOnWindowClose(label);
+
+ return label;
+ }
+
private ClickListenerCollection clickListeners;
private HorizontalAlignmentConstant horzAlign;
private MouseListenerCollection mouseListeners;
@@ -52,15 +79,6 @@
}
/**
- * This constructor is used to let the HTML constructors avoid work.
- *
- * @param element element
- */
- Label(Element element) {
- setElement(element);
- }
-
- /**
* Creates a label with the specified text.
*
* @param text the new label's text
@@ -81,6 +99,15 @@
setWordWrap(wordWrap);
}
+ /**
+ * This constructor is used to let the HTML constructors avoid work.
+ *
+ * @param element element
+ */
+ Label(Element element) {
+ setElement(element);
+ }
+
public void addClickListener(ClickListener listener) {
if (clickListeners == null) {
clickListeners = new ClickListenerCollection();
diff --git a/user/src/com/google/gwt/user/client/ui/ListBox.java b/user/src/com/google/gwt/user/client/ui/ListBox.java
index 41c27ef..a403499 100644
--- a/user/src/com/google/gwt/user/client/ui/ListBox.java
+++ b/user/src/com/google/gwt/user/client/ui/ListBox.java
@@ -15,10 +15,12 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
-import com.google.gwt.core.client.GWT;
/**
* A widget that presents a list of choices to the user, either as a list box or
@@ -148,6 +150,28 @@
private static final int INSERT_AT_END = -1;
private static final Impl impl = GWT.create(Impl.class);
+
+ /**
+ * Creates a ListBox widget that wraps an existing <select> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static ListBox wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ SelectElement.as(element);
+ assert Document.get().getBody().isOrHasChild(element);
+
+ ListBox listBox = new ListBox((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ listBox.onAttach();
+ RootPanel.detachOnWindowClose(listBox);
+
+ return listBox;
+ }
+
private ChangeListenerCollection changeListeners;
/**
@@ -168,6 +192,10 @@
setStyleName("gwt-ListBox");
}
+ private ListBox(Element element) {
+ super(element);
+ }
+
public void addChangeListener(ChangeListener listener) {
if (changeListeners == null) {
changeListeners = new ChangeListenerCollection();
diff --git a/user/src/com/google/gwt/user/client/ui/MultiWordSuggestOracle.java b/user/src/com/google/gwt/user/client/ui/MultiWordSuggestOracle.java
index 777b8a6..a4f91b3 100644
--- a/user/src/com/google/gwt/user/client/ui/MultiWordSuggestOracle.java
+++ b/user/src/com/google/gwt/user/client/ui/MultiWordSuggestOracle.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.IsSerializable;
import java.util.ArrayList;
@@ -103,7 +104,7 @@
*/
private static final String NORMALIZE_TO_SINGLE_WHITE_SPACE = "\\s+";
- private static HTML convertMe = new HTML();
+ private static HTML convertMe = GWT.isClient() ? new HTML() : null;
/**
* Associates substrings with words.
diff --git a/user/src/com/google/gwt/user/client/ui/NamedFrame.java b/user/src/com/google/gwt/user/client/ui/NamedFrame.java
index 2fa73e0..9efe1f7 100644
--- a/user/src/com/google/gwt/user/client/ui/NamedFrame.java
+++ b/user/src/com/google/gwt/user/client/ui/NamedFrame.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
@@ -37,7 +38,9 @@
private static JavaScriptObject PATTERN_NAME;
static {
- initStatics();
+ if (GWT.isClient()) {
+ initStatics();
+ }
}
private static native void initStatics() /*-{
diff --git a/user/src/com/google/gwt/user/client/ui/PasswordTextBox.java b/user/src/com/google/gwt/user/client/ui/PasswordTextBox.java
index b670cab..de63755 100644
--- a/user/src/com/google/gwt/user/client/ui/PasswordTextBox.java
+++ b/user/src/com/google/gwt/user/client/ui/PasswordTextBox.java
@@ -15,7 +15,10 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.InputElement;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
/**
* A text box that visually masks its input to prevent eavesdropping.
@@ -37,10 +40,36 @@
public class PasswordTextBox extends TextBox {
/**
+ * Creates a PasswordTextBox widget that wraps an existing <input
+ * type='password'> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static PasswordTextBox wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ assert InputElement.as(element).getType().equalsIgnoreCase("password");
+ assert Document.get().getBody().isOrHasChild(element);
+
+ PasswordTextBox textBox = new PasswordTextBox((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ textBox.onAttach();
+ RootPanel.detachOnWindowClose(textBox);
+
+ return textBox;
+ }
+
+ /**
* Creates an empty password text box.
*/
public PasswordTextBox() {
super(DOM.createInputPassword());
setStyleName("gwt-PasswordTextBox");
}
+
+ private PasswordTextBox(Element element) {
+ super(element);
+ }
}
diff --git a/user/src/com/google/gwt/user/client/ui/RootPanel.java b/user/src/com/google/gwt/user/client/ui/RootPanel.java
index 8205251..ea02702 100644
--- a/user/src/com/google/gwt/user/client/ui/RootPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/RootPanel.java
@@ -23,7 +23,9 @@
import com.google.gwt.i18n.client.BidiUtils;
import com.google.gwt.i18n.client.HasDirection;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/**
* The panel to which all other widgets must ultimately be added. RootPanels are
@@ -37,6 +39,21 @@
public class RootPanel extends AbsolutePanel {
private static HashMap<String, RootPanel> rootPanels = new HashMap<String, RootPanel>();
+ private static List<Widget> widgetsToDetach = new ArrayList<Widget>();
+
+ /**
+ * Adds a widget to the list of widgets to be detached when the page unloads.
+ *
+ * This method must be called for all widgets that have no parent widgets.
+ * These are most commonly {@link RootPanel RootPanels}, but can also be any
+ * widget used to wrap an existing element on the page. Failing to do this
+ * may cause these widgets to leak memory.
+ *
+ * @param widget the widget to be cleaned up when the page closes
+ */
+ public static void detachOnWindowClose(Widget widget) {
+ widgetsToDetach.add(widget);
+ }
/**
* Gets the default root panel. This panel wraps body of the browser's
@@ -82,7 +99,6 @@
// If we're in a RTL locale, set the RTL directionality
// on the entire document.
-
if (LocaleInfo.getCurrentLocale().isRTL()) {
BidiUtils.setDirectionOnElement(getRootElement(), HasDirection.Direction.RTL);
}
@@ -94,6 +110,7 @@
elem = getBodyElement();
}
rootPanels.put(id, rp = new RootPanel(elem));
+ widgetsToDetach.add(rp);
return rp;
}
@@ -115,17 +132,18 @@
private static native Element getRootElement() /*-{
return $doc;
}-*/;
-
+
private static void hookWindowClosing() {
// Catch the window closing event.
Window.addWindowCloseListener(new WindowCloseListener() {
public void onWindowClosed() {
- // When the window is closing, detach all root panels. This will cause
- // all of their children's event listeners to be unhooked, which will
- // avoid potential memory leaks.
- for (RootPanel gwt : rootPanels.values()) {
- if (gwt.isAttached()) {
- gwt.onDetach();
+ // When the window is closing, detach all widgets that need to be
+ // cleaned up. This will cause all of their event listeners
+ // to be unhooked, which will avoid potential memory leaks.
+ for (int i = 0; i < widgetsToDetach.size(); ++i) {
+ Widget widget = widgetsToDetach.get(i);
+ if (widget.isAttached()) {
+ widget.onDetach();
}
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/SimpleCheckBox.java b/user/src/com/google/gwt/user/client/ui/SimpleCheckBox.java
new file mode 100644
index 0000000..d241e1115
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/SimpleCheckBox.java
@@ -0,0 +1,117 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.InputElement;
+
+/**
+ * A simple checkbox widget, with no label.
+ *
+ * <h3>CSS Style Rules</h3>
+ * <ul class='css'>
+ * <li>.gwt-SimpleCheckBox { }</li>
+ * <li>.gwt-SimpleCheckBox-disabled { Applied when checkbox is disabled }</li>
+ * </ul>
+ */
+public class SimpleCheckBox extends FocusWidget implements HasName {
+
+ /**
+ * Creates a SimpleCheckBox widget that wraps an existing <input
+ * type='checkbox'> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static SimpleCheckBox wrap(Element element) {
+ // Assert that the element is of the correct type and is attached.
+ assert InputElement.as(element).getType().equalsIgnoreCase("checkbox");
+ assert Document.get().getBody().isOrHasChild(element);
+
+ SimpleCheckBox checkBox = new SimpleCheckBox(element);
+
+ // Mark it attached and remember it for cleanup.
+ checkBox.onAttach();
+ RootPanel.detachOnWindowClose(checkBox);
+
+ return checkBox;
+ }
+
+ /**
+ * Creates a new simple checkbox.
+ */
+ public SimpleCheckBox() {
+ setElement(Document.get().createCheckInputElement());
+ setStyleName("gwt-SimpleCheckBox");
+ }
+
+ SimpleCheckBox(Element element) {
+ setElement(element);
+ }
+
+ public String getName() {
+ return getInputElement().getName();
+ }
+
+ /**
+ * Determines whether this check box is currently checked.
+ *
+ * @return <code>true</code> if the check box is checked
+ */
+ public boolean isChecked() {
+ String propName = isAttached() ? "checked" : "defaultChecked";
+ return getInputElement().getPropertyBoolean(propName);
+ }
+
+ /**
+ * Checks or unchecks this check box.
+ *
+ * @param checked <code>true</code> to check the check box
+ */
+ public void setChecked(boolean checked) {
+ getInputElement().setChecked(checked);
+ getInputElement().setDefaultChecked(checked);
+ }
+
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (enabled) {
+ removeStyleDependentName("disabled");
+ } else {
+ addStyleDependentName("disabled");
+ }
+ }
+
+ public void setName(String name) {
+ getInputElement().setName(name);
+ }
+
+ /**
+ * This method is called when a widget is detached from the browser's
+ * document. Overridden because of IE bug that throws away checked state and
+ * in order to clear the event listener off of the <code>inputElem</code>.
+ */
+ @Override
+ protected void onUnload() {
+ setChecked(isChecked());
+ }
+
+ private InputElement getInputElement() {
+ return InputElement.as(getElement());
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/SimpleRadioButton.java b/user/src/com/google/gwt/user/client/ui/SimpleRadioButton.java
new file mode 100644
index 0000000..aac907b
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/SimpleRadioButton.java
@@ -0,0 +1,73 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.InputElement;
+
+/**
+ * A simple radio button widget, with no label.
+ *
+ * <h3>CSS Style Rules</h3>
+ * <ul class='css'>
+ * <li>.gwt-SimpleRadioButton { }</li>
+ * <li>.gwt-SimpleRadioButton-disabled { Applied when radio button is disabled }</li>
+ * </ul>
+ */
+public class SimpleRadioButton extends SimpleCheckBox {
+
+ /**
+ * Creates a SimpleRadioButton widget that wraps an existing <input
+ * type='radio'> element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static SimpleRadioButton wrap(Element element) {
+ // Assert that the element is of the correct type and is attached.
+ assert InputElement.as(element).getType().equalsIgnoreCase("radio");
+ assert Document.get().getBody().isOrHasChild(element);
+
+ SimpleRadioButton radioButton = new SimpleRadioButton(element);
+
+ // Mark it attached and remember it for cleanup.
+ radioButton.onAttach();
+ RootPanel.detachOnWindowClose(radioButton);
+
+ return radioButton;
+ }
+
+ /**
+ * Creates a new radio associated with a particular group name. All radio
+ * buttons associated with the same group name belong to a mutually-exclusive
+ * set.
+ *
+ * Radio buttons are grouped by their name attribute, so changing their name
+ * using the setName() method will also change their associated group.
+ *
+ * @param name the group name with which to associate the radio button
+ */
+ public SimpleRadioButton(String name) {
+ this(Document.get().createRadioInputElement(name));
+ setStyleName("gwt-SimpleRadioButton");
+ }
+
+ private SimpleRadioButton(Element element) {
+ super(element);
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/TabBar.java b/user/src/com/google/gwt/user/client/ui/TabBar.java
index d1e6a94..37d2bd0 100644
--- a/user/src/com/google/gwt/user/client/ui/TabBar.java
+++ b/user/src/com/google/gwt/user/client/ui/TabBar.java
@@ -316,6 +316,42 @@
}
/**
+ * Sets a tab's contents via HTML.
+ *
+ * Use care when setting an object's HTML; it is an easy way to expose
+ * script-based security problems. Consider using
+ * {@link #setTabText(int, String)} whenever possible.
+ *
+ * @param index the index of the tab whose HTML is to be set
+ * @param html the tab new HTML
+ */
+ public void setTabHTML(int index, String html) {
+ assert (index >= 0) && (index < getTabCount()) : "Tab index out of bounds";
+
+ ClickDelegatePanel delPanel = (ClickDelegatePanel) panel.getWidget(index + 1);
+ SimplePanel focusablePanel = delPanel.getFocusablePanel();
+ focusablePanel.setWidget(new HTML(html));
+ }
+
+ /**
+ * Sets a tab's text contents.
+ *
+ * @param index the index of the tab whose text is to be set
+ * @param text the object's new text
+ */
+ public void setTabText(int index, String text) {
+ assert (index >= 0) && (index < getTabCount()) : "Tab index out of bounds";
+
+ ClickDelegatePanel delPanel = (ClickDelegatePanel) panel.getWidget(index + 1);
+ SimplePanel focusablePanel = delPanel.getFocusablePanel();
+
+ // It is not safe to check if the current widget is an instanceof Label and
+ // reuse it here because HTML is an instanceof Label. Leaving an HTML would
+ // throw off the results of getTabHTML(int).
+ focusablePanel.setWidget(new Label(text));
+ }
+
+ /**
* Create a {@link SimplePanel} that will wrap the contents in a tab.
* Subclasses can use this method to wrap tabs in decorator panels.
*
diff --git a/user/src/com/google/gwt/user/client/ui/TextBox.java b/user/src/com/google/gwt/user/client/ui/TextBox.java
index 1108fbf..a3ff602 100644
--- a/user/src/com/google/gwt/user/client/ui/TextBox.java
+++ b/user/src/com/google/gwt/user/client/ui/TextBox.java
@@ -15,10 +15,12 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.InputElement;
+import com.google.gwt.i18n.client.BidiUtils;
+import com.google.gwt.i18n.client.HasDirection;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
-import com.google.gwt.i18n.client.HasDirection;
-import com.google.gwt.i18n.client.BidiUtils;
/**
* A standard single-line text box.
@@ -41,13 +43,35 @@
public class TextBox extends TextBoxBase implements HasDirection {
/**
+ * Creates a TextBox widget that wraps an existing <input type='text'>
+ * element.
+ *
+ * This element must already be attached to the document.
+ *
+ * @param element the element to be wrapped
+ */
+ public static TextBox wrap(com.google.gwt.dom.client.Element element) {
+ // Assert that the element is of the correct type and is attached.
+ assert InputElement.as(element).getType().equalsIgnoreCase("text");
+ assert Document.get().getBody().isOrHasChild(element);
+
+ TextBox textBox = new TextBox((Element) element);
+
+ // Mark it attached and remember it for cleanup.
+ textBox.onAttach();
+ RootPanel.detachOnWindowClose(textBox);
+
+ return textBox;
+ }
+
+ /**
* Creates an empty text box.
*/
public TextBox() {
super(DOM.createInputText());
setStyleName("gwt-TextBox");
}
-
+
/**
* Protected constructor for use by subclasses.
* @param element element
diff --git a/user/src/com/google/gwt/user/client/ui/TreeItem.java b/user/src/com/google/gwt/user/client/ui/TreeItem.java
index ac71ea3..f68728b 100644
--- a/user/src/com/google/gwt/user/client/ui/TreeItem.java
+++ b/user/src/com/google/gwt/user/client/ui/TreeItem.java
@@ -16,6 +16,7 @@
package com.google.gwt.user.client.ui;
import com.google.gwt.animation.client.Animation;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
@@ -177,29 +178,31 @@
* Static constructor to set up clonable elements.
*/
static {
- // Create the base table element that will be cloned.
- BASE_INTERNAL_ELEM = DOM.createTable();
- Element contentElem = DOM.createSpan();
- Element tbody = DOM.createTBody(), tr = DOM.createTR();
- Element tdImg = DOM.createTD(), tdContent = DOM.createTD();
- DOM.appendChild(BASE_INTERNAL_ELEM, tbody);
- DOM.appendChild(tbody, tr);
- DOM.appendChild(tr, tdImg);
- DOM.appendChild(tr, tdContent);
- DOM.setStyleAttribute(tdImg, "verticalAlign", "middle");
- DOM.setStyleAttribute(tdContent, "verticalAlign", "middle");
- DOM.appendChild(tdContent, contentElem);
- DOM.setStyleAttribute(contentElem, "display", "inline");
- setStyleName(contentElem, "gwt-TreeItem");
- DOM.setStyleAttribute(BASE_INTERNAL_ELEM, "whiteSpace", "nowrap");
+ if (GWT.isClient()) {
+ // Create the base table element that will be cloned.
+ BASE_INTERNAL_ELEM = DOM.createTable();
+ Element contentElem = DOM.createSpan();
+ Element tbody = DOM.createTBody(), tr = DOM.createTR();
+ Element tdImg = DOM.createTD(), tdContent = DOM.createTD();
+ DOM.appendChild(BASE_INTERNAL_ELEM, tbody);
+ DOM.appendChild(tbody, tr);
+ DOM.appendChild(tr, tdImg);
+ DOM.appendChild(tr, tdContent);
+ DOM.setStyleAttribute(tdImg, "verticalAlign", "middle");
+ DOM.setStyleAttribute(tdContent, "verticalAlign", "middle");
+ DOM.appendChild(tdContent, contentElem);
+ DOM.setStyleAttribute(contentElem, "display", "inline");
+ setStyleName(contentElem, "gwt-TreeItem");
+ DOM.setStyleAttribute(BASE_INTERNAL_ELEM, "whiteSpace", "nowrap");
- // Create the base element that will be cloned
- BASE_BARE_ELEM = DOM.createDiv();
+ // Create the base element that will be cloned
+ BASE_BARE_ELEM = DOM.createDiv();
- // Simulates padding from table element.
- DOM.setStyleAttribute(BASE_BARE_ELEM, "padding", "3px");
- DOM.appendChild(BASE_BARE_ELEM, contentElem);
- Accessibility.setRole(contentElem, Accessibility.ROLE_TREEITEM);
+ // Simulates padding from table element.
+ DOM.setStyleAttribute(BASE_BARE_ELEM, "padding", "3px");
+ DOM.appendChild(BASE_BARE_ELEM, contentElem);
+ Accessibility.setRole(contentElem, Accessibility.ROLE_TREEITEM);
+ }
}
private ArrayList<TreeItem> children;
private Element contentElem, childSpanElem, imageHolder;
diff --git a/user/src/com/google/gwt/user/rebind/rpc/JModTypeVisitor.java b/user/src/com/google/gwt/user/rebind/rpc/JModTypeVisitor.java
new file mode 100644
index 0000000..9ebeabe
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/rpc/JModTypeVisitor.java
@@ -0,0 +1,154 @@
+/*
+ * 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.user.rebind.rpc;
+
+import com.google.gwt.core.ext.typeinfo.JArrayType;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JGenericType;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JRawType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.JWildcardType;
+
+/**
+ * <p>
+ * This visitor supports transforming interior parts of a type. Subclasses
+ * should override the appropriate <code>endVisit</code> methods and, when
+ * such a method wants to replace its argument, assign the replacement to the
+ * field {@link #replacement}.
+ * </p>
+ *
+ * <p>
+ * Any specified replacements must be sensible no matter what context they might
+ * be used in. Specifically:
+ * </p>
+ * <ul>
+ * <li>Primitive types may only be replaced by primitive types.
+ * <li>Reference types may only be replaced by reference types.
+ * <li>Generic types may only be replaced by generic types, and the replacement
+ * should have the same number of type parameters as the original.
+ * </ul>
+ */
+class JModTypeVisitor extends JTypeVisitor {
+ /**
+ * A subclass's <code>endVisit</code> method can indicate that it wants to
+ * replace its argument by assigning the replacement to this field.
+ */
+ protected JType replacement;
+
+ @Override
+ public void accept(JType type) {
+ replacement = type;
+ acceptChildren(type);
+ endVisit(replacement);
+ }
+
+ public JClassType transform(JClassType type) {
+ // transforming a class type should give a class type back
+ return (JClassType) transform((JType) type);
+ }
+
+ public JGenericType transform(JGenericType type) {
+ // transforming a generic type should give a generic type back
+ return (JGenericType) transform((JType) type);
+ }
+
+ public JPrimitiveType transform(JPrimitiveType type) {
+ // transforming a primitive type should give a primitive type back
+ return (JPrimitiveType) transform((JType) type);
+ }
+
+ public JType transform(JType type) {
+ accept(type);
+ return replacement;
+ }
+
+ /**
+ * Do the same thing as {@link JTypeVisitor#acceptChildren(JType)}, but if
+ * any children types are replaced, reconstruct a version of <code>type</code>
+ * that has those corresponding replacements made and assign the new type to
+ * {@link #replacement}.
+ */
+ @Override
+ protected void acceptChildren(JType type) {
+ JArrayType typeArray = type.isArray();
+ if (typeArray != null) {
+ JType oldComponentType = typeArray.getComponentType();
+ JType newComponentType = transform(oldComponentType);
+
+ if (oldComponentType == newComponentType) {
+ replacement = type;
+ } else {
+ replacement = typeArray.getOracle().getArrayType(newComponentType);
+ }
+ }
+
+ JParameterizedType typeParameterized = type.isParameterized();
+ if (typeParameterized != null) {
+ JGenericType oldBaseType = typeParameterized.getBaseType();
+ JGenericType newBaseType = transform(oldBaseType);
+
+ JClassType oldEnclosingType = typeParameterized.getEnclosingType();
+ JClassType newEnclosingType = oldEnclosingType == null ? null
+ : transform(oldEnclosingType);
+
+ JClassType[] oldTypeArgs = typeParameterized.getTypeArgs();
+ JClassType[] newTypeArgs = new JClassType[oldTypeArgs.length];
+ boolean argsAllSame = true;
+ for (int i = 0; i < oldTypeArgs.length; i++) {
+ newTypeArgs[i] = transform(oldTypeArgs[i]);
+ if (newTypeArgs[i] != oldTypeArgs[i]) {
+ argsAllSame = false;
+ }
+ }
+
+ if (argsAllSame && oldBaseType == newBaseType
+ && oldEnclosingType == newEnclosingType) {
+ replacement = type;
+ } else {
+ replacement = typeParameterized.getOracle().getParameterizedType(
+ newBaseType, newEnclosingType, newTypeArgs);
+ }
+ }
+
+ JRawType typeRaw = type.isRawType();
+ if (typeRaw != null) {
+ JGenericType oldBaseType = typeRaw.getBaseType();
+ JGenericType newBaseType = transform(oldBaseType);
+
+ if (oldBaseType == newBaseType) {
+ replacement = type;
+ } else {
+ replacement = newBaseType.getRawType();
+ }
+ }
+
+ JWildcardType typeWild = type.isWildcard();
+ if (typeWild != null) {
+ JClassType oldBound = typeWild.getFirstBound();
+ JClassType newBound = transform(oldBound);
+
+ if (oldBound == newBound) {
+ replacement = type;
+ } else {
+ replacement = typeWild.getOracle().getWildcardType(
+ typeWild.getBoundType(), newBound);
+ }
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/JTypeVisitor.java b/user/src/com/google/gwt/user/rebind/rpc/JTypeVisitor.java
new file mode 100644
index 0000000..8764b6c
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/rpc/JTypeVisitor.java
@@ -0,0 +1,174 @@
+/*
+ * 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.user.rebind.rpc;
+
+import com.google.gwt.core.ext.typeinfo.JAnnotationType;
+import com.google.gwt.core.ext.typeinfo.JArrayType;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JEnumType;
+import com.google.gwt.core.ext.typeinfo.JGenericType;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JRawType;
+import com.google.gwt.core.ext.typeinfo.JRealClassType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.JTypeParameter;
+import com.google.gwt.core.ext.typeinfo.JWildcardType;
+
+/**
+ * A visitor that traverses the structure of a {@link JType}.
+ */
+class JTypeVisitor {
+ /**
+ * Accept <code>type</code>, traverse its children with
+ * {@link #acceptChildren(JType)}, and then call {@link #endVisit(JType)} on
+ * <code>type</code> itself.
+ */
+ public void accept(JType type) {
+ acceptChildren(type);
+ endVisit(type);
+ }
+
+ /**
+ * Call {@link #accept(JType)} on all children types of <code>type</code>.
+ * The children type of a type are its structural components. For example, an
+ * array type has one child, which is the component type of the array.
+ */
+ protected void acceptChildren(JType type) {
+ JArrayType typeArray = type.isArray();
+ if (typeArray != null) {
+ accept(typeArray.getComponentType());
+ endVisit(typeArray);
+ }
+
+ JParameterizedType typeParameterized = type.isParameterized();
+ if (typeParameterized != null) {
+ accept(typeParameterized.getBaseType());
+ for (JClassType typeArg : typeParameterized.getTypeArgs()) {
+ accept(typeArg);
+ }
+ endVisit(typeParameterized);
+ }
+
+ JRawType typeRaw = type.isRawType();
+ if (typeRaw != null) {
+ accept(typeRaw.getBaseType());
+ endVisit(typeRaw);
+ }
+
+ JWildcardType typeWild = type.isWildcard();
+ if (typeWild != null) {
+ accept(typeWild.getFirstBound());
+ endVisit(typeWild);
+ }
+ }
+
+ protected void endVisit(JAnnotationType typeAnnotation) {
+ }
+
+ protected void endVisit(JArrayType typeArray) {
+ }
+
+ protected void endVisit(JEnumType typeEnum) {
+ }
+
+ protected void endVisit(JGenericType typeGeneric) {
+ }
+
+ protected void endVisit(JParameterizedType typeParameterized) {
+ }
+
+ protected void endVisit(JPrimitiveType typePrimitive) {
+ }
+
+ protected void endVisit(JRawType typeRaw) {
+ }
+
+ protected void endVisit(JRealClassType typeReal) {
+ }
+
+ /**
+ * This endVisit is called on all visited types. By default it dispatches to
+ * one of the more specific endVisit methods, e.g.
+ * {@link #endVisit(JArrayType)}.
+ */
+ protected void endVisit(JType type) {
+ JAnnotationType typeAnnotation = type.isAnnotation();
+ if (typeAnnotation != null) {
+ endVisit(typeAnnotation);
+ return;
+ }
+
+ JArrayType typeArray = type.isArray();
+ if (typeArray != null) {
+ endVisit(typeArray);
+ return;
+ }
+
+ JEnumType typeEnum = type.isEnum();
+ if (typeEnum != null) {
+ endVisit(typeEnum);
+ return;
+ }
+
+ JGenericType typeGeneric = type.isGenericType();
+ if (typeGeneric != null) {
+ endVisit(typeGeneric);
+ return;
+ }
+
+ JParameterizedType typeParameterized = type.isParameterized();
+ if (typeParameterized != null) {
+ endVisit(typeParameterized);
+ return;
+ }
+
+ JPrimitiveType typePrimitive = type.isPrimitive();
+ if (typePrimitive != null) {
+ endVisit(typePrimitive);
+ return;
+ }
+
+ JRawType typeRaw = type.isRawType();
+ if (typeRaw != null) {
+ endVisit(typeRaw);
+ return;
+ }
+
+ JTypeParameter typeParam = type.isTypeParameter();
+ if (typeParam != null) {
+ endVisit(typeParam);
+ return;
+ }
+
+ JWildcardType typeWild = type.isWildcard();
+ if (typeWild != null) {
+ endVisit(typeWild);
+ return;
+ }
+
+ JRealClassType typeReal = (JRealClassType) type;
+ endVisit(typeReal);
+ }
+
+ protected void endVisit(JTypeParameter typeParam) {
+ }
+
+ protected void endVisit(JWildcardType typeWild) {
+ }
+
+}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
index b31aed9..f7c24ee 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -23,7 +23,6 @@
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
-import com.google.gwt.core.ext.typeinfo.JRawType;
import com.google.gwt.core.ext.typeinfo.JRealClassType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
@@ -82,38 +81,13 @@
* </p>
*/
public class SerializableTypeOracleBuilder {
- private interface Path {
+
+ interface Path {
Path getParent();
String toString();
}
- private enum TypeState {
- /**
- * The instantiability of a type has been determined.
- */
- CHECK_DONE("Check succeeded"),
- /**
- * The instantiability of a type is being checked.
- */
- CHECK_IN_PROGRESS("Check in progress"),
- /**
- * The instantiability of a type has not been checked.
- */
- NOT_CHECKED("Not checked");
-
- private final String message;
-
- TypeState(String message) {
- this.message = message;
- }
-
- @Override
- public String toString() {
- return message;
- }
- }
-
private class TypeInfoComputed {
/**
@@ -242,6 +216,32 @@
}
}
+ private enum TypeState {
+ /**
+ * The instantiability of a type has been determined.
+ */
+ CHECK_DONE("Check succeeded"),
+ /**
+ * The instantiability of a type is being checked.
+ */
+ CHECK_IN_PROGRESS("Check in progress"),
+ /**
+ * The instantiability of a type has not been checked.
+ */
+ NOT_CHECKED("Not checked");
+
+ private final String message;
+
+ TypeState(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return message;
+ }
+ }
+
/**
* Compares {@link JType}s according to their qualified source names.
*/
@@ -264,8 +264,6 @@
}
};
- private static final JClassType[] NO_JCLASSES = new JClassType[0];
-
static boolean canBeInstantiated(TreeLogger logger, JClassType type,
TreeLogger.Type logLevel) {
if (type.isEnum() == null) {
@@ -279,7 +277,8 @@
// Warn and return false.
logger.log(
logLevel,
- "Was not default instantiable (it must have a zero-argument constructor or no constructors at all)",
+ type.getParameterizedQualifiedSourceName()
+ + " was not default instantiable (it must have a zero-argument constructor or no constructors at all)",
null);
return false;
}
@@ -320,6 +319,16 @@
return customSerializer;
}
+ static JRealClassType getBaseType(JClassType type) {
+ if (type.isParameterized() != null) {
+ return type.isParameterized().getBaseType();
+ } else if (type.isRawType() != null) {
+ return type.isRawType().getBaseType();
+ }
+
+ return (JRealClassType) type;
+ }
+
static boolean isAutoSerializable(JClassType type) {
try {
JClassType isSerializable = getIsSerializableMarkerInterface(type);
@@ -430,16 +439,6 @@
return false;
}
- if (type.isLocalType()) {
- // Local types cannot be serialized
- logger.branch(
- logLevel,
- type.getParameterizedQualifiedSourceName()
- + " is a local type; it will be excluded from the set of serializable types",
- null);
- return false;
- }
-
if (type.isMemberType() && !type.isStatic()) {
// Non-static member types cannot be serialized
logger.branch(
@@ -535,6 +534,21 @@
};
}
+ private static Path createSupertypePath(final Path parent, final JType type,
+ final JClassType subtype) {
+ return new Path() {
+ public Path getParent() {
+ return parent;
+ }
+
+ @Override
+ public String toString() {
+ return "'" + type.getParameterizedQualifiedSourceName()
+ + "' is reachable as a supertype of type '" + subtype + "'";
+ }
+ };
+ }
+
private static Path createTypeArgumentPath(final Path parent,
final JClassType type, final int typeArgIndex, final JClassType typeArg) {
return new Path() {
@@ -625,6 +639,11 @@
return isAutoSerializable(type) || isManuallySerializable(type);
}
+ private static boolean isDirectlySerializable(JClassType type) {
+ return directlyImplementsMarkerInterface(type)
+ || isManuallySerializable(type);
+ }
+
private static boolean isManuallySerializable(JClassType type) {
return findCustomFieldSerializer(type.getOracle(), type) != null;
}
@@ -646,17 +665,18 @@
/**
* Cache of the {@link JClassType} for {@link Collection}.
*/
- private final JClassType collectionClass;
+ private final JGenericType collectionClass;
private OutputStream logOutputStream;
/**
* Cache of the {@link JClassType} for {@link Map}.
*/
- private final JClassType mapClass;
+ private final JGenericType mapClass;
private final Map<JClassType, TreeLogger> rootTypes = new LinkedHashMap<JClassType, TreeLogger>();
+ private final TypeConstrainer typeConstrainer;
private TypeFilter typeFilter = DEFAULT_TYPE_FILTER;
private final TypeOracle typeOracle;
@@ -689,10 +709,11 @@
public SerializableTypeOracleBuilder(TreeLogger logger, TypeOracle typeOracle)
throws UnableToCompleteException {
this.typeOracle = typeOracle;
+ typeConstrainer = new TypeConstrainer(typeOracle);
try {
- collectionClass = typeOracle.getType(Collection.class.getName());
- mapClass = typeOracle.getType(Map.class.getName());
+ collectionClass = typeOracle.getType(Collection.class.getName()).isGenericType();
+ mapClass = typeOracle.getType(Map.class.getName()).isGenericType();
} catch (NotFoundException e) {
logger.log(TreeLogger.ERROR, null, e);
throw new UnableToCompleteException();
@@ -741,6 +762,10 @@
throw new UnableToCompleteException();
}
+ for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
+ assert (!tic.isPendingInstantiable());
+ }
+
pruneUnreachableTypes();
logReachableTypes(logger);
@@ -802,8 +827,9 @@
* The method is exposed using default access to enable testing.
*/
final boolean checkTypeInstantiable(TreeLogger logger, JType type,
- boolean isSpeculative, final Path path) {
- return checkTypeInstantiable(logger, type, isSpeculative, path, null);
+ boolean isSpeculative, Path path) {
+ return checkTypeInstantiable(logger, type, isSpeculative, path,
+ new HashSet<JClassType>());
}
/**
@@ -812,7 +838,7 @@
* except that returns the set of instantiable subtypes.
*/
boolean checkTypeInstantiable(TreeLogger logger, JType type,
- boolean isSpeculative, final Path path, Set<JClassType> instSubtypes) {
+ boolean isSpeculative, Path path, Set<JClassType> instSubtypes) {
assert (type != null);
if (type.isPrimitive() != null) {
return true;
@@ -825,15 +851,14 @@
TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
classType.getParameterizedQualifiedSourceName(), null);
- if (!isAllowedByFilter(localLogger, classType, isSpeculative)) {
- return false;
- }
-
JTypeParameter isTypeParameter = classType.isTypeParameter();
if (isTypeParameter != null) {
if (typeParametersInRootTypes.contains(isTypeParameter)) {
return checkTypeInstantiable(localLogger,
- isTypeParameter.getFirstBound(), isSpeculative, path);
+ isTypeParameter.getFirstBound(), isSpeculative,
+ createTypeArgumentPath(path, isTypeParameter.getDeclaringClass(),
+ isTypeParameter.getOrdinal(), isTypeParameter.getFirstBound()),
+ instSubtypes);
}
/*
@@ -854,18 +879,9 @@
return success;
}
- TypeInfoComputed tic = getTypeInfoComputed(classType, path);
- if (tic.isPendingInstantiable()) {
- // just early out and pretend we will succeed
- return true;
- } else if (tic.isDone()) {
- return tic.hasInstantiableSubtypes();
- }
- tic.setPendingInstantiable();
-
JArrayType isArray = classType.isArray();
if (isArray != null) {
- return checkArrayInstantiable(localLogger, tic, isSpeculative, path);
+ return checkArrayInstantiable(localLogger, isArray, isSpeculative, path);
}
if (classType == typeOracle.getJavaLangObject()) {
@@ -877,108 +893,28 @@
getLogLevel(isSpeculative),
"In order to produce smaller client-side code, 'Object' is not allowed; consider using a more specific type",
null);
- tic.setInstantiable(false);
return false;
}
if (classType.isRawType() != null) {
- TreeLogger rawTypeLogger = localLogger.branch(
+ localLogger.branch(
TreeLogger.DEBUG,
"Type '"
+ classType.getQualifiedSourceName()
+ "' should be parameterized to help the compiler produce the smallest code size possible for your module",
null);
-
- if (classType.isAssignableTo(collectionClass)
- || classType.isAssignableTo(mapClass)) {
- /*
- * Backwards compatibility. Raw collections or maps force all object
- * subtypes to be considered. Fall through to the normal class handling.
- */
- checkAllSubtypesOfObject(rawTypeLogger, path);
- }
}
JClassType originalType = (JClassType) type;
-
- JRealClassType baseType;
- if (type.isRawType() != null) {
- baseType = type.isRawType().getBaseType();
- } else if (type.isParameterized() != null) {
- baseType = type.isParameterized().getBaseType();
- } else {
- baseType = (JRealClassType) originalType;
- }
-
- if (isSpeculative && tic.isDirectlySerializable()) {
+ if (isSpeculative && isDirectlySerializable(originalType)) {
+ // TODO: Our speculative tracking is off.
isSpeculative = false;
}
-
- JClassType[] typeArgs = NO_JCLASSES;
- JParameterizedType isParameterized = originalType.isParameterized();
- JGenericType baseAsGenericType = baseType.isGenericType();
-
- if (isParameterized != null) {
- typeArgs = isParameterized.getTypeArgs();
- } else if (baseAsGenericType != null) {
- List<JClassType> arguments = new ArrayList<JClassType>();
- for (JTypeParameter typeParameter : baseAsGenericType.getTypeParameters()) {
- arguments.add(typeParameter.getFirstBound());
- }
- typeArgs = arguments.toArray(NO_JCLASSES);
- }
-
- boolean parametersOkay = true;
- JRawType isRaw = originalType.isRawType();
- if (isParameterized != null || isRaw != null) {
- assert (baseAsGenericType != null);
- int numDeclaredParams = baseAsGenericType.getTypeParameters().length;
- if (numDeclaredParams == typeArgs.length) {
- for (int i = 0; i < numDeclaredParams; ++i) {
- JClassType typeArg = typeArgs[i];
- parametersOkay &= checkTypeArgument(localLogger, baseAsGenericType,
- i, typeArg, isSpeculative, path);
- }
- } else {
- /*
- * TODO: Does anyone actually depend on this odious behavior?
- *
- * Backwards compatibility. The number of parameterization arguments
- * specified via gwt.typeArgs could exceed the number of formal
- * parameters declared on the generic type. Therefore have to explicitly
- * visit them here and they must all be instantiable.
- */
- for (int i = 0; i < numDeclaredParams; ++i) {
- JClassType typeArg = typeArgs[i];
- parametersOkay &= checkTypeInstantiable(localLogger, typeArg,
- isSpeculative, path);
- }
- }
- }
-
- // Check whether the type is field serializable
- // TODO: Should we really continue if parametersOkay is false?
- boolean isFieldSerializable = parametersOkay
- && shouldConsiderFieldsForSerialization(localLogger, baseType,
- isSpeculative)
- && checkFields(localLogger, baseType, isSpeculative, path);
-
- // TODO: Figure out logLevel for canBeInstantiated
- boolean isInstantiable = isFieldSerializable
- && canBeInstantiated(localLogger, baseType, TreeLogger.WARN);
-
- boolean anySubtypes = false;
- if (parametersOkay && baseType.getSubtypes().length > 0) {
- TreeLogger subtypesLogger = localLogger.branch(TreeLogger.DEBUG,
- "Analyzing subclasses:", null);
- anySubtypes = checkSubtypes(subtypesLogger, originalType, baseType,
- instSubtypes, typeArgs, path, baseType, false, isFieldSerializable);
- }
-
- anySubtypes |= isInstantiable;
-
- tic.setInstantiable(isInstantiable);
- tic.setInstantiableSubytpes(anySubtypes);
+ //
+ // TreeLogger subtypesLogger = localLogger.branch(TreeLogger.DEBUG,
+ // "Analyzing subclasses:", null);
+ boolean anySubtypes = checkSubtypes(localLogger, originalType,
+ instSubtypes, path);
if (!anySubtypes && !isSpeculative) {
// No instantiable types were found
@@ -988,7 +924,7 @@
null);
}
- return tic.hasInstantiableSubtypes();
+ return anySubtypes;
}
int getTypeParameterExposure(JGenericType type, int index) {
@@ -1028,36 +964,54 @@
"Checking all subtypes of Object which qualify for serialization", null);
JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
for (JClassType cls : allTypes) {
- if (getTypeInfoComputed(cls, parent).isDeclaredSerializable()) {
- checkTypeInstantiable(localLogger, cls, true, parent);
+ if (isDeclaredSerializable(cls)) {
+ checkTypeInstantiable(localLogger, cls, true, createSubtypePath(parent,
+ cls, typeOracle.getJavaLangObject()));
}
}
}
- private boolean checkArrayInstantiable(TreeLogger localLogger,
- TypeInfoComputed tic, boolean isSpeculative, final Path path) {
- JArrayType isArray = tic.getType().isArray();
- assert (isArray != null);
+ private boolean checkArrayInstantiable(TreeLogger logger, JArrayType array,
+ boolean isSpeculative, Path path) {
- JType leafType = isArray.getLeafType();
+ JType leafType = array.getLeafType();
+ JWildcardType leafWild = leafType.isWildcard();
+ if (leafWild != null) {
+ JArrayType arrayType = getArrayType(typeOracle, array.getRank(),
+ leafWild.getUpperBound());
+ return checkArrayInstantiable(logger, arrayType, isSpeculative, path);
+ }
+
JClassType leafClass = leafType.isClassOrInterface();
JTypeParameter isLeafTypeParameter = leafType.isTypeParameter();
if (isLeafTypeParameter != null
&& !typeParametersInRootTypes.contains(isLeafTypeParameter)) {
// Don't deal with non root type parameters
- tic.setInstantiable(false);
- tic.setInstantiableSubytpes(true);
return true;
}
- TreeLogger branch = localLogger.branch(TreeLogger.DEBUG,
+ if (!isAllowedByFilter(logger, array, isSpeculative)) {
+ return false;
+ }
+
+ TypeInfoComputed tic = getTypeInfoComputed(array, path);
+ if (tic.isDone()) {
+ return tic.hasInstantiableSubtypes();
+ } else if (tic.isPendingInstantiable()) {
+ return true;
+ }
+ tic.setPendingInstantiable();
+
+ TreeLogger branch = logger.branch(TreeLogger.DEBUG,
"Analyzing component type:", null);
Set<JClassType> instantiableTypes = new HashSet<JClassType>();
- boolean succeeded = checkTypeInstantiable(branch,
- isArray.getComponentType(), isSpeculative, createArrayComponentPath(
- isArray, path), instantiableTypes);
+ boolean succeeded = checkTypeInstantiable(branch, array.getComponentType(),
+ isSpeculative, createArrayComponentPath(array, path), instantiableTypes);
if (succeeded && leafClass != null) {
+ TreeLogger covariantArrayLogger = logger.branch(TreeLogger.DEBUG,
+ "Covariant array types");
+
/*
* Compute covariant arrays for arrays of reference types.
*/
@@ -1068,11 +1022,14 @@
continue;
}
- for (int rank = 1; rank <= isArray.getRank(); ++rank) {
+ covariantArrayLogger.branch(
+ TreeLogger.DEBUG,
+ getArrayType(typeOracle, array.getRank(), instantiableType).getParameterizedQualifiedSourceName());
+
+ for (int rank = 1; rank <= array.getRank(); ++rank) {
JArrayType covariantArray = getArrayType(typeOracle, rank,
instantiableType);
- // TODO: Check the choice of path
TypeInfoComputed covariantArrayTic = getTypeInfoComputed(
covariantArray, path);
covariantArrayTic.setInstantiable(true);
@@ -1101,8 +1058,12 @@
// All fields on a manual serializable are considered speculative.
isSpeculative |= typeInfo.isManuallySerializable();
+ JClassType baseType = getBaseType(classOrInterface);
+
boolean allSucceeded = true;
- JField[] fields = classOrInterface.getFields();
+ // TODO: Propagating the constraints will produce better results as long
+ // as infinite expansion can be avoided in the process.
+ JField[] fields = baseType.getFields();
if (fields.length > 0) {
TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
"Analyzing the fields of type '"
@@ -1133,160 +1094,135 @@
boolean succeeded = allSucceeded || typeInfo.isManuallySerializable();
if (succeeded) {
- getTypeInfoComputed(classOrInterface, parent).setFieldSerializable();
+ typeInfo.setFieldSerializable();
}
return succeeded;
}
- private boolean checkFields(TreeLogger logger, JClassType classOrInterface,
- boolean isSpeculative, Path parent) {
- TypeInfoComputed typeInfo = getTypeInfoComputed(classOrInterface, parent);
+ private boolean checkSubtype(TreeLogger logger, JClassType classOrInterface,
+ JClassType originalType, boolean isSpeculative, Path parent) {
if (classOrInterface.isEnum() != null) {
// The fields of an enum are never serialized; they are always okay.
return true;
}
+ JParameterizedType isParameterized = classOrInterface.isParameterized();
+ if (isParameterized != null) {
+ if (isRawMapOrRawCollection(classOrInterface)) {
+ /*
+ * Backwards compatibility. Raw collections or maps force all object
+ * subtypes to be considered.
+ */
+ checkAllSubtypesOfObject(logger, parent);
+ } else {
+ TreeLogger paramsLogger = logger.branch(TreeLogger.DEBUG,
+ "Checking parameters of '"
+ + isParameterized.getParameterizedQualifiedSourceName() + "'");
+
+ for (JTypeParameter param : isParameterized.getBaseType().getTypeParameters()) {
+ if (!checkTypeArgument(paramsLogger, isParameterized.getBaseType(),
+ param.getOrdinal(),
+ isParameterized.getTypeArgs()[param.getOrdinal()], true, parent)) {
+ return false;
+ }
+ }
+ }
+ }
+
// Check all super type fields first (recursively).
JClassType superType = classOrInterface.getSuperclass();
- if (superType != null
- && getTypeInfoComputed(superType, parent).isDeclaredSerializable()) {
- boolean superTypeOk = checkFields(logger, superType, isSpeculative,
- parent);
+ if (superType != null && superType.isRawType() != null) {
+ superType = superType.isRawType().asParameterizedByWildcards();
+ }
+
+ if (superType != null && isDeclaredSerializable(superType)) {
+ superType = constrainTypeBy(superType, originalType);
+ if (superType == null) {
+ return false;
+ }
+
+ boolean superTypeOk = false;
+ if (superType != null) {
+ superTypeOk = checkSubtype(logger, superType, originalType,
+ isSpeculative, createSupertypePath(parent, superType,
+ classOrInterface));
+ }
+
/*
* If my super type did not check out, then I am not instantiable and we
* should error out... UNLESS I am *directly* serializable myself, in
* which case it's ok for me to be the root of a new instantiable
* hierarchy.
*/
- if (!superTypeOk && !typeInfo.isDirectlySerializable()) {
+ if (!superTypeOk && !isDirectlySerializable(classOrInterface)) {
return false;
}
}
- return checkDeclaredFields(logger, typeInfo, isSpeculative, parent);
+ TypeInfoComputed tic = getTypeInfoComputed(classOrInterface, parent);
+ return checkDeclaredFields(logger, tic, isSpeculative, parent);
}
/**
* Returns <code>true</code> if this type or one of its subtypes is
* instantiable relative to a known base type.
*/
- private boolean checkSubtypes(TreeLogger localLogger,
- JClassType originalType, JRealClassType baseType,
- Set<JClassType> instSubtypes, JClassType[] typeArgs, final Path path,
- JClassType currentSubtype, boolean checkSuperFields,
- boolean superIsFieldSerializable) {
- assert (currentSubtype instanceof JRealClassType || currentSubtype.isGenericType() != null);
-
- Path subtypePath = createSubtypePath(path, currentSubtype, originalType);
- TypeInfoComputed typeInfo = getTypeInfoComputed(currentSubtype, subtypePath);
-
- TreeLogger subtypeLogger = localLogger.branch(TreeLogger.DEBUG,
- currentSubtype.getParameterizedQualifiedSourceName(), null);
-
- /*
- * If super does not qualify and I am not directly serializable then I can't
- * be serializable.
- */
- boolean fieldSerializable = shouldConsiderFieldsForSerialization(
- subtypeLogger, currentSubtype, true);
- if (!superIsFieldSerializable) {
- fieldSerializable &= typeInfo.isDirectlySerializable();
- }
-
- if (fieldSerializable) {
- if (checkSuperFields) {
- fieldSerializable &= checkFields(subtypeLogger, typeInfo.getType(),
- true, subtypePath);
- } else {
- fieldSerializable &= checkDeclaredFields(subtypeLogger, typeInfo, true,
- subtypePath);
- }
- }
-
+ private boolean checkSubtypes(TreeLogger logger, JClassType originalType,
+ Set<JClassType> instSubtypes, Path path) {
+ JClassType baseType = getBaseType(originalType);
+ TreeLogger computationLogger = logger.branch(TreeLogger.DEBUG,
+ "Finding possibly instantiable subtypes");
+ List<JClassType> candidates = getPossiblyInstantiableSubtypes(
+ computationLogger, baseType);
boolean anySubtypes = false;
- List<JClassType> immediateSubtypes = TypeHierarchyUtils.getImmediateSubtypes(currentSubtype);
- for (JClassType immediateSubtype : immediateSubtypes) {
- if (immediateSubtype.isRawType() != null) {
- immediateSubtype = immediateSubtype.isRawType().getBaseType();
- }
- checkSuperFields = currentSubtype.isInterface() != null
- && immediateSubtype.isClass() != null;
- anySubtypes |= checkSubtypes(localLogger, originalType, baseType,
- instSubtypes, typeArgs, path, immediateSubtype, checkSuperFields,
- fieldSerializable);
- }
-
- if (!fieldSerializable) {
- // If the subtype is not instantiable do not check the arguments.
- return anySubtypes;
- }
-
- if (currentSubtype == baseType) {
- return anySubtypes;
- }
-
- JGenericType genericSub = currentSubtype.isGenericType();
- if (genericSub != null) {
- TreeLogger paramsLogger = subtypeLogger.branch(TreeLogger.DEBUG,
- "Checking parameters of '"
- + genericSub.getParameterizedQualifiedSourceName() + "'");
- Map<JTypeParameter, Set<JTypeParameter>> subParamsConstrainedBy = subParamsConstrainedBy(
- baseType, genericSub);
- for (int i = 0; i < genericSub.getTypeParameters().length; i++) {
- JTypeParameter param = genericSub.getTypeParameters()[i];
- TreeLogger paramLogger = paramsLogger.branch(TreeLogger.DEBUG,
- "Checking param '" + param.getParameterizedQualifiedSourceName()
- + "'");
- Set<JTypeParameter> constBy = subParamsConstrainedBy.get(param);
- if (constBy == null) {
- fieldSerializable &= checkTypeArgument(paramLogger, genericSub, i,
- param.getFirstBound(), true, path);
- } else {
- boolean paramOK = false;
- for (JTypeParameter constrained : constBy) {
- paramOK |= checkTypeArgument(paramLogger, genericSub, i,
- typeArgs[constrained.getOrdinal()], true, path);
- }
- fieldSerializable &= paramOK;
+ TreeLogger verificationLogger = logger.branch(TreeLogger.DEBUG,
+ "Verifying instantiability");
+ for (JClassType candidate : candidates) {
+ if (getBaseType(candidate) == baseType
+ && originalType.isRawType() == null) {
+ // Don't rely on the constrainer when we have perfect information.
+ candidate = originalType;
+ } else {
+ candidate = constrainTypeBy(candidate, originalType);
+ if (candidate == null) {
+ continue;
}
}
- } else {
- JParameterizedType isParameterized = originalType.isParameterized();
- /*
- * The subtype is not generic so it must be a concrete parameterization of
- * the query type. If the query type is also a concrete parameterization
- * then we should be able to exclude the subtype based on an assignability
- * check. If the query type does contain type parameters then we assume
- * that the subtype should be included. In order to be certain, we would
- * need to perform a full unification between the query type and subtype.
- */
- if (isParameterized != null) {
- HashSet<JTypeParameter> typeParamsInQueryType = new HashSet<JTypeParameter>();
- recordTypeParametersIn(isParameterized, typeParamsInQueryType);
-
- if (typeParamsInQueryType.isEmpty()) {
- if (!isParameterized.isAssignableFrom(currentSubtype)) {
- subtypeLogger.log(TreeLogger.DEBUG, "Excluding type '"
- + currentSubtype.getParameterizedQualifiedSourceName()
- + "' because it is not assignable to '"
- + isParameterized.getParameterizedQualifiedSourceName() + "'");
- fieldSerializable = false;
- }
- }
- }
- }
-
- if (fieldSerializable
- && canBeInstantiated(subtypeLogger, currentSubtype, TreeLogger.DEBUG)) {
- if (instSubtypes != null) {
- instSubtypes.add(currentSubtype);
+ if (!isAllowedByFilter(verificationLogger, candidate, true)) {
+ continue;
}
- typeInfo.setInstantiable(true);
- anySubtypes = true;
+ Path subtypePath = createSubtypePath(path, candidate, originalType);
+ TypeInfoComputed tic = getTypeInfoComputed(candidate, subtypePath);
+ if (tic.isDone()) {
+ anySubtypes |= tic.isInstantiable();
+ continue;
+ } else if (tic.isPendingInstantiable()) {
+ anySubtypes = true;
+ continue;
+ }
+ tic.setPendingInstantiable();
+
+ TreeLogger subtypeLogger = verificationLogger.branch(TreeLogger.DEBUG,
+ candidate.getParameterizedQualifiedSourceName());
+ boolean instantiable = checkSubtype(subtypeLogger, candidate,
+ originalType, true, subtypePath);
+ anySubtypes |= instantiable;
+ tic.setInstantiable(instantiable);
+
+ if (instantiable) {
+ subtypeLogger.branch(TreeLogger.DEBUG, "Is instantiable");
+ }
+
+ // Note we are leaving hasInstantiableSubtypes() as false which might be
+ // wrong but it is only used by arrays and thus it will never be looked at
+ // for this tic.
+ if (instantiable && instSubtypes != null) {
+ instSubtypes.add(candidate);
+ }
}
return anySubtypes;
@@ -1312,6 +1248,12 @@
*/
private boolean checkTypeArgument(TreeLogger logger, JGenericType baseType,
int paramIndex, JClassType typeArg, boolean isSpeculative, Path parent) {
+ JWildcardType isWildcard = typeArg.isWildcard();
+ if (isWildcard != null) {
+ return checkTypeArgument(logger, baseType, paramIndex,
+ isWildcard.getUpperBound(), isSpeculative, parent);
+ }
+
JArrayType typeArgAsArray = typeArg.isArray();
if (typeArgAsArray != null) {
JTypeParameter parameterOfTypeArgArray = typeArgAsArray.getLeafType().isTypeParameter();
@@ -1323,7 +1265,7 @@
TypeParameterFlowInfo otherFlowInfo = getFlowInfo(baseType,
paramIndex);
if (otherFlowInfo.getExposure() >= 0
- && flowInfoForArrayParam.infiniteArrayExpansionPathBetween(otherFlowInfo)) {
+ && otherFlowInfo.isTransitivelyAffectedBy(flowInfoForArrayParam)) {
logger.branch(
getLogLevel(isSpeculative),
"Cannot serialize type '"
@@ -1375,11 +1317,59 @@
}
}
+ /**
+ * If <code>type</code>'s base class does not inherit from
+ * <code>superType</code>'s base class, then return <code>type</code>
+ * unchanged. If it does, then return a subtype of <code>type</code> that
+ * includes all values in both <code>type</code> and <code>superType</code>.
+ * If there are definitely no such values, return <code>null</code>.
+ */
+ private JClassType constrainTypeBy(JClassType type, JClassType superType) {
+ return typeConstrainer.constrainTypeBy(type, superType);
+ }
+
private TypeParameterFlowInfo getFlowInfo(JGenericType type, int index) {
return typeParameterExposureComputer.computeTypeParameterExposure(type,
index);
}
+ /**
+ * Returns the subtypes of a given base type as parameterized by wildcards.
+ */
+ private List<JClassType> getPossiblyInstantiableSubtypes(TreeLogger logger,
+ JClassType baseType) {
+ assert (baseType == getBaseType(baseType));
+
+ List<JClassType> possiblyInstantiableTypes = new ArrayList<JClassType>();
+ if (baseType == typeOracle.getJavaLangObject()) {
+ return possiblyInstantiableTypes;
+ }
+
+ List<JClassType> candidates = new ArrayList<JClassType>();
+ candidates.add(baseType);
+ candidates.addAll(Arrays.asList(baseType.getSubtypes()));
+
+ for (JClassType subtype : candidates) {
+ JClassType subtypeBase = getBaseType(subtype);
+ if (maybeInstantiable(logger, subtypeBase)) {
+ /*
+ * Convert the generic type into a parameterization that only includes
+ * wildcards.
+ */
+ JGenericType isGeneric = subtype.isGenericType();
+ if (isGeneric != null) {
+ subtype = isGeneric.asParameterizedByWildcards();
+ } else {
+ assert (subtype instanceof JRealClassType);
+ }
+
+ possiblyInstantiableTypes.add(subtype);
+ }
+ }
+
+ return possiblyInstantiableTypes;
+ }
+
private TypeInfoComputed getTypeInfoComputed(JClassType type, Path path) {
TypeInfoComputed tic = typeToTypeInfoComputed.get(type);
if (tic == null) {
@@ -1394,6 +1384,22 @@
return isAllowedByFilter(logger, typeFilter, classType, isSpeculative);
}
+ /**
+ * Returns <code>true</code> if the type is Collection<? extends Object> or
+ * Map<? extends Object, ? extends Object>.
+ */
+ private boolean isRawMapOrRawCollection(JClassType type) {
+ if (type.asParameterizationOf(collectionClass) == collectionClass.asParameterizedByWildcards()) {
+ return true;
+ }
+
+ if (type.asParameterizationOf(mapClass) == mapClass.asParameterizedByWildcards()) {
+ return true;
+ }
+
+ return false;
+ }
+
private void logPath(TreeLogger logger, Path path) {
if (path == null) {
return;
@@ -1449,6 +1455,16 @@
}
}
+ private boolean maybeInstantiable(TreeLogger logger, JClassType type) {
+ boolean success = canBeInstantiated(logger, type, TreeLogger.DEBUG)
+ && shouldConsiderFieldsForSerialization(logger, type, true);
+ if (success) {
+ logger.branch(TreeLogger.DEBUG,
+ type.getParameterizedQualifiedSourceName() + " might be instantiable");
+ }
+ return success;
+ }
+
private boolean mightNotBeExposed(JGenericType baseType, int paramIndex) {
TypeParameterFlowInfo flowInfo = getFlowInfo(baseType, paramIndex);
return flowInfo.getMightNotBeExposed() || isManuallySerializable(baseType);
@@ -1498,36 +1514,4 @@
typeToTypeInfoComputed.remove(type);
}
}
-
- /**
- * Returns a map from each parameter in the subtype to the set of parameters,
- * if any, in the supertype which constrain that parameter.
- */
- private Map<JTypeParameter, Set<JTypeParameter>> subParamsConstrainedBy(
- JClassType superclass, JGenericType subclass) {
-
- Map<JTypeParameter, Set<JTypeParameter>> newTypeParameters = new LinkedHashMap<JTypeParameter, Set<JTypeParameter>>();
-
- JGenericType isGenericSuper = superclass.isGenericType();
- if (isGenericSuper != null) {
- JParameterizedType parameterization = subclass.asParameterizationOf(isGenericSuper);
- JClassType[] paramArgs = parameterization.getTypeArgs();
- for (int i = 0; i < paramArgs.length; ++i) {
- Set<JTypeParameter> typeParamsInParamArg = new HashSet<JTypeParameter>();
- recordTypeParametersIn(paramArgs[i], typeParamsInParamArg);
-
- for (JTypeParameter arg : typeParamsInParamArg) {
- Set<JTypeParameter> constBy = newTypeParameters.get(arg);
- if (constBy == null) {
- constBy = new LinkedHashSet<JTypeParameter>();
- newTypeParameters.put(arg, constBy);
- }
-
- constBy.add(isGenericSuper.getTypeParameters()[i]);
- }
- }
- }
-
- return newTypeParameters;
- }
}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeConstrainer.java b/user/src/com/google/gwt/user/rebind/rpc/TypeConstrainer.java
new file mode 100644
index 0000000..c089795
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeConstrainer.java
@@ -0,0 +1,362 @@
+/*
+ * 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.user.rebind.rpc;
+
+import com.google.gwt.core.ext.typeinfo.JArrayType;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JGenericType;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JRawType;
+import com.google.gwt.core.ext.typeinfo.JRealClassType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.JTypeParameter;
+import com.google.gwt.core.ext.typeinfo.JWildcardType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class defines the method
+ * {@link #constrainTypeBy(JClassType, JClassType)}.
+ */
+public class TypeConstrainer {
+ /**
+ * Check whether two base types have any subclasses in common. Note: this
+ * could surely be implemented much more efficiently.
+ */
+ private static boolean baseTypesOverlap(JClassType type1, JClassType type2) {
+ assert (type1 == getBaseType(type1));
+ assert (type2 == getBaseType(type2));
+
+ if (type1 == type2) {
+ return true;
+ }
+
+ HashSet<JClassType> subtypes1 = new HashSet<JClassType>();
+ subtypes1.add(type1);
+ for (JClassType sub1 : type1.getSubtypes()) {
+ subtypes1.add(getBaseType(sub1));
+ }
+
+ List<JClassType> subtypes2 = new ArrayList<JClassType>();
+ subtypes2.add(type2);
+ for (JClassType sub2 : type2.getSubtypes()) {
+ subtypes2.add(getBaseType(sub2));
+ }
+
+ for (JClassType sub2 : subtypes2) {
+ if (subtypes1.contains(sub2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static JClassType getBaseType(JClassType type) {
+ return SerializableTypeOracleBuilder.getBaseType(type);
+ }
+
+ private static boolean isRealOrParameterized(JClassType type) {
+ if (type.isParameterized() != null) {
+ return true;
+ }
+ if (type instanceof JRealClassType) {
+ return true;
+ }
+ return false;
+ }
+
+ private static JClassType[] makeArray(JClassType... classTypes) {
+ return classTypes;
+ }
+
+ /**
+ * Check whether <code>param</code> occurs anywhere within <code>type</code>.
+ */
+ private static boolean occurs(final JTypeParameter param, JClassType type) {
+ class OccursVisitor extends JTypeVisitor {
+ boolean foundIt = false;
+
+ @Override
+ public void endVisit(JTypeParameter seenParam) {
+ if (seenParam == param) {
+ foundIt = true;
+ }
+ }
+ }
+
+ OccursVisitor visitor = new OccursVisitor();
+ visitor.accept(type);
+ return visitor.foundIt;
+ }
+
+ private int freshTypeVariableCounter;
+
+ private final TypeOracle typeOracle;
+
+ public TypeConstrainer(TypeOracle typeOracle) {
+ this.typeOracle = typeOracle;
+ }
+
+ /**
+ * Return a subtype of <code>subType</code> that includes all values in both
+ * <code>subType</code> and <code>superType</code>. The returned type
+ * must have the same base type as <code>subType</code>. If there are
+ * definitely no such values, return <code>null</code>.
+ */
+ public JClassType constrainTypeBy(JClassType subType, JClassType superType) {
+ JParameterizedType superAsParameterized = superType.isParameterized();
+ if (superAsParameterized == null) {
+ // If the supertype is not parameterized, it will not be possible to
+ // constrain
+ // the subtype further.
+ return subType;
+ }
+
+ // Replace each wildcard in the subType with a fresh type variable.
+ // These type variables will be the ones that are constrained.
+ Map<JTypeParameter, JClassType> constraints = new HashMap<JTypeParameter, JClassType>();
+ JClassType subWithWildcardsReplaced = replaceWildcardsWithFreshTypeVariables(
+ subType, constraints);
+
+ // Rewrite subType so that it has the same base type as superType.
+ JParameterizedType subAsParameterized = subWithWildcardsReplaced.asParameterizationOf(superAsParameterized.getBaseType());
+ if (subAsParameterized == null) {
+ // The subtype's base does not inherit from the supertype's base,
+ // so again no constraint will be possible.
+ return subType;
+ }
+
+ // Check the rewritten type against superType
+ if (!typesMatch(subAsParameterized, superAsParameterized, constraints)) {
+ // The types are completely incompatible
+ return null;
+ }
+
+ // Apply the revised constraints to the original type
+ return substitute(subWithWildcardsReplaced, constraints);
+ }
+
+ /**
+ * Check whether two types can have any values in common. The
+ * <code>constraints</code> field holds known constraints for type
+ * parameters that appear in <code>type1</code>; this method may take
+ * advantage of those constraints in its decision, and it may tighten them so
+ * long as the tightening does not reject any values from the overlap of the
+ * two types.
+ *
+ * As an invariant, no key in <code>constraints</code> may occur inside any
+ * value in <code>constraints</code>.
+ *
+ * Note that this algorithm looks for overlap matches in the arguments of
+ * parameterized types rather than looking for exact matches. Looking for
+ * overlaps simplifies the algorithm but returns true more often than it has
+ * to.
+ */
+ boolean typesMatch(JClassType type1, JClassType type2,
+ Map<JTypeParameter, JClassType> constraints) {
+ JGenericType type1Generic = type1.isGenericType();
+ if (type1Generic != null) {
+ return typesMatch(type1Generic.asParameterizedByWildcards(), type2,
+ constraints);
+ }
+
+ JGenericType type2Generic = type2.isGenericType();
+ if (type2Generic != null) {
+ return typesMatch(type1, type2Generic.asParameterizedByWildcards(),
+ constraints);
+ }
+
+ JWildcardType type1Wild = type1.isWildcard();
+ if (type1Wild != null) {
+ return typesMatch(type1Wild.getUpperBound(), type2, constraints);
+ }
+
+ JWildcardType type2Wild = type2.isWildcard();
+ if (type2Wild != null) {
+ return typesMatch(type1, type2Wild.getUpperBound(), constraints);
+ }
+
+ JRawType type1Raw = type1.isRawType();
+ if (type1Raw != null) {
+ return typesMatch(type1Raw.asParameterizedByWildcards(), type2,
+ constraints);
+ }
+
+ JRawType type2Raw = type2.isRawType();
+ if (type2Raw != null) {
+ return typesMatch(type1, type2Raw.asParameterizedByWildcards(),
+ constraints);
+ }
+
+ assert (type1Generic == null);
+ assert (type2Generic == null);
+ assert (type1Wild == null);
+ assert (type2Wild == null);
+ assert (type1Raw == null);
+ assert (type2Raw == null);
+
+ if (type1 == type2) {
+ return true;
+ }
+
+ if (constraints.containsKey(type1)) {
+ JTypeParameter type1Parameter = (JTypeParameter) type1;
+ JClassType type2Class = type2;
+ JClassType type1Bound = constraints.get(type1Parameter);
+ assert (!occurs(type1Parameter, type1Bound));
+ if (!typesMatch(type1Bound, type2, constraints)) {
+ return false;
+ }
+
+ if (type1Bound.isAssignableFrom(type2Class)) {
+ constraints.put(type1Parameter, type2Class);
+ }
+ }
+
+ if (type1 == typeOracle.getJavaLangObject()) {
+ return true;
+ }
+
+ if (type2 == typeOracle.getJavaLangObject()) {
+ return true;
+ }
+
+ JTypeParameter type1Param = type1.isTypeParameter();
+ if (type1Param != null) {
+ // It would be nice to check that type1Param's bound is a match
+ // for type2, but that can introduce infinite recursions.
+ return true;
+ }
+
+ JTypeParameter type2Param = type2.isTypeParameter();
+ if (type2Param != null) {
+ // It would be nice to check that type1Param's bound is a match
+ // for type2, but that can introduce infinite recursions.
+ return true;
+ }
+
+ JArrayType type1Array = type1.isArray();
+ JArrayType type2Array = type2.isArray();
+ if (type1Array != null && type2Array != null) {
+ if (typesMatch(type1Array.getComponentType(),
+ type2Array.getComponentType(), constraints)) {
+ return true;
+ }
+ }
+
+ if (isRealOrParameterized(type1) && isRealOrParameterized(type2)) {
+ JClassType baseType1 = getBaseType(type1);
+ JClassType baseType2 = getBaseType(type2);
+ JParameterizedType type1Parameterized = type1.isParameterized();
+ JParameterizedType type2Parameterized = type2.isParameterized();
+
+ if (baseType1 == baseType2 && type1Parameterized != null
+ && type2Parameterized != null) {
+ // type1 and type2 are parameterized types with the same base type;
+ // compare their arguments
+ JClassType[] args1 = type1Parameterized.getTypeArgs();
+ JClassType[] args2 = type2Parameterized.getTypeArgs();
+ boolean allMatch = true;
+ for (int i = 0; i < args1.length; i++) {
+ if (!typesMatch(args1[i], args2[i], constraints)) {
+ allMatch = false;
+ }
+ }
+
+ if (allMatch) {
+ return true;
+ }
+ } else {
+ // The types have different base types, so just compare the base types
+ // for overlap.
+ if (baseTypesOverlap(baseType1, baseType2)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * The same as {@link #typesMatch(JClassType, JClassType, Map)}, but
+ * additionally support primitive types as well as class types.
+ */
+ boolean typesMatch(JType type1, JType type2,
+ Map<JTypeParameter, JClassType> constraints) {
+ if (type1 == type2) {
+ // This covers the case where both are primitives
+ return true;
+ }
+
+ JClassType type1Class = type1.isClassOrInterface();
+ JClassType type2Class = type2.isClassOrInterface();
+ if (type1Class != null && type2Class != null
+ && typesMatch(type1Class, type2Class, constraints)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Replace all wildcards in <code>type</code> with a fresh type variable.
+ * For each type variable created, add an entry in <code>constraints</code>
+ * mapping the type variable to its upper bound.
+ */
+ private JClassType replaceWildcardsWithFreshTypeVariables(JClassType type,
+ final Map<JTypeParameter, JClassType> constraints) {
+
+ JModTypeVisitor replacer = new JModTypeVisitor() {
+ @Override
+ public void endVisit(JWildcardType wildcardType) {
+ JTypeParameter newParam = new JTypeParameter("TP$"
+ + freshTypeVariableCounter++, -1);
+ newParam.setBounds(makeArray(typeOracle.getJavaLangObject()));
+ constraints.put(newParam, wildcardType.getUpperBound());
+ replacement = newParam;
+ }
+ };
+
+ return replacer.transform(type);
+ }
+
+ /**
+ * Substitute all occurrences in <code>type</code> of type parameters in
+ * <code>constraints</code> for a wildcard bounded by the parameter's entry
+ * in <code>constraints</code>. If the argument is <code>null</code>,
+ * return <code>null</code>.
+ */
+ private JClassType substitute(JClassType type,
+ final Map<JTypeParameter, JClassType> constraints) {
+ JModTypeVisitor substituter = new JModTypeVisitor() {
+ @Override
+ public void endVisit(JTypeParameter param) {
+ JClassType constr = constraints.get(param);
+ if (constr != null) {
+ replacement = constr;
+ }
+ }
+ };
+ return substituter.transform(type);
+ }
+}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java b/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
index b3ddb78..e3a2ba4 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
@@ -192,6 +192,35 @@
return checkDirectExposure();
}
+ boolean isTransitivelyAffectedBy(TypeParameterFlowInfo flowInfo) {
+ Boolean result = isTransitivelyAffectedByCache.get(flowInfo);
+ if (result != null) {
+ return result;
+ }
+
+ HashSet<TypeParameterFlowInfo> affectedBy = new HashSet<TypeParameterFlowInfo>();
+ Set<TypeParameterFlowInfo> affectedByWorklist = new LinkedHashSet<TypeParameterFlowInfo>();
+ affectedByWorklist.add(this);
+
+ result = false;
+ while (!affectedByWorklist.isEmpty()) {
+ TypeParameterFlowInfo currFlowInfo = affectedByWorklist.iterator().next();
+ affectedByWorklist.remove(currFlowInfo);
+
+ if (currFlowInfo == flowInfo) {
+ result = true;
+ break;
+ }
+
+ if (affectedBy.add(currFlowInfo)) {
+ affectedByWorklist.addAll(currFlowInfo.getAffectedBy());
+ }
+ }
+
+ isTransitivelyAffectedByCache.put(flowInfo, result);
+ return result;
+ }
+
boolean markExposedAsArray(int dim) {
if (exposure >= dim) {
return false;
@@ -291,35 +320,6 @@
return flowInfo;
}
- private boolean isTransitivelyAffectedBy(TypeParameterFlowInfo flowInfo) {
- Boolean result = isTransitivelyAffectedByCache.get(flowInfo);
- if (result != null) {
- return result;
- }
-
- HashSet<TypeParameterFlowInfo> affectedBy = new HashSet<TypeParameterFlowInfo>();
- Set<TypeParameterFlowInfo> affectedByWorklist = new LinkedHashSet<TypeParameterFlowInfo>();
- affectedByWorklist.add(this);
-
- result = false;
- while (!affectedByWorklist.isEmpty()) {
- TypeParameterFlowInfo currFlowInfo = affectedByWorklist.iterator().next();
- affectedByWorklist.remove(currFlowInfo);
-
- if (currFlowInfo == flowInfo) {
- result = true;
- break;
- }
-
- if (affectedBy.add(currFlowInfo)) {
- affectedByWorklist.addAll(currFlowInfo.getAffectedBy());
- }
- }
-
- isTransitivelyAffectedByCache.put(flowInfo, result);
- return result;
- }
-
private void recordCausesExposure(JGenericType type, int index, int level) {
assert (index < type.getTypeParameters().length);
TypeParameterFlowInfo flowInfo = getFlowInfo(type, index);
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
index 7b6b05e..00a16b0 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
@@ -428,7 +428,9 @@
}
public long readLong() {
- return Long.parseLong(extract(), 16);
+ // Keep synchronized with LongLib. The wire format are the two component
+ // parts of the double in the client code.
+ return (long) readDouble() + (long) readDouble();
}
public short readShort() {
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
index 6946d47..474dd3b 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
@@ -544,10 +544,15 @@
public void writeLong(long fieldValue) {
/*
- * Marshal down to client as a string; if we tried to marshal as a number
- * the JSON eval would lose precision.
+ * Client code represents longs internally as an array of two Numbers. In
+ * order to make serialization of longs faster, we'll send the component
+ * parts so that the value can be directly reconstituted on the client.
*/
- writeString(Long.toString(fieldValue, 16));
+ double[] parts = makeLongComponents((int) (fieldValue >> 32),
+ (int) fieldValue);
+ assert parts.length == 2;
+ writeDouble(parts[0]);
+ writeDouble(parts[1]);
}
@Override
diff --git a/user/test/com/google/gwt/core/CoreSuite.java b/user/test/com/google/gwt/core/CoreSuite.java
index 8084e34..0bc8039 100644
--- a/user/test/com/google/gwt/core/CoreSuite.java
+++ b/user/test/com/google/gwt/core/CoreSuite.java
@@ -17,6 +17,7 @@
import com.google.gwt.core.client.GWTTest;
import com.google.gwt.core.client.JavaScriptExceptionTest;
+import com.google.gwt.core.client.JsArrayTest;
import com.google.gwt.junit.tools.GWTTestSuite;
import junit.framework.Test;
@@ -30,6 +31,7 @@
// $JUnit-BEGIN$
suite.addTestSuite(JavaScriptExceptionTest.class);
+ suite.addTestSuite(JsArrayTest.class);
suite.addTestSuite(GWTTest.class);
// $JUnit-END$
diff --git a/user/test/com/google/gwt/core/client/JsArrayTest.java b/user/test/com/google/gwt/core/client/JsArrayTest.java
new file mode 100644
index 0000000..a06bf8e
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/JsArrayTest.java
@@ -0,0 +1,258 @@
+/*
+ * 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.core.client;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests JsArray variants.
+ */
+public class JsArrayTest extends GWTTestCase {
+
+ private static class JsPoint extends JavaScriptObject {
+ protected JsPoint() {
+ }
+
+ public final native int x() /*-{
+ return this.x;
+ }-*/;
+
+ public final native int y() /*-{
+ return this.y;
+ }-*/;
+ }
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.core.Core";
+ }
+
+ public void testJsArray() {
+ // All the test arrays start with 3 elements.
+ JsArray<JsPoint> jsArray = makeJsArray();
+ assertEquals(3, jsArray.length());
+
+ // Get the three points and make sure they are what we think.
+ JsPoint p0 = jsArray.get(0);
+ JsPoint p1 = jsArray.get(1);
+ JsPoint p2 = jsArray.get(2);
+
+ assertEquals(0, p0.x());
+ assertEquals(1, p0.y());
+ assertEquals(2, p1.x());
+ assertEquals(3, p1.y());
+ assertEquals(4, p2.x());
+ assertEquals(5, p2.y());
+
+ // Make sure the '3' element is null.
+ assertNull(jsArray.get(3));
+
+ // Make a new point and stick it in the '3' slot. It should come back with
+ // reference equality intact, and the array length should be bumped to 4.
+ JsPoint p3 = makeJsPoint(6, 7);
+ jsArray.set(3, p3);
+ assertEquals(p3, jsArray.get(3));
+ assertEquals(4, jsArray.length());
+
+ // Stick a non-JSO value in the '4' slot. Getting it should cause a type
+ // error in hosted mode.
+ if (!GWT.isScript()) {
+ try {
+ jsArray.<JsArrayString> cast().set(4, "bubba");
+ jsArray.get(4);
+ fail("Expected an exception getting an invalid value in hosted mode");
+ } catch (Throwable e) {
+ }
+ }
+ }
+
+ public void testJsArrayBoolean() {
+ // All the test arrays start with 3 elements.
+ JsArrayBoolean jsArray = makeJsArrayBoolean();
+ assertEquals(3, jsArray.length());
+
+ // Get the three points and make sure they are what we think.
+ assertEquals(true, jsArray.get(0));
+ assertEquals(false, jsArray.get(1));
+ assertEquals(true, jsArray.get(2));
+
+ // Make sure getting the '3' element throws an exception in hosted mode
+ // (this won't happen in web mode).
+ if (!GWT.isScript()) {
+ try {
+ jsArray.get(3);
+ fail("Expected an exception getting an invalid value in hosted mode");
+ } catch (Throwable e) {
+ }
+ }
+
+ // Stick a new boolean in the '3' slot. It should come back intact, and the
+ // array length should be bumped to 4.
+ jsArray.set(3, false);
+ assertEquals(false, jsArray.get(3));
+ assertEquals(4, jsArray.length());
+
+ // Stick a non-boolean value in the '4' slot. Getting it should cause a type
+ // error in hosted mode.
+ if (!GWT.isScript()) {
+ try {
+ jsArray.<JsArrayString> cast().set(4, "bubba");
+ jsArray.get(4);
+ fail("Expected an exception getting an invalid value in hosted mode");
+ } catch (Throwable e) {
+ }
+ }
+ }
+
+ public void testJsArrayInteger() {
+ // All the test arrays start with 3 elements.
+ JsArrayInteger jsArray = makeJsArrayInteger();
+ assertEquals(3, jsArray.length());
+
+ // Get the three points and make sure they are what we think.
+ assertEquals(0, jsArray.get(0));
+ assertEquals(1, jsArray.get(1));
+ assertEquals(2, jsArray.get(2));
+
+ // Make sure getting the '3' element throws an exception in hosted mode
+ // (this won't happen in web mode).
+ if (!GWT.isScript()) {
+ try {
+ jsArray.get(3);
+ fail("Expected an exception getting an invalid value in hosted mode");
+ } catch (Throwable e) {
+ }
+ }
+
+ // Stick a new number in the '3' slot. It should come back intact, and the
+ // array length should be bumped to 4.
+ jsArray.set(3, 3);
+ assertEquals(3, jsArray.get(3));
+ assertEquals(4, jsArray.length());
+
+ // Stick a non-numeric value in the '4' slot. Getting it should cause a type
+ // error in hosted mode.
+ if (!GWT.isScript()) {
+ try {
+ jsArray.<JsArrayString> cast().set(4, "bubba");
+ jsArray.get(4);
+ fail("Expected an exception getting an invalid value in hosted mode");
+ } catch (Throwable e) {
+ }
+ }
+ }
+
+ public void testJsArrayNumber() {
+ // All the test arrays start with 3 elements.
+ JsArrayNumber jsArray = makeJsArrayNumber();
+ assertEquals(3, jsArray.length());
+
+ // Get the three points and make sure they are what we think.
+ assertEquals(0.0, jsArray.get(0));
+ assertEquals(1.1, jsArray.get(1));
+ assertEquals(2.2, jsArray.get(2));
+
+ // Make sure getting the '3' element throws an exception in hosted mode
+ // (this won't happen in web mode).
+ if (!GWT.isScript()) {
+ try {
+ jsArray.get(3);
+ fail("Expected an exception getting an invalid value in hosted mode");
+ } catch (Throwable e) {
+ }
+ }
+
+ // Stick a new number in the '3' slot. It should come back intact, and the
+ // array length should be bumped to 4.
+ jsArray.set(3, 3.0);
+ assertEquals(3.0, jsArray.get(3));
+ assertEquals(4, jsArray.length());
+
+ // Stick a non-numeric value in the '4' slot. Getting it should cause a type
+ // error in hosted mode.
+ if (!GWT.isScript()) {
+ try {
+ jsArray.<JsArrayString> cast().set(4, "bubba");
+ jsArray.get(4);
+ fail("Expected an exception getting an invalid value in hosted mode");
+ } catch (Throwable e) {
+ }
+ }
+ }
+
+ public void testJsArrayString() {
+ // All the test arrays start with 3 elements.
+ JsArrayString jsArray = makeJsArrayString();
+ assertEquals(3, jsArray.length());
+
+ // Get the three points and make sure they are what we think.
+ String s0 = jsArray.get(0);
+ String s1 = jsArray.get(1);
+ String s2 = jsArray.get(2);
+
+ assertEquals("foo", s0);
+ assertEquals("bar", s1);
+ assertEquals("baz", s2);
+
+ // Make sure the '3' element is null.
+ assertNull(jsArray.get(3));
+
+ // Stick a new string in the '3' slot. It should come back intact, and the
+ // array length should be bumped to 4.
+ jsArray.set(3, "tintin");
+ assertEquals("tintin", jsArray.get(3));
+ assertEquals(4, jsArray.length());
+
+ // Stick a non-String value in the '4' slot. Getting it should cause a type
+ // error in hosted mode.
+ if (!GWT.isScript()) {
+ try {
+ jsArray.<JsArrayBoolean> cast().set(4, true);
+ jsArray.get(4);
+ fail("Expected an exception getting an invalid value in hosted mode");
+ } catch (Throwable e) {
+ }
+ }
+ }
+
+ private native JsArray<JsPoint> makeJsArray() /*-{
+ return [
+ { x: 0, y: 1},
+ { x: 2, y: 3},
+ { x: 4, y: 5},
+ ];
+ }-*/;
+
+ private native JsArrayBoolean makeJsArrayBoolean() /*-{
+ return [true, false, true];
+ }-*/;
+
+ private native JsArrayInteger makeJsArrayInteger() /*-{
+ return [0, 1, 2];
+ }-*/;
+
+ private native JsArrayNumber makeJsArrayNumber() /*-{
+ return [0.0, 1.1, 2.2];
+ }-*/;
+
+ private native JsArrayString makeJsArrayString() /*-{
+ return ['foo', 'bar', 'baz'];
+ }-*/;
+
+ private native JsPoint makeJsPoint(int newx, int newy) /*-{
+ return {x: newx, y: newy};
+ }-*/;
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/NativeLongTest.java b/user/test/com/google/gwt/dev/jjs/test/NativeLongTest.java
index 7edd64d..a01dc69 100644
--- a/user/test/com/google/gwt/dev/jjs/test/NativeLongTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/NativeLongTest.java
@@ -28,6 +28,9 @@
* constant fold them. The problem is that if you write assertEquals(2L,
* 4L/2L), the compiler will emit assertEquals(2L, 2L).
*/
+ private static volatile byte BYTE_FOUR = (byte) 4;
+ private static volatile char CHAR_FOUR = (char) 4;
+ private static volatile int INT_FOUR = 4;
private static volatile long LONG_100 = 100L;
private static volatile long LONG_1234 = 0x1234123412341234L;
private static volatile long LONG_1234_DECIMAL = 1234123412341234L;
@@ -37,11 +40,13 @@
private static volatile long LONG_DEADBEEF = 0xdeadbeefdeadbeefL;
private static volatile long LONG_DEADBEEF12341234 = 0xdeadbeef12341234L;
private static volatile long LONG_FFFFFFFF = 0xFFFFFFFFL;
+ private static volatile long LONG_FOUR = 4L;
private static volatile long LONG_ONE = 1L;
private static volatile long LONG_THREE = 3L;
private static volatile long LONG_TWO = 2L;
private static volatile long LONG_TWO_PWR_32 = 0x100000000L;
private static volatile long LONG_ZERO = 0L;
+ private static volatile short SHORT_FOUR = (short) 4;
@Override
public String getModuleName() {
@@ -129,10 +134,18 @@
public void testModifyingOps() {
long l = 20;
- l += 10;
- assertEquals(31, ++l);
- assertEquals(31, l++);
- assertEquals(32, l);
+ l += INT_FOUR;
+ assertEquals(25, ++l);
+ assertEquals(25, l++);
+ assertEquals(26, l);
+ l += BYTE_FOUR;
+ assertEquals(30, l);
+ l += CHAR_FOUR;
+ assertEquals(34, l);
+ l += SHORT_FOUR;
+ assertEquals(38, l);
+ l += INT_FOUR;
+ assertEquals(42, l);
}
public void testShift() {
@@ -140,6 +153,11 @@
assertEquals(LONG_ONE << 12, (LONG_ONE << 60) >>> (48));
assertTrue((LONG_ONE << 35) > (LONG_ONE << 30));
+ assertEquals(0x10L, LONG_ONE << BYTE_FOUR);
+ assertEquals(0x10L, LONG_ONE << CHAR_FOUR);
+ assertEquals(0x10L, LONG_ONE << SHORT_FOUR);
+ assertEquals(0x10L, LONG_ONE << INT_FOUR);
+ assertEquals(0x10L, LONG_ONE << LONG_FOUR);
assertEquals(1L, LONG_TWO_PWR_32 >> 32);
assertEquals(1L, LONG_TWO_PWR_32 >>> 32);
diff --git a/user/test/com/google/gwt/user/ClassInitTest.java b/user/test/com/google/gwt/user/ClassInitTest.java
new file mode 100644
index 0000000..8939c5f
--- /dev/null
+++ b/user/test/com/google/gwt/user/ClassInitTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.user;
+
+import com.google.gwt.junit.GWTMockUtilities;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+/**
+ * Tests that every class in com.google.gwt.user.client.ui can be init'd by the
+ * real Java runtime. By ensuring this, we ensure that these classes all may be
+ * referenced mocked out by pure Java unit tests, e.g. with EasyMock Class
+ * Extension
+ */
+public class ClassInitTest extends TestCase {
+ private static final String DOT_CLASS = ".class";
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ GWTMockUtilities.disarm();
+ }
+
+ @Override
+ public void tearDown() {
+ GWTMockUtilities.restore();
+ }
+
+ public void testOne() throws ClassNotFoundException {
+ String packageName = "com.google.gwt.user.client.ui";
+ String path = packageNameToPath(packageName);
+ File directory = pathToResourceDirectory(path);
+
+ if (directory.exists()) {
+ String[] files = directory.list();
+ for (String file : files) {
+ if (file.endsWith(DOT_CLASS)) {
+ String classname = classFileToClassName(file);
+ Class.forName(packageName + "." + classname);
+ }
+ }
+ }
+ }
+
+ private String classFileToClassName(String file) {
+ return file.substring(0, file.length() - DOT_CLASS.length());
+ }
+
+ private File pathToResourceDirectory(String name) {
+ File directory = new File(getClass().getResource(name).getFile());
+ return directory;
+ }
+
+ private String packageNameToPath(String name) {
+ name = "/" + name.replace('.', '/');
+ return name;
+ }
+}
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index c63ee5f..b34b49c 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -20,6 +20,7 @@
import com.google.gwt.user.client.CookieTest;
import com.google.gwt.user.client.WindowTest;
import com.google.gwt.user.client.ui.AbsolutePanelTest;
+import com.google.gwt.user.client.ui.AnchorTest;
import com.google.gwt.user.client.ui.CaptionPanelTest;
import com.google.gwt.user.client.ui.CheckBoxTest;
import com.google.gwt.user.client.ui.CompositeTest;
@@ -35,6 +36,7 @@
import com.google.gwt.user.client.ui.DialogBoxTest;
import com.google.gwt.user.client.ui.DisclosurePanelTest;
import com.google.gwt.user.client.ui.DockPanelTest;
+import com.google.gwt.user.client.ui.ElementWrappingTest;
import com.google.gwt.user.client.ui.FastStringMapTest;
import com.google.gwt.user.client.ui.FlexTableTest;
import com.google.gwt.user.client.ui.FlowPanelTest;
@@ -55,6 +57,8 @@
import com.google.gwt.user.client.ui.RadioButtonTest;
import com.google.gwt.user.client.ui.RichTextAreaTest;
import com.google.gwt.user.client.ui.ScrollPanelTest;
+import com.google.gwt.user.client.ui.SimpleCheckBoxTest;
+import com.google.gwt.user.client.ui.SimpleRadioButtonTest;
import com.google.gwt.user.client.ui.SplitPanelTest;
import com.google.gwt.user.client.ui.StackPanelTest;
import com.google.gwt.user.client.ui.TabBarTest;
@@ -81,6 +85,7 @@
"Test for suite for the com.google.gwt.ui module");
suite.addTestSuite(AbsolutePanelTest.class);
+ suite.addTestSuite(AnchorTest.class);
suite.addTestSuite(CaptionPanelTest.class);
suite.addTestSuite(CheckBoxTest.class);
suite.addTestSuite(ClippedImagePrototypeTest.class);
@@ -99,6 +104,7 @@
suite.addTestSuite(DisclosurePanelTest.class);
suite.addTestSuite(DockPanelTest.class);
suite.addTestSuite(DOMTest.class);
+ suite.addTestSuite(ElementWrappingTest.class);
suite.addTestSuite(FastStringMapTest.class);
suite.addTestSuite(FlexTableTest.class);
suite.addTestSuite(FlowPanelTest.class);
@@ -122,6 +128,8 @@
suite.addTestSuite(RadioButtonTest.class);
suite.addTestSuite(RichTextAreaTest.class);
suite.addTestSuite(ScrollPanelTest.class);
+ suite.addTestSuite(SimpleCheckBoxTest.class);
+ suite.addTestSuite(SimpleRadioButtonTest.class);
suite.addTestSuite(SplitPanelTest.class);
suite.addTestSuite(StackPanelTest.class);
suite.addTestSuite(TabBarTest.class);
@@ -135,6 +143,7 @@
suite.addTestSuite(WidgetOnLoadTest.class);
suite.addTestSuite(WindowTest.class);
suite.addTestSuite(XMLTest.class);
+ suite.addTestSuite(ClassInitTest.class);
return suite;
}
diff --git a/user/test/com/google/gwt/user/client/ui/AnchorTest.java b/user/test/com/google/gwt/user/client/ui/AnchorTest.java
new file mode 100644
index 0000000..b981b54
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/AnchorTest.java
@@ -0,0 +1,333 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.i18n.client.HasDirection;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.DOM;
+
+/**
+ * Tests for {@link Anchor}.
+ */
+public class AnchorTest extends GWTTestCase {
+
+ private static final String TEST_URL0 = "http://www.google.com/";
+ private static final String TEST_URL1 = "http://code.google.com/";
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.UserTest";
+ }
+
+ public void testProperties() {
+ Anchor anchor = new Anchor("foo", TEST_URL0);
+ assertEquals("foo", anchor.getText());
+ assertEquals("foo", anchor.getHTML());
+ assertEquals(TEST_URL0, anchor.getHref());
+
+ anchor.setText("bar");
+ assertEquals("bar", anchor.getText());
+
+ anchor.setHTML("baz");
+ assertEquals("baz", anchor.getHTML());
+
+ anchor.setHref(TEST_URL1);
+ assertEquals(TEST_URL1, anchor.getHref());
+
+ anchor.setDirection(HasDirection.Direction.RTL);
+ assertEquals(HasDirection.Direction.RTL, anchor.getDirection());
+
+ anchor.setWordWrap(true);
+ assertEquals(true, anchor.getWordWrap());
+
+ anchor.setTabIndex(42);
+ assertEquals(42, anchor.getTabIndex());
+ }
+
+ private final class TestClickListener implements ClickListener {
+ private int clicks = 0;
+ private Widget lastSender;
+
+ public void onClick(Widget sender) {
+ clicks++;
+ lastSender = sender;
+ }
+
+ public int getClicks() {
+ return clicks;
+ }
+
+ public Widget getLastSender() {
+ return lastSender;
+ }
+ }
+
+ public void testNoAttributes() {
+ Anchor anchor = new Anchor();
+
+ Panel p = getTestPanel();
+ p.add(anchor);
+
+ assertEquals(1, DOM.getChildCount(p.getElement()));
+ assertEquals("A", DOM.getChild(p.getElement(), 0).getTagName());
+ assertEquals(0, DOM.getChildCount(anchor.getElement()));
+
+ final String[] attrs = new String[] {
+ "href", "name", "id", "rel", "ref", "target"};
+ for (String attribute : attrs) {
+ assertAttributeNotPresent(attribute, anchor.getElement());
+ }
+ }
+
+ public void testScriptAnchor() {
+ Anchor anchor = new Anchor("Foo");
+
+ Panel p = getTestPanel();
+ p.add(anchor);
+
+ assertEquals(1, DOM.getChildCount(p.getElement()));
+ assertEquals("A", DOM.getChild(p.getElement(), 0).getTagName());
+ assertEquals("Foo", anchor.getText());
+ assertAttributeHasValue("javascript:", anchor.getElement(), "href");
+
+ for (String attribute : new String[] {
+ "name", "id", "rel", "ref", "target"}) {
+ assertAttributeNotPresent(attribute, anchor.getElement());
+ }
+ }
+
+ public void testScriptAnchorWithHTML() {
+ Anchor anchor = new Anchor("<span>Foo</span>", true);
+
+ Panel p = getTestPanel();
+ p.add(anchor);
+
+ assertEquals(1, DOM.getChildCount(p.getElement()));
+ assertEquals("A", DOM.getChild(p.getElement(), 0).getTagName());
+ assertEquals("SPAN", DOM.getChild(anchor.getElement(), 0).getTagName());
+ assertAttributeHasValue("javascript:", anchor.getElement(), "href");
+
+ for (String attribute : new String[] {
+ "name", "id", "rel", "ref", "target"}) {
+ assertAttributeNotPresent(attribute, anchor.getElement());
+ }
+ }
+
+ public void testEvents() {
+ Anchor anchor = new Anchor("Trigger obscure JavaScript things");
+
+ Panel p = getTestPanel();
+ p.add(anchor);
+
+ TestClickListener testListener = new TestClickListener();
+ anchor.addClickListener(testListener);
+
+ assertEquals(0, testListener.getClicks());
+ triggerEvent(anchor.getElement(), "click", false, "MouseEvents");
+ assertEquals(1, testListener.getClicks());
+ assertEquals(anchor, testListener.getLastSender());
+ }
+
+ public void testLink() {
+ Anchor anchor = new Anchor("Click me!", "http://nowhere.org/");
+
+ Panel p = getTestPanel();
+ p.add(anchor);
+
+ assertEquals(1, DOM.getChildCount(p.getElement()));
+ assertEquals("A", DOM.getChild(p.getElement(), 0).getTagName());
+ assertEquals("Click me!", anchor.getText());
+ assertAttributeHasValue("http://nowhere.org/", anchor.getElement(), "href");
+
+ for (String attribute : new String[] {
+ "name", "id", "rel", "ref", "target"}) {
+ assertAttributeNotPresent(attribute, anchor.getElement());
+ }
+ }
+
+ public void testLinkWithHTML() {
+ Anchor anchor = new Anchor("<span>Foo</span>", true,
+ "http://still.nowhere.org/");
+
+ Panel p = getTestPanel();
+ p.add(anchor);
+
+ assertEquals(1, DOM.getChildCount(p.getElement()));
+ assertEquals("A", DOM.getChild(p.getElement(), 0).getTagName());
+ assertEquals("SPAN", DOM.getChild(anchor.getElement(), 0).getTagName());
+ assertTrue("<span>Foo</span>".equalsIgnoreCase(anchor.getHTML()));
+
+ assertAttributeHasValue("http://still.nowhere.org/", anchor.getElement(),
+ "href");
+
+ for (String attribute : new String[] {
+ "name", "id", "rel", "ref", "target"}) {
+ assertAttributeNotPresent(attribute, anchor.getElement());
+ }
+ }
+
+ public void testLinkWithTarget() {
+ Anchor anchor = new Anchor("Click me!",
+ "http://and.now.a.word.from.our.sponsor.org/", "popup");
+
+ Panel p = getTestPanel();
+ p.add(anchor);
+
+ assertEquals(1, DOM.getChildCount(p.getElement()));
+ assertEquals("A", DOM.getChild(p.getElement(), 0).getTagName());
+ assertEquals("Click me!", anchor.getText());
+ assertAttributeHasValue("http://and.now.a.word.from.our.sponsor.org/",
+ anchor.getElement(), "href");
+ assertAttributeHasValue("popup", anchor.getElement(), "target");
+
+ for (String attribute : new String[] {
+ "name", "id", "rel", "ref"}) {
+ assertAttributeNotPresent(attribute, anchor.getElement());
+ }
+ }
+
+ public void testLinkWithHTMLAndTarget() {
+ Anchor anchor = new Anchor("<span>Foo</span>", true,
+ "http://more.ads.com/", "_blank");
+
+ Panel p = getTestPanel();
+ p.add(anchor);
+
+ assertEquals(1, DOM.getChildCount(p.getElement()));
+ assertEquals("A", DOM.getChild(p.getElement(), 0).getTagName());
+ assertEquals("SPAN", DOM.getChild(anchor.getElement(), 0).getTagName());
+ assertTrue("<span>Foo</span>".equalsIgnoreCase(anchor.getHTML()));
+
+ assertAttributeHasValue("http://more.ads.com/", anchor.getElement(), "href");
+ assertAttributeHasValue("_blank", anchor.getElement(), "target");
+
+ for (String attribute : new String[] {
+ "name", "id", "rel", "ref"}) {
+ assertAttributeNotPresent(attribute, anchor.getElement());
+ }
+ }
+
+ /**
+ * Tests that the getters interact with the generic setter correctly.
+ */
+ public void testGetterRoundtrip() {
+ Anchor anchor = new Anchor();
+ Panel p = getTestPanel();
+ p.add(anchor);
+
+ anchor.getElement().setAttribute("href",
+ "http://yet.another.made.up.url.org/");
+ assertEquals("http://yet.another.made.up.url.org/", anchor.getHref());
+
+ anchor.getElement().setAttribute("target", "_blank");
+ assertEquals("_blank", anchor.getTarget());
+
+ anchor.getElement().setAttribute("name", "Marty");
+ assertEquals("Marty", anchor.getName());
+
+ anchor.getElement().setAttribute("tabIndex", "23");
+ assertEquals(23, anchor.getTabIndex());
+ }
+
+ /**
+ * Tests that the setters interact with the generic getter correctly.
+ */
+ public void testSetterRoundtrip() {
+ Anchor anchor = new Anchor();
+ Panel p = getTestPanel();
+ p.add(anchor);
+
+ anchor.setHref("http://duh.no.more.ideas.net/");
+ assertEquals("http://duh.no.more.ideas.net/",
+ anchor.getElement().getAttribute("href"));
+
+ anchor.setTarget("_top");
+ assertEquals("_top", anchor.getElement().getAttribute("target"));
+
+ anchor.setName("Hieronymous");
+ assertEquals("Hieronymous", anchor.getElement().getAttribute("name"));
+
+ anchor.setName("Hieronymous");
+ assertEquals("Hieronymous", anchor.getElement().getAttribute("name"));
+
+ anchor.setTabIndex(42);
+ assertEquals(42, Integer.parseInt(anchor.getElement().getAttribute("tabIndex")));
+ }
+
+ /**
+ * Constructs a simple panel for testing.
+ *
+ * @return Panel, attached to the root panel.
+ */
+ private Panel getTestPanel() {
+ Panel p = new FlowPanel();
+ RootPanel.get().add(p);
+ return p;
+ }
+
+ /**
+ * Asserts that a given attribute is not present on an element.
+ *
+ * @param element The element to check.
+ * @param attribute The attribute to check.
+ */
+ private static void assertAttributeNotPresent(String attribute,
+ Element element) {
+ String value = element.getPropertyString(attribute);
+ assertTrue(attribute + " not present", (value == null)
+ || (value.equals("")));
+ }
+
+ /**
+ * Asserts that a given attribute has the expected valued.
+ *
+ * @param expected Expected value for the attribute.
+ * @param element Element to check on.
+ * @param attribute Attribute to check.
+ */
+ private static void assertAttributeHasValue(String expected, Element element,
+ String attribute) {
+ assertEquals("Attribute " + attribute + " has value '" + expected + "'",
+ expected, element.getPropertyString(attribute));
+ }
+
+ /**
+ * Triggers events in a cross-browser way.
+ *
+ * TODO: Refactor this and other utility methods into a Utility class or
+ * common base.
+ *
+ * @param element the element on which to trigger the event
+ * @param eventType the type of event to trigger
+ * @param canBubble true if the event can bubble
+ * @param eventClass the class of event
+ */
+ public native void triggerEvent(Element element, String eventType,
+ boolean canBubble, String eventClass) /*-{
+ // TODO: This is convenient for now, but could be a lot simpler if we added
+ // a GWTtier API for event triggering.
+ canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
+ if (element.fireEvent) {
+ var evt = element.ownerDocument.createEventObject();
+ element.fireEvent('on' + eventType, evt);
+ } else {
+ var evt = document.createEvent(eventClass);
+ evt.initEvent(eventType, canBubble, true);
+ element.dispatchEvent(evt);
+ }
+ }-*/;
+}
diff --git a/user/test/com/google/gwt/user/client/ui/DOMTest.java b/user/test/com/google/gwt/user/client/ui/DOMTest.java
index c58977f..9177c28 100644
--- a/user/test/com/google/gwt/user/client/ui/DOMTest.java
+++ b/user/test/com/google/gwt/user/client/ui/DOMTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -46,6 +46,7 @@
return (elem.childNodes.length);
}-*/;
+ @Override
public String getModuleName() {
return "com.google.gwt.user.UserTest";
}
@@ -130,6 +131,38 @@
}
/**
+ * Tests {@link DOM#getAbsoluteTop(Element)} and
+ * {@link DOM#getAbsoluteLeft(Element)} for consistency when the parent
+ * element has a border.
+ *
+ */
+ public void testGetAbsolutePositionWithPixelBorders() {
+ final Element outer = DOM.createDiv();
+ final Element inner = DOM.createDiv();
+
+ outer.getStyle().setProperty("position", "relative");
+ outer.getStyle().setProperty("width", "200px");
+ outer.getStyle().setProperty("height", "200px");
+
+ inner.getStyle().setProperty("position", "absolute");
+ inner.getStyle().setProperty("top", "30px");
+ inner.getStyle().setProperty("left", "40px");
+ inner.setInnerText("inner");
+
+ outer.appendChild(inner);
+ RootPanel.getBodyElement().appendChild(outer);
+
+ // Get the position without a border
+ int absTop = inner.getAbsoluteTop();
+ int absLeft = inner.getAbsoluteLeft();
+
+ // Get the position with a border
+ outer.getStyle().setProperty("border", "2px solid blue");
+ assertEquals(2, inner.getAbsoluteTop() - absTop);
+ assertEquals(2, inner.getAbsoluteLeft() - absLeft);
+ }
+
+ /**
* Tests the ability to do a parent-ward walk in the DOM.
*/
public void testGetParent() {
@@ -167,18 +200,18 @@
public void testIsOrHasChild() {
Element div = DOM.createDiv();
Element childDiv = DOM.createDiv();
-
+
assertFalse(DOM.isOrHasChild(div, childDiv));
assertTrue(DOM.isOrHasChild(div, div));
-
+
DOM.appendChild(div, childDiv);
assertTrue(DOM.isOrHasChild(div, childDiv));
assertFalse(DOM.isOrHasChild(childDiv, div));
-
+
DOM.appendChild(RootPanel.getBodyElement(), div);
assertTrue(DOM.isOrHasChild(div, childDiv));
assertTrue(DOM.isOrHasChild(div, div));
- assertFalse(DOM.isOrHasChild(childDiv, div));
+ assertFalse(DOM.isOrHasChild(childDiv, div));
}
/**
@@ -234,6 +267,7 @@
assertEndsWith("b0.gif", DOM.getImgSrc(image));
delayTestFinish(2000);
new Timer() {
+ @Override
public void run() {
assertEndsWith("b0.gif", DOM.getElementProperty(image, "src"));
finishTest();
@@ -273,6 +307,7 @@
assertEndsWith("a1.gif", DOM.getImgSrc(images[2]));
delayTestFinish(2000);
new Timer() {
+ @Override
public void run() {
assertEndsWith("a1.gif", DOM.getElementProperty(images[0], "src"));
assertEndsWith("b1.gif", DOM.getElementProperty(images[1], "src"));
@@ -314,6 +349,7 @@
assertEndsWith("a2.gif", DOM.getImgSrc(images[2]));
delayTestFinish(2000);
new Timer() {
+ @Override
public void run() {
assertEndsWith("b2.gif", DOM.getElementProperty(images[0], "src"));
assertEndsWith("a2.gif", DOM.getElementProperty(images[1], "src"));
@@ -358,6 +394,7 @@
assertEndsWith("b3.gif", DOM.getImgSrc(images[3]));
delayTestFinish(2000);
new Timer() {
+ @Override
public void run() {
assertEndsWith("b3.gif", DOM.getElementProperty(images[0], "src"));
assertEndsWith("a3.gif", DOM.getElementProperty(images[1], "src"));
@@ -407,6 +444,7 @@
assertEndsWith("b4.gif", DOM.getImgSrc(images[4]));
delayTestFinish(2000);
new Timer() {
+ @Override
public void run() {
assertEndsWith("a4.gif", DOM.getElementProperty(images[0], "src"));
assertEndsWith("a4.gif", DOM.getElementProperty(images[1], "src"));
diff --git a/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java b/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java
new file mode 100644
index 0000000..7c927ef
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests for various methods of the form {@link Button#wrap(Element)}.
+ */
+public class ElementWrappingTest extends GWTTestCase {
+
+ private static final String TEST_URL = "http://www.google.com/";
+ private static final String IMG_URL = "http://www.google.com/images/logo_sm.gif";
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.UserTest";
+ }
+
+ public void testAnchor() {
+ ensureDiv().setInnerHTML("<a id='foo' href='" + TEST_URL + "'>myAnchor</a>");
+ Anchor anchor = Anchor.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(anchor);
+ assertEquals(TEST_URL, anchor.getHref());
+ assertEquals("myAnchor", anchor.getText());
+ }
+
+ public void testButton() {
+ ensureDiv().setInnerHTML("<button id='foo'>myButton</button>");
+ Button button = Button.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(button);
+ assertEquals("myButton", button.getText());
+ }
+
+ public void testFileUpload() {
+ ensureDiv().setInnerHTML("<input type='file' id='foo'>myInput</input>");
+ FileUpload upload = FileUpload.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(upload);
+ }
+
+ public void testFormPanel() {
+ ensureDiv().setInnerHTML("<form id='foo'></form>");
+ FormPanel formPanel = FormPanel.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(formPanel);
+ }
+
+ public void testFrame() {
+ ensureDiv().setInnerHTML("<iframe id='foo'>myFrame</iframe>");
+ Frame frame = Frame.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(frame);
+ }
+
+ public void testHidden() {
+ ensureDiv().setInnerHTML("<input type='hidden' id='foo'></input>");
+ Hidden hidden = Hidden.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(hidden);
+ }
+
+ public void testHTML() {
+ ensureDiv().setInnerHTML("<div id='foo'>myHTML</div>");
+ HTML html = HTML.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(html);
+ assertEquals("myHTML", html.getHTML());
+ }
+
+ public void testImage() {
+ ensureDiv().setInnerHTML("<img id='foo' src='" + IMG_URL + "'>");
+ Image image = Image.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(image);
+ assertEquals(IMG_URL, image.getUrl());
+ }
+
+ public void testInlineHTML() {
+ ensureDiv().setInnerHTML("<span id='foo'>myInlineHTML</span>");
+ InlineHTML html = InlineHTML.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(html);
+ assertEquals("myInlineHTML", html.getHTML());
+ }
+
+ public void testInlineLabel() {
+ ensureDiv().setInnerHTML("<span id='foo'>myInlineLabel</span>");
+ InlineLabel label = InlineLabel.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(label);
+ assertEquals("myInlineLabel", label.getText());
+ }
+
+ public void testLabel() {
+ ensureDiv().setInnerHTML("<div id='foo'>myLabel</div>");
+ Label label = Label.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(label);
+ assertEquals("myLabel", label.getText());
+ }
+
+ public void testListBox() {
+ ensureDiv().setInnerHTML("<select id='foo'></select>");
+ ListBox listBox = ListBox.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(listBox);
+ }
+
+ public void testPasswordTextBox() {
+ ensureDiv().setInnerHTML("<input type='password' id='foo'></input>");
+ PasswordTextBox textBox = PasswordTextBox.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(textBox);
+ }
+
+ public void testSimpleCheckBox() {
+ ensureDiv().setInnerHTML("<input type='checkbox' id='foo'></input>");
+ SimpleCheckBox checkBox = SimpleCheckBox.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(checkBox);
+ }
+
+ public void testSimpleRadioButton() {
+ ensureDiv().setInnerHTML("<input type='radio' id='foo'></input>");
+ SimpleRadioButton radio = SimpleRadioButton.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(radio);
+ }
+
+ public void testTextBox() {
+ ensureDiv().setInnerHTML("<input type='text' id='foo'></input>");
+ TextBox textBox = TextBox.wrap(Document.get().getElementById("foo"));
+
+ assertExistsAndAttached(textBox);
+ }
+
+ private void assertExistsAndAttached(Widget widget) {
+ assertNotNull(widget);
+ assertTrue(widget.isAttached());
+ }
+
+ private Element ensureDiv() {
+ Document doc = Document.get();
+ Element div = doc.getElementById("wrapperDiv");
+ if (div == null) {
+ div = doc.createDivElement();
+ div.setId("wrapperDiv");
+ doc.getBody().appendChild(div);
+ }
+ return div;
+ }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/SimpleCheckBoxTest.java b/user/test/com/google/gwt/user/client/ui/SimpleCheckBoxTest.java
new file mode 100644
index 0000000..7df2ec6
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/SimpleCheckBoxTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests for {@link SimpleCheckBox}.
+ */
+public class SimpleCheckBoxTest extends GWTTestCase {
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.UserTest";
+ }
+
+ public void testProperties() {
+ SimpleCheckBox checkbox = new SimpleCheckBox();
+
+ checkbox.setName("myName");
+ assertEquals("myName", checkbox.getName());
+
+ checkbox.setTabIndex(42);
+ assertEquals(42, checkbox.getTabIndex());
+
+ checkbox.setEnabled(false);
+ assertEquals(false, checkbox.isEnabled());
+
+ // Test the 'checked' state across attachment and detachment
+ // (this value has a tendency to get lost on some browsers).
+ checkbox.setChecked(true);
+ assertEquals(true, checkbox.isChecked());
+
+ RootPanel.get().add(checkbox);
+ assertEquals(true, checkbox.isChecked());
+
+ RootPanel.get().remove(checkbox);
+ assertEquals(true, checkbox.isChecked());
+ }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/SimpleRadioButtonTest.java b/user/test/com/google/gwt/user/client/ui/SimpleRadioButtonTest.java
new file mode 100644
index 0000000..9910868
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/SimpleRadioButtonTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests for {@link SimpleRadioButton}.
+ */
+public class SimpleRadioButtonTest extends GWTTestCase {
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.UserTest";
+ }
+
+ public void testProperties() {
+ SimpleRadioButton radio = new SimpleRadioButton("myName");
+ assertEquals("myName", radio.getName());
+
+ radio.setTabIndex(42);
+ assertEquals(42, radio.getTabIndex());
+
+ radio.setEnabled(false);
+ assertEquals(false, radio.isEnabled());
+
+ // Test the 'checked' state across attachment and detachment
+ // (this value has a tendency to get lost on some browsers).
+ radio.setChecked(true);
+ assertEquals(true, radio.isChecked());
+
+ RootPanel.get().add(radio);
+ assertEquals(true, radio.isChecked());
+
+ RootPanel.get().remove(radio);
+ assertEquals(true, radio.isChecked());
+ }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/TabBarTest.java b/user/test/com/google/gwt/user/client/ui/TabBarTest.java
index a84c5e9..9b99077 100644
--- a/user/test/com/google/gwt/user/client/ui/TabBarTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TabBarTest.java
@@ -20,7 +20,7 @@
import com.google.gwt.user.client.Element;
/**
- * TODO: document me.
+ * Tests the {@link TabBar} widget.
*/
public class TabBarTest extends GWTTestCase {
int selected;
@@ -108,6 +108,32 @@
assertEquals("baz", bar.getTabHTML(1));
}
+ public void testSetTextAndHTML() {
+ final TabBar bar = createTabBar();
+ bar.addTab("foo");
+ bar.addTab("bar");
+ bar.addTab("baz");
+ bar.addTab(new Grid(0, 0));
+
+ bar.setTabText(1, "w00t");
+ assertEquals("w00t", bar.getTabHTML(1));
+
+ // toLowerCase() is necessary in these assertions because IE capitalizes
+ // HTML tags read from innerHTML.
+ bar.setTabHTML(1, "<i>w00t!</i>");
+ assertEquals("<i>w00t!</i>", bar.getTabHTML(1).toLowerCase());
+
+ // Set the text knowing that we currently have an HTML. This should replace
+ // the HTML with a Label.
+ bar.setTabText(1, "<b>w00t</b>");
+ assertEquals("<b>w00t</b>", bar.getTabHTML(1).toLowerCase());
+
+ // Set the text knowing that we currently have a Grid. This should replace
+ // the Grid with a Label.
+ bar.setTabText(3, "w00t");
+ assertEquals("w00t", bar.getTabHTML(3));
+ }
+
/**
* Verify that if a tab contains a widget, {@link TabBar#getTabHTML(int)}
* returns the HTML of the focusable element with the widget inside of it.
diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
index 8050a43..5915a67 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -21,11 +21,13 @@
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JRawType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefLoader;
import com.google.gwt.dev.javac.CompilationUnit;
@@ -46,8 +48,10 @@
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@@ -84,6 +88,11 @@
}
@Override
+ public int hashCode() {
+ return sourceName.hashCode() * (maybeInstantiated ? 17 : 43);
+ }
+
+ @Override
public String toString() {
return "{ " + sourceName + ", " + Boolean.toString(maybeInstantiated)
+ " }";
@@ -856,6 +865,85 @@
}
/**
+ * Test the situation where an abstract class has an unconstrained type
+ * parameter but all of its concrete subclasses add helpful constraints to it.
+ *
+ * @throws NotFoundException
+ * @throws UnableToCompleteException
+ */
+ public void testConcreteClassesConstrainATypeParameter()
+ throws NotFoundException, UnableToCompleteException {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ addStandardClasses(units);
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public abstract class Holder<T extends Serializable> implements Serializable {\n");
+ code.append(" T x;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Holder", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class StringHolder extends Holder<String> implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("StringHolder", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class DateHolder extends Holder<Date> implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("DateHolder", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class Date implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Date", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class UnrelatedClass implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("UnrelatedClass", code));
+ }
+
+ TreeLogger logger = createLogger();
+ TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+ JGenericType holder = to.getType("Holder").isGenericType();
+ JClassType stringHolder = to.getType("StringHolder");
+ JClassType dateHolder = to.getType("DateHolder");
+ JClassType unrelatedClass = to.getType("UnrelatedClass");
+
+ SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+ logger, to);
+ sob.addRootType(logger, holder.getRawType());
+ SerializableTypeOracle so = sob.build(logger);
+
+ JClassType string = to.getType(String.class.getCanonicalName());
+ JClassType date = to.getType("Date");
+
+ assertSerializableTypes(so, holder.getRawType(), stringHolder, dateHolder,
+ string, date);
+ assertFieldSerializable(so, holder.getRawType());
+ assertInstantiable(so, stringHolder);
+ assertInstantiable(so, dateHolder);
+ assertInstantiable(so, string);
+ assertInstantiable(so, date);
+ assertNotInstantiableOrFieldSerializable(so, unrelatedClass);
+ }
+
+ /**
* Tests that a method signature which returns an Array type also includes the
* possible covariant array types which could contain a serializable type.
*/
@@ -884,6 +972,162 @@
}
/**
+ * If the query type extends a raw type, be sure to pick up the parameters of
+ * the raw supertype.
+ *
+ * @throws UnableToCompleteException
+ * @throws NotFoundException
+ */
+ public void testExtensionFromRaw1() throws UnableToCompleteException,
+ NotFoundException {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ addStandardClasses(units);
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class HashSet<T extends SerClass> implements Serializable {\n");
+ code.append(" T[] x;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("HashSet", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class NameSet extends HashSet implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("NameSet", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class SerClass implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("SerClass", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class SerClassSub extends SerClass {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("SerClassSub", code));
+ }
+
+ TreeLogger logger = createLogger();
+ TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+ JGenericType hashSet = to.getType("HashSet").isGenericType();
+ JClassType nameSet = to.getType("NameSet");
+ JClassType serClass = to.getType("SerClass");
+ JClassType serClassSub = to.getType("SerClassSub");
+
+ SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+ logger, to);
+ sob.addRootType(logger, nameSet);
+ SerializableTypeOracle so = sob.build(logger);
+
+ JArrayType arrayOfSerClass = to.getArrayType(serClass);
+ JArrayType arrayOfSerClassSub = to.getArrayType(serClassSub);
+
+ assertSerializableTypes(so, hashSet.getRawType(), nameSet, serClass,
+ serClassSub, arrayOfSerClass, arrayOfSerClassSub);
+ assertFieldSerializable(so, hashSet.getRawType());
+ assertNotInstantiable(so, hashSet.getRawType());
+ assertInstantiable(so, nameSet);
+ assertInstantiable(so, serClass);
+ assertInstantiable(so, serClassSub);
+ assertInstantiable(so, arrayOfSerClass);
+ assertInstantiable(so, arrayOfSerClassSub);
+ }
+
+ /**
+ * If a subtype of a root type extends from the raw version of that root type,
+ * then when visiting the fields of the raw version, take advantage of
+ * information from the original root type.
+ *
+ * @throws UnableToCompleteException
+ * @throws NotFoundException
+ */
+ public void testExtensionFromRaw2() throws UnableToCompleteException,
+ NotFoundException {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ addStandardClasses(units);
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class HashSet<T extends Serializable> implements Serializable {\n");
+ code.append(" T[] x;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("HashSet", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class NameSet<T extends SerClass> extends HashSet implements Serializable {\n");
+ code.append(" T exposed;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("NameSet", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class SerClass implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("SerClass", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class SerClassSub extends SerClass {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("SerClassSub", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class UnrelatedClass implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("UnrelatedClass", code));
+ }
+
+ TreeLogger logger = createLogger();
+ TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+ JClassType string = to.getType(String.class.getCanonicalName());
+ JGenericType hashSet = to.getType("HashSet").isGenericType();
+ JGenericType nameSet = to.getType("NameSet").isGenericType();
+ JClassType unrelatedClass = to.getType("UnrelatedClass");
+ JClassType serClass = to.getType("SerClass");
+ JClassType serClassSub = to.getType("SerClassSub");
+ JParameterizedType hashSetOfString = to.getParameterizedType(hashSet,
+ makeArray(string));
+
+ SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+ logger, to);
+ sob.addRootType(logger, hashSetOfString);
+ SerializableTypeOracle so = sob.build(logger);
+
+ JArrayType arrayOfString = to.getArrayType(string);
+
+ assertSerializableTypes(so, hashSet.getRawType(), nameSet.getRawType(),
+ string, arrayOfString, serClass, serClassSub);
+ assertInstantiable(so, hashSet.getRawType());
+ assertInstantiable(so, nameSet.getRawType());
+ assertInstantiable(so, string);
+ assertInstantiable(so, serClass);
+ assertInstantiable(so, serClassSub);
+ assertNotInstantiable(so, unrelatedClass);
+ }
+
+ /**
* Expansion via parameterized types, where the type is exposed.
*/
public void testInfiniteParameterizedTypeExpansionCase1()
@@ -1134,6 +1378,131 @@
}
/**
+ * Tests that the type constrainer can accurately detect when an interface
+ * matches another type.
+ */
+ public void testNonOverlappingInterfaces() throws UnableToCompleteException,
+ NotFoundException {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ addStandardClasses(units);
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public interface Intf1 extends Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Intf1", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public interface Intf2 extends Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Intf2", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public interface Intf3 extends Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Intf3", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class Implements12 implements Intf1, Intf2 {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Implements12", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class ImplementsNeither implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("ImplementsNeither", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public interface List<T> extends Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("List", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class ListOfIntf1 implements List<Intf1> {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("ListOfIntf1", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class ListOfIntf2 implements List<Intf2> {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("ListOfIntf2", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class ListOfIntf3 implements List<Intf3> {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("ListOfIntf3", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class ListOfImplements12 implements List<Implements12> {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("ListOfImplements12", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class ListOfImplementsNeither implements List<ImplementsNeither> {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("ListOfImplementsNeither", code));
+ }
+
+ TreeLogger logger = createLogger();
+ TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+ JClassType intf1 = to.getType("Intf1");
+ JClassType intf2 = to.getType("Intf2");
+ JClassType intf3 = to.getType("Intf3");
+ JClassType implements12 = to.getType("Implements12");
+ JClassType implementsNeither = to.getType("ImplementsNeither");
+ JGenericType list = to.getType("List").isGenericType();
+ JClassType listOfIntf1 = to.getType("ListOfIntf1");
+ JClassType listOfIntf2 = to.getType("ListOfIntf2");
+ JClassType listOfIntf3 = to.getType("ListOfIntf3");
+ JClassType listOfImplements12 = to.getType("ListOfImplements12");
+ JClassType listOfImplementsNeither = to.getType("ListOfImplementsNeither");
+
+ TypeConstrainer typeConstrainer = new TypeConstrainer(to);
+ Map<JTypeParameter, JClassType> emptyConstraints = new HashMap<JTypeParameter, JClassType>();
+ assertTrue(typeConstrainer.typesMatch(intf1, intf2, emptyConstraints));
+ assertFalse(typeConstrainer.typesMatch(intf1, intf3, emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(implements12, intf1, emptyConstraints));
+ assertFalse(typeConstrainer.typesMatch(implements12, intf3,
+ emptyConstraints));
+ assertFalse(typeConstrainer.typesMatch(implementsNeither, intf1,
+ emptyConstraints));
+
+ JParameterizedType parameterizedListOfIntf1 = to.getParameterizedType(list,
+ makeArray(intf1));
+
+ SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+ logger, to);
+ sob.addRootType(logger, parameterizedListOfIntf1);
+ SerializableTypeOracle so = sob.build(logger);
+
+ assertSerializableTypes(so, listOfIntf1, listOfIntf2, listOfImplements12);
+ assertNotFieldSerializable(so, listOfIntf3);
+ assertNotFieldSerializable(so, listOfImplementsNeither);
+ }
+
+ /**
* Tests that a method signature which has no serializable types will result
* in a failure.
*/
@@ -1239,6 +1608,63 @@
}
/**
+ * Tests that adding a raw collection as a root type pulls in the world.
+ *
+ * @throws UnableToCompleteException
+ * @throws NotFoundException
+ */
+ public void testRawCollection() throws UnableToCompleteException,
+ NotFoundException {
+
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ addStandardClasses(units);
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("import java.util.Collection;\n");
+ code.append("public interface List<T> extends Serializable, Collection<T> {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("List", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("import java.util.Collection;\n");
+ code.append("public class LinkedList<T> implements List<T> {\n");
+ code.append(" T head;");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("LinkedList", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("import java.util.Collection;\n");
+ code.append("public class RandomClass implements Serializable {\n");
+ // TODO(mmendez): Lex, this should fail, but your fix will allow it if
+ // we get here from a raw collection.
+ // code.append(" Object obj;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("RandomClass", code));
+ }
+
+ TreeLogger logger = createLogger();
+ TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+ JGenericType list = to.getType("List").isGenericType();
+ JGenericType linkedList = to.getType("LinkedList").isGenericType();
+ JClassType randomClass = to.getType("RandomClass");
+
+ SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+ logger, to);
+ sob.addRootType(logger, list.getRawType());
+ SerializableTypeOracle so = sob.build(logger);
+
+ assertInstantiable(so, linkedList.getRawType());
+ assertInstantiable(so, randomClass);
+ }
+
+ /**
* Tests that raw type with type parameters that are instantiable are
* themselves instantiable.
*
@@ -1284,6 +1710,42 @@
assertSerializableTypes(so, rawA, serializableClass);
}
+ /**
+ * Tests that a type paramter that occurs within its bounds will not result in
+ * infinite recursion.
+ *
+ * @throws UnableToCompleteException
+ * @throws NotFoundException
+ */
+ public void testRootTypeParameterWithSelfInBounds()
+ throws UnableToCompleteException, NotFoundException {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ addStandardClasses(units);
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class A<Ta extends A<Ta>> implements Serializable {\n");
+ code.append(" Ta ta;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("A", code));
+ }
+
+ TreeLogger logger = createLogger();
+ TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+ JGenericType a = to.getType("A").isGenericType();
+ JClassType rawA = a.getRawType();
+ JTypeParameter ta = a.getTypeParameters()[0];
+
+ SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+ logger, to);
+ sob.addRootType(logger, ta);
+ SerializableTypeOracle so = sob.build(logger);
+
+ assertSerializableTypes(so, rawA);
+ }
+
/*
* Tests the isAssignable test for deciding whether a subclass should be
* pulled in.
@@ -1521,6 +1983,131 @@
}
/**
+ * Miscellaneous direct tests of {@link TypeConstrainer}.
+ *
+ * @throws UnableToCompleteException
+ * @throws NotFoundException
+ */
+ public void testTypeConstrainer() throws UnableToCompleteException,
+ NotFoundException {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ addStandardClasses(units);
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public interface Intf1 {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Intf1", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public interface Intf2 {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Intf2", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public interface Intf3 {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Intf3", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public class Implements12 implements Intf1, Intf2 {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Implements12", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public class ImplementsNeither {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("ImplementsNeither", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public class Sup {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Sup", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public class Sub extends Sup {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Sub", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public class Holder<T> {\n");
+ code.append(" T x;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Holder", code));
+ }
+ TreeLogger logger = createLogger();
+ TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+ JClassType intf1 = to.getType("Intf1");
+ JClassType intf2 = to.getType("Intf2");
+ JClassType intf3 = to.getType("Intf3");
+ JClassType implements12 = to.getType("Implements12");
+ JClassType implementsNeither = to.getType("ImplementsNeither");
+ JClassType string = to.getType(String.class.getCanonicalName());
+ JClassType sub = to.getType("Sub");
+ JClassType sup = to.getType("Sup");
+ JGenericType holder = to.getType("Holder").isGenericType();
+
+ JClassType arrayOfInt = to.getArrayType(JPrimitiveType.INT);
+ JClassType arrayOfFloat = to.getArrayType(JPrimitiveType.FLOAT);
+ JClassType arrayOfString = to.getArrayType(string);
+ JClassType arrayOfWildExtString = to.getArrayType(to.getWildcardType(
+ BoundType.EXTENDS, string));
+ JClassType holderOfString = to.getParameterizedType(holder,
+ makeArray(to.getWildcardType(BoundType.EXTENDS, string)));
+ JClassType holderOfSub = to.getParameterizedType(holder,
+ makeArray(to.getWildcardType(BoundType.EXTENDS, sub)));
+ JClassType holderOfSup = to.getParameterizedType(holder,
+ makeArray(to.getWildcardType(BoundType.EXTENDS, sup)));
+
+ TypeConstrainer typeConstrainer = new TypeConstrainer(to);
+ Map<JTypeParameter, JClassType> emptyConstraints = new HashMap<JTypeParameter, JClassType>();
+
+ assertTrue(typeConstrainer.typesMatch(intf1, intf2, emptyConstraints));
+ assertFalse(typeConstrainer.typesMatch(intf1, intf3, emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(implements12, intf1, emptyConstraints));
+ assertFalse(typeConstrainer.typesMatch(implements12, intf3,
+ emptyConstraints));
+ assertFalse(typeConstrainer.typesMatch(implementsNeither, intf1,
+ emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(to.getJavaLangObject(),
+ arrayOfString, emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(arrayOfString,
+ to.getJavaLangObject(), emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(sub, sup, emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(sub, sub, emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(arrayOfFloat, arrayOfFloat,
+ emptyConstraints));
+ assertFalse(typeConstrainer.typesMatch(arrayOfFloat, arrayOfInt,
+ emptyConstraints));
+ assertFalse(typeConstrainer.typesMatch(arrayOfFloat, arrayOfString,
+ emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(arrayOfString, arrayOfString,
+ emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(arrayOfString, arrayOfWildExtString,
+ emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(holderOfSub, holderOfSup,
+ emptyConstraints));
+ assertFalse(typeConstrainer.typesMatch(holderOfSub, holderOfString,
+ emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(holder.getRawType(), holderOfSub,
+ emptyConstraints));
+ assertTrue(typeConstrainer.typesMatch(holderOfSub, holder.getRawType(),
+ emptyConstraints));
+ assertFalse(typeConstrainer.typesMatch(holder.getRawType(), intf1,
+ emptyConstraints));
+
+ assertTrue(emptyConstraints.isEmpty());
+ }
+
+ /**
* Tests root types that have type parameters.
*
* @throws UnableToCompleteException
@@ -1556,6 +2143,9 @@
JClassType b = to.getType("B");
JTypeParameter syntheticTypeParam = new JTypeParameter("U", 0);
+ // Force the type parameter to have a declaring class
+ JClassType mockType = new JGenericType(to, a.getPackage(), null, false,
+ "C", false, new JTypeParameter[] {syntheticTypeParam});
syntheticTypeParam.setBounds(makeArray(b));
JParameterizedType parameterizedType = to.getParameterizedType(a,