KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > formatter > ContentFormatter


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 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.jface.text.formatter;
13
14
15 import java.util.ArrayList JavaDoc;
16 import java.util.Collections JavaDoc;
17 import java.util.HashMap JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.Map JavaDoc;
20
21 import org.eclipse.core.runtime.Assert;
22
23 import org.eclipse.jface.text.BadLocationException;
24 import org.eclipse.jface.text.BadPositionCategoryException;
25 import org.eclipse.jface.text.DefaultPositionUpdater;
26 import org.eclipse.jface.text.DocumentEvent;
27 import org.eclipse.jface.text.IDocument;
28 import org.eclipse.jface.text.IDocumentExtension3;
29 import org.eclipse.jface.text.IPositionUpdater;
30 import org.eclipse.jface.text.IRegion;
31 import org.eclipse.jface.text.ITypedRegion;
32 import org.eclipse.jface.text.Position;
33 import org.eclipse.jface.text.TextUtilities;
34 import org.eclipse.jface.text.TypedPosition;
35
36
37 /**
38  * Standard implementation of <code>IContentFormatter</code>.
39  * The formatter supports two operation modes: partition aware and
40  * partition unaware. <p>
41  * In the partition aware mode, the formatter determines the
42  * partitioning of the document region to be formatted. For each
43  * partition it determines all document positions which are affected
44  * when text changes are applied to the partition. Those which overlap
45  * with the partition are remembered as character positions. These
46  * character positions are passed over to the formatting strategy
47  * registered for the partition's content type. The formatting strategy
48  * returns a string containing the formatted document partition as well
49  * as the adapted character positions. The formatted partition replaces
50  * the old content of the partition. The remembered document positions
51  * are updated with the adapted character positions. In addition, all
52  * other document positions are accordingly adapted to the formatting
53  * changes.<p>
54  * In the partition unaware mode, the document's partitioning is ignored
55  * and the document is considered consisting of only one partition of
56  * the content type <code>IDocument.DEFAULT_CONTENT_TYPE</code>. The
57  * formatting process is similar to the partition aware mode, with the
58  * exception of having only one partition.<p>
59  * Usually, clients instantiate this class and configure it before using it.
60  *
61  * @see IContentFormatter
62  * @see IDocument
63  * @see ITypedRegion
64  * @see Position
65  */

66 public class ContentFormatter implements IContentFormatter {
67
68     /**
69      * Defines a reference to either the offset or the end offset of
70      * a particular position.
71      */

72     static class PositionReference implements Comparable JavaDoc {
73
74         /** The referenced position */
75         protected Position fPosition;
76         /** The reference to either the offset or the end offset */
77         protected boolean fRefersToOffset;
78         /** The original category of the referenced position */
79         protected String JavaDoc fCategory;
80
81         /**
82          * Creates a new position reference.
83          *
84          * @param position the position to be referenced
85          * @param refersToOffset <code>true</code> if position offset should be referenced
86          * @param category the category the given position belongs to
87          */

88         protected PositionReference(Position position, boolean refersToOffset, String JavaDoc category) {
89             fPosition= position;
90             fRefersToOffset= refersToOffset;
91             fCategory= category;
92         }
93
94         /**
95          * Returns the offset of the referenced position.
96          *
97          * @return the offset of the referenced position
98          */

99         protected int getOffset() {
100             return fPosition.getOffset();
101         }
102
103         /**
104          * Manipulates the offset of the referenced position.
105          *
106          * @param offset the new offset of the referenced position
107          */

108         protected void setOffset(int offset) {
109             fPosition.setOffset(offset);
110         }
111
112         /**
113          * Returns the length of the referenced position.
114          *
115          * @return the length of the referenced position
116          */

117         protected int getLength() {
118             return fPosition.getLength();
119         }
120
121         /**
122          * Manipulates the length of the referenced position.
123          *
124          * @param length the new length of the referenced position
125          */

126         protected void setLength(int length) {
127             fPosition.setLength(length);
128         }
129
130         /**
131          * Returns whether this reference points to the offset or end offset
132          * of the references position.
133          *
134          * @return <code>true</code> if the offset of the position is referenced, <code>false</code> otherwise
135          */

136         protected boolean refersToOffset() {
137             return fRefersToOffset;
138         }
139
140         /**
141          * Returns the category of the referenced position.
142          *
143          * @return the category of the referenced position
144          */

145         protected String JavaDoc getCategory() {
146             return fCategory;
147         }
148
149         /**
150          * Returns the referenced position.
151          *
152          * @return the referenced position
153          */

154         protected Position getPosition() {
155             return fPosition;
156         }
157
158         /**
159          * Returns the referenced character position
160          *
161          * @return the referenced character position
162          */

163         protected int getCharacterPosition() {
164             if (fRefersToOffset)
165                 return getOffset();
166             return getOffset() + getLength();
167         }
168
169         /*
170          * @see Comparable#compareTo(Object)
171          */

172         public int compareTo(Object JavaDoc obj) {
173
174             if (obj instanceof PositionReference) {
175                 PositionReference r= (PositionReference) obj;
176                 return getCharacterPosition() - r.getCharacterPosition();
177             }
178
179             throw new ClassCastException JavaDoc();
180         }
181     }
182
183     /**
184      * The position updater used to update the remembered partitions.
185      *
186      * @see IPositionUpdater
187      * @see DefaultPositionUpdater
188      */

189     class NonDeletingPositionUpdater extends DefaultPositionUpdater {
190
191         /**
192          * Creates a new updater for the given category.
193          *
194          * @param category the category
195          */

196         protected NonDeletingPositionUpdater(String JavaDoc category) {
197             super(category);
198         }
199
200         /*
201          * @see DefaultPositionUpdater#notDeleted()
202          */

203         protected boolean notDeleted() {
204             return true;
205         }
206     }
207
208     /**
209      * The position updater which runs as first updater on the document's positions.
210      * Used to remove all affected positions from their categories to avoid them
211      * from being regularly updated.
212      *
213      * @see IPositionUpdater
214      */

215     class RemoveAffectedPositions implements IPositionUpdater {
216         /*
217          * @see IPositionUpdater#update(DocumentEvent)
218          */

219         public void update(DocumentEvent event) {
220             removeAffectedPositions(event.getDocument());
221         }
222     }
223
224     /**
225      * The position updater which runs as last updater on the document's positions.
226      * Used to update all affected positions and adding them back to their
227      * original categories.
228      *
229      * @see IPositionUpdater
230      */

231     class UpdateAffectedPositions implements IPositionUpdater {
232
233         /** The affected positions */
234         private int[] fPositions;
235         /** The offset */
236         private int fOffset;
237
238         /**
239          * Creates a new updater.
240          *
241          * @param positions the affected positions
242          * @param offset the offset
243          */

244         public UpdateAffectedPositions(int[] positions, int offset) {
245             fPositions= positions;
246             fOffset= offset;
247         }
248
249         /*
250          * @see IPositionUpdater#update(DocumentEvent)
251          */

252         public void update(DocumentEvent event) {
253             updateAffectedPositions(event.getDocument(), fPositions, fOffset);
254         }
255     }
256
257
258     /** Internal position category used for the formatter partitioning */
259     private final static String JavaDoc PARTITIONING= "__formatter_partitioning"; //$NON-NLS-1$
260

261     /** The map of <code>IFormattingStrategy</code> objects */
262     private Map JavaDoc fStrategies;
263     /** The indicator of whether the formatter operates in partition aware mode or not */
264     private boolean fIsPartitionAware= true;
265
266     /** The partition information managing document position categories */
267     private String JavaDoc[] fPartitionManagingCategories;
268     /** The list of references to offset and end offset of all overlapping positions */
269     private List JavaDoc fOverlappingPositionReferences;
270     /** Position updater used for partitioning positions */
271     private IPositionUpdater fPartitioningUpdater;
272     /**
273      * The document partitioning used by this formatter.
274      * @since 3.0
275      */

276     private String JavaDoc fPartitioning;
277     /**
278      * The document this formatter works on.
279      * @since 3.0
280      */

281     private IDocument fDocument;
282     /**
283      * The external partition managing categories.
284      * @since 3.0
285      */

286     private String JavaDoc[] fExternalPartitonManagingCategories;
287     /**
288      * Indicates whether <code>fPartitionManagingCategories</code> must be computed.
289      * @since 3.0
290      */

291     private boolean fNeedsComputation= true;
292
293
294     /**
295      * Creates a new content formatter. The content formatter operates by default
296      * in the partition-aware mode. There are no preconfigured formatting strategies.
297      * Will use the default document partitioning if not further configured.
298      */

299     public ContentFormatter() {
300         fPartitioning= IDocumentExtension3.DEFAULT_PARTITIONING;
301     }
302
303     /**
304      * Registers a strategy for a particular content type. If there is already a strategy
305      * registered for this type, the new strategy is registered instead of the old one.
306      * If the given content type is <code>null</code> the given strategy is registered for
307      * all content types as is called only once per formatting session.
308      *
309      * @param strategy the formatting strategy to register, or <code>null</code> to remove an existing one
310      * @param contentType the content type under which to register
311      */

312     public void setFormattingStrategy(IFormattingStrategy strategy, String JavaDoc contentType) {
313
314         Assert.isNotNull(contentType);
315
316         if (fStrategies == null)
317             fStrategies= new HashMap JavaDoc();
318
319         if (strategy == null)
320             fStrategies.remove(contentType);
321         else
322             fStrategies.put(contentType, strategy);
323     }
324
325     /**
326      * Informs this content formatter about the names of those position categories
327      * which are used to manage the document's partitioning information and thus should
328      * be ignored when this formatter updates positions.
329      *
330      * @param categories the categories to be ignored
331      * @deprecated incompatible with an open set of document partitionings. The provided information is only used
332      * if this formatter can not compute the partition managing position categories.
333      */

334     public void setPartitionManagingPositionCategories(String JavaDoc[] categories) {
335         fExternalPartitonManagingCategories= TextUtilities.copy(categories);
336     }
337
338     /**
339      * Sets the document partitioning to be used by this formatter.
340      *
341      * @param partitioning the document partitioning
342      * @since 3.0
343      */

344     public void setDocumentPartitioning(String JavaDoc partitioning) {
345         fPartitioning= partitioning;
346     }
347
348     /**
349      * Sets the formatter's operation mode.
350      *
351      * @param enable indicates whether the formatting process should be partition ware
352      */

353     public void enablePartitionAwareFormatting(boolean enable) {
354         fIsPartitionAware= enable;
355     }
356
357     /*
358      * @see IContentFormatter#getFormattingStrategy(String)
359      */

360     public IFormattingStrategy getFormattingStrategy(String JavaDoc contentType) {
361
362         Assert.isNotNull(contentType);
363
364         if (fStrategies == null)
365             return null;
366
367         return (IFormattingStrategy) fStrategies.get(contentType);
368     }
369
370     /*
371      * @see IContentFormatter#format(IDocument, IRegion)
372      */

373     public void format(IDocument document, IRegion region) {
374         fNeedsComputation= true;
375         fDocument= document;
376         try {
377
378             if (fIsPartitionAware)
379                 formatPartitions(region);
380             else
381                 formatRegion(region);
382
383         } finally {
384             fNeedsComputation= true;
385             fDocument= null;
386         }
387     }
388
389     /**
390      * Determines the partitioning of the given region of the document.
391      * Informs the formatting strategies of each partition about the start,
392      * the process, and the termination of the formatting session.
393      *
394      * @param region the document region to be formatted
395      * @since 3.0
396      */

397     private void formatPartitions(IRegion region) {
398
399         addPartitioningUpdater();
400
401         try {
402
403             TypedPosition[] ranges= getPartitioning(region);
404             if (ranges != null) {
405                 start(ranges, getIndentation(region.getOffset()));
406                 format(ranges);
407                 stop(ranges);
408             }
409
410         } catch (BadLocationException x) {
411         }
412
413         removePartitioningUpdater();
414     }
415
416     /**
417      * Formats the given region with the strategy registered for the default
418      * content type. The strategy is informed about the start, the process, and
419      * the termination of the formatting session.
420      *
421      * @param region the region to be formatted
422      * @since 3.0
423      */

424     private void formatRegion(IRegion region) {
425
426         IFormattingStrategy strategy= getFormattingStrategy(IDocument.DEFAULT_CONTENT_TYPE);
427         if (strategy != null) {
428             strategy.formatterStarts(getIndentation(region.getOffset()));
429             format(strategy, new TypedPosition(region.getOffset(), region.getLength(), IDocument.DEFAULT_CONTENT_TYPE));
430             strategy.formatterStops();
431         }
432     }
433
434     /**
435      * Returns the partitioning of the given region of the document to be formatted.
436      * As one partition after the other will be formatted and formatting will
437      * probably change the length of the formatted partition, it must be kept
438      * track of the modifications in order to submit the correct partition to all
439      * formatting strategies. For this, all partitions are remembered as positions
440      * in a dedicated position category. (As formatting strategies might rely on each
441      * other, calling them in reversed order is not an option.)
442      *
443      * @param region the region for which the partitioning must be determined
444      * @return the partitioning of the specified region
445      * @exception BadLocationException of region is invalid in the document
446      * @since 3.0
447      */

448     private TypedPosition[] getPartitioning(IRegion region) throws BadLocationException {
449
450         ITypedRegion[] regions= TextUtilities.computePartitioning(fDocument, fPartitioning, region.getOffset(), region.getLength(), false);
451         TypedPosition[] positions= new TypedPosition[regions.length];
452
453         for (int i= 0; i < regions.length; i++) {
454             positions[i]= new TypedPosition(regions[i]);
455             try {
456                 fDocument.addPosition(PARTITIONING, positions[i]);
457             } catch (BadPositionCategoryException x) {
458                 // should not happen
459
}
460         }
461
462         return positions;
463     }
464
465     /**
466      * Fires <code>formatterStarts</code> to all formatter strategies
467      * which will be involved in the forthcoming formatting process.
468      *
469      * @param regions the partitioning of the document to be formatted
470      * @param indentation the initial indentation
471      */

472     private void start(TypedPosition[] regions, String JavaDoc indentation) {
473         for (int i= 0; i < regions.length; i++) {
474             IFormattingStrategy s= getFormattingStrategy(regions[i].getType());
475             if (s != null)
476                 s.formatterStarts(indentation);
477         }
478     }
479
480     /**
481      * Formats one partition after the other using the formatter strategy registered for
482      * the partition's content type.
483      *
484      * @param ranges the partitioning of the document region to be formatted
485      * @since 3.0
486      */

487     private void format(TypedPosition[] ranges) {
488         for (int i= 0; i < ranges.length; i++) {
489             IFormattingStrategy s= getFormattingStrategy(ranges[i].getType());
490             if (s != null) {
491                 format(s, ranges[i]);
492             }
493         }
494     }
495
496     /**
497      * Formats the given region of the document using the specified formatting
498      * strategy. In order to maintain positions correctly, first all affected
499      * positions determined, after all document listeners have been informed about
500      * the coming change, the affected positions are removed to avoid that they
501      * are regularly updated. After all position updaters have run, the affected
502      * positions are updated with the formatter's information and added back to
503      * their categories, right before the first document listener is informed about
504      * that a change happened.
505      *
506      * @param strategy the strategy to be used
507      * @param region the region to be formatted
508      * @since 3.0
509      */

510     private void format(IFormattingStrategy strategy, TypedPosition region) {
511         try {
512
513             final int offset= region.getOffset();
514             int length= region.getLength();
515
516             String JavaDoc content= fDocument.get(offset, length);
517             final int[] positions= getAffectedPositions(offset, length);
518             String JavaDoc formatted= strategy.format(content, isLineStart(offset), getIndentation(offset), positions);
519
520             if (formatted != null && !formatted.equals(content)) {
521
522                 IPositionUpdater first= new RemoveAffectedPositions();
523                 fDocument.insertPositionUpdater(first, 0);
524                 IPositionUpdater last= new UpdateAffectedPositions(positions, offset);
525                 fDocument.addPositionUpdater(last);
526
527                 fDocument.replace(offset, length, formatted);
528
529                 fDocument.removePositionUpdater(first);
530                 fDocument.removePositionUpdater(last);
531             }
532
533         } catch (BadLocationException x) {
534             // should not happen
535
}
536     }
537
538     /**
539      * Fires <code>formatterStops</code> to all formatter strategies which were
540      * involved in the formatting process which is about to terminate.
541      *
542      * @param regions the partitioning of the document which has been formatted
543      */

544     private void stop(TypedPosition[] regions) {
545         for (int i= 0; i < regions.length; i++) {
546             IFormattingStrategy s= getFormattingStrategy(regions[i].getType());
547             if (s != null)
548                 s.formatterStops();
549         }
550     }
551
552     /**
553      * Installs those updaters which the formatter needs to keep track of the partitions.
554      * @since 3.0
555      */

556     private void addPartitioningUpdater() {
557         fPartitioningUpdater= new NonDeletingPositionUpdater(PARTITIONING);
558         fDocument.addPositionCategory(PARTITIONING);
559         fDocument.addPositionUpdater(fPartitioningUpdater);
560     }
561
562     /**
563      * Removes the formatter's internal position updater and category.
564      *
565      * @since 3.0
566      */

567     private void removePartitioningUpdater() {
568
569         try {
570
571             fDocument.removePositionUpdater(fPartitioningUpdater);
572             fDocument.removePositionCategory(PARTITIONING);
573             fPartitioningUpdater= null;
574
575         } catch (BadPositionCategoryException x) {
576             // should not happen
577
}
578     }
579
580     /**
581      * Returns the partition managing position categories for the formatted document.
582      *
583      * @return the position managing position categories
584      * @since 3.0
585      */

586     private String JavaDoc[] getPartitionManagingCategories() {
587         if (fNeedsComputation) {
588             fNeedsComputation= false;
589             fPartitionManagingCategories= TextUtilities.computePartitionManagingCategories(fDocument);
590             if (fPartitionManagingCategories == null)
591                 fPartitionManagingCategories= fExternalPartitonManagingCategories;
592         }
593         return fPartitionManagingCategories;
594     }
595
596     /**
597      * Determines whether the given document position category should be ignored
598      * by this formatter's position updating.
599      *
600      * @param category the category to check
601      * @return <code>true</code> if the category should be ignored, <code>false</code> otherwise
602      */

603     private boolean ignoreCategory(String JavaDoc category) {
604
605         if (PARTITIONING.equals(category))
606             return true;
607
608         String JavaDoc[] categories= getPartitionManagingCategories();
609         if (categories != null) {
610             for (int i= 0; i < categories.length; i++) {
611                 if (categories[i].equals(category))
612                     return true;
613             }
614         }
615
616         return false;
617     }
618
619     /**
620      * Determines all embracing, overlapping, and follow up positions
621      * for the given region of the document.
622      *
623      * @param offset the offset of the document region to be formatted
624      * @param length the length of the document to be formatted
625      * @since 3.0
626      */

627     private void determinePositionsToUpdate(int offset, int length) {
628
629         String JavaDoc[] categories= fDocument.getPositionCategories();
630         if (categories != null) {
631             for (int i= 0; i < categories.length; i++) {
632
633                 if (ignoreCategory(categories[i]))
634                     continue;
635
636                 try {
637
638                     Position[] positions= fDocument.getPositions(categories[i]);
639
640                     for (int j= 0; j < positions.length; j++) {
641
642                         Position p= positions[j];
643                         if (p.overlapsWith(offset, length)) {
644
645                             if (offset < p.getOffset())
646                                 fOverlappingPositionReferences.add(new PositionReference(p, true, categories[i]));
647
648                             if (p.getOffset() + p.getLength() < offset + length)
649                                 fOverlappingPositionReferences.add(new PositionReference(p, false, categories[i]));
650                         }
651                     }
652
653                 } catch (BadPositionCategoryException x) {
654                     // can not happen
655
}
656             }
657         }
658     }
659
660     /**
661      * Returns all offset and the end offset of all positions overlapping with the
662      * specified document range.
663      *
664      * @param offset the offset of the document region to be formatted
665      * @param length the length of the document to be formatted
666      * @return all character positions of the interleaving positions
667      * @since 3.0
668      */

669     private int[] getAffectedPositions(int offset, int length) {
670
671         fOverlappingPositionReferences= new ArrayList JavaDoc();
672
673         determinePositionsToUpdate(offset, length);
674
675         Collections.sort(fOverlappingPositionReferences);
676
677         int[] positions= new int[fOverlappingPositionReferences.size()];
678         for (int i= 0; i < positions.length; i++) {
679             PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i);
680             positions[i]= r.getCharacterPosition() - offset;
681         }
682
683         return positions;
684     }
685
686     /**
687      * Removes the affected positions from their categories to avoid
688      * that they are invalidly updated.
689      *
690      * @param document the document
691      */

692     private void removeAffectedPositions(IDocument document) {
693         int size= fOverlappingPositionReferences.size();
694         for (int i= 0; i < size; i++) {
695             PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i);
696             try {
697                 document.removePosition(r.getCategory(), r.getPosition());
698             } catch (BadPositionCategoryException x) {
699                 // can not happen
700
}
701         }
702     }
703
704     /**
705      * Updates all the overlapping positions. Note, all other positions are
706      * automatically updated by their document position updaters.
707      *
708      * @param document the document to has been formatted
709      * @param positions the adapted character positions to be used to update the document positions
710      * @param offset the offset of the document region that has been formatted
711      */

712     protected void updateAffectedPositions(IDocument document, int[] positions, int offset) {
713
714         if (document != fDocument)
715             return;
716
717         if (positions.length == 0)
718             return;
719
720         for (int i= 0; i < positions.length; i++) {
721
722             PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i);
723
724             if (r.refersToOffset())
725                 r.setOffset(offset + positions[i]);
726             else
727                 r.setLength((offset + positions[i]) - r.getOffset());
728
729             Position p= r.getPosition();
730             String JavaDoc category= r.getCategory();
731             if (!document.containsPosition(category, p.offset, p.length)) {
732                 try {
733                     if (positionAboutToBeAdded(document, category, p))
734                         document.addPosition(r.getCategory(), p);
735                 } catch (BadPositionCategoryException x) {
736                     // can not happen
737
} catch (BadLocationException x) {
738                     // should not happen
739
}
740             }
741
742         }
743
744         fOverlappingPositionReferences= null;
745     }
746
747     /**
748      * The given position is about to be added to the given position category of the given document. <p>
749      * This default implementation return <code>true</code>.
750      *
751      * @param document the document
752      * @param category the position category
753      * @param position the position that will be added
754      * @return <code>true</code> if the position can be added, <code>false</code> if it should be ignored
755      */

756     protected boolean positionAboutToBeAdded(IDocument document, String JavaDoc category, Position position) {
757         return true;
758     }
759
760     /**
761      * Returns the indentation of the line of the given offset.
762      *
763      * @param offset the offset
764      * @return the indentation of the line of the offset
765      * @since 3.0
766      */

767     private String JavaDoc getIndentation(int offset) {
768
769         try {
770             int start= fDocument.getLineOfOffset(offset);
771             start= fDocument.getLineOffset(start);
772
773             int end= start;
774             char c= fDocument.getChar(end);
775             while ('\t' == c || ' ' == c)
776                 c= fDocument.getChar(++end);
777
778             return fDocument.get(start, end - start);
779         } catch (BadLocationException x) {
780         }
781
782         return ""; //$NON-NLS-1$
783
}
784
785     /**
786      * Determines whether the offset is the beginning of a line in the given document.
787      *
788      * @param offset the offset
789      * @return <code>true</code> if offset is the beginning of a line
790      * @exception BadLocationException if offset is invalid in document
791      * @since 3.0
792      */

793     private boolean isLineStart(int offset) throws BadLocationException {
794         int start= fDocument.getLineOfOffset(offset);
795         start= fDocument.getLineOffset(start);
796         return (start == offset);
797     }
798 }
799
Popular Tags