| /* |
| * Copyright 2009 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.dev.jjs.impl; |
| |
| import com.google.gwt.core.ext.linker.StatementRanges; |
| import com.google.gwt.core.ext.soyc.Range; |
| import com.google.gwt.dev.jjs.SourceInfo; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Limits top-level blocks to MAX_BLOCK_SIZE statements. |
| */ |
| public class JsIEBlockTextTransformer extends JsAbstractTextTransformer { |
| |
| // uncomment to test |
| |
| // private static final int MAX_BLOCK_SIZE = 10; |
| private static final int MAX_BLOCK_SIZE = 1 << 15 - 1; |
| |
| private int currentStatementCount; |
| |
| private boolean doSplits; |
| |
| private Set<Integer> statementsAddedBlockClose = new HashSet<Integer>(); |
| |
| private Set<Integer> statementsAddedBlockOpen = new HashSet<Integer>(); |
| |
| public JsIEBlockTextTransformer(JsAbstractTextTransformer xformer) { |
| super(xformer); |
| } |
| |
| public JsIEBlockTextTransformer(String js, StatementRanges statementRanges, |
| Map<Range, SourceInfo> sourceInfoMap) { |
| super(js, statementRanges, sourceInfoMap); |
| } |
| |
| /** |
| * Do not perform clustering, only fix up IE7 block issue. |
| */ |
| @Override |
| public void exec() { |
| doSplits = statementRanges.numStatements() > MAX_BLOCK_SIZE; |
| if (doSplits) { |
| int statementIndices[] = new int[statementRanges.numStatements()]; |
| for (int i = 0; i < statementRanges.numStatements(); i++) { |
| statementIndices[i] = i; |
| } |
| recomputeJsAndStatementRanges(statementIndices); |
| } |
| } |
| |
| public Set<Integer> getStatementsAddedBlockClose() { |
| return statementsAddedBlockClose; |
| } |
| |
| public Set<Integer> getStatementsAddedBlockOpen() { |
| return statementsAddedBlockOpen; |
| } |
| |
| /** |
| * Record start of statement, and optionally inject new open block. |
| */ |
| @Override |
| protected void beginStatement(int index, StringBuilder newJs, ArrayList<Integer> starts) { |
| if (doSplits && currentStatementCount == 0) { |
| super.beginStatement(index, newJs, starts); |
| newJs.append('{'); |
| statementsAddedBlockOpen.add(Integer.valueOf(index)); |
| } else if (!doSplits) { |
| super.beginStatement(index, newJs, starts); |
| } |
| } |
| |
| @Override |
| protected void beginStatements(StringBuilder newJs, ArrayList<Integer> starts, |
| ArrayList<Integer> ends) { |
| super.beginStatements(newJs, starts, ends); |
| currentStatementCount = 0; |
| } |
| |
| /** |
| * Record end of statement, and optionally inject close block, if block is |
| * full. |
| */ |
| @Override |
| protected void endStatement(int index, StringBuilder newJs, ArrayList<Integer> ends) { |
| currentStatementCount++; |
| if (doSplits && currentStatementCount == MAX_BLOCK_SIZE) { |
| newJs.append('}'); |
| super.endStatement(index, newJs, ends); |
| currentStatementCount = 0; |
| statementsAddedBlockClose.add(Integer.valueOf(index)); |
| } else if (!doSplits) { |
| super.endStatement(index, newJs, ends); |
| } |
| } |
| |
| /** |
| * Used to close a trailing block which never filled. |
| */ |
| @Override |
| protected void endStatements(StringBuilder newJs, ArrayList<Integer> starts, |
| ArrayList<Integer> ends) { |
| optionallyCloseLastBlock(newJs, ends); |
| super.endStatements(newJs, starts, ends); |
| } |
| |
| /** |
| * Fixes the index ranges of individual expressions in the generated |
| * JS after chunking statements into blocks that satisfy the IE block |
| * size problem. Loops over each expression, determines whether the |
| * statement in which it falls has a brace inserted before/after, and |
| * shifts forward according to where it falls in the block. |
| */ |
| @Override |
| protected void updateSourceInfoMap() { |
| if (sourceInfoMap != null) { |
| Range[] oldExpressionRanges = sourceInfoMap.keySet().toArray(new Range[0]); |
| Arrays.sort(oldExpressionRanges, Range.SOURCE_ORDER_COMPARATOR); |
| |
| // iterate over expression ranges and shift |
| Map<Range, SourceInfo> updatedInfoMap = new HashMap<Range, SourceInfo>(); |
| Range entireProgram = |
| new Range(0, originalStatementRanges.end(originalStatementRanges.numStatements() - 1)); |
| int shift = 0; |
| |
| // set to keep track of which statements have already shifted. |
| // need to account for when a shift has already been added for the extra |
| // open brace in a statement--sometimes there are multiple expressions |
| // that all start at the same place a the beginning of a statement in |
| // the expression list |
| // ex: _.gC=function x()... yields the expressions _, _.gC, _.gC = ... |
| Set<Integer> shiftAdded = new HashSet<Integer>(); |
| |
| for (int i = 0, j = 0; j < oldExpressionRanges.length; j++) { |
| Range oldExpression = oldExpressionRanges[j]; |
| if (oldExpression.equals(entireProgram)) { |
| continue; |
| } |
| |
| if (originalStatementRanges.start(i) > oldExpression.getStart() |
| || oldExpression.getEnd() > originalStatementRanges.end(i)) { |
| |
| // expression should fall in the next statement |
| i++; |
| assert originalStatementRanges.start(i) <= oldExpression.getStart() |
| && oldExpression.getEnd() <= originalStatementRanges.end(i); |
| |
| if (statementsAddedBlockClose.contains(Integer.valueOf(i - 1))) { |
| // there's an extra statement index in the addedBlockClose list, |
| // which corresponds to the extra closing brace at the end of the |
| // program. but this index doesn't match up to the indices in the |
| // old statement ranges--it's equal to the # of statements in the |
| // original code divided by the IE block size |
| if (i != statementRanges.numStatements()) { |
| shift++; |
| } |
| } |
| } |
| |
| if (statementsAddedBlockOpen.contains(Integer.valueOf(i)) |
| && oldExpression.getStart() == originalStatementRanges.start(i) |
| && !shiftAdded.contains(i)) { |
| |
| shift++; |
| shiftAdded.add(Integer.valueOf(i)); |
| } |
| |
| int newStart = oldExpression.getStart() + shift; |
| int newEnd = oldExpression.getEnd() + shift; |
| |
| Range newExpression = new Range(newStart, newEnd); |
| updatedInfoMap.put(newExpression, sourceInfoMap.get(oldExpression)); |
| } |
| |
| updatedInfoMap.put(new Range(0, entireProgram.getEnd() + shift), |
| sourceInfoMap.get(entireProgram)); |
| |
| sourceInfoMap = updatedInfoMap; |
| } |
| } |
| |
| /** |
| * Close last block if it never filled. |
| */ |
| private void optionallyCloseLastBlock(StringBuilder newJs, ArrayList<Integer> ends) { |
| if (doSplits && currentStatementCount > 0 && currentStatementCount < MAX_BLOCK_SIZE) { |
| newJs.append("}"); |
| ends.add(newJs.length()); |
| statementsAddedBlockClose.add(Integer.valueOf(ends.size() - 1)); |
| } |
| } |
| |
| } |