001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jface.text.projection;
011:
012: import java.util.ArrayList;
013: import java.util.List;
014:
015: import org.eclipse.jface.text.AbstractDocument;
016: import org.eclipse.jface.text.BadLocationException;
017: import org.eclipse.jface.text.BadPositionCategoryException;
018: import org.eclipse.jface.text.DefaultLineTracker;
019: import org.eclipse.jface.text.DocumentEvent;
020: import org.eclipse.jface.text.IDocument;
021: import org.eclipse.jface.text.IDocumentExtension;
022: import org.eclipse.jface.text.IDocumentInformationMapping;
023: import org.eclipse.jface.text.IDocumentListener;
024: import org.eclipse.jface.text.ILineTracker;
025: import org.eclipse.jface.text.IRegion;
026: import org.eclipse.jface.text.ITextStore;
027: import org.eclipse.jface.text.Position;
028: import org.eclipse.jface.text.Region;
029: import org.eclipse.jface.text.TextUtilities;
030:
031: /**
032: * A <code>ProjectionDocument</code> represents a projection of its master
033: * document. The contents of a projection document is a sequence of fragments of
034: * the master document, i.e. the projection document can be thought as being
035: * constructed from the master document by not copying the whole master document
036: * but omitting several ranges of the master document.
037: * <p>
038: * The projection document indirectly utilizes its master document as
039: * <code>ITextStore</code> by means of a <code>ProjectionTextStore</code>.
040: * <p>
041: * The content of a projection document can be changed in two ways. Either by a
042: * text replace applied to the master document or the projection document. Or by
043: * changing the projection between the master document and the projection
044: * document. For the latter the two methods <code>addMasterDocumentRange</code>
045: * and <code>removeMasterDocumentRange</code> are provided. For any
046: * manipulation, the projection document sends out a
047: * {@link org.eclipse.jface.text.projection.ProjectionDocumentEvent} describing
048: * the change.
049: * <p>
050: * Clients are not supposed to directly instantiate this class. In order to
051: * obtain a projection document, a
052: * {@link org.eclipse.jface.text.projection.ProjectionDocumentManager}should be
053: * used. This class is not intended to be subclassed outside of its origin
054: * package.</p>
055: *
056: * @since 3.0
057: */
058: public class ProjectionDocument extends AbstractDocument {
059:
060: /**
061: * Prefix of the name of the position category used to keep track of the master
062: * document's fragments that correspond to the segments of the projection
063: * document.
064: */
065: private final static String FRAGMENTS_CATEGORY_PREFIX = "__fragmentsCategory"; //$NON-NLS-1$
066:
067: /**
068: * Name of the position category used to keep track of the project
069: * document's segments that correspond to the fragments of the master
070: * document.
071: */
072: private final static String SEGMENTS_CATEGORY = "__segmentsCategory"; //$NON-NLS-1$
073:
074: /** The master document */
075: private IDocument fMasterDocument;
076: /** The master document as document extension */
077: private IDocumentExtension fMasterDocumentExtension;
078: /** The fragments' position category */
079: private String fFragmentsCategory;
080: /** The segment's position category */
081: private String fSegmentsCategory;
082: /** The document event issued by the master document */
083: private DocumentEvent fMasterEvent;
084: /** The document event to be issued by the projection document */
085: private ProjectionDocumentEvent fSlaveEvent;
086: /** The original document event generated by a direct manipulation of this projection document */
087: private DocumentEvent fOriginalEvent;
088: /** Indicates whether the projection document initiated a master document update or not */
089: private boolean fIsUpdating = false;
090: /** Indicated whether the projection document is in auto expand mode nor not */
091: private boolean fIsAutoExpanding = false;
092: /** The position updater for the segments */
093: private SegmentUpdater fSegmentUpdater;
094: /** The position updater for the fragments */
095: private FragmentUpdater fFragmentsUpdater;
096: /** The projection mapping */
097: private ProjectionMapping fMapping;
098:
099: /**
100: * Creates a projection document for the given master document.
101: *
102: * @param masterDocument the master document
103: */
104: public ProjectionDocument(IDocument masterDocument) {
105: super ();
106:
107: fMasterDocument = masterDocument;
108: if (fMasterDocument instanceof IDocumentExtension)
109: fMasterDocumentExtension = (IDocumentExtension) fMasterDocument;
110:
111: fSegmentsCategory = SEGMENTS_CATEGORY;
112: fFragmentsCategory = FRAGMENTS_CATEGORY_PREFIX + hashCode();
113: fMasterDocument.addPositionCategory(fFragmentsCategory);
114: fFragmentsUpdater = new FragmentUpdater(fFragmentsCategory);
115: fMasterDocument.addPositionUpdater(fFragmentsUpdater);
116:
117: fMapping = new ProjectionMapping(masterDocument,
118: fFragmentsCategory, this , fSegmentsCategory);
119:
120: ITextStore s = new ProjectionTextStore(masterDocument, fMapping);
121: ILineTracker tracker = new DefaultLineTracker();
122:
123: setTextStore(s);
124: setLineTracker(tracker);
125:
126: completeInitialization();
127:
128: initializeProjection();
129: tracker.set(s.get(0, s.getLength()));
130: }
131:
132: /**
133: * Disposes this projection document.
134: */
135: public void dispose() {
136: fMasterDocument.removePositionUpdater(fFragmentsUpdater);
137: try {
138: fMasterDocument.removePositionCategory(fFragmentsCategory);
139: } catch (BadPositionCategoryException x) {
140: // allow multiple dispose calls
141: }
142: }
143:
144: private void internalError() {
145: throw new IllegalStateException();
146: }
147:
148: /**
149: * Returns the fragments of the master documents.
150: *
151: * @return the fragment of the master document
152: */
153: protected final Position[] getFragments() {
154: try {
155: return fMasterDocument.getPositions(fFragmentsCategory);
156: } catch (BadPositionCategoryException e) {
157: internalError();
158: }
159: // unreachable
160: return null;
161: }
162:
163: /**
164: * Returns the segments of this projection document.
165: *
166: * @return the segments of this projection document
167: */
168: protected final Position[] getSegments() {
169: try {
170: return getPositions(fSegmentsCategory);
171: } catch (BadPositionCategoryException e) {
172: internalError();
173: }
174: // unreachable
175: return null;
176: }
177:
178: /**
179: * Returns the projection mapping used by this document.
180: *
181: * @return the projection mapping used by this document
182: */
183: public IDocumentInformationMapping getProjectionMapping() {
184: return fMapping;
185: }
186:
187: /**
188: * Returns the master document of this projection document.
189: *
190: * @return the master document of this projection document
191: */
192: public IDocument getMasterDocument() {
193: return fMasterDocument;
194: }
195:
196: /*
197: * @see org.eclipse.jface.text.IDocumentExtension4#getDefaultLineDelimiter()
198: * @since 3.1
199: */
200: public String getDefaultLineDelimiter() {
201: return TextUtilities.getDefaultLineDelimiter(fMasterDocument);
202: }
203:
204: /**
205: * Initializes the projection document from the master document based on
206: * the master's fragments.
207: */
208: private void initializeProjection() {
209:
210: try {
211:
212: addPositionCategory(fSegmentsCategory);
213: fSegmentUpdater = new SegmentUpdater(fSegmentsCategory);
214: addPositionUpdater(fSegmentUpdater);
215:
216: int offset = 0;
217: Position[] fragments = getFragments();
218: for (int i = 0; i < fragments.length; i++) {
219: Fragment fragment = (Fragment) fragments[i];
220: Segment segment = new Segment(offset, fragment
221: .getLength());
222: segment.fragment = fragment;
223: addPosition(fSegmentsCategory, segment);
224: offset += fragment.length;
225: }
226:
227: } catch (BadPositionCategoryException x) {
228: internalError();
229: } catch (BadLocationException x) {
230: internalError();
231: }
232: }
233:
234: /**
235: * Creates a segment for the given fragment at the given position inside the list of segments.
236: *
237: * @param fragment the corresponding fragment
238: * @param index the index in the list of segments
239: * @return the created segment
240: * @throws BadLocationException in case the fragment is invalid
241: * @throws BadPositionCategoryException in case the segment category is invalid
242: */
243: private Segment createSegmentFor(Fragment fragment, int index)
244: throws BadLocationException, BadPositionCategoryException {
245:
246: int offset = 0;
247: if (index > 0) {
248: Position[] segments = getSegments();
249: Segment segment = (Segment) segments[index - 1];
250: offset = segment.getOffset() + segment.getLength();
251: }
252:
253: Segment segment = new Segment(offset, 0);
254: segment.fragment = fragment;
255: fragment.segment = segment;
256: addPosition(fSegmentsCategory, segment);
257: return segment;
258: }
259:
260: /**
261: * Adds the given range of the master document to this projection document.
262: *
263: * @param offsetInMaster offset of the master document range
264: * @param lengthInMaster length of the master document range
265: * @param masterDocumentEvent the master document event that causes this
266: * projection change or <code>null</code> if none
267: * @throws BadLocationException if the given range is invalid in the master
268: * document
269: */
270: private void internalAddMasterDocumentRange(int offsetInMaster,
271: int lengthInMaster, DocumentEvent masterDocumentEvent)
272: throws BadLocationException {
273: if (lengthInMaster == 0)
274: return;
275:
276: try {
277:
278: Position[] fragments = getFragments();
279: int index = fMasterDocument.computeIndexInCategory(
280: fFragmentsCategory, offsetInMaster);
281:
282: Fragment left = null;
283: Fragment right = null;
284:
285: if (index < fragments.length) {
286: Fragment fragment = (Fragment) fragments[index];
287: if (offsetInMaster == fragment.offset)
288: if (fragment.length == 0) // the fragment does not overlap - it is a zero-length fragment at the same offset
289: left = fragment;
290: else
291: throw new IllegalArgumentException(
292: "overlaps with existing fragment"); //$NON-NLS-1$
293: if (offsetInMaster + lengthInMaster == fragment.offset)
294: right = fragment;
295: }
296:
297: if (0 < index && index <= fragments.length) {
298: Fragment fragment = (Fragment) fragments[index - 1];
299: if (fragment.includes(offsetInMaster))
300: throw new IllegalArgumentException(
301: "overlaps with existing fragment"); //$NON-NLS-1$
302: if (fragment.getOffset() + fragment.getLength() == offsetInMaster)
303: left = fragment;
304: }
305:
306: int offsetInSlave = 0;
307: if (index > 0) {
308: Fragment fragment = (Fragment) fragments[index - 1];
309: Segment segment = fragment.segment;
310: offsetInSlave = segment.getOffset()
311: + segment.getLength();
312: }
313:
314: ProjectionDocumentEvent event = new ProjectionDocumentEvent(
315: this , offsetInSlave, 0, fMasterDocument.get(
316: offsetInMaster, lengthInMaster),
317: offsetInMaster, lengthInMaster, masterDocumentEvent);
318: super .fireDocumentAboutToBeChanged(event);
319:
320: // check for neighboring fragment
321: if (left != null && right != null) {
322:
323: int endOffset = right.getOffset() + right.getLength();
324: left.setLength(endOffset - left.getOffset());
325: left.segment.setLength(left.segment.getLength()
326: + right.segment.getLength());
327:
328: removePosition(fSegmentsCategory, right.segment);
329: fMasterDocument.removePosition(fFragmentsCategory,
330: right);
331:
332: } else if (left != null) {
333: int endOffset = offsetInMaster + lengthInMaster;
334: left.setLength(endOffset - left.getOffset());
335: left.segment.markForStretch();
336:
337: } else if (right != null) {
338: right.setOffset(right.getOffset() - lengthInMaster);
339: right.setLength(right.getLength() + lengthInMaster);
340: right.segment.markForStretch();
341:
342: } else {
343: // create a new segment
344: Fragment fragment = new Fragment(offsetInMaster,
345: lengthInMaster);
346: fMasterDocument.addPosition(fFragmentsCategory,
347: fragment);
348: Segment segment = createSegmentFor(fragment, index);
349: segment.markForStretch();
350: }
351:
352: getTracker().replace(event.getOffset(), event.getLength(),
353: event.getText());
354: super .fireDocumentChanged(event);
355:
356: } catch (BadPositionCategoryException x) {
357: internalError();
358: }
359: }
360:
361: /**
362: * Finds the fragment of the master document that represents the given range.
363: *
364: * @param offsetInMaster the offset of the range in the master document
365: * @param lengthInMaster the length of the range in the master document
366: * @return the fragment representing the given master document range
367: */
368: private Fragment findFragment(int offsetInMaster, int lengthInMaster) {
369: Position[] fragments = getFragments();
370: for (int i = 0; i < fragments.length; i++) {
371: Fragment f = (Fragment) fragments[i];
372: if (f.getOffset() <= offsetInMaster
373: && offsetInMaster + lengthInMaster <= f.getOffset()
374: + f.getLength())
375: return f;
376: }
377: return null;
378: }
379:
380: /**
381: * Removes the given range of the master document from this projection
382: * document.
383: *
384: * @param offsetInMaster the offset of the range in the master document
385: * @param lengthInMaster the length of the range in the master document
386: *
387: * @throws BadLocationException if the given range is not valid in the
388: * master document
389: * @throws IllegalArgumentException if the given range is not projected in
390: * this projection document or is not completely comprised by
391: * an existing fragment
392: */
393: private void internalRemoveMasterDocumentRange(int offsetInMaster,
394: int lengthInMaster) throws BadLocationException {
395: try {
396:
397: IRegion imageRegion = fMapping
398: .toExactImageRegion(new Region(offsetInMaster,
399: lengthInMaster));
400: if (imageRegion == null)
401: throw new IllegalArgumentException();
402:
403: Fragment fragment = findFragment(offsetInMaster,
404: lengthInMaster);
405: if (fragment == null)
406: throw new IllegalArgumentException();
407:
408: ProjectionDocumentEvent event = new ProjectionDocumentEvent(
409: this , imageRegion.getOffset(), imageRegion
410: .getLength(),
411: "", offsetInMaster, lengthInMaster); //$NON-NLS-1$
412: super .fireDocumentAboutToBeChanged(event);
413:
414: if (fragment.getOffset() == offsetInMaster) {
415: fragment.setOffset(offsetInMaster + lengthInMaster);
416: fragment.setLength(fragment.getLength()
417: - lengthInMaster);
418: } else if (fragment.getOffset() + fragment.getLength() == offsetInMaster
419: + lengthInMaster) {
420: fragment.setLength(fragment.getLength()
421: - lengthInMaster);
422: } else {
423: // split fragment into three fragments, let position updater remove it
424:
425: // add fragment for the region to be removed
426: Fragment newFragment = new Fragment(offsetInMaster,
427: lengthInMaster);
428: Segment segment = new Segment(imageRegion.getOffset(),
429: imageRegion.getLength());
430: newFragment.segment = segment;
431: segment.fragment = newFragment;
432: fMasterDocument.addPosition(fFragmentsCategory,
433: newFragment);
434: addPosition(fSegmentsCategory, segment);
435:
436: // add fragment for the remainder right of the deleted range in the original fragment
437: int offset = offsetInMaster + lengthInMaster;
438: newFragment = new Fragment(offset, fragment.getOffset()
439: + fragment.getLength() - offset);
440: offset = imageRegion.getOffset()
441: + imageRegion.getLength();
442: segment = new Segment(offset, fragment.segment
443: .getOffset()
444: + fragment.segment.getLength() - offset);
445: newFragment.segment = segment;
446: segment.fragment = newFragment;
447: fMasterDocument.addPosition(fFragmentsCategory,
448: newFragment);
449: addPosition(fSegmentsCategory, segment);
450:
451: // adjust length of initial fragment (the left one)
452: fragment.setLength(offsetInMaster
453: - fragment.getOffset());
454: fragment.segment.setLength(imageRegion.getOffset()
455: - fragment.segment.getOffset());
456: }
457:
458: getTracker().replace(event.getOffset(), event.getLength(),
459: event.getText());
460: super .fireDocumentChanged(event);
461:
462: } catch (BadPositionCategoryException x) {
463: internalError();
464: }
465: }
466:
467: /**
468: * Returns the sequence of all master document regions which are contained
469: * in the given master document range and which are not yet part of this
470: * projection document.
471: *
472: * @param offsetInMaster the range offset in the master document
473: * @param lengthInMaster the range length in the master document
474: * @return the sequence of regions which are not yet part of the projection
475: * document
476: * @throws BadLocationException in case the given range is invalid in the
477: * master document
478: */
479: public final IRegion[] computeUnprojectedMasterRegions(
480: int offsetInMaster, int lengthInMaster)
481: throws BadLocationException {
482:
483: IRegion[] fragments = null;
484: IRegion imageRegion = fMapping.toImageRegion(new Region(
485: offsetInMaster, lengthInMaster));
486: if (imageRegion != null)
487: fragments = fMapping.toExactOriginRegions(imageRegion);
488:
489: if (fragments == null || fragments.length == 0)
490: return new IRegion[] { new Region(offsetInMaster,
491: lengthInMaster) };
492:
493: List gaps = new ArrayList();
494:
495: IRegion region = fragments[0];
496: if (offsetInMaster < region.getOffset())
497: gaps.add(new Region(offsetInMaster, region.getOffset()
498: - offsetInMaster));
499:
500: for (int i = 0; i < fragments.length - 1; i++) {
501: IRegion left = fragments[i];
502: IRegion right = fragments[i + 1];
503: int leftEnd = left.getOffset() + left.getLength();
504: if (leftEnd < right.getOffset())
505: gaps.add(new Region(leftEnd, right.getOffset()
506: - leftEnd));
507: }
508:
509: region = fragments[fragments.length - 1];
510: int leftEnd = region.getOffset() + region.getLength();
511: int rightEnd = offsetInMaster + lengthInMaster;
512: if (leftEnd < rightEnd)
513: gaps.add(new Region(leftEnd, rightEnd - leftEnd));
514:
515: IRegion[] result = new IRegion[gaps.size()];
516: gaps.toArray(result);
517: return result;
518: }
519:
520: /**
521: * Returns the first master document region which is contained in the given
522: * master document range and which is not yet part of this projection
523: * document.
524: *
525: * @param offsetInMaster the range offset in the master document
526: * @param lengthInMaster the range length in the master document
527: * @return the first region that is not yet part of the projection document
528: * @throws BadLocationException in case the given range is invalid in the
529: * master document
530: * @since 3.1
531: */
532: private IRegion computeFirstUnprojectedMasterRegion(
533: int offsetInMaster, int lengthInMaster)
534: throws BadLocationException {
535:
536: IRegion[] fragments = null;
537: IRegion imageRegion = fMapping.toImageRegion(new Region(
538: offsetInMaster, lengthInMaster));
539: if (imageRegion != null)
540: fragments = fMapping.toExactOriginRegions(imageRegion);
541:
542: if (fragments == null || fragments.length == 0)
543: return new Region(offsetInMaster, lengthInMaster);
544:
545: IRegion region = fragments[0];
546: if (offsetInMaster < region.getOffset())
547: return new Region(offsetInMaster, region.getOffset()
548: - offsetInMaster);
549:
550: for (int i = 0; i < fragments.length - 1; i++) {
551: IRegion left = fragments[i];
552: IRegion right = fragments[i + 1];
553: int leftEnd = left.getOffset() + left.getLength();
554: if (leftEnd < right.getOffset())
555: return new Region(leftEnd, right.getOffset() - leftEnd);
556: }
557:
558: region = fragments[fragments.length - 1];
559: int leftEnd = region.getOffset() + region.getLength();
560: int rightEnd = offsetInMaster + lengthInMaster;
561: if (leftEnd < rightEnd)
562: return new Region(leftEnd, rightEnd - leftEnd);
563:
564: return null;
565: }
566:
567: /**
568: * Ensures that the given range of the master document is part of this
569: * projection document.
570: *
571: * @param offsetInMaster the offset of the master document range
572: * @param lengthInMaster the length of the master document range
573: * @throws BadLocationException in case the master event is not valid
574: */
575: public void addMasterDocumentRange(int offsetInMaster,
576: int lengthInMaster) throws BadLocationException {
577: addMasterDocumentRange(offsetInMaster, lengthInMaster, null);
578: }
579:
580: /**
581: * Ensures that the given range of the master document is part of this
582: * projection document.
583: *
584: * @param offsetInMaster the offset of the master document range
585: * @param lengthInMaster the length of the master document range
586: * @param masterDocumentEvent the master document event which causes this
587: * projection change, or <code>null</code> if none
588: * @throws BadLocationException in case the master event is not valid
589: */
590: private void addMasterDocumentRange(int offsetInMaster,
591: int lengthInMaster, DocumentEvent masterDocumentEvent)
592: throws BadLocationException {
593: /*
594: * Calling internalAddMasterDocumentRange may cause other master ranges
595: * to become unfolded, resulting in re-entrant calls to this method. In
596: * order to not add a region twice, we have to compute the next region
597: * to add in every iteration.
598: *
599: * To place an upper bound on the number of iterations, we use the number
600: * of fragments * 2 as the limit.
601: */
602: int limit = Math.max(getFragments().length * 2, 20);
603: while (true) {
604: if (limit-- < 0)
605: throw new IllegalArgumentException(
606: "safety loop termination"); //$NON-NLS-1$
607:
608: IRegion gap = computeFirstUnprojectedMasterRegion(
609: offsetInMaster, lengthInMaster);
610: if (gap == null)
611: return;
612:
613: internalAddMasterDocumentRange(gap.getOffset(), gap
614: .getLength(), masterDocumentEvent);
615: }
616: }
617:
618: /**
619: * Ensures that the given range of the master document is not part of this
620: * projection document.
621: *
622: * @param offsetInMaster the offset of the master document range
623: * @param lengthInMaster the length of the master document range
624: * @throws BadLocationException in case the master event is not valid
625: */
626: public void removeMasterDocumentRange(int offsetInMaster,
627: int lengthInMaster) throws BadLocationException {
628: IRegion[] fragments = computeProjectedMasterRegions(
629: offsetInMaster, lengthInMaster);
630: if (fragments == null || fragments.length == 0)
631: return;
632:
633: for (int i = 0; i < fragments.length; i++) {
634: IRegion fragment = fragments[i];
635: internalRemoveMasterDocumentRange(fragment.getOffset(),
636: fragment.getLength());
637: }
638: }
639:
640: /**
641: * Returns the sequence of all master document regions with are contained in the given master document
642: * range and which are part of this projection document. May return <code>null</code> if no such
643: * regions exist.
644: *
645: * @param offsetInMaster the range offset in the master document
646: * @param lengthInMaster the range length in the master document
647: * @return the sequence of regions which are part of the projection document or <code>null</code>
648: * @throws BadLocationException in case the given range is invalid in the master document
649: */
650: final public IRegion[] computeProjectedMasterRegions(
651: int offsetInMaster, int lengthInMaster)
652: throws BadLocationException {
653: IRegion imageRegion = fMapping.toImageRegion(new Region(
654: offsetInMaster, lengthInMaster));
655: return imageRegion != null ? fMapping
656: .toExactOriginRegions(imageRegion) : null;
657: }
658:
659: /**
660: * Returns whether this projection is being updated.
661: *
662: * @return <code>true</code> if the document is updating
663: */
664: protected boolean isUpdating() {
665: return fIsUpdating;
666: }
667:
668: /*
669: * @see org.eclipse.jface.text.IDocument#replace(int, int, java.lang.String)
670: */
671: public void replace(int offset, int length, String text)
672: throws BadLocationException {
673: try {
674: fIsUpdating = true;
675: if (fMasterDocumentExtension != null)
676: fMasterDocumentExtension
677: .stopPostNotificationProcessing();
678:
679: super .replace(offset, length, text);
680:
681: } finally {
682: fIsUpdating = false;
683: if (fMasterDocumentExtension != null)
684: fMasterDocumentExtension
685: .resumePostNotificationProcessing();
686: }
687: }
688:
689: /*
690: * @see org.eclipse.jface.text.IDocument#set(java.lang.String)
691: */
692: public void set(String text) {
693: try {
694: fIsUpdating = true;
695: if (fMasterDocumentExtension != null)
696: fMasterDocumentExtension
697: .stopPostNotificationProcessing();
698:
699: super .set(text);
700:
701: } finally {
702: fIsUpdating = false;
703: if (fMasterDocumentExtension != null)
704: fMasterDocumentExtension
705: .resumePostNotificationProcessing();
706: }
707: }
708:
709: /**
710: * Transforms a document event of the master document into a projection
711: * document based document event.
712: *
713: * @param masterEvent the master document event
714: * @return the slave document event
715: * @throws BadLocationException in case the master event is not valid
716: */
717: private ProjectionDocumentEvent normalize(DocumentEvent masterEvent)
718: throws BadLocationException {
719: if (!isUpdating()) {
720: IRegion imageRegion = fMapping
721: .toExactImageRegion(new Region(masterEvent
722: .getOffset(), masterEvent.getLength()));
723: if (imageRegion != null)
724: return new ProjectionDocumentEvent(this , imageRegion
725: .getOffset(), imageRegion.getLength(),
726: masterEvent.getText(), masterEvent);
727: return null;
728: }
729:
730: ProjectionDocumentEvent event = new ProjectionDocumentEvent(
731: this , fOriginalEvent.getOffset(), fOriginalEvent
732: .getLength(), fOriginalEvent.getText(),
733: masterEvent);
734: fOriginalEvent = null;
735: return event;
736: }
737:
738: /**
739: * Ensures that when the master event affects this projection document, that the whole region described by the
740: * event is part of this projection document.
741: *
742: * @param masterEvent the master document event
743: * @return <code>true</code> if masterEvent affects this projection document
744: * @throws BadLocationException in case the master event is not valid
745: */
746: protected final boolean adaptProjectionToMasterChange(
747: DocumentEvent masterEvent) throws BadLocationException {
748: if (!isUpdating()
749: && fFragmentsUpdater.affectsPositions(masterEvent)
750: || fIsAutoExpanding && masterEvent.getLength() > 0) {
751:
752: addMasterDocumentRange(masterEvent.getOffset(), masterEvent
753: .getLength(), masterEvent);
754: return true;
755:
756: } else if (fMapping.getImageLength() == 0
757: && masterEvent.getLength() == 0) {
758:
759: Position[] fragments = getFragments();
760: if (fragments.length == 0) {
761: // there is no segment in this projection document, thus one must be created
762: // need to bypass the usual infrastructure as the new segment/fragment would be of length 0 and thus the segmentation be not well formed
763: try {
764: Fragment fragment = new Fragment(0, 0);
765: fMasterDocument.addPosition(fFragmentsCategory,
766: fragment);
767: createSegmentFor(fragment, 0);
768: } catch (BadPositionCategoryException x) {
769: internalError();
770: }
771: }
772: }
773:
774: return isUpdating();
775: }
776:
777: /**
778: * When called, this projection document is informed about a forthcoming
779: * change of its master document. This projection document checks whether
780: * the master document change affects it and if so informs all document
781: * listeners.
782: *
783: * @param masterEvent the master document event
784: */
785: public void masterDocumentAboutToBeChanged(DocumentEvent masterEvent) {
786: try {
787:
788: boolean assertNotNull = adaptProjectionToMasterChange(masterEvent);
789: fSlaveEvent = normalize(masterEvent);
790: if (assertNotNull && fSlaveEvent == null)
791: internalError();
792:
793: fMasterEvent = masterEvent;
794: if (fSlaveEvent != null)
795: delayedFireDocumentAboutToBeChanged();
796:
797: } catch (BadLocationException e) {
798: internalError();
799: }
800: }
801:
802: /**
803: * When called, this projection document is informed about a change of its
804: * master document. If this projection document is affected it informs all
805: * of its document listeners.
806: *
807: * @param masterEvent the master document event
808: */
809: public void masterDocumentChanged(DocumentEvent masterEvent) {
810: if (!isUpdating() && masterEvent == fMasterEvent) {
811: if (fSlaveEvent != null) {
812: try {
813: getTracker().replace(fSlaveEvent.getOffset(),
814: fSlaveEvent.getLength(),
815: fSlaveEvent.getText());
816: fireDocumentChanged(fSlaveEvent);
817: } catch (BadLocationException e) {
818: internalError();
819: }
820: } else if (ensureWellFormedSegmentation(masterEvent
821: .getOffset()))
822: fMapping.projectionChanged();
823: }
824: }
825:
826: /*
827: * @see org.eclipse.jface.text.AbstractDocument#fireDocumentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
828: */
829: protected void fireDocumentAboutToBeChanged(DocumentEvent event) {
830: fOriginalEvent = event;
831: // delay it until there is a notification from the master document
832: // at this point, it is expensive to construct the master document information
833: }
834:
835: /**
836: * Fires the slave document event as about-to-be-changed event to all registered listeners.
837: */
838: private void delayedFireDocumentAboutToBeChanged() {
839: super .fireDocumentAboutToBeChanged(fSlaveEvent);
840: }
841:
842: /**
843: * Ignores the given event and sends the semantically equal slave document event instead.
844: *
845: * @param event the event to be ignored
846: */
847: protected void fireDocumentChanged(DocumentEvent event) {
848: super .fireDocumentChanged(fSlaveEvent);
849: }
850:
851: /*
852: * @see org.eclipse.jface.text.AbstractDocument#updateDocumentStructures(org.eclipse.jface.text.DocumentEvent)
853: */
854: protected void updateDocumentStructures(DocumentEvent event) {
855: super .updateDocumentStructures(event);
856: ensureWellFormedSegmentation(computeAnchor(event));
857: fMapping.projectionChanged();
858: }
859:
860: private int computeAnchor(DocumentEvent event) {
861: if (event instanceof ProjectionDocumentEvent) {
862: ProjectionDocumentEvent slave = (ProjectionDocumentEvent) event;
863: Object changeType = slave.getChangeType();
864: if (ProjectionDocumentEvent.CONTENT_CHANGE == changeType) {
865: DocumentEvent master = slave.getMasterEvent();
866: if (master != null)
867: return master.getOffset();
868: } else if (ProjectionDocumentEvent.PROJECTION_CHANGE == changeType) {
869: return slave.getMasterOffset();
870: }
871: }
872: return -1;
873: }
874:
875: private boolean ensureWellFormedSegmentation(int anchorOffset) {
876: boolean changed = false;
877: Position[] segments = getSegments();
878: for (int i = 0; i < segments.length; i++) {
879: Segment segment = (Segment) segments[i];
880: if (segment.isDeleted() || segment.getLength() == 0) {
881: try {
882: removePosition(fSegmentsCategory, segment);
883: fMasterDocument.removePosition(fFragmentsCategory,
884: segment.fragment);
885: changed = true;
886: } catch (BadPositionCategoryException e) {
887: internalError();
888: }
889: } else if (i < segments.length - 1) {
890: Segment next = (Segment) segments[i + 1];
891: if (next.isDeleted() || next.getLength() == 0)
892: continue;
893: Fragment fragment = segment.fragment;
894: if (fragment.getOffset() + fragment.getLength() == next.fragment
895: .getOffset()) {
896: // join fragments and their corresponding segments
897: segment.setLength(segment.getLength()
898: + next.getLength());
899: fragment.setLength(fragment.getLength()
900: + next.fragment.getLength());
901: next.delete();
902: }
903: }
904: }
905:
906: if (changed && anchorOffset != -1) {
907: Position[] changedSegments = getSegments();
908: if (changedSegments == null || changedSegments.length == 0) {
909: Fragment fragment = new Fragment(anchorOffset, 0);
910: try {
911: fMasterDocument.addPosition(fFragmentsCategory,
912: fragment);
913: createSegmentFor(fragment, 0);
914: } catch (BadLocationException e) {
915: internalError();
916: } catch (BadPositionCategoryException e) {
917: internalError();
918: }
919: }
920: }
921:
922: return changed;
923: }
924:
925: /*
926: * @see IDocumentExtension#registerPostNotificationReplace(IDocumentListener, IDocumentExtension.IReplace)
927: */
928: public void registerPostNotificationReplace(
929: IDocumentListener owner, IDocumentExtension.IReplace replace) {
930: if (!isUpdating())
931: throw new UnsupportedOperationException();
932: super .registerPostNotificationReplace(owner, replace);
933: }
934:
935: /**
936: * Sets the auto expand mode for this document.
937: *
938: * @param autoExpandMode <code>true</code> if auto-expanding
939: */
940: public void setAutoExpandMode(boolean autoExpandMode) {
941: fIsAutoExpanding = autoExpandMode;
942: }
943:
944: /**
945: * Replaces all master document ranges with the given master document range.
946: *
947: * @param offsetInMaster the offset in the master document
948: * @param lengthInMaster the length in the master document
949: * @throws BadLocationException if the given range of the master document is not valid
950: */
951: public void replaceMasterDocumentRanges(int offsetInMaster,
952: int lengthInMaster) throws BadLocationException {
953: try {
954:
955: ProjectionDocumentEvent event = new ProjectionDocumentEvent(
956: this , 0, fMapping.getImageLength(), fMasterDocument
957: .get(offsetInMaster, lengthInMaster),
958: offsetInMaster, lengthInMaster);
959: super .fireDocumentAboutToBeChanged(event);
960:
961: Position[] fragments = getFragments();
962: for (int i = 0; i < fragments.length; i++) {
963: Fragment fragment = (Fragment) fragments[i];
964: fMasterDocument.removePosition(fFragmentsCategory,
965: fragment);
966: removePosition(fSegmentsCategory, fragment.segment);
967: }
968:
969: Fragment fragment = new Fragment(offsetInMaster,
970: lengthInMaster);
971: Segment segment = new Segment(0, 0);
972: segment.fragment = fragment;
973: fragment.segment = segment;
974: fMasterDocument.addPosition(fFragmentsCategory, fragment);
975: addPosition(fSegmentsCategory, segment);
976:
977: getTracker()
978: .set(
979: fMasterDocument.get(offsetInMaster,
980: lengthInMaster));
981: super .fireDocumentChanged(event);
982:
983: } catch (BadPositionCategoryException x) {
984: internalError();
985: }
986: }
987: }
|