Adds the remaining (and more complex) Java 7 new language features.
Adds the remaining Java 7 new language features: namely, multiexception catch
and try-with-resources.
Fixes issue 7999, issue 6960.
Change-Id: If973b8c847d1202aca794221f32ae4b33b616f9c
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
index 6c12533..d149f4f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
@@ -50,7 +50,7 @@
/**
* Construct a bare-bones deserialized external class.
*/
- private JClassType(String name) {
+ JClassType(String name) {
super(SourceOrigin.UNKNOWN, name);
isAbstract = false;
setExternal(true);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
index dede802..f11627e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -47,7 +47,7 @@
}
private Object readResolve() {
- return new JMethod(signature, enclosingType);
+ return new JMethod(signature, enclosingType, false);
}
}
@@ -141,15 +141,30 @@
}
/**
+ * Creates an externalized representation for a method that needs to be resolved.
+ * Useful to refer to methods of magic classes during GwtAstBuilder execution.
+ *
+ * @param fullClassName the class where the method is defined.
+ * @param signature the signature of the method (including its name).
+ *
+ */
+ public static JMethod getExternalizedMethod(String fullClassName, String signature,
+ boolean isStatic) {
+
+ JClassType cls = new JClassType(fullClassName);
+ return new JMethod(signature, cls, isStatic);
+ }
+
+ /**
* Construct a bare-bones deserialized external method.
*/
- private JMethod(String signature, JDeclaredType enclosingType) {
+ private JMethod(String signature, JDeclaredType enclosingType, boolean isStatic) {
super(SourceOrigin.UNKNOWN);
this.name = signature.substring(0, signature.indexOf('('));
this.enclosingType = enclosingType;
this.signature = signature;
this.isAbstract = false;
- this.isStatic = false;
+ this.isStatic = isStatic;
this.access = AccessModifier.PUBLIC.ordinal();
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java
index 9afa7be..ee89165 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java
@@ -100,7 +100,8 @@
* allows us to preserve type information during the latter phases of
* compilation.
*/
- public JMethodCall(SourceInfo info, JExpression instance, JMethod method, JType overrideReturnType) {
+ public JMethodCall(SourceInfo info, JExpression instance, JMethod method,
+ JType overrideReturnType) {
super(info);
assert (method != null);
assert (instance != null || method.isStatic());
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTryStatement.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTryStatement.java
index 1f2d0ca..1c2a707 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTryStatement.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTryStatement.java
@@ -17,6 +17,7 @@
import com.google.gwt.dev.jjs.SourceInfo;
+import java.io.Serializable;
import java.util.List;
/**
@@ -24,27 +25,58 @@
*/
public class JTryStatement extends JStatement {
- private final List<JLocalRef> catchArgs;
- private final List<JBlock> catchBlocks;
+ /**
+ * Represents the catch clause parts of the try statement.
+ */
+ public static class CatchClause implements Serializable {
+ private final List<JType> catchTypes;
+ private final JLocalRef arg;
+ private final JBlock block;
+
+ public CatchClause(List<JType> catchTypes, JLocalRef arg, JBlock block) {
+ this.catchTypes = catchTypes;
+ this.arg = arg;
+ this.block = block;
+ }
+
+ public List<JType> getTypes() {
+ return catchTypes;
+ }
+
+ public JLocalRef getArg() {
+ return arg;
+ }
+
+ public JBlock getBlock() {
+ return block;
+ }
+ }
+
+ private final List<CatchClause> catchClauses;
private final JBlock finallyBlock;
private final JBlock tryBlock;
- public JTryStatement(SourceInfo info, JBlock tryBlock, List<JLocalRef> catchArgs,
- List<JBlock> catchBlocks, JBlock finallyBlock) {
+ /**
+ * Construct a Java try statement.
+ *
+ * Parameters catchTypes, catchArgs and catchBlocks must agree on size. Each element of each
+ * of these lists corresponds to a catch statement.
+ *
+ * @param info the source information.
+ * @param tryBlock the statement block inside the try construct.
+ * @param catchClauses each element of this list contains a catch clause.
+ * @param finallyBlock the statement block corresponding to the finally construct.
+ */
+ public JTryStatement(SourceInfo info, JBlock tryBlock, List<CatchClause> catchClauses,
+ JBlock finallyBlock) {
super(info);
- assert (catchArgs.size() == catchBlocks.size());
this.tryBlock = tryBlock;
- this.catchArgs = catchArgs;
- this.catchBlocks = catchBlocks;
+ this.catchClauses = catchClauses;
this.finallyBlock = finallyBlock;
}
- public List<JLocalRef> getCatchArgs() {
- return catchArgs;
- }
-
- public List<JBlock> getCatchBlocks() {
- return catchBlocks;
+ public List<CatchClause> getCatchClauses() {
+ return catchClauses;
}
public JBlock getFinallyBlock() {
@@ -58,8 +90,11 @@
public void traverse(JVisitor visitor, Context ctx) {
if (visitor.visit(this, ctx)) {
visitor.accept(tryBlock);
- visitor.accept(catchArgs);
- visitor.accept(catchBlocks);
+
+ for (CatchClause clause : catchClauses) {
+ visitor.accept(clause.getArg());
+ visitor.accept(clause.getBlock());
+ }
// TODO: normalize this so it's never null?
if (finallyBlock != null) {
visitor.accept(finallyBlock);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java
index c055674..f903e8b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java
@@ -17,6 +17,8 @@
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JBinaryOperator;
import com.google.gwt.dev.jjs.ast.JBlock;
import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
import com.google.gwt.dev.jjs.ast.JExpression;
@@ -28,11 +30,13 @@
import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JStatement;
import com.google.gwt.dev.jjs.ast.JThrowStatement;
import com.google.gwt.dev.jjs.ast.JTryStatement;
+import com.google.gwt.dev.jjs.ast.JType;
import java.util.ArrayList;
import java.util.List;
@@ -58,11 +62,11 @@
// @Override
@Override
public void endVisit(JTryStatement x, Context ctx) {
- if (x.getCatchBlocks().isEmpty()) {
+ if (x.getCatchClauses().isEmpty()) {
return;
}
- SourceInfo catchInfo = x.getCatchBlocks().get(0).getSourceInfo();
+ SourceInfo catchInfo = x.getCatchClauses().get(0).getBlock().getSourceInfo();
JLocal exVar = popTempLocal();
JBlock newCatchBlock = new JBlock(catchInfo);
@@ -77,19 +81,34 @@
/*
* Build up a series of if, else if statements to test the type of the
- * exception object against the type of the user's catch block.
+ * exception object against the types of the user's catch block. Each catch block might have
+ * multiple types in Java 7.
*
* Go backwards so we can nest the else statements in the correct order!
*/
- // rethrow the current exception if no one caught it
+ // rethrow the current exception if no one caught it.
JStatement cur = new JThrowStatement(catchInfo, new JLocalRef(catchInfo, exVar));
- for (int i = x.getCatchBlocks().size() - 1; i >= 0; --i) {
- JBlock block = x.getCatchBlocks().get(i);
- JLocalRef arg = x.getCatchArgs().get(i);
+ for (int i = x.getCatchClauses().size() - 1; i >= 0; i--) {
+ JTryStatement.CatchClause clause = x.getCatchClauses().get(i);
+ JBlock block = clause.getBlock();
+ JLocalRef arg = clause.getArg();
+ List<JType> exceptionsTypes = clause.getTypes();
catchInfo = block.getSourceInfo();
- JReferenceType argType = (JReferenceType) arg.getType();
- // if ($e instanceof ArgType) { var userVar = $e; <user code> }
- JExpression ifTest = new JInstanceOf(catchInfo, argType, new JLocalRef(catchInfo, exVar));
+
+ // if ($e instanceof ArgType1 or $e instanceof ArgType2 ...) {
+ // var userVar = $e; <user code>
+ // }
+
+ // Handle the first Exception type.
+ JExpression ifTest = new JInstanceOf(catchInfo, (JReferenceType) exceptionsTypes.get(0),
+ new JLocalRef(catchInfo, exVar));
+ // Handle the rest of the Exception types if any.
+ for (int j = 1; j < exceptionsTypes.size(); j++) {
+ JExpression orExp = new JInstanceOf(catchInfo, (JReferenceType) exceptionsTypes.get(j),
+ new JLocalRef(catchInfo, exVar));
+ ifTest = new JBinaryOperation(catchInfo, JPrimitiveType.BOOLEAN, JBinaryOperator.OR,
+ ifTest, orExp);
+ }
JDeclarationStatement declaration =
new JDeclarationStatement(catchInfo, arg, new JLocalRef(catchInfo, exVar));
block.addStmt(0, declaration);
@@ -98,10 +117,13 @@
}
newCatchBlock.addStmt(cur);
- x.getCatchArgs().clear();
- x.getCatchArgs().add(new JLocalRef(newCatchBlock.getSourceInfo(), exVar));
- x.getCatchBlocks().clear();
- x.getCatchBlocks().add(newCatchBlock);
+
+ // Replace with a single catch block.
+ x.getCatchClauses().clear();
+ List<JType> newCatchTypes = new ArrayList<JType>(1);
+ newCatchTypes.add(exVar.getType());
+ x.getCatchClauses().add(new JTryStatement.CatchClause(newCatchTypes,
+ new JLocalRef(newCatchBlock.getSourceInfo(), exVar), newCatchBlock));
}
// @Override
@@ -115,7 +137,7 @@
// @Override
@Override
public boolean visit(JTryStatement x, Context ctx) {
- if (!x.getCatchBlocks().isEmpty()) {
+ if (!x.getCatchClauses().isEmpty()) {
pushTempLocal(x.getSourceInfo());
}
return true;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index 0907c57..2c778cf 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -579,24 +579,31 @@
@Override
public void endVisit(JTryStatement x, Context ctx) {
// 1) Remove catch blocks whose exception type is not instantiable.
- List<JLocalRef> catchArgs = x.getCatchArgs();
- List<JBlock> catchBlocks = x.getCatchBlocks();
- Iterator<JLocalRef> itA = catchArgs.iterator();
- Iterator<JBlock> itB = catchBlocks.iterator();
- while (itA.hasNext()) {
- JLocalRef localRef = itA.next();
- itB.next();
- JReferenceType type = (JReferenceType) localRef.getType();
- if (!program.typeOracle.isInstantiatedType(type) || type == program.getTypeNull()) {
- itA.remove();
- itB.remove();
+ List<JTryStatement.CatchClause> catchClauses = x.getCatchClauses();
+
+ Iterator<JTryStatement.CatchClause> itClauses = catchClauses.iterator();
+ while (itClauses.hasNext()) {
+ JTryStatement.CatchClause clause = itClauses.next();
+ // Go over the types in the multiexception and remove the ones that are not instantiable.
+ Iterator<JType> itTypes = clause.getTypes().iterator();
+ while (itTypes.hasNext()) {
+ JReferenceType type = (JReferenceType) itTypes.next();
+ if (!program.typeOracle.isInstantiatedType(type) || type == program.getTypeNull()) {
+ itTypes.remove();
+ madeChanges();
+ }
+ }
+
+ // if all exception types are gone then remove whole clause.
+ if (clause.getTypes().isEmpty()) {
+ itClauses.remove();
madeChanges();
}
}
// Compute properties regarding the state of this try statement
boolean noTry = Simplifier.isEmpty(x.getTryBlock());
- boolean noCatch = catchArgs.size() == 0;
+ boolean noCatch = catchClauses.size() == 0;
boolean noFinally = Simplifier.isEmpty(x.getFinallyBlock());
if (noTry) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 81e13ee..c0e1423 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -432,12 +432,9 @@
@Override
public boolean visit(JTryStatement x, Context ctx) {
accept(x.getTryBlock());
-
- List<JLocalRef> catchArgs = x.getCatchArgs();
- List<JBlock> catchBlocks = x.getCatchBlocks();
- for (int i = 0, c = catchArgs.size(); i < c; ++i) {
- JLocalRef arg = catchArgs.get(i);
- JBlock catchBlock = catchBlocks.get(i);
+ for (JTryStatement.CatchClause clause : x.getCatchClauses()) {
+ JLocalRef arg = clause.getArg();
+ JBlock catchBlock = clause.getBlock();
JsCatch jsCatch = new JsCatch(x.getSourceInfo(), peek(), arg.getTarget().getName());
JsParameter jsParam = jsCatch.getParameter();
names.put(arg.getTarget(), jsParam.getName());
@@ -1466,12 +1463,12 @@
}
}
- int size = x.getCatchArgs().size();
- assert (size < 2 && size == x.getCatchBlocks().size());
+ int size = x.getCatchClauses().size();
+ assert (size < 2);
if (size == 1) {
JsBlock catchBlock = (JsBlock) pop(); // catchBlocks
pop(); // catchArgs
- JsCatch jsCatch = catchMap.get(x.getCatchBlocks().get(0));
+ JsCatch jsCatch = catchMap.get(x.getCatchClauses().get(0).getBlock());
jsCatch.setBody(catchBlock);
jsTry.getCatches().add(jsCatch);
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
index c6c75e5..efa5c90 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -177,7 +177,9 @@
import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.UnaryExpression;
+import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference;
import org.eclipse.jdt.internal.compiler.ast.WhileStatement;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
@@ -286,7 +288,8 @@
}
}
- private void processClassLiteral(JsNameRef nameRef, SourceInfo info, JType type, JsContext ctx) {
+ private void processClassLiteral(JsNameRef nameRef, SourceInfo info, JType type,
+ JsContext ctx) {
assert !ctx.isLvalue();
JsniClassLiteral classLiteral = new JsniClassLiteral(info, nameRef.getIdent(), type);
nativeMethodBody.addClassRef(classLiteral);
@@ -1423,19 +1426,162 @@
List<JBlock> catchBlocks = pop(x.catchBlocks);
JBlock tryBlock = pop(x.tryBlock);
- List<JLocalRef> catchArgs = new ArrayList<JLocalRef>();
+ if (x.resources.length > 0) {
+ tryBlock = normalizeTryWithResources(info, x, tryBlock, scope);
+ }
+ List<JTryStatement.CatchClause> catchClauses =
+ new ArrayList<JTryStatement.CatchClause>();
if (x.catchBlocks != null) {
- for (Argument argument : x.catchArguments) {
+ for (int i = 0; i < x.catchArguments.length; i++) {
+ Argument argument = x.catchArguments[i];
JLocal local = (JLocal) curMethod.locals.get(argument.binding);
- catchArgs.add(new JLocalRef(info, local));
+
+ List<JType> catchTypes = new ArrayList<JType>();
+ if (argument.type instanceof UnionTypeReference) {
+ // This is a multiexception
+ for (TypeReference type : ((UnionTypeReference) argument.type).typeReferences) {
+ catchTypes.add(typeMap.get(type.resolvedType));
+ }
+ } else {
+ // Regular exception
+ catchTypes.add(local.getType());
+ }
+ catchClauses.add(new JTryStatement.CatchClause(catchTypes, new JLocalRef(info, local),
+ catchBlocks.get(i)));
}
}
- push(new JTryStatement(info, tryBlock, catchArgs, catchBlocks, finallyBlock));
+ push(new JTryStatement(info, tryBlock, catchClauses, finallyBlock));
} catch (Throwable e) {
throw translateException(x, e);
}
}
+ private JBlock normalizeTryWithResources(SourceInfo info, TryStatement x, JBlock tryBlock,
+ BlockScope scope) {
+ /**
+ * Apply the following source transformation:
+ *
+ * try (A1 a1 = new A1(); ... ; An an = new An()) {
+ * ... tryBlock...
+ * } ...catch/finally blocks
+ *
+ * to
+ *
+ * try {
+ * A1 a1 = new A1();... ; An an = new An();
+ * Throwable $exception = null;
+ * try {
+ * ... tryBlock...
+ * } catch (Throwable t) {
+ * $exception = t;
+ * throw t;
+ * } finally {
+ * $exception = Exceptions.safeClose(an, $exception);
+ * ...
+ * $exception = Exceptions.safeClose(a1, $exception);
+ * if ($exception != null) {
+ * throw $exception;
+ * }
+ * } ...catch/finally blocks
+ *
+ */
+
+ JBlock innerBlock = new JBlock(info);
+ // add resource variables
+ List<JLocal> resourceVariables = new ArrayList<JLocal>();
+ for (int i = x.resources.length - 1; i >= 0; i--) {
+ // Needs to iterate back to front to be inline with the contents of the stack.
+
+ JDeclarationStatement resourceDecl = pop(x.resources[i]);
+
+ JLocal resourceVar = (JLocal) curMethod.locals.get(x.resources[i].binding);
+ resourceVariables.add(0, resourceVar);
+ innerBlock.addStmt(0, resourceDecl);
+ }
+
+ // add exception variable
+ JLocal exceptionVar =
+ createTempLocal(info, "$primary_ex", javaLangThrowable, false, curMethod.body);
+
+ innerBlock.addStmt(makeDeclaration(info, exceptionVar, JNullLiteral.INSTANCE));
+
+ // create catch block
+ List<JTryStatement.CatchClause> catchClauses = new ArrayList<JTryStatement.CatchClause>(1);
+
+ List<JType> clauseTypes = new ArrayList<JType>(1);
+ clauseTypes.add(javaLangThrowable);
+
+ // add catch exception variable.
+ JLocal catchVar =
+ createTempLocal(info, "$caught_ex", javaLangThrowable, false, curMethod.body);
+
+ JBlock catchBlock = new JBlock(info);
+ catchBlock.addStmt(createAssignment(info, javaLangThrowable, exceptionVar, catchVar));
+ catchBlock.addStmt(new JThrowStatement(info, new JLocalRef(info, exceptionVar)));
+
+ catchClauses.add(new JTryStatement.CatchClause(clauseTypes, new JLocalRef(info, catchVar),
+ catchBlock));
+
+ // create finally block
+ JBlock finallyBlock = new JBlock(info);
+ for (int i = x.resources.length - 1; i >= 0; i--) {
+ finallyBlock.addStmt(createCloseBlockFor(info, x.resources[i],
+ resourceVariables.get(i), exceptionVar, scope));
+ }
+
+ // if (exception != null) throw exception
+ JExpression exceptionNotNull = new JBinaryOperation(info, JPrimitiveType.BOOLEAN,
+ JBinaryOperator.NEQ, new JLocalRef(info, exceptionVar), JNullLiteral.INSTANCE);
+ finallyBlock.addStmt(new JIfStatement(info, exceptionNotNull,
+ new JThrowStatement(info, new JLocalRef(info, exceptionVar)), null));
+
+ // Stitch all together into a inner try block
+ innerBlock.addStmt(new JTryStatement(info, tryBlock, catchClauses,
+ finallyBlock));
+ return innerBlock;
+ }
+
+ private JLocal createTempLocal(SourceInfo info, String prefix, JType type, boolean isFinal,
+ JMethodBody enclosingMethodBody) {
+ int index = curMethod.body.getLocals().size() + 1;
+ return JProgram.createLocal(info, prefix + "_" + index,
+ javaLangThrowable, false, curMethod.body);
+ }
+
+ private JStatement createCloseBlockFor(final SourceInfo info, final LocalDeclaration resource,
+ JLocal resourceVar, JLocal exceptionVar, BlockScope scope) {
+ /**
+ * Create the following code:
+ *
+ * $ex = Exceptions.safeClose(resource, $ex);
+ *
+ * which is equivalent to
+ *
+ * if (resource != null) {
+ * try {
+ * resource.close();
+ * } catch (Throwable t) {
+ * if ($ex == null) {
+ * $ex = t;
+ * } else {
+ * $ex.addSuppressed(t);
+ * }
+ * }
+ */
+
+ JMethodCall safeCloseCall = new JMethodCall(info, null, SAFE_CLOSE_METHOD);
+ safeCloseCall.addArg(0, new JLocalRef(info, resourceVar));
+ safeCloseCall.addArg(1, new JLocalRef(info, exceptionVar));
+
+ return new JBinaryOperation(info, javaLangThrowable, JBinaryOperator.ASG, new JLocalRef(info,
+ exceptionVar), safeCloseCall).makeStatement();
+ }
+
+ private JStatement createAssignment(SourceInfo info, JType type, JLocal lhs, JLocal rhs) {
+ return new JBinaryOperation(info, type, JBinaryOperator.ASG, new JLocalRef(info, lhs),
+ new JLocalRef(info, rhs)).makeStatement();
+ }
+
@Override
public void endVisit(TypeDeclaration x, ClassScope scope) {
endVisit(x);
@@ -2784,6 +2930,8 @@
private static final char[] VALUE = "Value".toCharArray();
private static final char[] VALUE_OF = "valueOf".toCharArray();
private static final char[] VALUES = "values".toCharArray();
+ private static final char[] CLOSE = "close".toCharArray();
+ private static final char[] ADDSUPRESSED = "addSuppressed".toCharArray();
static {
InternalCompilerException.preload();
@@ -2896,6 +3044,8 @@
JClassType javaLangString = null;
+ JClassType javaLangThrowable = null;
+
Map<MethodDeclaration, JsniMethod> jsniMethods;
Map<String, Binding> jsniRefs;
@@ -2909,6 +3059,16 @@
private String sourceMapPath;
/**
+ * Externalized class and method form for Exceptions.safeClose() to provide support
+ * for try-with-resources.
+ *
+ * The externalized form will be resolved during AST stitching.
+ */
+ static JMethod SAFE_CLOSE_METHOD = JMethod.getExternalizedMethod("com.google.gwt.lang.Exceptions",
+ "safeClose(Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)Ljava/lang/Throwable;", true);
+
+
+ /**
* Builds all the GWT AST nodes that correspond to one Java source file.
*
* @param cud The compiled form of the Java source from the JDT.
@@ -2939,6 +3099,7 @@
javaLangObject = (JClassType) typeMap.get(cud.scope.getJavaLangObject());
javaLangString = (JClassType) typeMap.get(cud.scope.getJavaLangString());
javaLangClass = (JClassType) typeMap.get(cud.scope.getJavaLangClass());
+ javaLangThrowable = (JClassType) typeMap.get(cud.scope.getJavaLangThrowable());
for (TypeDeclaration typeDecl : cud.types) {
// Resolve super type / interface relationships.
@@ -2964,7 +3125,7 @@
javaLangObject = null;
javaLangString = null;
javaLangClass = null;
-
+ javaLangThrowable = null;
return result;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java
index 691752a..d2c69de 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java
@@ -132,6 +132,7 @@
protected static final char[] CHARS_NATIVE = "native ".toCharArray();
protected static final char[] CHARS_NEW = "new ".toCharArray();
protected static final char[] CHARS_NULL = "null".toCharArray();
+ protected static final char[] CHARS_PIPE = " | ".toCharArray();
protected static final char[] CHARS_PRIVATE = "private ".toCharArray();
protected static final char[] CHARS_PROTECTED = "protected ".toCharArray();
protected static final char[] CHARS_PUBLIC = "public ".toCharArray();
@@ -887,15 +888,22 @@
public boolean visit(JTryStatement x, Context ctx) {
print(CHARS_TRY);
accept(x.getTryBlock());
- for (int i = 0, c = x.getCatchArgs().size(); i < c; ++i) {
+ for (JTryStatement.CatchClause clause : x.getCatchClauses()) {
print(CHARS_CATCH);
lparen();
- JLocalRef localRef = x.getCatchArgs().get(i);
- accept(localRef.getTarget());
+
+ Iterator<JType> it = clause.getTypes().iterator();
+ printTypeName(it.next());
+ while (it.hasNext()) {
+ print(CHARS_PIPE);
+ printTypeName(it.next());
+ }
+ space();
+
+ printName(clause.getArg().getTarget());
rparen();
space();
- JBlock block = x.getCatchBlocks().get(i);
- accept(block);
+ accept(clause.getBlock());
}
if (x.getFinallyBlock() != null) {
print(CHARS_FINALLY);
@@ -1150,11 +1158,15 @@
}
protected void visitCollectionWithCommas(Iterator<? extends JNode> iter) {
+ visitCollectionWith(CHARS_COMMA, iter);
+ }
+
+ protected void visitCollectionWith(char[] ch, Iterator<? extends JNode> iter) {
if (iter.hasNext()) {
accept(iter.next());
}
while (iter.hasNext()) {
- print(CHARS_COMMA);
+ print(ch);
accept(iter.next());
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
index ae6205c..5ac6ea7 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
@@ -34,7 +34,6 @@
import com.google.gwt.dev.jjs.ast.JInstanceOf;
import com.google.gwt.dev.jjs.ast.JInterfaceType;
import com.google.gwt.dev.jjs.ast.JLocal;
-import com.google.gwt.dev.jjs.ast.JLocalRef;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JModVisitor;
@@ -291,8 +290,8 @@
public void endVisit(JTryStatement x, Context ctx) {
// Never tighten args to catch blocks
// Fake an assignment-to-self to prevent tightening
- for (JLocalRef arg : x.getCatchArgs()) {
- addAssignment(arg.getTarget(), arg);
+ for (JTryStatement.CatchClause clause : x.getCatchClauses()) {
+ addAssignment(clause.getArg().getTarget(), clause.getArg());
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
index 5aa134c..346eff1 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
@@ -61,6 +61,7 @@
import com.google.gwt.dev.jjs.ast.JReturnStatement;
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JThisRef;
+import com.google.gwt.dev.jjs.ast.JTryStatement;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JVariable;
import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
@@ -318,6 +319,20 @@
}
@Override
+ public void endVisit(JTryStatement x, Context ctx) {
+ // Needs to resolve the Exceptions Types explicitly they are multiple in Java 7 and
+ // potentially different from the one in the exception variable.
+ for (JTryStatement.CatchClause clause : x.getCatchClauses()) {
+ List<JType> types = clause.getTypes();
+ for (int i = 0; i < types.size(); i++) {
+ JReferenceType resolvedType = translate((JReferenceType) types.get(i));
+ assert resolvedType.replaces(types.get(i));
+ types.set(i, resolvedType);
+ }
+ }
+ }
+
+ @Override
public void endVisit(JVariable x, Context ctx) {
x.setType(translate(x.getType()));
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java
index f643eb7..9b7f6d3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java
@@ -763,7 +763,8 @@
List<Integer> catchBlockPos = new ArrayList<Integer>();
List<List<Exit>> catchExits = new ArrayList<List<Exit>>();
- for (JBlock b : x.getCatchBlocks()) {
+ for (JTryStatement.CatchClause clause : x.getCatchClauses()) {
+ JBlock b = clause.getBlock();
catchBlockPos.add(nodes.size());
accept(b);
catchExits.add(removeCurrentExits());
@@ -790,12 +791,16 @@
if (e.isThrow()) {
// If execution of the try block completes abruptly because of a
// throw of a value V, then there is a choice:
- nextCatchBlock : for (int i = 0; i < x.getCatchArgs().size(); ++i) {
+ nextCatchBlock : for (int i = 0; i < x.getCatchClauses().size(); ++i) {
// If the run-time type of V is assignable (�5.2) to the
// Parameter of any catch clause of the try statement, then
// the first (leftmost) such catch clause is selected.
- JClassType catchType =
- (JClassType) x.getCatchArgs().get(i).getType();
+
+ // TODO(rluble): we are safely overapproximating the exception
+ // caught in this block by the type of the exception variable.
+ // We could do better in multiexceptions.
+ JClassType catchType =
+ (JClassType) x.getCatchClauses().get(i).getArg().getType();
JType exceptionType = e.getExceptionType();
boolean canCatch = false;
@@ -806,7 +811,12 @@
// Catch clause fully covers exception type. We'll land
// here for sure.
canCatch = true;
- fullCatch = true;
+ // Safe approximation. If it is a multi exception with only one
+ // exception declared in the clause then the variable is of the
+ // the same type as the exception declared and the approximation
+ // is exact hence and this is a full catch.
+ fullCatch = x.getCatchClauses().get(i).getTypes().size() == 1 &&
+ x.getCatchClauses().get(i).getTypes().get(0) == catchType;
} else if (typeOracle.canTriviallyCast(catchType, exceptionType)) {
// We can land here if we throw some subclass of
// exceptionType
@@ -864,12 +874,16 @@
// If execution of the try block completes abruptly because of a
// throw of a value V, then there is a choice:
- nextCatchBlock : for (int i = 0; i < x.getCatchArgs().size(); ++i) {
+ nextCatchBlock : for (int i = 0; i < x.getCatchClauses().size(); ++i) {
// If the run-time type of V is assignable to the parameter of any
// catch clause of the try statement, then the first
// (leftmost) such catch clause is selected.
- JClassType catchType =
- (JClassType) x.getCatchArgs().get(i).getType();
+
+ // TODO(rluble): we are safely overapproximating the exception
+ // caught in this block by the type of the exception variable.
+ // We could do better in multiexceptions.
+ JClassType catchType =
+ (JClassType) x.getCatchClauses().get(i).getArg().getType();
JType exceptionType = e.getExceptionType();
boolean canCatch = false;
@@ -880,7 +894,12 @@
// Catch clause fully covers exception type. We'll land
// here for sure.
canCatch = true;
- fullCatch = true;
+ // Safe approximation. If it is a multi exception with only one
+ // exception declared in the clause then the variable is of the
+ // the same type as the exception declared and the approximation
+ // is exact hence and this is a full catch.
+ fullCatch = x.getCatchClauses().get(i).getTypes().size() == 1 &&
+ x.getCatchClauses().get(i).getTypes().get(0) == catchType;
} else if (typeOracle.canTriviallyCast(catchType, exceptionType)) {
// We can land here if we throw some subclass of
// exceptionType
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java
index c437453..09e4bab 100644
--- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java
+++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java
@@ -65,5 +65,29 @@
static boolean throwAssertionError_Object(Object message) {
throw new AssertionError(message);
}
+
+ /**
+ * Use by the try-with-resources construct. Look at
+ * {@link com.google.gwt.dev.jjs.impl.GwtAstBuilder.createCloseBlockFor}.
+ *
+ * @param resource a resource implementing the AutoCloseable interface.
+ * @param mainException an exception being propagated.
+ * @return an exception to propagate or {@code null} if none.
+ */
+ static Throwable safeClose(AutoCloseable resource, Throwable mainException) {
+ if (resource == null) {
+ return mainException;
+ }
+
+ try {
+ resource.close();
+ } catch (Throwable e) {
+ if (mainException == null) {
+ return e;
+ }
+ mainException.addSuppressed(e);
+ }
+ return mainException;
+ }
// CHECKSTYLE_ON
}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
index 747ff4a..a17a51e 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
@@ -255,12 +255,29 @@
}
});
+ sourceOracle.addOrReplace(new MockJavaResource("java.lang.AutoCloseable") {
+ @Override
+ public CharSequence getContent() {
+ return ""
+ + "package java.lang;"
+ + "public interface AutoCloseable { "
+ + " void close() throws Exception;"
+ + "}";
+ }
+ });
+
sourceOracle.addOrReplace(new MockJavaResource("com.google.gwt.lang.Exceptions") {
@Override
public CharSequence getContent() {
- return "package com.google.gwt.lang;" +
- "public class Exceptions { static boolean throwAssertionError() { throw new RuntimeException(); } }";
- }
+ return ""
+ + "package com.google.gwt.lang;"
+ + "public class Exceptions { "
+ + " static boolean throwAssertionError() { throw new RuntimeException(); }"
+ + " static Throwable safeClose(AutoCloseable resource, Throwable mainException) {"
+ + " return mainException;"
+ + " } "
+ + "}";
+ }
});
sourceOracle.addOrReplace(new MockJavaResource("java.lang.String") {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java
index 5e261ea..3ed0dbf 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java
@@ -32,10 +32,14 @@
*/
public class Java7AstTest extends JJSTestBase {
+ // TODO(rluble): add similar tests to ensure that the AST construction is correct for all types
+ // of nodes.
@Override
public void setUp() {
sourceLevel = SourceLevel.JAVA7;
- addAll(Java7MockResources.LIST_T, Java7MockResources.ARRAYLIST_T);
+ addAll(Java7MockResources.LIST_T, Java7MockResources.ARRAYLIST_T,
+ JavaResourceBase.AUTOCLOSEABLE, Java7MockResources.TEST_RESOURCE,
+ Java7MockResources.EXCEPTION1, Java7MockResources.EXCEPTION2);
}
public void testCompileNewStyleLiterals() throws Exception {
@@ -115,5 +119,4 @@
JMethodBody body = (JMethodBody) mainMethod.getBody();
return body.getBlock();
}
-
}
diff --git a/user/src/com/google/gwt/core/shared/SerializableThrowable.java b/user/src/com/google/gwt/core/shared/SerializableThrowable.java
index 82ccaae..cbbd612 100644
--- a/user/src/com/google/gwt/core/shared/SerializableThrowable.java
+++ b/user/src/com/google/gwt/core/shared/SerializableThrowable.java
@@ -22,6 +22,7 @@
* A serializable copy of a {@link Throwable}, including its causes and stack trace. It overrides
* {@code #toString} to mimic original {@link Throwable#toString()} so that {@link #printStackTrace}
* will work as if it is coming from the original exception.
+ *
* <p>
* This class is especially useful for logging and testing as the emulated Throwable class does not
* serialize recursively and does not serialize the stack trace. This class, as an alternative, can
@@ -30,6 +31,8 @@
* <p>
* Please note that, to get more useful stack traces from client side, this class needs to be used
* in conjunction with {@link com.google.gwt.core.server.StackTraceDeobfuscator}.
+ * <p>
+ * NOTE: Does not serialize suppressed exceptions to remain compatible with Java 6 and below.
*/
public final class SerializableThrowable extends Throwable {
diff --git a/user/super/com/google/gwt/emul/java/lang/AutoCloseable.java b/user/super/com/google/gwt/emul/java/lang/AutoCloseable.java
new file mode 100644
index 0000000..7bf4144
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/lang/AutoCloseable.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 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 java.lang;
+
+/**
+ * See <a
+ * href="http://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html">the
+ * official Java API doc</a> for details.
+ */
+public interface AutoCloseable {
+
+ /**
+ * Closes this resource.
+ */
+ void close() throws Exception;
+}
diff --git a/user/super/com/google/gwt/emul/java/lang/Throwable.java b/user/super/com/google/gwt/emul/java/lang/Throwable.java
index dd9ecb9..e929e59 100644
--- a/user/super/com/google/gwt/emul/java/lang/Throwable.java
+++ b/user/super/com/google/gwt/emul/java/lang/Throwable.java
@@ -37,30 +37,73 @@
* to ensure that only the detailMessage field is serialized. Changing the
* field modifiers below may necessitate a change to the server's
* SerializabilityUtil.fieldQualifiesForSerialization(Field) method.
+ *
+ * TODO(rluble): Add remaining functionality for suppressed Exceptions (e.g.
+ * printing). Also review the class for missing Java 7 compatibility.
*/
private transient Throwable cause;
private String detailMessage;
+ private transient Throwable[] suppressedExceptions;
private transient StackTraceElement[] stackTrace;
-
- {
- fillInStackTrace();
- }
+ private transient boolean disableSuppression;
public Throwable() {
+ fillInStackTrace();
}
public Throwable(String message) {
this.detailMessage = message;
+ fillInStackTrace();
}
public Throwable(String message, Throwable cause) {
this.cause = cause;
this.detailMessage = message;
+ fillInStackTrace();
}
public Throwable(Throwable cause) {
this.detailMessage = (cause == null) ? null : cause.toString();
this.cause = cause;
+ fillInStackTrace();
+ }
+
+ /**
+ * Constructor that allows subclasses disabling exception suppression and stack traces.
+ * Those features should only be disabled in very specific cases.
+ */
+ protected Throwable(String message, Throwable cause, boolean enableSuppression,
+ boolean writetableStackTrace) {
+ if (writetableStackTrace) {
+ fillInStackTrace();
+ }
+ this.cause = cause;
+ this.detailMessage = message;
+ this.disableSuppression = !enableSuppression;
+ }
+
+ /**
+ * Call to add an exception that was suppressed. Used by try-with-resources.
+ */
+ public final void addSuppressed(Throwable exception) {
+ if (exception == null) {
+ throw new NullPointerException("Cannot suppress a null exception.");
+ }
+ if (exception == this) {
+ throw new IllegalArgumentException("Exception can not suppress itself.");
+ }
+
+ if (disableSuppression) {
+ return;
+ }
+
+ if (suppressedExceptions == null) {
+ suppressedExceptions = new Throwable[] { exception };
+ } else {
+ // TRICK: This is not correct Java (would give an OOBE, but it works in JS and
+ // this code will only be executed in JS.
+ suppressedExceptions[suppressedExceptions.length] = exception;
+ }
}
/**
@@ -97,6 +140,17 @@
return stackTrace;
}
+ /**
+ * Returns the array of Exception that this one suppressedExceptions.
+ */
+ public final Throwable[] getSuppressed() {
+ if (suppressedExceptions == null) {
+ suppressedExceptions = new Throwable[0];
+ }
+
+ return suppressedExceptions;
+ }
+
public Throwable initCause(Throwable cause) {
if (this.cause != null) {
throw new IllegalStateException("Can't overwrite cause");
diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java
index f849634..26a32ac 100644
--- a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java
+++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java
@@ -63,4 +63,245 @@
}
assertEquals(1, result);
}
+
+ final List<String> log = new ArrayList<String>();
+
+ public class Resource implements AutoCloseable {
+
+ String name;
+ public Resource(String name) {
+ this.name = name;
+ log.add("Open " + name);
+ }
+
+ public void doSomething() {
+ log.add("doSomething " + name);
+ }
+
+ public void throwException(String text) throws E1 {
+ throw new E1(text + " in " + name);
+ }
+
+ public void close() throws Exception {
+ log.add("Close " + name);
+ }
+ }
+
+ public class ResourceWithExceptionOnClose extends Resource {
+
+
+ public ResourceWithExceptionOnClose(String name) {
+ super(name);
+ }
+
+ public void close() throws Exception {
+ throw new E1("Exception in close " + name);
+ }
+ }
+
+ public void testResource() throws Exception{
+ log.clear();
+ try (Resource c = new Resource("A")) {
+
+ c.doSomething();
+ }
+
+ assertContentsInOrder(log,
+ "Open A",
+ "doSomething A",
+ "Close A");
+ }
+
+ public void test3Resources() throws Exception{
+ log.clear();
+ try (Resource rA = new Resource("A");
+ Resource rB = new Resource("B");
+ Resource rC = new Resource("C")) {
+
+ rA.doSomething();
+ rB.doSomething();
+ rC.doSomething();
+ }
+
+ assertContentsInOrder(log,
+ "Open A",
+ "Open B",
+ "Open C",
+ "doSomething A",
+ "doSomething B",
+ "doSomething C",
+ "Close C",
+ "Close B",
+ "Close A"
+ );
+ }
+
+ public void testResourcesWithExceptions() throws Exception{
+ log.clear();
+ try (Resource rA = new Resource("A");
+ Resource rB = new ResourceWithExceptionOnClose("B");
+ Resource rC = new Resource("C")) {
+
+ rA.doSomething();
+ rB.doSomething();
+ rC.doSomething();
+ } catch (Exception e) {
+ log.add(e.getMessage());
+ } finally {
+ log.add("finally");
+ }
+
+ assertContentsInOrder(log,
+ "Open A",
+ "Open B",
+ "Open C",
+ "doSomething A",
+ "doSomething B",
+ "doSomething C",
+ "Close C",
+ "Close A",
+ "Exception in close B",
+ "finally"
+ );
+ }
+
+ public void testResourcesWithSuppressedExceptions() throws Exception{
+ log.clear();
+ try (Resource rA = new ResourceWithExceptionOnClose("A");
+ Resource rB = new Resource("B");
+ Resource rC = new ResourceWithExceptionOnClose("C")) {
+
+ rA.doSomething();
+ rB.doSomething();
+ rC.doSomething();
+ } catch (Exception e) {
+ log.add(e.getMessage());
+ for (Throwable t : e.getSuppressed()) {
+ log.add("Suppressed: " + t.getMessage());
+ }
+ }
+
+ assertContentsInOrder(log,
+ "Open A",
+ "Open B",
+ "Open C",
+ "doSomething A",
+ "doSomething B",
+ "doSomething C",
+ "Close B",
+ "Exception in close C",
+ "Suppressed: Exception in close A"
+ );
+
+ log.clear();
+ try (Resource rA = new Resource("A");
+ Resource rB = new ResourceWithExceptionOnClose("B");
+ Resource rC = new Resource("C")) {
+
+ rA.doSomething();
+ rB.throwException("E1 here");
+ rC.doSomething();
+ } catch (Exception e) {
+ log.add(e.getMessage());
+ for (Throwable t : e.getSuppressed()) {
+ log.add("Suppressed: " + t.getMessage());
+ }
+ } finally {
+ log.add("finally");
+ }
+
+ assertContentsInOrder(log,
+ "Open A",
+ "Open B",
+ "Open C",
+ "doSomething A",
+ "Close C",
+ "Close A",
+ "E1 here in B",
+ "Suppressed: Exception in close B",
+ "finally"
+ );
+ }
+
+ public void testAddSuppressedExceptions() {
+ Throwable throwable = new Throwable("primary");
+ assertNotNull(throwable.getSuppressed());
+ assertEquals(0, throwable.getSuppressed().length);
+ Throwable suppressed1 = new Throwable("suppressed1");
+ throwable.addSuppressed(suppressed1);
+ assertEquals(1, throwable.getSuppressed().length);
+ assertEquals(suppressed1, throwable.getSuppressed()[0]);
+ Throwable suppressed2 = new Throwable("suppressed2");
+ throwable.addSuppressed(suppressed2);
+ assertEquals(2, throwable.getSuppressed().length);
+ assertEquals(suppressed1, throwable.getSuppressed()[0]);
+ assertEquals(suppressed2, throwable.getSuppressed()[1]);
+ }
+
+ private void assertContentsInOrder(Iterable<String> contents, String... elements ) {
+ int sz = elements.length;
+ Iterator<String> it = contents.iterator();
+ for (int i = 0; i < sz; i++) {
+ assertTrue(it.hasNext());
+ String expected = it.next();
+ assertEquals(elements[i], expected);
+ }
+ assertFalse(it.hasNext());
+ }
+
+ public static class E1 extends Exception {
+ String name;
+ public E1(String name) {
+ this.name = name;
+ }
+
+ public int methodE1() {
+ return 0;
+ }
+
+ @Override
+ public String getMessage() {
+ return name;
+ }
+ }
+
+ public static class E2 extends E1 {
+ public E2(String name) {
+ super(name);
+ }
+
+ public int methodE2() {
+ return 1;
+ }
+ }
+
+ public static class E3 extends E1 {
+ public E3(String name) {
+ super(name);
+ }
+
+ public int methodE3() {
+ return 2;
+ }
+ }
+
+ public void testMultiExceptions() {
+
+ int choose = 0;
+
+ try {
+ if (choose == 0) {
+ throw new E1("e1");
+ } else if (choose ==1) {
+ throw new E2("e2");
+ }
+
+ fail("Exception was not trown");
+ } catch (E2 | E3 x) {
+ // The compiler will assign x a common supertype/superinterface of E2 and E3.
+ // Here we make sure that this clause is not entered when the supertype is thrown.
+ fail("Caught E1 instead of E2|E3");
+ } catch (E1 x) {
+ }
+ }
}
diff --git a/user/test/com/google/gwt/dev/jjs/test/Java7Test.java b/user/test/com/google/gwt/dev/jjs/test/Java7Test.java
index 2ef1c47..e463279 100644
--- a/user/test/com/google/gwt/dev/jjs/test/Java7Test.java
+++ b/user/test/com/google/gwt/dev/jjs/test/Java7Test.java
@@ -49,4 +49,22 @@
public void testSwitchOnString() {
}
+
+ public void testResource() throws Exception {
+ }
+
+ public void test3Resources() throws Exception {
+ }
+
+ public void testResourcesWithExceptions() throws Exception {
+ }
+
+ public void testResourcesWithSuppressedExceptions() throws Exception {
+ }
+
+ public void testMultiExceptions() {
+ }
+
+ public void testAddSuppressedExceptions() {
+ }
}