Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/port80/eclipse/editors/util/RegionContentFormatter.java


1   package com.port80.eclipse.editors.util;
2   
3   import java.util.ArrayList;
4   import java.util.Collections;
5   import java.util.HashMap;
6   import java.util.List;
7   import java.util.Map;
8   
9   import org.eclipse.jface.text.BadLocationException;
10  import org.eclipse.jface.text.BadPositionCategoryException;
11  import org.eclipse.jface.text.DefaultPositionUpdater;
12  import org.eclipse.jface.text.DocumentEvent;
13  import org.eclipse.jface.text.IDocument;
14  import org.eclipse.jface.text.IPositionUpdater;
15  import org.eclipse.jface.text.IRegion;
16  import org.eclipse.jface.text.ITypedRegion;
17  import org.eclipse.jface.text.Position;
18  import org.eclipse.jface.text.TypedPosition;
19  import org.eclipse.jface.text.formatter.IContentFormatter;
20  import org.eclipse.jface.text.formatter.IFormattingStrategy;
21  import org.eclipse.jface.util.Assert;
22  
23  /**
24   * Hack of ContentFormatter class to provide information about the region
25   * being formatted so that positions can be updated in IFormattingStrategy.
26   * 
27   * @see PerlContentFormatter#format(IDocument, IFormattingStrategy, TypedPosition)
28   * @author chrisl
29   */
30  
31  /**
32   * Standard implementation of <code>IContentFormatter</code>.
33   * The formatter supports two operation modi: partition aware and
34   * partition unaware. <p>
35   * In the partition aware mode, the formatter determines the 
36   * partitioning of the document region to be formatted. For each 
37   * partition it determines all document positions  which are affected 
38   * when text changes are applied to the partition. Those which overlap
39   * with the partition are remembered as character positions. These
40   * character positions are passed over to the formatting strategy
41   * registered for the partition's content type. The formatting strategy
42   * returns a string containing the formatted document partition as well
43   * as the adapted character positions. The formatted partition replaces
44   * the old content of the partition. The remembered document postions 
45   * are updated with the adapted character positions. In addition, all
46   * other document positions are accordingly adapted to the formatting 
47   * changes.<p>
48   * In the partition unaware mode, the document's partitioning is ignored
49   * and the document is considered consisting of only one partition of 
50   * the content type <code>IDocument.DEFAULT_CONTENT_TYPE</code>. The 
51   * formatting process is similar to the partition aware mode, with the 
52   * exception of having only one partition.<p>
53   * Usually, clients instantiate this class and configure it before using it.
54   *
55   * @see IContentFormatter
56   * @see IDocument
57   * @see ITypedRegion
58   * @see Position
59   */
60  public class RegionContentFormatter implements IContentFormatter {
61  
62    /**
63     * Defines a reference to either the offset or the end offset of
64     * a particular position.
65     */
66    static class PositionReference implements Comparable {
67  
68      /** The referenced position */
69      protected Position fPosition;
70      /** The reference to either the offset or the end offset */
71      protected boolean fRefersToOffset;
72      /** The original category of the referenced position */
73      protected String fCategory;
74  
75      protected PositionReference(Position position, boolean refersToOffset, String category) {
76        fPosition = position;
77        fRefersToOffset = refersToOffset;
78        fCategory = category;
79      }
80  
81      /**
82       * Returns the offset of the referenced position.
83       */
84      protected int getOffset() {
85        return fPosition.getOffset();
86      }
87  
88      /**
89       * Manipulates the offset of the referenced position.
90       */
91      protected void setOffset(int offset) {
92        fPosition.setOffset(offset);
93      }
94  
95      /**
96       * Returns the length of the referenced position.
97       */
98      protected int getLength() {
99        return fPosition.getLength();
100     }
101 
102     /**
103      * Manipulates the length of the referenced position.
104      */
105     protected void setLength(int length) {
106       fPosition.setLength(length);
107     }
108 
109     /**
110      * Returns whether this reference points to the offset or endoffset
111      * of the references position.
112      */
113     protected boolean refersToOffset() {
114       return fRefersToOffset;
115     }
116 
117     /**
118      * Returns the category of the referenced position.
119      */
120     protected String getCategory() {
121       return fCategory;
122     }
123 
124     /**
125      * Returns the referenced position.
126      */
127     protected Position getPosition() {
128       return fPosition;
129     }
130 
131     /**
132      * Returns the referenced character position
133      */
134     protected int getCharacterPosition() {
135       if (fRefersToOffset)
136         return getOffset();
137       return getOffset() + getLength();
138     }
139 
140     /**
141      * @see Comparable#compareTo(Object)
142      */
143     public int compareTo(Object obj) {
144 
145       if (obj instanceof PositionReference) {
146         PositionReference r = (PositionReference) obj;
147         return getCharacterPosition() - r.getCharacterPosition();
148       }
149 
150       throw new ClassCastException();
151     }
152   };
153 
154   /**
155    * The position updater used to adapt all to update the 
156    * remembered partitions.
157    *
158    * @see IPositionUpdater
159    * @see DefaultPositionUpdater
160    */
161   class NonDeletingPositionUpdater extends DefaultPositionUpdater {
162 
163     protected NonDeletingPositionUpdater(String category) {
164       super(category);
165     }
166 
167     /*
168      * @see DefaultPositionUpdater#notDeleted()
169      */
170     protected boolean notDeleted() {
171       return true;
172     }
173   };
174 
175   /**
176    * The position updater which runs as first updater on the document's positions.
177    * Used to remove all affected positions from their categories to avoid them
178    * from being regularily updated.
179    * 
180    * @see IPositionUpdater
181    */
182   class RemoveAffectedPositions implements IPositionUpdater {
183     /**
184      * @see IPositionUpdater#update(DocumentEvent)
185      */
186     public void update(DocumentEvent event) {
187       removeAffectedPositions(event.getDocument());
188     }
189   };
190 
191   /**
192    * The position updater which runs as last updater on the document's positions.
193    * Used to update all affected positions and adding them back to their
194    * original categories.
195    * 
196    * @see IPositionUpdater
197    */
198   class UpdateAffectedPositions implements IPositionUpdater {
199 
200     private int[] fPositions;
201     private int fOffset;
202 
203     public UpdateAffectedPositions(int[] positions, int offset) {
204       fPositions = positions;
205       fOffset = offset;
206     }
207 
208     /**
209      * @see IPositionUpdater#update(DocumentEvent)
210      */
211     public void update(DocumentEvent event) {
212       updateAffectedPositions(event.getDocument(), fPositions, fOffset);
213     }
214   };
215 
216   /** Internal position category used for the formatter partitioning */
217   private final static String PARTITIONING = "__formatter_partitioning"; //$NON-NLS-1$
218 
219   /** The map of <code>IFormattingStrategy</code> objects */
220   private Map fStrategies;
221   /** The indicator of whether the formatter operates in partition aware mode or not */
222   private boolean fIsPartitionAware = true;
223 
224   /** The partition information managing document position categories */
225   private String[] fPartitionManagingCategories;
226   /** The list of references to offset and end offset of all overlapping positions */
227   private List fOverlappingPositionReferences;
228   /** Position updater used for partitioning positions */
229   private IPositionUpdater fPartitioningUpdater;
230 
231   /**
232    * Creates a new content formatter. The content formatter operates by default
233    * in the partition-aware mode. There are no preconfigured formatting strategies.
234    */
235   public RegionContentFormatter() {}
236 
237   /**
238    * Registers a strategy for a particular content type. If there is already a strategy
239    * registered for this type, the new strategy is registered instead of the old one.
240    * If the given content type is <code>null</code> the given strategy is registered for
241    * all content types as is called only once per formatting session.
242    *
243    * @param strategy the formatting strategy to register, or <code>null</code> to remove an existing one
244    * @param contentType the content type under which to register, or <code>null</code> for all content types
245    */
246   public void setFormattingStrategy(IFormattingStrategy strategy, String contentType) {
247 
248     Assert.isNotNull(contentType);
249 
250     if (fStrategies == null)
251       fStrategies = new HashMap();
252 
253     if (strategy == null)
254       fStrategies.remove(contentType);
255     else
256       fStrategies.put(contentType, strategy);
257   }
258 
259   /**
260    * Informs this content formatter about the names of those position categories
261    * which are used to manage the document's partitioning information and thus should
262    * be ignored when this formatter updates positions.
263    *
264    * @param categories the categories to be ignored
265    */
266   public void setPartitionManagingPositionCategories(String[] categories) {
267     fPartitionManagingCategories = categories;
268   }
269 
270   /**
271    * Sets the formatter's operation mode.
272    * 
273    * @param enable indicates whether the formatting process should be partition ware
274    */
275   public void enablePartitionAwareFormatting(boolean enable) {
276     fIsPartitionAware = enable;
277   }
278 
279   /*
280    * @see IContentFormatter#getFormattingStrategy
281    */
282   public IFormattingStrategy getFormattingStrategy(String contentType) {
283 
284     Assert.isNotNull(contentType);
285 
286     if (fStrategies == null)
287       return null;
288 
289     return (IFormattingStrategy) fStrategies.get(contentType);
290   }
291 
292   /*
293    * @see IContentFormatter#format
294    */
295   public void format(IDocument document, IRegion region) {
296     if (fIsPartitionAware)
297       formatPartitions(document, region);
298     else
299       formatRegion(document, region);
300   }
301 
302   /**
303    * Removes the affected positions from their categories to avoid
304    * that they are invalidly updated.
305    * 
306    * @param document the document 
307    */
308   void removeAffectedPositions(IDocument document) {
309     int size = fOverlappingPositionReferences.size();
310     for (int i = 0; i < size; i++) {
311       PositionReference r = (PositionReference) fOverlappingPositionReferences.get(i);
312       try {
313         document.removePosition(r.getCategory(), r.getPosition());
314       } catch (BadPositionCategoryException x) {
315         // can not happen
316       }
317     }
318   }
319 
320   /**
321    * Updates all the overlapping positions. Note, all other positions are
322    * automatically updated by their document position updaters.
323    *
324    * @param document the document to has been formatted
325    * @param positions the adapted character positions to be used to update the document positions
326    * @param offset the offset of the document region that has been formatted
327    */
328   void updateAffectedPositions(IDocument document, int[] positions, int offset) {
329 
330     if (positions.length == 0)
331       return;
332 
333     Map added = new HashMap(positions.length * 2);
334 
335     for (int i = 0; i < positions.length; i++) {
336 
337       PositionReference r = (PositionReference) fOverlappingPositionReferences.get(i);
338 
339       if (r.refersToOffset())
340         r.setOffset(offset + positions[i]);
341       else
342         r.setLength((offset + positions[i]) - r.getOffset());
343 
344       if (added.get(r.getPosition()) == null) {
345         try {
346           document.addPosition(r.getCategory(), r.getPosition());
347           added.put(r.getPosition(), r.getPosition());
348         } catch (BadPositionCategoryException x) {
349           // can not happen
350         } catch (BadLocationException x) {
351           // should not happen
352         }
353       }
354 
355     }
356 
357     fOverlappingPositionReferences = null;
358   }
359 
360   /**
361    * Determines the partitioning of the given region of the document.
362    * Informs for each partition about the start, the process, and the
363    * termination of the formatting session.
364    */
365   private void formatPartitions(IDocument document, IRegion region) {
366 
367     addPartitioningUpdater(document);
368 
369     try {
370 
371       TypedPosition[] ranges = getPartitioning(document, region);
372       if (ranges != null) {
373         start(ranges, getIndentation(document, region.getOffset()));
374         format(document, ranges);
375         stop(ranges);
376       }
377 
378     } catch (BadLocationException x) {}
379 
380     removePartitioningUpdater(document);
381   }
382 
383   /**
384    * Informs for the given region about the start, the process, and
385    * the termination of the formatting session.
386    */
387   private void formatRegion(IDocument document, IRegion region) {
388 
389     IFormattingStrategy strategy = getFormattingStrategy(IDocument.DEFAULT_CONTENT_TYPE);
390     if (strategy != null) {
391       strategy.formatterStarts(getIndentation(document, region.getOffset()));
392       format(
393         document,
394         strategy,
395         new TypedPosition(
396           region.getOffset(),
397           region.getLength(),
398           IDocument.DEFAULT_CONTENT_TYPE));
399       strategy.formatterStops();
400     }
401   }
402 
403   /**
404    * Returns the partitioning of the given region of the specified document.
405    * As one partition after the other will be formatted and formatting will 
406    * probably change the length of the formatted partition, it must be kept 
407    * track of the modifications in order to submit the correct partition to all 
408    * formatting strategies. For this, all partitions are remembered as positions
409    * in a dedicated position category. (As formatting stratgies might rely on each
410    * other, calling them in reversed order is not an option.)
411    *
412    * @param document the document
413    * @param region the region for which the partitioning must be determined
414    * @return the partitioning of the specified region
415    * @exception BadLocationException of region is invalid in the document
416    */
417   private TypedPosition[] getPartitioning(IDocument document, IRegion region) throws BadLocationException {
418 
419     ITypedRegion[] regions = document.computePartitioning(region.getOffset(), region.getLength());
420     TypedPosition[] positions = new TypedPosition[regions.length];
421 
422     for (int i = 0; i < regions.length; i++) {
423       positions[i] = new TypedPosition(regions[i]);
424       try {
425         document.addPosition(PARTITIONING, positions[i]);
426       } catch (BadPositionCategoryException x) {
427         // should not happen
428       }
429     }
430 
431     return positions;
432   }
433 
434   /**
435    * Fires <code>formatterStarts</code> to all formatter strategies
436    * which will be involved in the forthcoming formatting process.
437    * 
438    * @param regions the partitioning of the document to be formatted
439    * @param indentation the initial indentation
440    */
441   private void start(TypedPosition[] regions, String indentation) {
442     for (int i = 0; i < regions.length; i++) {
443       IFormattingStrategy s = getFormattingStrategy(regions[i].getType());
444       if (s != null)
445         s.formatterStarts(indentation);
446     }
447   }
448 
449   /**
450    * Formats one partition after the other using the formatter strategy registered for
451    * the partition's content type.
452    *
453    * @param document to document to be formatted
454    * @param ranges the partitioning of the document region to be formatted
455    */
456   private void format(final IDocument document, TypedPosition[] ranges) {
457     for (int i = 0; i < ranges.length; i++) {
458       IFormattingStrategy s = getFormattingStrategy(ranges[i].getType());
459       if (s != null) {
460         format(document, s, ranges[i]);
461       }
462     }
463   }
464 
465   /**
466    * Formats the given region of the document using the specified formatting
467    * strategy. In order to maintain positions correctly, first all affected 
468    * positions determined, after all document listeners have been informed about
469    * the upcoming change, the affected positions are removed to avoid that they
470    * are regularily updated. After all position updaters have run, the affected
471    * positions are updated with the formatter's information and added back to 
472    * their categories, right before the first document listener is informed about
473    * that a change happend.
474    * 
475    * @param document the document to be formatted
476    * @param strategy the strategy to be used
477    * @param region the region to be formatted
478    */
479   private void format(final IDocument document, IFormattingStrategy strategy, TypedPosition region) {
480     try {
481 
482       final int offset = region.getOffset();
483       int length = region.getLength();
484 
485       final int[] positions = getAffectedPositions(document, offset, length);
486       //String content = document.get(offset, length);
487       String formatted =
488         ((IRegionFormattingStrategy)strategy).format(
489           document,
490           region,
491           isLineStart(document, offset),
492           getIndentation(document, offset),
493           positions);
494 
495       IPositionUpdater first = new RemoveAffectedPositions();
496       document.insertPositionUpdater(first, 0);
497       IPositionUpdater last = new UpdateAffectedPositions(positions, offset);
498       document.addPositionUpdater(last);
499 
500       document.replace(offset, length, formatted);
501 
502       document.removePositionUpdater(first);
503       document.removePositionUpdater(last);
504 
505     } catch (BadLocationException x) {
506       // should not happen
507     }
508   }
509 
510   /**
511    * Fires <code>formatterStops</code> to all formatter strategies which were
512    * involved in the formatting process which is about to terminate.
513    *
514    * @param regions the partitioning of the document which has been formatted
515    */
516   private void stop(TypedPosition[] regions) {
517     for (int i = 0; i < regions.length; i++) {
518       IFormattingStrategy s = getFormattingStrategy(regions[i].getType());
519       if (s != null)
520         s.formatterStops();
521     }
522   }
523 
524   /**
525    * Installs those updaters which the formatter needs to keep 
526    * track of the partitions.
527    *
528    * @param document the document to be formatted
529    */
530   private void addPartitioningUpdater(IDocument document) {
531     fPartitioningUpdater = new NonDeletingPositionUpdater(PARTITIONING);
532     document.addPositionCategory(PARTITIONING);
533     document.addPositionUpdater(fPartitioningUpdater);
534   }
535 
536   /**
537    * Removes the formatter's internal position updater and category.
538    *
539    * @param document the document that has been formatted
540    */
541   private void removePartitioningUpdater(IDocument document) {
542 
543     try {
544 
545       document.removePositionUpdater(fPartitioningUpdater);
546       document.removePositionCategory(PARTITIONING);
547       fPartitioningUpdater = null;
548 
549     } catch (BadPositionCategoryException x) {
550       // should not happen
551     }
552   }
553 
554   /**
555    * Determines whether the given document position category should be ignored
556    * by this formatter's position updating.
557    *
558    * @param category the category to check
559    * @return <code>true</code> if the category should be ignored, <code>false</code> otherwise
560    */
561   private boolean ignoreCategory(String category) {
562 
563     if (PARTITIONING.equals(category))
564       return true;
565 
566     if (fPartitionManagingCategories != null) {
567       for (int i = 0; i < fPartitionManagingCategories.length; i++) {
568         if (fPartitionManagingCategories[i].equals(category))
569           return true;
570       }
571     }
572 
573     return false;
574   }
575 
576   /**
577    * Determines all embracing, overlapping, and follow up positions 
578    * for the given region of the document.
579    *
580    * @param document the document to be formatted
581    * @param offset the offset of the document region to be formatted
582    * @param length the length of the document to be formatted
583    */
584   private void determinePositionsToUpdate(IDocument document, int offset, int length) {
585 
586     String[] categories = document.getPositionCategories();
587     if (categories != null) {
588       for (int i = 0; i < categories.length; i++) {
589 
590         if (ignoreCategory(categories[i]))
591           continue;
592 
593         try {
594 
595           Position[] positions = document.getPositions(categories[i]);
596 
597           for (int j = 0; j < positions.length; j++) {
598 
599             Position p = (Position) positions[j];
600             if (p.overlapsWith(offset, length)) {
601 
602               if (offset < p.getOffset())
603                 fOverlappingPositionReferences.add(
604                   new PositionReference(p, true, categories[i]));
605 
606               if (p.getOffset() + p.getLength() < offset + length)
607                 fOverlappingPositionReferences.add(
608                   new PositionReference(p, false, categories[i]));
609             }
610           }
611 
612         } catch (BadPositionCategoryException x) {
613           // can not happen
614         }
615       }
616     }
617   }
618 
619   /**
620    * Returns all offset and the end offset of all positions overlapping with the 
621    * specified document range.
622    *
623    * @param document the document to be formatted
624    * @param offset the offset of the document region to be formatted
625    * @param length the length of the document to be formatted
626    * @return all character positions of the interleaving positions
627    */
628   private int[] getAffectedPositions(IDocument document, int offset, int length) {
629 
630     fOverlappingPositionReferences = new ArrayList();
631 
632     determinePositionsToUpdate(document, offset, length);
633 
634     Collections.sort(fOverlappingPositionReferences);
635 
636     int[] positions = new int[fOverlappingPositionReferences.size()];
637     for (int i = 0; i < positions.length; i++) {
638       PositionReference r = (PositionReference) fOverlappingPositionReferences.get(i);
639       positions[i] = r.getCharacterPosition() - offset;
640     }
641 
642     return positions;
643   }
644 
645   /**
646    * Returns the indentation of the line of the given offset.
647    *
648    * @param document the document
649    * @param offset the offset
650    * @return the indentation of the line of the offset
651    */
652   private String getIndentation(IDocument document, int offset) {
653 
654     try {
655       int start = document.getLineOfOffset(offset);
656       start = document.getLineOffset(start);
657 
658       int end = start;
659       char c = document.getChar(end);
660       while ('\t' == c || ' ' == c)
661         c = document.getChar(++end);
662 
663       return document.get(start, end - start);
664     } catch (BadLocationException x) {}
665 
666     return ""; //$NON-NLS-1$
667   }
668 
669   /**
670    * Determines whether the offset is the beginning of a line in the given document.
671    *
672    * @param document the document
673    * @param offset the offset
674    * @return <code>true</code> if offset is the beginning of a line
675    * @exception BadLocationException if offset is invalid in document
676    */
677   private boolean isLineStart(IDocument document, int offset) throws BadLocationException {
678     int start = document.getLineOfOffset(offset);
679     start = document.getLineOffset(start);
680     return (start == offset);
681   }
682 }