| /* |
| * Copyright 2007 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.dev.jjs.impl; |
| |
| import com.google.gwt.dev.jjs.SourceInfo; |
| import com.google.gwt.dev.jjs.ast.Context; |
| import com.google.gwt.dev.jjs.ast.JBlock; |
| import com.google.gwt.dev.jjs.ast.JDeclarationStatement; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JIfStatement; |
| import com.google.gwt.dev.jjs.ast.JInstanceOf; |
| 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.JMethodBody; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JModVisitor; |
| 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 java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Merge multi-catch blocks into a single catch block that uses instanceof tests |
| * to determine which user block to run. |
| */ |
| public class CatchBlockNormalizer { |
| |
| /** |
| * Collapses all multi-catch blocks into a single catch block. |
| */ |
| private class CollapseCatchBlocks extends JModVisitor { |
| |
| // @Override |
| @Override |
| public void endVisit(JMethodBody x, Context ctx) { |
| clearLocals(); |
| currentMethodBody = null; |
| } |
| |
| // @Override |
| @Override |
| public void endVisit(JTryStatement x, Context ctx) { |
| if (x.getCatchBlocks().isEmpty()) { |
| return; |
| } |
| |
| SourceInfo catchInfo = x.getCatchBlocks().get(0).getSourceInfo(); |
| JLocal exVar = popTempLocal(); |
| JBlock newCatchBlock = new JBlock(catchInfo); |
| |
| { |
| // $e = Exceptions.caught($e) |
| JMethod caughtMethod = program.getIndexedMethod("Exceptions.caught"); |
| JMethodCall call = new JMethodCall(catchInfo, null, caughtMethod); |
| call.addArg(new JLocalRef(catchInfo, exVar)); |
| newCatchBlock.addStmt(JProgram.createAssignmentStmt(catchInfo, new JLocalRef(catchInfo, |
| exVar), call)); |
| } |
| |
| /* |
| * 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. |
| * |
| * Go backwards so we can nest the else statements in the correct order! |
| */ |
| // 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); |
| 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)); |
| JDeclarationStatement declaration = |
| new JDeclarationStatement(catchInfo, arg, new JLocalRef(catchInfo, exVar)); |
| block.addStmt(0, declaration); |
| // nest the previous as an else for me |
| cur = new JIfStatement(catchInfo, ifTest, block, cur); |
| } |
| |
| newCatchBlock.addStmt(cur); |
| x.getCatchArgs().clear(); |
| x.getCatchArgs().add(new JLocalRef(newCatchBlock.getSourceInfo(), exVar)); |
| x.getCatchBlocks().clear(); |
| x.getCatchBlocks().add(newCatchBlock); |
| } |
| |
| // @Override |
| @Override |
| public boolean visit(JMethodBody x, Context ctx) { |
| currentMethodBody = x; |
| clearLocals(); |
| return true; |
| } |
| |
| // @Override |
| @Override |
| public boolean visit(JTryStatement x, Context ctx) { |
| if (!x.getCatchBlocks().isEmpty()) { |
| pushTempLocal(x.getSourceInfo()); |
| } |
| return true; |
| } |
| } |
| |
| public static void exec(JProgram program) { |
| new CatchBlockNormalizer(program).execImpl(); |
| } |
| |
| private JMethodBody currentMethodBody; |
| private int localIndex; |
| private final JProgram program; |
| private final List<JLocal> tempLocals = new ArrayList<JLocal>(); |
| |
| private CatchBlockNormalizer(JProgram program) { |
| this.program = program; |
| } |
| |
| private void clearLocals() { |
| tempLocals.clear(); |
| localIndex = 0; |
| } |
| |
| private void execImpl() { |
| CollapseCatchBlocks collapser = new CollapseCatchBlocks(); |
| collapser.accept(program); |
| } |
| |
| private JLocal popTempLocal() { |
| return tempLocals.get(--localIndex); |
| } |
| |
| private void pushTempLocal(SourceInfo sourceInfo) { |
| if (localIndex == tempLocals.size()) { |
| JLocal newTemp = |
| JProgram.createLocal(sourceInfo, "$e" + localIndex, program.getTypeJavaLangObject(), |
| false, currentMethodBody); |
| tempLocals.add(newTemp); |
| } |
| ++localIndex; |
| } |
| |
| } |