/******************************************************************************* * Copyright (c) 2006, 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.codegen.CodeStream; import org.eclipse.jdt.internal.compiler.flow.FlowContext; import org.eclipse.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; /** * CombinedBinaryExpression is an implementation of BinaryExpression that * specifically attempts to mitigate the issues raised by expressions which * have a very deep leftmost branch. It does so by maintaining a table of * direct references to its subexpressions, and implementing non-recursive * variants of the most significant recursive algorithms of its ancestors. * The subexpressions table only holds intermediate binary expressions. Its * role is to provide the reversed navigation through the left relationship * of BinaryExpression to Expression. To cope with potentially very deep * left branches, an instance of CombinedBinaryExpression is created once in * a while, using variable thresholds held by {@link #arityMax}. * As a specific case, the topmost node of all binary expressions that are * deeper than one is a CombinedBinaryExpression, but it has no references * table.<br> * Notes: * <ul> * <li>CombinedBinaryExpression is not meant to behave in other ways than * BinaryExpression in any observable respect;</li> * <li>visitors that implement their own traversal upon binary expressions * should consider taking advantage of combined binary expressions, or * else face a risk of StackOverflowError upon deep instances;</li> * <li>callers that need to change the operator should rebuild the expression * from scratch, or else amend the references table as needed to cope with * the resulting, separated expressions.</li> * </ul> */ public class CombinedBinaryExpression extends BinaryExpression { /** * The number of consecutive binary expressions of this' left branch that * bear the same operator as this.<br> * Notes: * <ul><li>the presence of a CombinedBinaryExpression instance resets * arity, even when its operator is compatible;</li> * <li>this property is maintained by the parser.</li> * </ul> */ public int arity; /** * The threshold that will trigger the creation of the next full-fledged * CombinedBinaryExpression. This field is only maintained for the * topmost binary expression (it is 0 otherwise). It enables a variable * policy, which scales better with very large expressions. */ public int arityMax; /** * Upper limit for {@link #arityMax}. */ public static final int ARITY_MAX_MAX = 160; /** * Default lower limit for {@link #arityMax}. */ public static final int ARITY_MAX_MIN = 20; /** * Default value for the first term of the series of {@link #arityMax} * values. Changing this allows for experimentation. Not meant to be * changed during a parse operation. */ public static int defaultArityMaxStartingValue = ARITY_MAX_MIN; /** * A table of references to the binary expressions of this' left branch. * Instances of CombinedBinaryExpression are not repeated here. Instead, * the left subexpression of referencesTable[0] may be a combined binary * expression, if appropriate. Null when this only cares about tracking * the expression's arity. */ public BinaryExpression referencesTable[]; /** * Make a new CombinedBinaryExpression. If arity is strictly greater than one, * a references table is built and initialized with the reverse relationship of * the one defined by {@link BinaryExpression#left}. arity and left must be * compatible with each other (that is, there must be at least arity - 1 * consecutive compatible binary expressions into the leftmost branch of left, * the topmost of which being left's immediate left expression). * @param left the left branch expression * @param right the right branch expression * @param operator the operator for this binary expression - only PLUS for now * @param arity the number of binary expressions of a compatible operator that * already exist into the leftmost branch of left (including left); must * be strictly greater than 0 */ public CombinedBinaryExpression(Expression left, Expression right, int operator, int arity) { super(left, right, operator); initArity(left, arity); } public CombinedBinaryExpression(CombinedBinaryExpression expression) { super(expression); initArity(expression.left, expression.arity); } public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { // keep implementation in sync with BinaryExpression#analyseCode if (this.referencesTable == null) { return super.analyseCode(currentScope, flowContext, flowInfo); } BinaryExpression cursor; if ((cursor = this.referencesTable[0]).resolvedType.id != TypeIds.T_JavaLangString) { cursor.left.checkNPE(currentScope, flowContext, flowInfo); } flowInfo = cursor.left.analyseCode(currentScope, flowContext, flowInfo). unconditionalInits(); for (int i = 0, end = this.arity; i < end; i ++) { if ((cursor = this.referencesTable[i]).resolvedType.id != TypeIds.T_JavaLangString) { cursor.right.checkNPE(currentScope, flowContext, flowInfo); } flowInfo = cursor.right. analyseCode(currentScope, flowContext, flowInfo). unconditionalInits(); } if (this.resolvedType.id != TypeIds.T_JavaLangString) { this.right.checkNPE(currentScope, flowContext, flowInfo); } return this.right.analyseCode(currentScope, flowContext, flowInfo). unconditionalInits(); } public void generateOptimizedStringConcatenation(BlockScope blockScope, CodeStream codeStream, int typeID) { // keep implementation in sync with BinaryExpression and Expression // #generateOptimizedStringConcatenation if (this.referencesTable == null) { super.generateOptimizedStringConcatenation(blockScope, codeStream, typeID); } else { if ((((this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) == OperatorIds.PLUS) && ((this.bits & ASTNode.ReturnTypeIDMASK) == TypeIds.T_JavaLangString)) { if (this.constant != Constant.NotAConstant) { codeStream.generateConstant(this.constant, this.implicitConversion); codeStream.invokeStringConcatenationAppendForType( this.implicitConversion & TypeIds.COMPILE_TYPE_MASK); } else { BinaryExpression cursor = this.referencesTable[0]; int restart = 0; // int cursorTypeID; int pc = codeStream.position; for (restart = this.arity - 1; restart >= 0; restart--) { if ((cursor = this.referencesTable[restart]).constant != Constant.NotAConstant) { codeStream.generateConstant(cursor.constant, cursor.implicitConversion); codeStream.invokeStringConcatenationAppendForType( cursor.implicitConversion & TypeIds.COMPILE_TYPE_MASK); break; } // never happens for now - may reconsider if we decide to // cover more than string concatenation // if (!((((cursor = this.referencesTable[restart]).bits & // ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) == // OperatorIds.PLUS) & // ((cursorTypeID = cursor.bits & ASTNode.ReturnTypeIDMASK) == // TypeIds.T_JavaLangString)) { // if (cursorTypeID == T_JavaLangString && // cursor.constant != Constant.NotAConstant && // cursor.constant.stringValue().length() == 0) { // break; // optimize str + "" // } // cursor.generateCode(blockScope, codeStream, true); // codeStream.invokeStringConcatenationAppendForType( // cursorTypeID); // break; // } } restart++; if (restart == 0) { // reached the leftmost expression cursor.left.generateOptimizedStringConcatenation( blockScope, codeStream, cursor.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK); } int pcAux; for (int i = restart; i < this.arity; i++) { codeStream.recordPositionsFrom(pc, (cursor = this.referencesTable[i]).left.sourceStart); pcAux = codeStream.position; cursor.right.generateOptimizedStringConcatenation(blockScope, codeStream, cursor.right.implicitConversion & TypeIds.COMPILE_TYPE_MASK); codeStream.recordPositionsFrom(pcAux, cursor.right.sourceStart); } codeStream.recordPositionsFrom(pc, this.left.sourceStart); pc = codeStream.position; this.right.generateOptimizedStringConcatenation( blockScope, codeStream, this.right.implicitConversion & TypeIds.COMPILE_TYPE_MASK); codeStream.recordPositionsFrom(pc, this.right.sourceStart); } } else { super.generateOptimizedStringConcatenation(blockScope, codeStream, typeID); } } } public void generateOptimizedStringConcatenationCreation(BlockScope blockScope, CodeStream codeStream, int typeID) { // keep implementation in sync with BinaryExpression // #generateOptimizedStringConcatenationCreation if (this.referencesTable == null) { super.generateOptimizedStringConcatenationCreation(blockScope, codeStream, typeID); } else { if ((((this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) == OperatorIds.PLUS) && ((this.bits & ASTNode.ReturnTypeIDMASK) == TypeIds.T_JavaLangString) && this.constant == Constant.NotAConstant) { int pc = codeStream.position; BinaryExpression cursor = this.referencesTable[this.arity - 1]; // silence warnings int restart = 0; for (restart = this.arity - 1; restart >= 0; restart--) { if (((((cursor = this.referencesTable[restart]).bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) == OperatorIds.PLUS) && ((cursor.bits & ASTNode.ReturnTypeIDMASK) == TypeIds.T_JavaLangString)) { if (cursor.constant != Constant.NotAConstant) { codeStream.newStringContatenation(); // new: java.lang.StringBuffer codeStream.dup(); codeStream.ldc(cursor.constant.stringValue()); codeStream.invokeStringConcatenationStringConstructor(); // invokespecial: java.lang.StringBuffer.<init>(Ljava.lang.String;)V break; } } else { cursor.generateOptimizedStringConcatenationCreation(blockScope, codeStream, cursor.implicitConversion & TypeIds.COMPILE_TYPE_MASK); break; } } restart++; if (restart == 0) { // reached the leftmost expression cursor.left.generateOptimizedStringConcatenationCreation( blockScope, codeStream, cursor.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK); } int pcAux; for (int i = restart; i < this.arity; i++) { codeStream.recordPositionsFrom(pc, (cursor = this.referencesTable[i]).left.sourceStart); pcAux = codeStream.position; cursor.right.generateOptimizedStringConcatenation(blockScope, codeStream, cursor.right.implicitConversion & TypeIds.COMPILE_TYPE_MASK); codeStream.recordPositionsFrom(pcAux, cursor.right.sourceStart); } codeStream.recordPositionsFrom(pc, this.left.sourceStart); pc = codeStream.position; this.right.generateOptimizedStringConcatenation( blockScope, codeStream, this.right.implicitConversion & TypeIds.COMPILE_TYPE_MASK); codeStream.recordPositionsFrom(pc, this.right.sourceStart); } else { super.generateOptimizedStringConcatenationCreation(blockScope, codeStream, typeID); } } } private void initArity(Expression expression, int value) { this.arity = value; if (value > 1) { this.referencesTable = new BinaryExpression[value]; this.referencesTable[value - 1] = (BinaryExpression) expression; for (int i = value - 1; i > 0; i--) { this.referencesTable[i - 1] = (BinaryExpression) this.referencesTable[i].left; } } else { this.arityMax = defaultArityMaxStartingValue; } } public StringBuffer printExpressionNoParenthesis(int indent, StringBuffer output) { // keep implementation in sync with // BinaryExpression#printExpressionNoParenthesis and // OperatorExpression#printExpression if (this.referencesTable == null) { return super.printExpressionNoParenthesis(indent, output); } String operatorString = operatorToString(); for (int i = this.arity - 1; i >= 0; i--) { output.append('('); } output = this.referencesTable[0].left. printExpression(indent, output); for (int i = 0, end = this.arity; i < end; i++) { output.append(' ').append(operatorString).append(' '); output = this.referencesTable[i].right. printExpression(0, output); output.append(')'); } output.append(' ').append(operatorString).append(' '); return this.right.printExpression(0, output); } public TypeBinding resolveType(BlockScope scope) { // keep implementation in sync with BinaryExpression#resolveType if (this.referencesTable == null) { return super.resolveType(scope); } BinaryExpression cursor; if ((cursor = this.referencesTable[0]).left instanceof CastExpression) { cursor.left.bits |= ASTNode.DisableUnnecessaryCastCheck; // will check later on } cursor.left.resolveType(scope); for (int i = 0, end = this.arity; i < end; i ++) { this.referencesTable[i].nonRecursiveResolveTypeUpwards(scope); } nonRecursiveResolveTypeUpwards(scope); return this.resolvedType; } public void traverse(ASTVisitor visitor, BlockScope scope) { if (this.referencesTable == null) { super.traverse(visitor, scope); } else { if (visitor.visit(this, scope)) { int restart; for (restart = this.arity - 1; restart >= 0; restart--) { if (!visitor.visit( this.referencesTable[restart], scope)) { visitor.endVisit( this.referencesTable[restart], scope); break; } } restart++; // restart now points to the deepest BE for which // visit returned true, if any if (restart == 0) { this.referencesTable[0].left.traverse(visitor, scope); } for (int i = restart, end = this.arity; i < end; i++) { this.referencesTable[i].right.traverse(visitor, scope); visitor.endVisit(this.referencesTable[i], scope); } this.right.traverse(visitor, scope); } visitor.endVisit(this, scope); } } /** * Change {@link #arityMax} if and as needed. The current policy is to double * arityMax each time this method is called, until it reaches * {@link #ARITY_MAX_MAX}. Other policies may consider incrementing it less * agressively. Call only after an appropriate value has been assigned to * {@link #left}. */ // more sophisticate increment policies would leverage the leftmost expression // to hold an indication of the number of uses of a given arityMax in a row public void tuneArityMax() { if (this.arityMax < ARITY_MAX_MAX) { this.arityMax *= 2; } } }