/******************************************************************************* * Copyright (c) 2000, 2010 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.formatter.align; import org.eclipse.core.runtime.Assert; import org.eclipse.jdt.internal.formatter.Location; import org.eclipse.jdt.internal.formatter.Scribe; /** * Alignment management * * @since 2.1 */ public class Alignment { // Kind of alignment public int kind; public static final int ALLOCATION= 1; public static final int ANNOTATION_MEMBERS_VALUE_PAIRS= 2; public static final int ARRAY_INITIALIZER= 3; public static final int ASSIGNMENT= 4; public static final int BINARY_EXPRESSION= 5; public static final int CASCADING_MESSAGE_SEND= 6; public static final int COMPACT_IF= 7; public static final int COMPOUND_ASSIGNMENT= 8; public static final int CONDITIONAL_EXPRESSION= 9; public static final int ENUM_CONSTANTS= 10; public static final int ENUM_CONSTANTS_ARGUMENTS= 11; public static final int EXPLICIT_CONSTRUCTOR_CALL= 12; public static final int FIELD_DECLARATION_ASSIGNMENT= 13; public static final int LOCAL_DECLARATION_ASSIGNMENT= 14; public static final int MESSAGE_ARGUMENTS= 15; public static final int MESSAGE_SEND= 16; public static final int METHOD_ARGUMENTS= 17; public static final int METHOD_DECLARATION= 18; public static final int MULTIPLE_FIELD= 19; public static final int SUPER_CLASS= 20; public static final int SUPER_INTERFACES= 21; public static final int THROWS= 22; public static final int TYPE_MEMBERS= 23; public static final int STRING_CONCATENATION= 24; // name of alignment public String name; public static final String[] NAMES= { "", //$NON-NLS-1$ "allocation", //$NON-NLS-1$ "annotationMemberValuePairs", //$NON-NLS-1$ "array_initializer", //$NON-NLS-1$ "assignmentAlignment", //$NON-NLS-1$ "binaryExpressionAlignment", //$NON-NLS-1$ "cascadingMessageSendAlignment", //$NON-NLS-1$ "compactIf", //$NON-NLS-1$ "compoundAssignmentAlignment", //$NON-NLS-1$ "conditionalExpression", //$NON-NLS-1$ "enumConstants", //$NON-NLS-1$ "enumConstantArguments", //$NON-NLS-1$ "explicit_constructor_call", //$NON-NLS-1$ "fieldDeclarationAssignmentAlignment", //$NON-NLS-1$ "localDeclarationAssignmentAlignment", //$NON-NLS-1$ "messageArguments", //$NON-NLS-1$ "messageAlignment", //$NON-NLS-1$ "methodArguments", //$NON-NLS-1$ "methodDeclaration", //$NON-NLS-1$ "multiple_field", //$NON-NLS-1$ "superclass", //$NON-NLS-1$ "superInterfaces", //$NON-NLS-1$ "throws", //$NON-NLS-1$ "typeMembers", //$NON-NLS-1$ "stringConcatenation", //$NON-NLS-1$ }; // link to enclosing alignment public Alignment enclosing; // start location of this alignment public Location location; // indentation management public int fragmentIndex; public int fragmentCount; public int[] fragmentIndentations; public boolean needRedoColumnAlignment; // chunk management public int chunkStartIndex; public int chunkKind; // break management public int originalIndentationLevel; public int breakIndentationLevel; public int shiftBreakIndentationLevel; public int[] fragmentBreaks; public boolean wasSplit; public boolean blockAlign= false; public boolean tooLong= false; public Scribe scribe; // reset private boolean reset= false; /* * Alignment modes */ public static final int M_FORCE= 1; // if bit set, then alignment will be non-optional (default is optional) public static final int M_INDENT_ON_COLUMN= 2; // if bit set, broken fragments will be aligned on current location column (default is to break at current indentation level) public static final int M_INDENT_BY_ONE= 4; // if bit set, broken fragments will be indented one level below current (not using continuation indentation) // split modes can be combined either with M_FORCE or M_INDENT_ON_COLUMN /** * foobar(#fragment1, #fragment2, * <ul> * <li>#fragment3, #fragment4</li> * </ul> */ public static final int M_COMPACT_SPLIT= 16; // fill each line with all possible fragments /** * foobar( * <ul> * <li>#fragment1, #fragment2,</li> * <li>#fragment5, #fragment4,</li> * </ul> */ public static final int M_COMPACT_FIRST_BREAK_SPLIT= 32; // compact mode, but will first try to break before first fragment /** * foobar( * <ul> * <li>#fragment1,</li> * <li>#fragment2,</li> * <li>#fragment3</li> * <li>#fragment4,</li> * </ul> */ public static final int M_ONE_PER_LINE_SPLIT= 32 + 16; // one fragment per line /** * foobar( * <ul> * <li>#fragment1,</li> * <li>#fragment2,</li> * <li>#fragment3</li> * <li>#fragment4,</li> * </ul> */ public static final int M_NEXT_SHIFTED_SPLIT= 64; // one fragment per line, subsequent are indented further /** * foobar(#fragment1, * <ul> * <li>#fragment2,</li> * <li>#fragment3</li> * <li>#fragment4,</li> * </ul> */ public static final int M_NEXT_PER_LINE_SPLIT= 64 + 16; // one per line, except first fragment (if possible) //64+32 //64+32+16 // mode controlling column alignments /** * <table BORDER COLS=4 WIDTH="100%" > * <tr> * <td>#fragment1A</td> * <td>#fragment2A</td> * <td>#fragment3A</td> * <td>#very-long-fragment4A</td> * </tr> * <tr> * <td>#fragment1B</td> * <td>#long-fragment2B</td> * <td>#fragment3B</td> * <td>#fragment4B</td> * </tr> * <tr> * <td>#very-long-fragment1C</td> * <td>#fragment2C</td> * <td>#fragment3C</td> * <td>#fragment4C</td> * </tr> * </table> */ public static final int M_MULTICOLUMN= 256; // fragments are on same line, but multiple line of fragments will be aligned vertically public static final int M_NO_ALIGNMENT= 0; public int mode; public static final int SPLIT_MASK= M_ONE_PER_LINE_SPLIT | M_NEXT_SHIFTED_SPLIT | M_COMPACT_SPLIT | M_COMPACT_FIRST_BREAK_SPLIT | M_NEXT_PER_LINE_SPLIT; // alignment tie-break rules - when split is needed, will decide whether innermost/outermost alignment is to be chosen public static final int R_OUTERMOST= 1; public static final int R_INNERMOST= 2; public int tieBreakRule; public int startingColumn= -1; // alignment effects on a per fragment basis public static final int NONE= 0; public static final int BREAK= 1; // chunk kind public static final int CHUNK_FIELD= 1; public static final int CHUNK_METHOD= 2; public static final int CHUNK_TYPE= 3; public static final int CHUNK_ENUM= 4; // location to align and break on. public Alignment(int kind, int mode, int tieBreakRule, Scribe scribe, int fragmentCount, int sourceRestart, int continuationIndent) { Assert.isTrue(kind >= ALLOCATION && kind <= STRING_CONCATENATION); this.kind= kind; this.name= NAMES[kind]; this.location= new Location(scribe, sourceRestart); this.mode= mode; this.tieBreakRule= tieBreakRule; this.fragmentCount= fragmentCount; this.scribe= scribe; this.originalIndentationLevel= this.scribe.indentationLevel; this.wasSplit= false; // initialize the break indentation level, using modes and continuationIndentationLevel preference final int indentSize= this.scribe.indentationSize; int currentColumn= this.location.outputColumn; if (currentColumn == 1) { currentColumn= this.location.outputIndentationLevel + 1; } if ((mode & M_INDENT_ON_COLUMN) != 0) { // indent broken fragments at next indentation level, based on current column this.breakIndentationLevel= this.scribe.getNextIndentationLevel(currentColumn); if (this.breakIndentationLevel == this.location.outputIndentationLevel) { this.breakIndentationLevel+= (continuationIndent * indentSize); } } else if ((mode & M_INDENT_BY_ONE) != 0) { // indent broken fragments exactly one level deeper than current indentation this.breakIndentationLevel= this.location.outputIndentationLevel + indentSize; } else { this.breakIndentationLevel= this.location.outputIndentationLevel + continuationIndent * indentSize; } this.shiftBreakIndentationLevel= this.breakIndentationLevel + indentSize; this.fragmentIndentations= new int[this.fragmentCount]; this.fragmentBreaks= new int[this.fragmentCount]; // check for forced alignments if ((this.mode & M_FORCE) != 0) { couldBreak(); } } public boolean checkChunkStart(int chunk, int startIndex, int sourceRestart) { if (this.chunkKind != chunk) { this.chunkKind= chunk; // when redoing same chunk alignment, must not reset if (startIndex != this.chunkStartIndex) { this.chunkStartIndex= startIndex; this.location.update(this.scribe, sourceRestart); reset(); } return true; } return false; } public void checkColumn() { if ((this.mode & M_MULTICOLUMN) != 0) { int currentIndentation= this.scribe.getNextIndentationLevel(this.scribe.column + (this.scribe.needSpace ? 1 : 0)); int fragmentIndentation= this.fragmentIndentations[this.fragmentIndex]; if (currentIndentation > fragmentIndentation) { this.fragmentIndentations[this.fragmentIndex]= currentIndentation; if (fragmentIndentation != 0) { for (int i= this.fragmentIndex + 1; i < this.fragmentCount; i++) { this.fragmentIndentations[i]= 0; } this.needRedoColumnAlignment= true; } } // backtrack only once all fragments got checked if (this.needRedoColumnAlignment && this.fragmentIndex == this.fragmentCount - 1) { // alignment too small // if (CodeFormatterVisitor.DEBUG){ // System.out.println("ALIGNMENT TOO SMALL"); // System.out.println(this); // } this.needRedoColumnAlignment= false; int relativeDepth= 0; Alignment targetAlignment= this.scribe.memberAlignment; while (targetAlignment != null) { if (targetAlignment == this) { throw new AlignmentException(AlignmentException.ALIGN_TOO_SMALL, relativeDepth); } targetAlignment= targetAlignment.enclosing; relativeDepth++; } } } } public int depth() { int depth= 0; Alignment current= this.enclosing; while (current != null) { depth++; current= current.enclosing; } return depth; } /** * Returns whether the alignment can be aligned or not. Only used for message send alignment, it * currently blocks its alignment when it's at the first nesting level of a message send. It * allow to save space on the argument broken line by reducing the number of indentations. */ public boolean canAlign() { if (this.tooLong) { return true; } boolean canAlign= true; Alignment enclosingAlignment= this.enclosing; while (enclosingAlignment != null) { switch (enclosingAlignment.kind) { case Alignment.ALLOCATION: case Alignment.MESSAGE_ARGUMENTS: // message send inside arguments, avoid to align if (enclosingAlignment.isWrapped() && (enclosingAlignment.fragmentIndex > 0 || enclosingAlignment.fragmentCount < 2)) { return !this.blockAlign; } if (enclosingAlignment.tooLong) { return true; } canAlign= false; break; case Alignment.MESSAGE_SEND: // multiple depth of message send, hence allow current to align switch (this.kind) { case Alignment.ALLOCATION: case Alignment.MESSAGE_ARGUMENTS: case Alignment.MESSAGE_SEND: Alignment superEnclosingAlignment= enclosingAlignment.enclosing; while (superEnclosingAlignment != null) { switch (superEnclosingAlignment.kind) { case Alignment.ALLOCATION: case Alignment.MESSAGE_ARGUMENTS: case Alignment.MESSAGE_SEND: // block the alignment of the intermediate message send if (this.scribe.nlsTagCounter == 0) { enclosingAlignment.blockAlign= true; } return !this.blockAlign; } superEnclosingAlignment= superEnclosingAlignment.enclosing; } break; } return !this.blockAlign; } enclosingAlignment= enclosingAlignment.enclosing; } return canAlign && !this.blockAlign; } public boolean couldBreak() { int i; switch (this.mode & SPLIT_MASK) { /* # aligned fragment * foo( * #AAAAA, #BBBBB, * #CCCC); */ case M_COMPACT_FIRST_BREAK_SPLIT: if (this.fragmentBreaks[0] == NONE) { this.fragmentBreaks[0]= BREAK; this.fragmentIndentations[0]= this.breakIndentationLevel; return this.wasSplit= true; } i= this.fragmentIndex; do { if (this.fragmentBreaks[i] == NONE) { this.fragmentBreaks[i]= BREAK; this.fragmentIndentations[i]= this.breakIndentationLevel; return this.wasSplit= true; } } while (--i >= 0); break; /* # aligned fragment * foo(#AAAAA, #BBBBB, * #CCCC); */ case M_COMPACT_SPLIT: i= this.fragmentIndex; do { if (this.fragmentBreaks[i] == NONE) { this.fragmentBreaks[i]= BREAK; this.fragmentIndentations[i]= this.breakIndentationLevel; return this.wasSplit= true; } } while (--i >= 0); break; /* # aligned fragment * foo( * #AAAAA, * #BBBBB, * #CCCC); */ case M_NEXT_SHIFTED_SPLIT: if (this.fragmentBreaks[0] == NONE) { this.fragmentBreaks[0]= BREAK; this.fragmentIndentations[0]= this.breakIndentationLevel; for (i= 1; i < this.fragmentCount; i++) { this.fragmentBreaks[i]= BREAK; this.fragmentIndentations[i]= this.shiftBreakIndentationLevel; } return this.wasSplit= true; } break; /* # aligned fragment * foo( * #AAAAA, * #BBBBB, * #CCCC); */ case M_ONE_PER_LINE_SPLIT: if (this.fragmentBreaks[0] == NONE) { for (i= 0; i < this.fragmentCount; i++) { this.fragmentBreaks[i]= BREAK; this.fragmentIndentations[i]= this.breakIndentationLevel; } return this.wasSplit= true; } break; /* # aligned fragment * foo(#AAAAA, * #BBBBB, * #CCCC); */ case M_NEXT_PER_LINE_SPLIT: if (this.fragmentBreaks[0] == NONE) { if (this.fragmentCount > 1 && this.fragmentBreaks[1] == NONE) { if ((this.mode & M_INDENT_ON_COLUMN) != 0) { this.fragmentIndentations[0]= this.breakIndentationLevel; } for (i= 1; i < this.fragmentCount; i++) { this.fragmentBreaks[i]= BREAK; this.fragmentIndentations[i]= this.breakIndentationLevel; } return this.wasSplit= true; } } break; } return false; // cannot split better } public boolean isWrapped() { return this.fragmentBreaks[this.fragmentIndex] == BREAK; } public int wrappedIndex() { for (int i= 0, max= this.fragmentCount; i < max; i++) { if (this.fragmentBreaks[i] == BREAK) { return i; } } return -1; } // perform alignment effect for current fragment public void performFragmentEffect() { if ((this.mode & M_MULTICOLUMN) == 0) { switch (this.mode & SPLIT_MASK) { case Alignment.M_COMPACT_SPLIT: case Alignment.M_COMPACT_FIRST_BREAK_SPLIT: case Alignment.M_NEXT_PER_LINE_SPLIT: case Alignment.M_NEXT_SHIFTED_SPLIT: case Alignment.M_ONE_PER_LINE_SPLIT: break; default: return; } } int fragmentIndentation= this.fragmentIndentations[this.fragmentIndex]; if (this.startingColumn < 0 || (fragmentIndentation + 1) < this.startingColumn) { if (this.fragmentBreaks[this.fragmentIndex] == BREAK) { this.scribe.printNewLine(); } if (fragmentIndentation > 0) { this.scribe.indentationLevel= fragmentIndentation; } } } // reset fragment indentation/break status public void reset() { this.wasSplit= false; if (this.fragmentCount > 0) { this.fragmentIndentations= new int[this.fragmentCount]; this.fragmentBreaks= new int[this.fragmentCount]; } // check for forced alignments if ((this.mode & M_FORCE) != 0) { couldBreak(); } this.reset= true; } public void toFragmentsString(StringBuffer buffer) { // default implementation } public String toString() { StringBuffer buffer= new StringBuffer(10); return toString(buffer, -1); } public String toString(StringBuffer buffer, int level) { // Compute the indentation at the given level StringBuffer indentation= new StringBuffer(); for (int i= 0; i < level; i++) { indentation.append('\t'); } // First line is for class and name buffer.append(indentation); buffer .append("<kind: ") //$NON-NLS-1$ .append(this.kind) .append("> "); //$NON-NLS-1$ buffer .append("<name: ") //$NON-NLS-1$ .append(this.name) .append(">\n"); //$NON-NLS-1$ // Line for depth and break indentation buffer.append(indentation); buffer .append("<depth=") //$NON-NLS-1$ .append(depth()) .append("><breakIndent=") //$NON-NLS-1$ .append(this.breakIndentationLevel) .append("><shiftBreakIndent=") //$NON-NLS-1$ .append(this.shiftBreakIndentationLevel) .append(">\n"); //$NON-NLS-1$ // Line to display the location buffer.append(indentation); buffer .append("<location=") //$NON-NLS-1$ .append(this.location.toString()) .append(">\n"); //$NON-NLS-1$ // Lines for fragments buffer .append(indentation) .append("<fragments:\n"); //$NON-NLS-1$ for (int i= 0; i < this.fragmentCount; i++) { buffer .append(indentation) .append(" - ") //$NON-NLS-1$ .append(i) .append(": ") //$NON-NLS-1$ .append("<break: ") //$NON-NLS-1$ .append(this.fragmentBreaks[i] > 0 ? "YES" : "NO") //$NON-NLS-1$ //$NON-NLS-2$ .append(">") //$NON-NLS-1$ .append("<indent: ") //$NON-NLS-1$ .append(this.fragmentIndentations[i]) .append(">\n"); //$NON-NLS-1$ } buffer .append(indentation) .append(">\n"); //$NON-NLS-1$ // Display enclosing if (this.enclosing != null && level >= 0) { buffer .append(indentation) .append("<enclosing assignement:\n"); //$NON-NLS-1$ this.enclosing.toString(buffer, level + 1); buffer .append(indentation) .append(">\n"); //$NON-NLS-1$ } // Return the result return buffer.toString(); } public void update() { for (int i= 1; i < this.fragmentCount; i++) { if (this.fragmentBreaks[i] == BREAK) { this.fragmentIndentations[i]= this.breakIndentationLevel; } } } public boolean wasReset() { return this.reset; } }