| /******************************************************************************* |
| * Copyright (c) 2000, 2005 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.jdt.internal.formatter.Location; |
| import org.eclipse.jdt.internal.formatter.Scribe; |
| |
| /** |
| * Alignment management |
| * |
| * @since 2.1 |
| */ |
| public class Alignment { |
| |
| // name of alignment |
| public String name; |
| |
| // 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 Scribe scribe; |
| |
| /* |
| * 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; |
| |
| // alignment effects on a per fragment basis |
| public static int NONE = 0; |
| public static 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(String name, int mode, int tieBreakRule, Scribe scribe, int fragmentCount, int sourceRestart, int continuationIndent){ |
| |
| this.name = name; |
| 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 kind, int startIndex, int sourceRestart) { |
| if (this.chunkKind != kind) { |
| this.chunkKind = kind; |
| |
| // 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 boolean couldBreak(){ |
| int i; |
| switch(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 wasSplit = true; |
| } |
| i = this.fragmentIndex; |
| do { |
| if (this.fragmentBreaks[i] == NONE) { |
| this.fragmentBreaks[i] = BREAK; |
| this.fragmentIndentations[i] = this.breakIndentationLevel; |
| return 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 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 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 wasSplit = true; |
| } |
| /* # 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 wasSplit = true; |
| } |
| } |
| break; |
| } |
| return false; // cannot split better |
| } |
| |
| public Alignment getAlignment(String targetName) { |
| |
| if (targetName.equals(this.name)) return this; |
| if (this.enclosing == null) return null; |
| |
| return this.enclosing.getAlignment(targetName); |
| } |
| |
| // 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; |
| } |
| } |
| |
| if (this.fragmentBreaks[this.fragmentIndex] == BREAK) { |
| this.scribe.printNewLine(); |
| } |
| if (this.fragmentIndentations[this.fragmentIndex] > 0) { |
| this.scribe.indentationLevel = this.fragmentIndentations[this.fragmentIndex]; |
| } |
| } |
| |
| // reset fragment indentation/break status |
| public void reset() { |
| |
| if (fragmentCount > 0){ |
| this.fragmentIndentations = new int[this.fragmentCount]; |
| this.fragmentBreaks = new int[this.fragmentCount]; |
| } |
| |
| // check for forced alignments |
| if ((mode & M_FORCE) != 0) { |
| couldBreak(); |
| } |
| } |
| |
| public void toFragmentsString(StringBuffer buffer){ |
| // default implementation |
| } |
| |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(10); |
| buffer |
| .append(getClass().getName()) |
| .append(':') |
| .append("<name: ") //$NON-NLS-1$ |
| .append(this.name) |
| .append(">"); //$NON-NLS-1$ |
| if (this.enclosing != null) { |
| buffer |
| .append("<enclosingName: ") //$NON-NLS-1$ |
| .append(this.enclosing.name) |
| .append('>'); |
| } |
| buffer.append('\n'); |
| |
| for (int i = 0; i < this.fragmentCount; i++){ |
| buffer |
| .append(" - fragment ") //$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('\n'); |
| return buffer.toString(); |
| } |
| |
| public void update() { |
| for (int i = 1; i < this.fragmentCount; i++){ |
| if (this.fragmentBreaks[i] == BREAK) { |
| this.fragmentIndentations[i] = this.breakIndentationLevel; |
| } |
| } |
| } |
| } |