KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > projection > ProjectionDocument


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 package org.eclipse.jface.text.projection;
12
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.List JavaDoc;
16
17 import org.eclipse.jface.text.AbstractDocument;
18 import org.eclipse.jface.text.BadLocationException;
19 import org.eclipse.jface.text.BadPositionCategoryException;
20 import org.eclipse.jface.text.DefaultLineTracker;
21 import org.eclipse.jface.text.DocumentEvent;
22 import org.eclipse.jface.text.IDocument;
23 import org.eclipse.jface.text.IDocumentExtension;
24 import org.eclipse.jface.text.IDocumentListener;
25 import org.eclipse.jface.text.ILineTracker;
26 import org.eclipse.jface.text.IRegion;
27 import org.eclipse.jface.text.ITextStore;
28 import org.eclipse.jface.text.Position;
29 import org.eclipse.jface.text.Region;
30 import org.eclipse.jface.text.TextUtilities;
31
32
33 /**
34  * A <code>ProjectionDocument</code> represents a projection of its master
35  * document. The contents of a projection document is a sequence of fragments of
36  * the master document, i.e. the projection document can be thought as being
37  * constructed from the master document by not copying the whole master document
38  * but omitting several ranges of the master document.
39  * <p>
40  * The projection document indirectly utilizes its master document as
41  * <code>ITextStore</code> by means of a <code>ProjectionTextStore</code>.
42  * <p>
43  * The content of a projection document can be changed in two ways. Either by a
44  * text replace applied to the master document or the projection document. Or by
45  * changing the projection between the master document and the projection
46  * document. For the latter the two methods <code>addMasterDocumentRange</code>
47  * and <code>removeMasterDocumentRange</code> are provided. For any
48  * manipulation, the projection document sends out a
49  * {@link org.eclipse.jface.text.projection.ProjectionDocumentEvent} describing
50  * the change.
51  * <p>
52  * Clients are not supposed to directly instantiate this class. In order to
53  * obtain a projection document, a
54  * {@link org.eclipse.jface.text.projection.ProjectionDocumentManager}should be
55  * used. This class is not intended to be subclassed outside of its origin
56  * package.</p>
57  *
58  * @since 3.0
59  */

60 public class ProjectionDocument extends AbstractDocument {
61
62
63     /**
64      * Prefix of the name of the position category used to keep track of the master
65      * document's fragments that correspond to the segments of the projection
66      * document.
67      */

68     private final static String JavaDoc FRAGMENTS_CATEGORY_PREFIX= "__fragmentsCategory"; //$NON-NLS-1$
69

70     /**
71      * Name of the position category used to keep track of the project
72      * document's segments that correspond to the fragments of the master
73      * document.
74      */

75     private final static String JavaDoc SEGMENTS_CATEGORY= "__segmentsCategory"; //$NON-NLS-1$
76

77
78     /** The master document */
79     private IDocument fMasterDocument;
80     /** The master document as document extension */
81     private IDocumentExtension fMasterDocumentExtension;
82     /** The fragments' position category */
83     private String JavaDoc fFragmentsCategory;
84     /** The segment's position category */
85     private String JavaDoc fSegmentsCategory;
86     /** The document event issued by the master document */
87     private DocumentEvent fMasterEvent;
88     /** The document event to be issued by the projection document */
89     private ProjectionDocumentEvent fSlaveEvent;
90     /** The original document event generated by a direct manipulation of this projection document */
91     private DocumentEvent fOriginalEvent;
92     /** Indicates whether the projection document initiated a master document update or not */
93     private boolean fIsUpdating= false;
94     /** Indicated whether the projection document is in auto expand mode nor not */
95     private boolean fIsAutoExpanding= false;
96     /** The position updater for the segments */
97     private SegmentUpdater fSegmentUpdater;
98     /** The position updater for the fragments */
99     private FragmentUpdater fFragmentsUpdater;
100     /** The projection mapping */
101     private ProjectionMapping fMapping;
102
103     /**
104      * Creates a projection document for the given master document.
105      *
106      * @param masterDocument the master document
107      */

108     public ProjectionDocument(IDocument masterDocument) {
109         super();
110
111         fMasterDocument= masterDocument;
112         if (fMasterDocument instanceof IDocumentExtension)
113             fMasterDocumentExtension= (IDocumentExtension) fMasterDocument;
114
115         fSegmentsCategory= SEGMENTS_CATEGORY;
116         fFragmentsCategory= FRAGMENTS_CATEGORY_PREFIX + hashCode();
117         fMasterDocument.addPositionCategory(fFragmentsCategory);
118         fFragmentsUpdater= new FragmentUpdater(fFragmentsCategory);
119         fMasterDocument.addPositionUpdater(fFragmentsUpdater);
120
121         fMapping= new ProjectionMapping(masterDocument, fFragmentsCategory, this, fSegmentsCategory);
122
123         ITextStore s= new ProjectionTextStore(masterDocument, fMapping);
124         ILineTracker tracker= new DefaultLineTracker();
125
126         setTextStore(s);
127         setLineTracker(tracker);
128
129         completeInitialization();
130
131         initializeProjection();
132         tracker.set(s.get(0, s.getLength()));
133     }
134
135     /**
136      * Disposes this projection document.
137      */

138     public void dispose() {
139         fMasterDocument.removePositionUpdater(fFragmentsUpdater);
140         try {
141             fMasterDocument.removePositionCategory(fFragmentsCategory);
142         } catch (BadPositionCategoryException x) {
143             // allow multiple dispose calls
144
}
145     }
146
147     private void internalError() {
148         throw new IllegalStateException JavaDoc();
149     }
150
151     /**
152      * Returns the fragments of the master documents.
153      *
154      * @return the fragment of the master document
155      */

156     protected final Position[] getFragments() {
157         try {
158             return fMasterDocument.getPositions(fFragmentsCategory);
159         } catch (BadPositionCategoryException e) {
160             internalError();
161         }
162         // unreachable
163
return null;
164     }
165
166     /**
167      * Returns the segments of this projection document.
168      *
169      * @return the segments of this projection document
170      */

171     protected final Position[] getSegments() {
172         try {
173             return getPositions(fSegmentsCategory);
174         } catch (BadPositionCategoryException e) {
175             internalError();
176         }
177         // unreachable
178
return null;
179     }
180
181     /**
182      * Returns the projection mapping used by this document.
183      *
184      * @return the projection mapping used by this document
185      */

186     public ProjectionMapping getProjectionMapping(){
187         return fMapping;
188     }
189
190     /**
191      * Returns the master document of this projection document.
192      *
193      * @return the master document of this projection document
194      */

195     public IDocument getMasterDocument() {
196         return fMasterDocument;
197     }
198     
199     /*
200      * @see org.eclipse.jface.text.IDocumentExtension4#getDefaultLineDelimiter()
201      * @since 3.1
202      */

203     public String JavaDoc getDefaultLineDelimiter() {
204         return TextUtilities.getDefaultLineDelimiter(fMasterDocument);
205     }
206     
207     /**
208      * Initializes the projection document from the master document based on
209      * the master's fragments.
210      */

211     private void initializeProjection() {
212
213         try {
214
215             addPositionCategory(fSegmentsCategory);
216             fSegmentUpdater= new SegmentUpdater(fSegmentsCategory);
217             addPositionUpdater(fSegmentUpdater);
218
219             int offset= 0;
220             Position[] fragments= getFragments();
221             for (int i= 0; i < fragments.length; i++) {
222                 Fragment fragment= (Fragment) fragments[i];
223                 Segment segment= new Segment(offset, fragment.getLength());
224                 segment.fragment= fragment;
225                 addPosition(fSegmentsCategory, segment);
226                 offset += fragment.length;
227             }
228
229         } catch (BadPositionCategoryException x) {
230             internalError();
231         } catch (BadLocationException x) {
232             internalError();
233         }
234     }
235
236     /**
237      * Creates a segment for the given fragment at the given position inside the list of segments.
238      *
239      * @param fragment the corresponding fragment
240      * @param index the index in the list of segments
241      * @return the created segment
242      * @throws BadLocationException in case the fragment is invalid
243      * @throws BadPositionCategoryException in case the segment category is invalid
244      */

245     private Segment createSegmentFor(Fragment fragment, int index) throws BadLocationException, BadPositionCategoryException {
246
247         int offset= 0;
248         if (index > 0) {
249             Position[] segments= getSegments();
250             Segment segment= (Segment) segments[index - 1];
251             offset= segment.getOffset() + segment.getLength();
252         }
253
254         Segment segment= new Segment(offset, 0);
255         segment.fragment= fragment;
256         fragment.segment= segment;
257         addPosition(fSegmentsCategory, segment);
258         return segment;
259     }
260
261     /**
262      * Adds the given range of the master document to this projection document.
263      *
264      * @param offsetInMaster offset of the master document range
265      * @param lengthInMaster length of the master document range
266      * @param masterDocumentEvent the master document event that causes this
267      * projection change or <code>null</code> if none
268      * @throws BadLocationException if the given range is invalid in the master
269      * document
270      */

271     private void internalAddMasterDocumentRange(int offsetInMaster, int lengthInMaster, DocumentEvent masterDocumentEvent) throws BadLocationException {
272         if (lengthInMaster == 0)
273             return;
274
275         try {
276
277             Position[] fragments= getFragments();
278             int index= fMasterDocument.computeIndexInCategory(fFragmentsCategory, offsetInMaster);
279
280             Fragment left= null;
281             Fragment right= null;
282
283             if (index < fragments.length) {
284                 Fragment fragment= (Fragment) fragments[index];
285                 if (offsetInMaster == fragment.offset)
286                     if (fragment.length == 0) // the fragment does not overlap - it is a zero-length fragment at the same offset
287
left= fragment;
288                     else
289                         throw new IllegalArgumentException JavaDoc("overlaps with existing fragment"); //$NON-NLS-1$
290
if (offsetInMaster + lengthInMaster == fragment.offset)
291                     right= fragment;
292             }
293
294             if (0 < index && index <= fragments.length) {
295                 Fragment fragment= (Fragment) fragments[index - 1];
296                 if (fragment.includes(offsetInMaster))
297                     throw new IllegalArgumentException JavaDoc("overlaps with existing fragment"); //$NON-NLS-1$
298
if (fragment.getOffset() + fragment.getLength() == offsetInMaster)
299                     left= fragment;
300             }
301
302             int offsetInSlave= 0;
303             if (index > 0) {
304                 Fragment fragment= (Fragment) fragments[index - 1];
305                 Segment segment= fragment.segment;
306                 offsetInSlave= segment.getOffset() + segment.getLength();
307             }
308
309             ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, offsetInSlave, 0, fMasterDocument.get(offsetInMaster, lengthInMaster), offsetInMaster, lengthInMaster, masterDocumentEvent);
310             super.fireDocumentAboutToBeChanged(event);
311
312             // check for neighboring fragment
313
if (left != null && right != null) {
314
315                 int endOffset= right.getOffset() + right.getLength();
316                 left.setLength(endOffset - left.getOffset());
317                 left.segment.setLength(left.segment.getLength() + right.segment.getLength());
318
319                 removePosition(fSegmentsCategory, right.segment);
320                 fMasterDocument.removePosition(fFragmentsCategory, right);
321
322             } else if (left != null) {
323                 int endOffset= offsetInMaster +lengthInMaster;
324                 left.setLength(endOffset - left.getOffset());
325                 left.segment.markForStretch();
326
327             } else if (right != null) {
328                 right.setOffset(right.getOffset() - lengthInMaster);
329                 right.setLength(right.getLength() + lengthInMaster);
330                 right.segment.markForStretch();
331
332             } else {
333                 // create a new segment
334
Fragment fragment= new Fragment(offsetInMaster, lengthInMaster);
335                 fMasterDocument.addPosition(fFragmentsCategory, fragment);
336                 Segment segment= createSegmentFor(fragment, index);
337                 segment.markForStretch();
338             }
339
340             getTracker().replace(event.getOffset(), event.getLength(), event.getText());
341             super.fireDocumentChanged(event);
342
343         } catch (BadPositionCategoryException x) {
344             internalError();
345         }
346     }
347
348     /**
349      * Finds the fragment of the master document that represents the given range.
350      *
351      * @param offsetInMaster the offset of the range in the master document
352      * @param lengthInMaster the length of the range in the master document
353      * @return the fragment representing the given master document range
354      */

355     private Fragment findFragment(int offsetInMaster, int lengthInMaster) {
356         Position[] fragments= getFragments();
357         for (int i= 0; i < fragments.length; i++) {
358             Fragment f= (Fragment) fragments[i];
359             if (f.getOffset() <= offsetInMaster && offsetInMaster + lengthInMaster <= f.getOffset() + f.getLength())
360                 return f;
361         }
362         return null;
363     }
364
365     /**
366      * Removes the given range of the master document from this projection
367      * document.
368      *
369      * @param offsetInMaster the offset of the range in the master document
370      * @param lengthInMaster the length of the range in the master document
371      *
372      * @throws BadLocationException if the given range is not valid in the
373      * master document
374      * @throws IllegalArgumentException if the given range is not projected in
375      * this projection document or is not completely comprised by
376      * an existing fragment
377      */

378     private void internalRemoveMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException {
379         try {
380
381             IRegion imageRegion= fMapping.toExactImageRegion(new Region(offsetInMaster, lengthInMaster));
382             if (imageRegion == null)
383                 throw new IllegalArgumentException JavaDoc();
384
385             Fragment fragment= findFragment(offsetInMaster, lengthInMaster);
386             if (fragment == null)
387                 throw new IllegalArgumentException JavaDoc();
388
389             ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, imageRegion.getOffset(), imageRegion.getLength(), "", offsetInMaster, lengthInMaster); //$NON-NLS-1$
390
super.fireDocumentAboutToBeChanged(event);
391
392             if (fragment.getOffset() == offsetInMaster) {
393                 fragment.setOffset(offsetInMaster + lengthInMaster);
394                 fragment.setLength(fragment.getLength() - lengthInMaster);
395             } else if (fragment.getOffset() + fragment.getLength() == offsetInMaster + lengthInMaster) {
396                 fragment.setLength(fragment.getLength() - lengthInMaster);
397             } else {
398                 // split fragment into three fragments, let position updater remove it
399

400                 // add fragment for the region to be removed
401
Fragment newFragment= new Fragment(offsetInMaster, lengthInMaster);
402                 Segment segment= new Segment(imageRegion.getOffset(), imageRegion.getLength());
403                 newFragment.segment= segment;
404                 segment.fragment= newFragment;
405                 fMasterDocument.addPosition(fFragmentsCategory, newFragment);
406                 addPosition(fSegmentsCategory, segment);
407
408                 // add fragment for the remainder right of the deleted range in the original fragment
409
int offset= offsetInMaster + lengthInMaster;
410                 newFragment= new Fragment(offset, fragment.getOffset() + fragment.getLength() - offset);
411                 offset= imageRegion.getOffset() + imageRegion.getLength();
412                 segment= new Segment(offset, fragment.segment.getOffset() + fragment.segment.getLength() - offset);
413                 newFragment.segment= segment;
414                 segment.fragment= newFragment;
415                 fMasterDocument.addPosition(fFragmentsCategory, newFragment);
416                 addPosition(fSegmentsCategory, segment);
417
418                 // adjust length of initial fragment (the left one)
419
fragment.setLength(offsetInMaster - fragment.getOffset());
420                 fragment.segment.setLength(imageRegion.getOffset() - fragment.segment.getOffset());
421             }
422
423             getTracker().replace(event.getOffset(), event.getLength(), event.getText());
424             super.fireDocumentChanged(event);
425
426         } catch (BadPositionCategoryException x) {
427             internalError();
428         }
429     }
430
431     /**
432      * Returns the sequence of all master document regions which are contained
433      * in the given master document range and which are not yet part of this
434      * projection document.
435      *
436      * @param offsetInMaster the range offset in the master document
437      * @param lengthInMaster the range length in the master document
438      * @return the sequence of regions which are not yet part of the projection
439      * document
440      * @throws BadLocationException in case the given range is invalid in the
441      * master document
442      */

443     public final IRegion[] computeUnprojectedMasterRegions(int offsetInMaster, int lengthInMaster) throws BadLocationException {
444
445         IRegion[] fragments= null;
446         IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster));
447         if (imageRegion != null)
448             fragments= fMapping.toExactOriginRegions(imageRegion);
449
450         if (fragments == null || fragments.length == 0)
451             return new IRegion[] { new Region(offsetInMaster, lengthInMaster) };
452
453         List JavaDoc gaps= new ArrayList JavaDoc();
454
455         IRegion region= fragments[0];
456         if (offsetInMaster < region.getOffset())
457             gaps.add(new Region(offsetInMaster, region.getOffset() - offsetInMaster));
458
459         for (int i= 0; i < fragments.length - 1; i++) {
460             IRegion left= fragments[i];
461             IRegion right= fragments[i + 1];
462             int leftEnd= left.getOffset() + left.getLength();
463             if (leftEnd < right.getOffset())
464                 gaps.add(new Region(leftEnd, right.getOffset() - leftEnd));
465         }
466
467         region= fragments[fragments.length - 1];
468         int leftEnd= region.getOffset() + region.getLength();
469         int rightEnd= offsetInMaster + lengthInMaster;
470         if (leftEnd < rightEnd)
471             gaps.add(new Region(leftEnd, rightEnd - leftEnd));
472
473         IRegion[] result= new IRegion[gaps.size()];
474         gaps.toArray(result);
475         return result;
476     }
477
478     /**
479      * Returns the first master document region which is contained in the given
480      * master document range and which is not yet part of this projection
481      * document.
482      *
483      * @param offsetInMaster the range offset in the master document
484      * @param lengthInMaster the range length in the master document
485      * @return the first region that is not yet part of the projection document
486      * @throws BadLocationException in case the given range is invalid in the
487      * master document
488      * @since 3.1
489      */

490     private IRegion computeFirstUnprojectedMasterRegion(int offsetInMaster, int lengthInMaster) throws BadLocationException {
491
492         IRegion[] fragments= null;
493         IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster));
494         if (imageRegion != null)
495             fragments= fMapping.toExactOriginRegions(imageRegion);
496
497         if (fragments == null || fragments.length == 0)
498             return new Region(offsetInMaster, lengthInMaster);
499
500         IRegion region= fragments[0];
501         if (offsetInMaster < region.getOffset())
502             return new Region(offsetInMaster, region.getOffset() - offsetInMaster);
503
504         for (int i= 0; i < fragments.length - 1; i++) {
505             IRegion left= fragments[i];
506             IRegion right= fragments[i + 1];
507             int leftEnd= left.getOffset() + left.getLength();
508             if (leftEnd < right.getOffset())
509                 return new Region(leftEnd, right.getOffset() - leftEnd);
510         }
511
512         region= fragments[fragments.length - 1];
513         int leftEnd= region.getOffset() + region.getLength();
514         int rightEnd= offsetInMaster + lengthInMaster;
515         if (leftEnd < rightEnd)
516             return new Region(leftEnd, rightEnd - leftEnd);
517
518         return null;
519     }
520
521     /**
522      * Ensures that the given range of the master document is part of this
523      * projection document.
524      *
525      * @param offsetInMaster the offset of the master document range
526      * @param lengthInMaster the length of the master document range
527      * @throws BadLocationException in case the master event is not valid
528      */

529     public void addMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException {
530         addMasterDocumentRange(offsetInMaster, lengthInMaster, null);
531     }
532
533     /**
534      * Ensures that the given range of the master document is part of this
535      * projection document.
536      *
537      * @param offsetInMaster the offset of the master document range
538      * @param lengthInMaster the length of the master document range
539      * @param masterDocumentEvent the master document event which causes this
540      * projection change, or <code>null</code> if none
541      * @throws BadLocationException in case the master event is not valid
542      */

543     private void addMasterDocumentRange(int offsetInMaster, int lengthInMaster, DocumentEvent masterDocumentEvent) throws BadLocationException {
544         /*
545          * Calling internalAddMasterDocumentRange may cause other master ranges
546          * to become unfolded, resulting in re-entrant calls to this method. In
547          * order to not add a region twice, we have to compute the next region
548          * to add in every iteration.
549          *
550          * To place an upper bound on the number of iterations, we use the number
551          * of fragments * 2 as the limit.
552          */

553         int limit= Math.max(getFragments().length * 2, 20);
554         while (true) {
555             if (limit-- < 0)
556                 throw new IllegalArgumentException JavaDoc("safety loop termination"); //$NON-NLS-1$
557

558             IRegion gap= computeFirstUnprojectedMasterRegion(offsetInMaster, lengthInMaster);
559             if (gap == null)
560                 return;
561
562             internalAddMasterDocumentRange(gap.getOffset(), gap.getLength(), masterDocumentEvent);
563         }
564     }
565
566     /**
567      * Ensures that the given range of the master document is not part of this
568      * projection document.
569      *
570      * @param offsetInMaster the offset of the master document range
571      * @param lengthInMaster the length of the master document range
572      * @throws BadLocationException in case the master event is not valid
573      */

574     public void removeMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException {
575         IRegion[] fragments= computeProjectedMasterRegions(offsetInMaster, lengthInMaster);
576         if (fragments == null || fragments.length == 0)
577             return;
578
579         for (int i= 0; i < fragments.length; i++) {
580             IRegion fragment= fragments[i];
581             internalRemoveMasterDocumentRange(fragment.getOffset(), fragment.getLength());
582         }
583     }
584
585     /**
586      * Returns the sequence of all master document regions with are contained in the given master document
587      * range and which are part of this projection document. May return <code>null</code> if no such
588      * regions exist.
589      *
590      * @param offsetInMaster the range offset in the master document
591      * @param lengthInMaster the range length in the master document
592      * @return the sequence of regions which are part of the projection document or <code>null</code>
593      * @throws BadLocationException in case the given range is invalid in the master document
594      */

595     final public IRegion[] computeProjectedMasterRegions(int offsetInMaster, int lengthInMaster) throws BadLocationException {
596         IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster));
597         return imageRegion != null ? fMapping.toExactOriginRegions(imageRegion) : null;
598     }
599
600     /**
601      * Returns whether this projection is being updated.
602      *
603      * @return <code>true</code> if the document is updating
604      */

605     protected boolean isUpdating() {
606         return fIsUpdating;
607     }
608
609     /*
610      * @see org.eclipse.jface.text.IDocument#replace(int, int, java.lang.String)
611      */

612     public void replace(int offset, int length, String JavaDoc text) throws BadLocationException {
613         try {
614             fIsUpdating= true;
615             if (fMasterDocumentExtension != null)
616                 fMasterDocumentExtension.stopPostNotificationProcessing();
617
618             super.replace(offset, length, text);
619
620         } finally {
621             fIsUpdating= false;
622             if (fMasterDocumentExtension != null)
623                 fMasterDocumentExtension.resumePostNotificationProcessing();
624         }
625     }
626
627     /*
628      * @see org.eclipse.jface.text.IDocument#set(java.lang.String)
629      */

630     public void set(String JavaDoc text) {
631         try {
632             fIsUpdating= true;
633             if (fMasterDocumentExtension != null)
634                 fMasterDocumentExtension.stopPostNotificationProcessing();
635
636             super.set(text);
637
638         } finally {
639             fIsUpdating= false;
640             if (fMasterDocumentExtension != null)
641                 fMasterDocumentExtension.resumePostNotificationProcessing();
642         }
643     }
644
645     /**
646      * Transforms a document event of the master document into a projection
647      * document based document event.
648      *
649      * @param masterEvent the master document event
650      * @return the slave document event
651      * @throws BadLocationException in case the master event is not valid
652      */

653     private ProjectionDocumentEvent normalize(DocumentEvent masterEvent) throws BadLocationException {
654         if (!isUpdating()) {
655             IRegion imageRegion= fMapping.toExactImageRegion(new Region(masterEvent.getOffset(), masterEvent.getLength()));
656             if (imageRegion != null)
657                 return new ProjectionDocumentEvent(this, imageRegion.getOffset(), imageRegion.getLength(), masterEvent.getText(), masterEvent);
658             return null;
659         }
660
661         ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, fOriginalEvent.getOffset(), fOriginalEvent.getLength(), fOriginalEvent.getText(), masterEvent);
662         fOriginalEvent= null;
663         return event;
664     }
665
666     /**
667      * Ensures that when the master event affects this projection document, that the whole region described by the
668      * event is part of this projection document.
669      *
670      * @param masterEvent the master document event
671      * @return <code>true</code> if masterEvent affects this projection document
672      * @throws BadLocationException in case the master event is not valid
673      */

674     protected final boolean adaptProjectionToMasterChange(DocumentEvent masterEvent) throws BadLocationException {
675         if (!isUpdating() && fFragmentsUpdater.affectsPositions(masterEvent) || fIsAutoExpanding && masterEvent.getLength() > 0) {
676
677             addMasterDocumentRange(masterEvent.getOffset(), masterEvent.getLength(), masterEvent);
678             return true;
679
680         } else if (fMapping.getImageLength() == 0 && masterEvent.getLength() == 0) {
681
682             Position[] fragments= getFragments();
683             if (fragments.length == 0) {
684                 // there is no segment in this projection document, thus one must be created
685
// need to bypass the usual infrastructure as the new segment/fragment would be of length 0 and thus the segmentation be not well formed
686
try {
687                     Fragment fragment= new Fragment(0, 0);
688                     fMasterDocument.addPosition(fFragmentsCategory, fragment);
689                     createSegmentFor(fragment, 0);
690                 } catch (BadPositionCategoryException x) {
691                     internalError();
692                 }
693             }
694         }
695
696         return isUpdating();
697     }
698
699     /**
700      * When called, this projection document is informed about a forthcoming
701      * change of its master document. This projection document checks whether
702      * the master document change affects it and if so informs all document
703      * listeners.
704      *
705      * @param masterEvent the master document event
706      */

707     public void masterDocumentAboutToBeChanged(DocumentEvent masterEvent) {
708         try {
709
710             boolean assertNotNull= adaptProjectionToMasterChange(masterEvent);
711             fSlaveEvent= normalize(masterEvent);
712             if (assertNotNull && fSlaveEvent == null)
713                 internalError();
714
715             fMasterEvent= masterEvent;
716             if (fSlaveEvent != null)
717                 delayedFireDocumentAboutToBeChanged();
718
719         } catch (BadLocationException e) {
720             internalError();
721         }
722     }
723
724     /**
725      * When called, this projection document is informed about a change of its
726      * master document. If this projection document is affected it informs all
727      * of its document listeners.
728      *
729      * @param masterEvent the master document event
730      */

731     public void masterDocumentChanged(DocumentEvent masterEvent) {
732         if ( !isUpdating() && masterEvent == fMasterEvent) {
733             if (fSlaveEvent != null) {
734                 try {
735                     getTracker().replace(fSlaveEvent.getOffset(), fSlaveEvent.getLength(), fSlaveEvent.getText());
736                     fireDocumentChanged(fSlaveEvent);
737                 } catch (BadLocationException e) {
738                     internalError();
739                 }
740             } else if (ensureWellFormedSegmentation(masterEvent.getOffset()))
741                 fMapping.projectionChanged();
742         }
743     }
744
745     /*
746      * @see org.eclipse.jface.text.AbstractDocument#fireDocumentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
747      */

748     protected void fireDocumentAboutToBeChanged(DocumentEvent event) {
749         fOriginalEvent= event;
750         // delay it until there is a notification from the master document
751
// at this point, it is expensive to construct the master document information
752
}
753
754     /**
755      * Fires the slave document event as about-to-be-changed event to all registered listeners.
756      */

757     private void delayedFireDocumentAboutToBeChanged() {
758         super.fireDocumentAboutToBeChanged(fSlaveEvent);
759     }
760
761     /**
762      * Ignores the given event and sends the semantically equal slave document event instead.
763      *
764      * @param event the event to be ignored
765      */

766     protected void fireDocumentChanged(DocumentEvent event) {
767         super.fireDocumentChanged(fSlaveEvent);
768     }
769
770     /*
771      * @see org.eclipse.jface.text.AbstractDocument#updateDocumentStructures(org.eclipse.jface.text.DocumentEvent)
772      */

773     protected void updateDocumentStructures(DocumentEvent event) {
774         super.updateDocumentStructures(event);
775         ensureWellFormedSegmentation(computeAnchor(event));
776         fMapping.projectionChanged();
777     }
778
779     private int computeAnchor(DocumentEvent event) {
780         if (event instanceof ProjectionDocumentEvent) {
781             ProjectionDocumentEvent slave= (ProjectionDocumentEvent) event;
782             Object JavaDoc changeType= slave.getChangeType();
783             if (ProjectionDocumentEvent.CONTENT_CHANGE == changeType) {
784                 DocumentEvent master= slave.getMasterEvent();
785                 if (master != null)
786                     return master.getOffset();
787             } else if (ProjectionDocumentEvent.PROJECTION_CHANGE == changeType) {
788                 return slave.getMasterOffset();
789             }
790         }
791         return -1;
792     }
793
794     private boolean ensureWellFormedSegmentation(int anchorOffset) {
795         boolean changed= false;
796         Position[] segments= getSegments();
797         for (int i= 0; i < segments.length; i++) {
798             Segment segment= (Segment) segments[i];
799             if (segment.isDeleted() || segment.getLength() == 0) {
800                 try {
801                     removePosition(fSegmentsCategory, segment);
802                     fMasterDocument.removePosition(fFragmentsCategory, segment.fragment);
803                     changed= true;
804                 } catch (BadPositionCategoryException e) {
805                     internalError();
806                 }
807             } else if (i < segments.length - 1) {
808                 Segment next= (Segment) segments[i + 1];
809                 if (next.isDeleted() || next.getLength() == 0)
810                     continue;
811                 Fragment fragment= segment.fragment;
812                 if (fragment.getOffset() + fragment.getLength() == next.fragment.getOffset()) {
813                     // join fragments and their corresponding segments
814
segment.setLength(segment.getLength() + next.getLength());
815                     fragment.setLength(fragment.getLength() + next.fragment.getLength());
816                     next.delete();
817                 }
818             }
819         }
820
821         if (changed && anchorOffset != -1) {
822             Position[] changedSegments= getSegments();
823             if (changedSegments == null || changedSegments.length == 0) {
824                 Fragment fragment= new Fragment(anchorOffset, 0);
825                 try {
826                     fMasterDocument.addPosition(fFragmentsCategory, fragment);
827                     createSegmentFor(fragment, 0);
828                 } catch (BadLocationException e) {
829                     internalError();
830                 } catch (BadPositionCategoryException e) {
831                     internalError();
832                 }
833             }
834         }
835
836         return changed;
837     }
838
839     /*
840      * @see IDocumentExtension#registerPostNotificationReplace(IDocumentListener, IDocumentExtension.IReplace)
841      */

842     public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) {
843         if (!isUpdating())
844             throw new UnsupportedOperationException JavaDoc();
845         super.registerPostNotificationReplace(owner, replace);
846     }
847
848     /**
849      * Sets the auto expand mode for this document.
850      *
851      * @param autoExpandMode <code>true</code> if auto-expanding
852      */

853     public void setAutoExpandMode(boolean autoExpandMode) {
854         fIsAutoExpanding= autoExpandMode;
855     }
856
857     /**
858      * Replaces all master document ranges with the given master document range.
859      *
860      * @param offsetInMaster the offset in the master document
861      * @param lengthInMaster the length in the master document
862      * @throws BadLocationException if the given range of the master document is not valid
863      */

864     public void replaceMasterDocumentRanges(int offsetInMaster, int lengthInMaster) throws BadLocationException {
865         try {
866
867             ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, 0, fMapping.getImageLength(), fMasterDocument.get(offsetInMaster, lengthInMaster), offsetInMaster, lengthInMaster);
868             super.fireDocumentAboutToBeChanged(event);
869
870             Position[] fragments= getFragments();
871             for (int i= 0; i < fragments.length; i++) {
872                 Fragment fragment= (Fragment) fragments[i];
873                 fMasterDocument.removePosition(fFragmentsCategory, fragment);
874                 removePosition(fSegmentsCategory, fragment.segment);
875             }
876
877             Fragment fragment= new Fragment(offsetInMaster, lengthInMaster);
878             Segment segment= new Segment(0, 0);
879             segment.fragment= fragment;
880             fragment.segment= segment;
881             fMasterDocument.addPosition(fFragmentsCategory, fragment);
882             addPosition(fSegmentsCategory, segment);
883
884             getTracker().set(fMasterDocument.get(offsetInMaster, lengthInMaster));
885             super.fireDocumentChanged(event);
886
887         } catch (BadPositionCategoryException x) {
888             internalError();
889         }
890     }
891 }
892
Popular Tags