KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > formatter > comment > CommentRegion


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11
12 package org.eclipse.jdt.internal.formatter.comment;
13
14 import java.util.Iterator JavaDoc;
15 import java.util.LinkedList JavaDoc;
16
17 import org.eclipse.core.runtime.Assert;
18
19 import org.eclipse.text.edits.MalformedTreeException;
20 import org.eclipse.text.edits.TextEdit;
21
22 import org.eclipse.jface.text.BadLocationException;
23 import org.eclipse.jface.text.DefaultLineTracker;
24 import org.eclipse.jface.text.IDocument;
25 import org.eclipse.jface.text.ILineTracker;
26 import org.eclipse.jface.text.IRegion;
27 import org.eclipse.jface.text.Position;
28
29 import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
30 import org.eclipse.jdt.internal.formatter.CodeFormatterVisitor;
31 import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions;
32 import org.eclipse.jdt.internal.formatter.Scribe;
33
34 /**
35  * Comment region in a source code document.
36  *
37  * @since 3.0
38  */

39 public class CommentRegion extends Position implements IHtmlTagDelimiters, IBorderAttributes, ICommentAttributes {
40
41     /** Default comment range delimiter */
42     protected static final String JavaDoc COMMENT_RANGE_DELIMITER= " "; //$NON-NLS-1$
43

44     /** Default line prefix length */
45     private static final int COMMENT_PREFIX_LENGTH= 3;
46
47     /** The borders of this region */
48     private int fBorders= 0;
49
50     /** Should all blank lines be cleared during formatting? */
51     protected boolean fClear;
52
53     /** The line delimiter used in this comment region */
54     private final String JavaDoc fDelimiter;
55
56     /** The document to format */
57     private final IDocument fDocument;
58
59     /** The lines in this comment region */
60     private final LinkedList JavaDoc fLines= new LinkedList JavaDoc();
61     
62     /** The formatting preferences */
63     protected final DefaultCodeFormatterOptions preferences;
64     
65     /** The comment ranges in this comment region */
66     private final LinkedList JavaDoc fRanges= new LinkedList JavaDoc();
67
68     /** Is this comment region a single line region? */
69     private final boolean fSingleLine;
70
71     /** Number of spaces representing tabulator */
72     private int fTabSize;
73
74     /** the scribe used to create edits */
75     protected Scribe scribe;
76
77     /**
78      * Creates a new comment region.
79      *
80      * @param document the document which contains the comment region
81      * @param position the position of this comment region in the document
82      * @param formatter the given code formatter
83      */

84     public CommentRegion(final IDocument document, final Position position, final CodeFormatterVisitor formatter) {
85         super(position.getOffset(), position.getLength());
86
87         this.preferences = formatter.preferences;
88         fDelimiter = this.preferences.line_separator;
89         fDocument= document;
90         
91         fTabSize= DefaultCodeFormatterOptions.SPACE == this.preferences.tab_char ? this.preferences.indentation_size : this.preferences.tab_size;
92
93         this.scribe = formatter.scribe;
94
95         final ILineTracker tracker= new DefaultLineTracker();
96
97         IRegion range= null;
98         CommentLine line= null;
99
100         tracker.set(getText(0, getLength()));
101         final int lines= tracker.getNumberOfLines();
102
103         fSingleLine= lines == 1;
104
105         try {
106
107             for (int index= 0; index < lines; index++) {
108
109                 range= tracker.getLineInformation(index);
110                 line= createLine();
111                 line.append(new CommentRange(range.getOffset(), range.getLength()));
112
113                 fLines.add(line);
114             }
115
116         } catch (BadLocationException exception) {
117             // Should not happen
118
}
119     }
120
121     /**
122      * Appends the comment range to this comment region.
123      *
124      * @param range comment range to append to this comment region
125      */

126     protected final void append(final CommentRange range) {
127         fRanges.addLast(range);
128     }
129
130     /**
131      * Can the comment range be appended to the comment line?
132      *
133      * @param line comment line where to append the comment range
134      * @param previous comment range which is the predecessor of the current
135      * comment range
136      * @param next comment range to test whether it can be appended to the
137      * comment line
138      * @param index amount of space in the comment line used by already
139      * inserted comment ranges
140      * @param width the maximal width of text in this comment region
141      * measured in average character widths
142      * @return <code>true</code> iff the comment range can be added to the
143      * line, <code>false</code> otherwise
144      */

145     protected boolean canAppend(final CommentLine line, final CommentRange previous, final CommentRange next, final int index, final int width) {
146         return index == 0 || index + next.getLength() <= width;
147     }
148
149     /**
150      * Can the whitespace between the two comment ranges be formatted?
151      *
152      * @param previous previous comment range which was already formatted,
153      * can be <code>null</code>
154      * @param next next comment range to be formatted
155      * @return <code>true</code> iff the next comment range can be
156      * formatted, <code>false</code> otherwise.
157      */

158     protected boolean canFormat(final CommentRange previous, final CommentRange next) {
159         return previous != null;
160     }
161
162     /**
163      * Formats the comment region with the given indentation level.
164      *
165      * @param indentationLevel the indentation level
166      * @return the resulting text edit of the formatting process
167      * @since 3.1
168      */

169     public final TextEdit format(int indentationLevel, boolean returnEdit) {
170         final String JavaDoc probe= getText(0, CommentLine.NON_FORMAT_START_PREFIX.length());
171         if (!probe.startsWith(CommentLine.NON_FORMAT_START_PREFIX)) {
172
173             int margin= this.preferences.comment_line_length;
174             String JavaDoc indentation= computeIndentation(indentationLevel);
175             margin= Math.max(COMMENT_PREFIX_LENGTH + 1, margin - stringToLength(indentation) - COMMENT_PREFIX_LENGTH);
176
177             tokenizeRegion();
178             markRegion();
179             wrapRegion(margin);
180             formatRegion(indentation, margin);
181
182         }
183         if (returnEdit) {
184             return this.scribe.getRootEdit();
185         }
186         return null;
187     }
188
189     /**
190      * Formats this comment region.
191      *
192      * @param indentation the indentation of this comment region
193      * @param width the maximal width of text in this comment region
194      * measured in average character widths
195      */

196     protected void formatRegion(final String JavaDoc indentation, final int width) {
197
198         final int last= fLines.size() - 1;
199         if (last >= 0) {
200
201             CommentLine lastLine= (CommentLine)fLines.get(last);
202             CommentRange lastRange= lastLine.getLast();
203             lastLine.formatLowerBorder(lastRange, indentation, width);
204
205             CommentLine previous;
206             CommentLine next= null;
207             CommentRange range= null;
208             for (int line= last; line >= 0; line--) {
209
210                 previous= next;
211                 next= (CommentLine)fLines.get(line);
212
213                 range= next.formatLine(previous, range, indentation, line);
214             }
215             next.formatUpperBorder(range, indentation, width);
216         }
217     }
218
219     /**
220      * Returns the line delimiter used in this comment region.
221      *
222      * @return the line delimiter for this comment region
223      */

224     protected final String JavaDoc getDelimiter() {
225         return fDelimiter;
226     }
227
228     /**
229      * Returns the line delimiter used in this comment line break.
230      *
231      * @param predecessor the predecessor comment line after the line break
232      * @param successor the successor comment line before the line break
233      * @param previous the comment range after the line break
234      * @param next the comment range before the line break
235      * @param indentation indentation of the formatted line break
236      * @return the line delimiter for this comment line break
237      */

238     protected String JavaDoc getDelimiter(final CommentLine predecessor, final CommentLine successor, final CommentRange previous, final CommentRange next, final String JavaDoc indentation) {
239         return fDelimiter + indentation + successor.getContentPrefix();
240     }
241
242     /**
243      * Returns the range delimiter for this comment range break.
244      *
245      * @param previous the previous comment range to the right of the range
246      * delimiter
247      * @param next the next comment range to the left of the range delimiter
248      * @return the delimiter for this comment range break
249      */

250     protected String JavaDoc getDelimiter(final CommentRange previous, final CommentRange next) {
251         return COMMENT_RANGE_DELIMITER;
252     }
253
254     /**
255      * Returns the document of this comment region.
256      *
257      * @return the document of this region
258      */

259     protected final IDocument getDocument() {
260         return fDocument;
261     }
262
263     /**
264      * Returns the comment ranges in this comment region
265      *
266      * @return the comment ranges in this region
267      */

268     protected final LinkedList JavaDoc getRanges() {
269         return fRanges;
270     }
271
272     /**
273      * Returns the number of comment lines in this comment region.
274      *
275      * @return the number of lines in this comment region
276      */

277     protected final int getSize() {
278         return fLines.size();
279     }
280
281     /**
282      * Returns the text of this comment region in the indicated range.
283      *
284      * @param position the offset of the comment range to retrieve in
285      * comment region coordinates
286      * @param count the length of the comment range to retrieve
287      * @return the content of this comment region in the indicated range
288      */

289     protected final String JavaDoc getText(final int position, final int count) {
290
291         String JavaDoc content= ""; //$NON-NLS-1$
292
try {
293             content= fDocument.get(getOffset() + position, count);
294         } catch (BadLocationException exception) {
295             // Should not happen
296
}
297         return content;
298     }
299
300     /**
301      * Does the border <code>border</code> exist?
302      *
303      * @param border the type of the border, must be a border attribute of
304      * <code>CommentRegion</code>
305      * @return <code>true</code> iff this border exists,
306      * <code>false</code> otherwise
307      */

308     protected final boolean hasBorder(final int border) {
309         return (fBorders & border) == border;
310     }
311
312     /**
313      * Does the comment range consist of letters and digits only?
314      *
315      * @param range the comment range to text
316      * @return <code>true</code> iff the comment range consists of letters
317      * and digits only, <code>false</code> otherwise
318      */

319     protected final boolean isAlphaNumeric(final CommentRange range) {
320
321         final String JavaDoc token= getText(range.getOffset(), range.getLength());
322
323         for (int index= 0; index < token.length(); index++) {
324             if (!ScannerHelper.isLetterOrDigit(token.charAt(index)))
325                 return false;
326         }
327         return true;
328     }
329
330     /**
331      * Does the comment range contain no letters and digits?
332      *
333      * @param range the comment range to text
334      * @return <code>true</code> iff the comment range contains no letters
335      * and digits, <code>false</code> otherwise
336      */

337     protected final boolean isNonAlphaNumeric(final CommentRange range) {
338
339         final String JavaDoc token= getText(range.getOffset(), range.getLength());
340
341         for (int index= 0; index < token.length(); index++) {
342             if (ScannerHelper.isLetterOrDigit(token.charAt(index)))
343                 return false;
344         }
345         return true;
346     }
347
348     /**
349      * Should blank lines be cleared during formatting?
350      *
351      * @return <code>true</code> iff blank lines should be cleared,
352      * <code>false</code> otherwise
353      */

354     protected final boolean isClearLines() {
355         return fClear;
356     }
357
358     /**
359      * Is this comment region a single line region?
360      *
361      * @return <code>true</code> iff this region is single line,
362      * <code>false</code> otherwise
363      */

364     protected final boolean isSingleLine() {
365         return fSingleLine;
366     }
367
368     /**
369      * Logs a text edit operation occurred during the formatting process
370      *
371      * @param change the changed text
372      * @param position offset measured in comment region coordinates where
373      * to apply the changed text
374      * @param count length of the range where to apply the changed text
375      */

376     protected final void logEdit(final String JavaDoc change, final int position, final int count) {
377         try {
378             final int base= getOffset() + position;
379             final String JavaDoc content= fDocument.get(base, count);
380
381             if (!change.equals(content)) {
382                 if (count > 0) {
383                     this.scribe.addReplaceEdit(base, base + count - 1, change);
384                 } else {
385                     this.scribe.addInsertEdit(base, change);
386                 }
387             }
388         } catch (BadLocationException exception) {
389             // Should not happen
390
CommentFormatterUtil.log(exception);
391         } catch (MalformedTreeException exception) {
392             // Do nothing
393
CommentFormatterUtil.log(exception);
394         }
395     }
396
397     /**
398      * Marks the comment ranges in this comment region.
399      */

400     protected void markRegion() {
401         // Do nothing
402
}
403
404     /**
405      * Set the border type <code>border</code> to true.
406      *
407      * @param border the type of the border. Must be a border attribute of
408      * <code>CommentRegion</code>
409      */

410     protected final void setBorder(final int border) {
411         fBorders |= border;
412     }
413
414     /**
415      * Returns the indentation of the given indentation level.
416      *
417      * @param indentationLevel the indentation level
418      * @return the indentation of the given indentation level
419      * @since 3.1
420      */

421     private String JavaDoc computeIndentation(int indentationLevel) {
422         if (DefaultCodeFormatterOptions.TAB == this.preferences.tab_char)
423             return replicate("\t", indentationLevel); //$NON-NLS-1$
424

425         if (DefaultCodeFormatterOptions.SPACE == this.preferences.tab_char)
426             return replicate(" ", indentationLevel * this.preferences.tab_size); //$NON-NLS-1$
427

428         if (DefaultCodeFormatterOptions.MIXED == this.preferences.tab_char) {
429             int tabSize= this.preferences.tab_size;
430             int indentSize= this.preferences.indentation_size;
431             int spaceEquivalents= indentationLevel * indentSize;
432             return replicate("\t", spaceEquivalents / tabSize) + replicate(" ", spaceEquivalents % tabSize); //$NON-NLS-1$ //$NON-NLS-2$
433
}
434         
435         Assert.isTrue(false);
436         return null;
437     }
438     
439     /**
440      * Returns the given string n-times replicated.
441      *
442      * @param string the string
443      * @param n n
444      * @return the given string n-times replicated
445      * @since 3.1
446      */

447     private String JavaDoc replicate(String JavaDoc string, int n) {
448         StringBuffer JavaDoc buffer= new StringBuffer JavaDoc(n*string.length());
449         for (int i= 0; i < n; i++)
450             buffer.append(string);
451         return buffer.toString();
452     }
453
454     /**
455      * Computes the equivalent indentation for a string
456      *
457      * @param reference the string to compute the indentation for
458      * @return the indentation string
459      */

460     protected final String JavaDoc stringToIndent(final String JavaDoc reference) {
461         return replicate(" ", stringToLength(reference)); //$NON-NLS-1$
462
}
463
464     /**
465      * Returns the length of the string in expanded characters.
466      *
467      * @param reference the string to get the length for
468      * @return the length of the string in expanded characters
469      */

470     protected final int stringToLength(final String JavaDoc reference) {
471         return expandTabs(reference).length();
472     }
473
474     /**
475      * Expands the given string's tabs according to the given tab size.
476      *
477      * @param string the string
478      * @return the expanded string
479      * @since 3.1
480      */

481     private String JavaDoc expandTabs(String JavaDoc string) {
482         StringBuffer JavaDoc expanded= new StringBuffer JavaDoc();
483         for (int i= 0, n= string.length(), chars= 0; i < n; i++) {
484             char ch= string.charAt(i);
485             if (ch == '\t') {
486                 for (; chars < fTabSize; chars++)
487                     expanded.append(' ');
488                 chars= 0;
489             } else {
490                 expanded.append(ch);
491                 chars++;
492                 if (chars >= fTabSize)
493                     chars= 0;
494             }
495         
496         }
497         return expanded.toString();
498     }
499
500     /**
501      * Tokenizes the comment region.
502      */

503     protected void tokenizeRegion() {
504
505         int index= 0;
506         CommentLine line= null;
507
508         for (final Iterator JavaDoc iterator= fLines.iterator(); iterator.hasNext(); index++) {
509
510             line= (CommentLine)iterator.next();
511
512             line.scanLine(index);
513             line.tokenizeLine(index);
514         }
515     }
516
517     /**
518      * Wraps the comment ranges in this comment region into comment lines.
519      *
520      * @param width the maximal width of text in this comment region
521      * measured in average character widths
522      */

523     protected void wrapRegion(final int width) {
524
525         fLines.clear();
526
527         int index= 0;
528         boolean adapted= false;
529
530         CommentLine successor= null;
531         CommentLine predecessor= null;
532
533         CommentRange previous= null;
534         CommentRange next= null;
535
536         while (!fRanges.isEmpty()) {
537
538             index= 0;
539             adapted= false;
540
541             predecessor= successor;
542             successor= createLine();
543             fLines.add(successor);
544
545             while (!fRanges.isEmpty()) {
546                 next= (CommentRange)fRanges.getFirst();
547
548                 if (canAppend(successor, previous, next, index, width)) {
549
550                     if (!adapted && predecessor != null) {
551
552                         successor.adapt(predecessor);
553                         adapted= true;
554                     }
555
556                     fRanges.removeFirst();
557                     successor.append(next);
558
559                     index += (next.getLength() + 1);
560                     previous= next;
561                 } else
562                     break;
563             }
564         }
565     }
566
567     /**
568      * Creates a new line for this region.
569      *
570      * @return a new line for this region
571      * @since 3.1
572      */

573     protected CommentLine createLine() {
574         return new SingleCommentLine(this);
575     }
576 }
577
Popular Tags