001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.formatter.align;
011:
012: import org.eclipse.jdt.internal.formatter.Location;
013: import org.eclipse.jdt.internal.formatter.Scribe;
014:
015: /**
016: * Alignment management
017: *
018: * @since 2.1
019: */
020: public class Alignment {
021:
022: // name of alignment
023: public String name;
024:
025: // link to enclosing alignment
026: public Alignment enclosing;
027:
028: // start location of this alignment
029: public Location location;
030:
031: // indentation management
032: public int fragmentIndex;
033: public int fragmentCount;
034: public int[] fragmentIndentations;
035: public boolean needRedoColumnAlignment;
036:
037: // chunk management
038: public int chunkStartIndex;
039: public int chunkKind;
040:
041: // break management
042: public int originalIndentationLevel;
043: public int breakIndentationLevel;
044: public int shiftBreakIndentationLevel;
045: public int[] fragmentBreaks;
046: public boolean wasSplit;
047:
048: public Scribe scribe;
049:
050: /*
051: * Alignment modes
052: */
053: public static final int M_FORCE = 1; // if bit set, then alignment will be non-optional (default is optional)
054: 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)
055: 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)
056:
057: // split modes can be combined either with M_FORCE or M_INDENT_ON_COLUMN
058:
059: /** foobar(#fragment1, #fragment2, <ul>
060: * <li> #fragment3, #fragment4 </li>
061: * </ul>
062: */
063: public static final int M_COMPACT_SPLIT = 16; // fill each line with all possible fragments
064:
065: /** foobar(<ul>
066: * <li> #fragment1, #fragment2, </li>
067: * <li> #fragment5, #fragment4, </li>
068: * </ul>
069: */
070: public static final int M_COMPACT_FIRST_BREAK_SPLIT = 32; // compact mode, but will first try to break before first fragment
071:
072: /** foobar(<ul>
073: * <li> #fragment1, </li>
074: * <li> #fragment2, </li>
075: * <li> #fragment3 </li>
076: * <li> #fragment4, </li>
077: * </ul>
078: */
079: public static final int M_ONE_PER_LINE_SPLIT = 32 + 16; // one fragment per line
080:
081: /**
082: * foobar(<ul>
083: * <li> #fragment1, </li>
084: * <li> #fragment2, </li>
085: * <li> #fragment3 </li>
086: * <li> #fragment4, </li>
087: * </ul>
088: */
089: public static final int M_NEXT_SHIFTED_SPLIT = 64; // one fragment per line, subsequent are indented further
090:
091: /** foobar(#fragment1, <ul>
092: * <li> #fragment2, </li>
093: * <li> #fragment3 </li>
094: * <li> #fragment4, </li>
095: * </ul>
096: */
097: public static final int M_NEXT_PER_LINE_SPLIT = 64 + 16; // one per line, except first fragment (if possible)
098:
099: //64+32
100: //64+32+16
101:
102: // mode controlling column alignments
103: /**
104: * <table BORDER COLS=4 WIDTH="100%" >
105: * <tr><td>#fragment1A</td> <td>#fragment2A</td> <td>#fragment3A</td> <td>#very-long-fragment4A</td></tr>
106: * <tr><td>#fragment1B</td> <td>#long-fragment2B</td> <td>#fragment3B</td> <td>#fragment4B</td></tr>
107: * <tr><td>#very-long-fragment1C</td> <td>#fragment2C</td> <td>#fragment3C</td> <td>#fragment4C</td></tr>
108: * </table>
109: */
110: public static final int M_MULTICOLUMN = 256; // fragments are on same line, but multiple line of fragments will be aligned vertically
111:
112: public static final int M_NO_ALIGNMENT = 0;
113:
114: public int mode;
115:
116: public static final int SPLIT_MASK = M_ONE_PER_LINE_SPLIT
117: | M_NEXT_SHIFTED_SPLIT | M_COMPACT_SPLIT
118: | M_COMPACT_FIRST_BREAK_SPLIT | M_NEXT_PER_LINE_SPLIT;
119:
120: // alignment tie-break rules - when split is needed, will decide whether innermost/outermost alignment is to be chosen
121: public static final int R_OUTERMOST = 1;
122: public static final int R_INNERMOST = 2;
123: public int tieBreakRule;
124:
125: // alignment effects on a per fragment basis
126: public static final int NONE = 0;
127: public static final int BREAK = 1;
128:
129: // chunk kind
130: public static final int CHUNK_FIELD = 1;
131: public static final int CHUNK_METHOD = 2;
132: public static final int CHUNK_TYPE = 3;
133: public static final int CHUNK_ENUM = 4;
134:
135: // location to align and break on.
136: public Alignment(String name, int mode, int tieBreakRule,
137: Scribe scribe, int fragmentCount, int sourceRestart,
138: int continuationIndent) {
139:
140: this .name = name;
141: this .location = new Location(scribe, sourceRestart);
142: this .mode = mode;
143: this .tieBreakRule = tieBreakRule;
144: this .fragmentCount = fragmentCount;
145: this .scribe = scribe;
146: this .originalIndentationLevel = this .scribe.indentationLevel;
147: this .wasSplit = false;
148:
149: // initialize the break indentation level, using modes and continuationIndentationLevel preference
150: final int indentSize = this .scribe.indentationSize;
151: int currentColumn = this .location.outputColumn;
152: if (currentColumn == 1) {
153: currentColumn = this .location.outputIndentationLevel + 1;
154: }
155:
156: if ((mode & M_INDENT_ON_COLUMN) != 0) {
157: // indent broken fragments at next indentation level, based on current column
158: this .breakIndentationLevel = this .scribe
159: .getNextIndentationLevel(currentColumn);
160: if (this .breakIndentationLevel == this .location.outputIndentationLevel) {
161: this .breakIndentationLevel += (continuationIndent * indentSize);
162: }
163: } else if ((mode & M_INDENT_BY_ONE) != 0) {
164: // indent broken fragments exactly one level deeper than current indentation
165: this .breakIndentationLevel = this .location.outputIndentationLevel
166: + indentSize;
167: } else {
168: this .breakIndentationLevel = this .location.outputIndentationLevel
169: + continuationIndent * indentSize;
170: }
171: this .shiftBreakIndentationLevel = this .breakIndentationLevel
172: + indentSize;
173:
174: this .fragmentIndentations = new int[this .fragmentCount];
175: this .fragmentBreaks = new int[this .fragmentCount];
176:
177: // check for forced alignments
178: if ((this .mode & M_FORCE) != 0) {
179: couldBreak();
180: }
181: }
182:
183: public boolean checkChunkStart(int kind, int startIndex,
184: int sourceRestart) {
185: if (this .chunkKind != kind) {
186: this .chunkKind = kind;
187:
188: // when redoing same chunk alignment, must not reset
189: if (startIndex != this .chunkStartIndex) {
190: this .chunkStartIndex = startIndex;
191: this .location.update(this .scribe, sourceRestart);
192: reset();
193: }
194: return true;
195: }
196: return false;
197: }
198:
199: public void checkColumn() {
200: if ((this .mode & M_MULTICOLUMN) != 0) {
201: int currentIndentation = this .scribe
202: .getNextIndentationLevel(this .scribe.column
203: + (this .scribe.needSpace ? 1 : 0));
204: int fragmentIndentation = this .fragmentIndentations[this .fragmentIndex];
205: if (currentIndentation > fragmentIndentation) {
206: this .fragmentIndentations[this .fragmentIndex] = currentIndentation;
207: if (fragmentIndentation != 0) {
208: for (int i = this .fragmentIndex + 1; i < this .fragmentCount; i++) {
209: this .fragmentIndentations[i] = 0;
210: }
211: this .needRedoColumnAlignment = true;
212: }
213: }
214: // backtrack only once all fragments got checked
215: if (this .needRedoColumnAlignment
216: && this .fragmentIndex == this .fragmentCount - 1) { // alignment too small
217:
218: // if (CodeFormatterVisitor.DEBUG){
219: // System.out.println("ALIGNMENT TOO SMALL");
220: // System.out.println(this);
221: // }
222: this .needRedoColumnAlignment = false;
223: int relativeDepth = 0;
224: Alignment targetAlignment = this .scribe.memberAlignment;
225: while (targetAlignment != null) {
226: if (targetAlignment == this ) {
227: throw new AlignmentException(
228: AlignmentException.ALIGN_TOO_SMALL,
229: relativeDepth);
230: }
231: targetAlignment = targetAlignment.enclosing;
232: relativeDepth++;
233: }
234: }
235: }
236: }
237:
238: public boolean couldBreak() {
239: int i;
240: switch (mode & SPLIT_MASK) {
241:
242: /* # aligned fragment
243: * foo(
244: * #AAAAA, #BBBBB,
245: * #CCCC);
246: */
247: case M_COMPACT_FIRST_BREAK_SPLIT:
248: if (this .fragmentBreaks[0] == NONE) {
249: this .fragmentBreaks[0] = BREAK;
250: this .fragmentIndentations[0] = this .breakIndentationLevel;
251: return wasSplit = true;
252: }
253: i = this .fragmentIndex;
254: do {
255: if (this .fragmentBreaks[i] == NONE) {
256: this .fragmentBreaks[i] = BREAK;
257: this .fragmentIndentations[i] = this .breakIndentationLevel;
258: return wasSplit = true;
259: }
260: } while (--i >= 0);
261: break;
262: /* # aligned fragment
263: * foo(#AAAAA, #BBBBB,
264: * #CCCC);
265: */
266: case M_COMPACT_SPLIT:
267: i = this .fragmentIndex;
268: do {
269: if (this .fragmentBreaks[i] == NONE) {
270: this .fragmentBreaks[i] = BREAK;
271: this .fragmentIndentations[i] = this .breakIndentationLevel;
272: return wasSplit = true;
273: }
274: } while (--i >= 0);
275: break;
276:
277: /* # aligned fragment
278: * foo(
279: * #AAAAA,
280: * #BBBBB,
281: * #CCCC);
282: */
283: case M_NEXT_SHIFTED_SPLIT:
284: if (this .fragmentBreaks[0] == NONE) {
285: this .fragmentBreaks[0] = BREAK;
286: this .fragmentIndentations[0] = this .breakIndentationLevel;
287: for (i = 1; i < this .fragmentCount; i++) {
288: this .fragmentBreaks[i] = BREAK;
289: this .fragmentIndentations[i] = this .shiftBreakIndentationLevel;
290: }
291: return wasSplit = true;
292: }
293: break;
294:
295: /* # aligned fragment
296: * foo(
297: * #AAAAA,
298: * #BBBBB,
299: * #CCCC);
300: */
301: case M_ONE_PER_LINE_SPLIT:
302: if (this .fragmentBreaks[0] == NONE) {
303: for (i = 0; i < this .fragmentCount; i++) {
304: this .fragmentBreaks[i] = BREAK;
305: this .fragmentIndentations[i] = this .breakIndentationLevel;
306: }
307: return wasSplit = true;
308: }
309: /* # aligned fragment
310: * foo(#AAAAA,
311: * #BBBBB,
312: * #CCCC);
313: */
314: case M_NEXT_PER_LINE_SPLIT:
315: if (this .fragmentBreaks[0] == NONE) {
316: if (this .fragmentCount > 1
317: && this .fragmentBreaks[1] == NONE) {
318: if ((this .mode & M_INDENT_ON_COLUMN) != 0) {
319: this .fragmentIndentations[0] = this .breakIndentationLevel;
320: }
321: for (i = 1; i < this .fragmentCount; i++) {
322: this .fragmentBreaks[i] = BREAK;
323: this .fragmentIndentations[i] = this .breakIndentationLevel;
324: }
325: return wasSplit = true;
326: }
327: }
328: break;
329: }
330: return false; // cannot split better
331: }
332:
333: public Alignment getAlignment(String targetName) {
334:
335: if (targetName.equals(this .name))
336: return this ;
337: if (this .enclosing == null)
338: return null;
339:
340: return this .enclosing.getAlignment(targetName);
341: }
342:
343: // perform alignment effect for current fragment
344: public void performFragmentEffect() {
345: if ((this .mode & M_MULTICOLUMN) == 0) {
346: switch (this .mode & SPLIT_MASK) {
347: case Alignment.M_COMPACT_SPLIT:
348: case Alignment.M_COMPACT_FIRST_BREAK_SPLIT:
349: case Alignment.M_NEXT_PER_LINE_SPLIT:
350: case Alignment.M_NEXT_SHIFTED_SPLIT:
351: case Alignment.M_ONE_PER_LINE_SPLIT:
352: break;
353: default:
354: return;
355: }
356: }
357:
358: if (this .fragmentBreaks[this .fragmentIndex] == BREAK) {
359: this .scribe.printNewLine();
360: }
361: if (this .fragmentIndentations[this .fragmentIndex] > 0) {
362: this .scribe.indentationLevel = this .fragmentIndentations[this .fragmentIndex];
363: }
364: }
365:
366: // reset fragment indentation/break status
367: public void reset() {
368:
369: if (fragmentCount > 0) {
370: this .fragmentIndentations = new int[this .fragmentCount];
371: this .fragmentBreaks = new int[this .fragmentCount];
372: }
373:
374: // check for forced alignments
375: if ((mode & M_FORCE) != 0) {
376: couldBreak();
377: }
378: }
379:
380: public void toFragmentsString(StringBuffer buffer) {
381: // default implementation
382: }
383:
384: public String toString() {
385: StringBuffer buffer = new StringBuffer(10);
386: buffer.append(getClass().getName()).append(':').append(
387: "<name: ") //$NON-NLS-1$
388: .append(this .name).append(">"); //$NON-NLS-1$
389: if (this .enclosing != null) {
390: buffer.append("<enclosingName: ") //$NON-NLS-1$
391: .append(this .enclosing.name).append('>');
392: }
393: buffer.append('\n');
394:
395: for (int i = 0; i < this .fragmentCount; i++) {
396: buffer.append(" - fragment ") //$NON-NLS-1$
397: .append(i).append(": ") //$NON-NLS-1$
398: .append("<break: ") //$NON-NLS-1$
399: .append(this .fragmentBreaks[i] > 0 ? "YES" : "NO") //$NON-NLS-1$ //$NON-NLS-2$
400: .append(">") //$NON-NLS-1$
401: .append("<indent: ") //$NON-NLS-1$
402: .append(this .fragmentIndentations[i]).append(">\n"); //$NON-NLS-1$
403: }
404: buffer.append('\n');
405: return buffer.toString();
406: }
407:
408: public void update() {
409: for (int i = 1; i < this .fragmentCount; i++) {
410: if (this .fragmentBreaks[i] == BREAK) {
411: this .fragmentIndentations[i] = this .breakIndentationLevel;
412: }
413: }
414: }
415:
416: public boolean isWrapped() {
417: for (int i = 0, max = this .fragmentCount; i < max; i++) {
418: if (this .fragmentBreaks[i] == BREAK) {
419: return true;
420: }
421: }
422: return false;
423: }
424: }
|