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.ui.texteditor;
011:
012: import java.util.ArrayList;
013: import java.util.Iterator;
014: import java.util.List;
015:
016: import org.osgi.framework.Bundle;
017:
018: import org.eclipse.core.resources.IMarker;
019: import org.eclipse.core.resources.IResourceStatus;
020:
021: import org.eclipse.core.runtime.Assert;
022: import org.eclipse.core.runtime.CoreException;
023: import org.eclipse.core.runtime.IConfigurationElement;
024: import org.eclipse.core.runtime.IExtensionPoint;
025: import org.eclipse.core.runtime.ILog;
026: import org.eclipse.core.runtime.IStatus;
027: import org.eclipse.core.runtime.Platform;
028: import org.eclipse.core.runtime.Status;
029:
030: import org.eclipse.core.filebuffers.IPersistableAnnotationModel;
031:
032: import org.eclipse.jface.text.BadLocationException;
033: import org.eclipse.jface.text.IDocument;
034: import org.eclipse.jface.text.Position;
035: import org.eclipse.jface.text.source.Annotation;
036: import org.eclipse.jface.text.source.AnnotationModel;
037: import org.eclipse.jface.text.source.IAnnotationMap;
038:
039: import org.eclipse.ui.editors.text.EditorsUI;
040:
041: import org.eclipse.ui.PlatformUI;
042:
043: /**
044: * Abstract implementation of a marker-based annotation model.
045: * <p>
046: * Markers are provided by an underlying source (a subclass responsibility).
047: * Markers whose textual range gets deleted during text editing are removed
048: * from the model on save. The {@link #updateMarkers(IDocument)} method can be used
049: * to force the model to update the source's markers with any changes to their
050: * locations due to edits. Clients can register a {@link org.eclipse.ui.texteditor.IMarkerUpdater}
051: * objects in order to define the process of marker updating. Registration can be done
052: * using the <code>"org.eclipse.ui.markerUpdaters"</code> extension point.
053: * </p>
054: * <p>
055: * Subclasses must implement the following methods:
056: * <ul>
057: * <li><code>retrieveMarkers</code></li>
058: * <li><code>isAcceptable</code></li>
059: * <li><code>deleteMarkers</code></li>
060: * <li><code>listenToMarkerChanges</code></li>
061: * </ul>
062: * </p>
063: */
064: public abstract class AbstractMarkerAnnotationModel extends
065: AnnotationModel implements IPersistableAnnotationModel {
066:
067: /** List of annotations whose text range became invalid because of document changes */
068: private List fDeletedAnnotations = new ArrayList(2);
069: /** List of registered and instantiated marker updaters */
070: private List fInstantiatedMarkerUpdaters = null;
071: /** List of registered but not yet instantiated marker updaters */
072: private List fMarkerUpdaterSpecifications = null;
073:
074: /**
075: * Retrieves all markers from this model.
076: * <p>
077: * Subclasses must implement this method.</p>
078: *
079: * @return the list of markers
080: * @throws CoreException if there is a problem getting the markers
081: */
082: protected abstract IMarker[] retrieveMarkers() throws CoreException;
083:
084: /**
085: * Deletes the given markers from this model.
086: * <p>
087: * Subclasses must implement this method.</p>
088: *
089: * @param markers the array of markers
090: * @throws CoreException if there are problems deleting the markers
091: */
092: protected abstract void deleteMarkers(IMarker[] markers)
093: throws CoreException;
094:
095: /**
096: * Tells the model whether it should listen for marker changes.
097: * <p>
098: * Subclasses must implement this method.</p>
099: *
100: * @param listen <code>true</code> if this model should listen, and
101: * <code>false</code> otherwise
102: */
103: protected abstract void listenToMarkerChanges(boolean listen);
104:
105: /**
106: * Determines whether the marker is acceptable as an addition to this model.
107: * If the marker, say, represents an aspect or range of no interest to this
108: * model, the marker is rejected.
109: * <p>
110: * Subclasses must implement this method.</p>
111: *
112: * @param marker the marker
113: * @return <code>true</code> if the marker is acceptable
114: */
115: protected abstract boolean isAcceptable(IMarker marker);
116:
117: /**
118: * Creates a new annotation model. The annotation model does not manage any
119: * annotations and is not connected to any document.
120: */
121: protected AbstractMarkerAnnotationModel() {
122: }
123:
124: /**
125: * Adds the given marker updater to this annotation model.
126: * It is the client's responsibility to ensure the consistency
127: * of the set of registered marker updaters.
128: *
129: * @param markerUpdater the marker updater to be added
130: */
131: protected void addMarkerUpdater(IMarkerUpdater markerUpdater) {
132: if (!fInstantiatedMarkerUpdaters.contains(markerUpdater))
133: fInstantiatedMarkerUpdaters.add(markerUpdater);
134: }
135:
136: /**
137: * Removes the given marker updater from this annotation model.
138: *
139: * @param markerUpdater the marker updater to be removed
140: */
141: protected void removeMarkerUpdater(IMarkerUpdater markerUpdater) {
142: fInstantiatedMarkerUpdaters.remove(markerUpdater);
143: }
144:
145: /**
146: * Creates a new annotation for the given marker.
147: * <p>
148: * Subclasses may override.</p>
149: *
150: * @param marker the marker
151: * @return the new marker annotation
152: */
153: protected MarkerAnnotation createMarkerAnnotation(IMarker marker) {
154: return new MarkerAnnotation(marker);
155: }
156:
157: /**
158: * Handles an unanticipated <code>CoreException</code> in
159: * a standard manner.
160: *
161: * @param exception the exception
162: * @param message a message to aid debugging
163: */
164: protected void handleCoreException(CoreException exception,
165: String message) {
166:
167: Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID);
168: ILog log = Platform.getLog(bundle);
169: if (message != null)
170: log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,
171: IStatus.OK, message, exception));
172: else
173: log.log(exception.getStatus());
174: }
175:
176: /**
177: * Creates and returns the character position of the given marker based
178: * on its attributes.
179: * <p>
180: * Subclasses may override.</p>
181: *
182: * @param marker the marker
183: * @return the new position or <code>null</code> if the marker attributes do not specify a valid position
184: */
185: protected Position createPositionFromMarker(IMarker marker) {
186:
187: int start = MarkerUtilities.getCharStart(marker);
188: int end = MarkerUtilities.getCharEnd(marker);
189:
190: if (start > end) {
191: end = start + end;
192: start = end - start;
193: end = end - start;
194: }
195:
196: if (start == -1 && end == -1) {
197: // marker line number is 1-based
198: int line = MarkerUtilities.getLineNumber(marker);
199: if (line > 0 && fDocument != null) {
200: try {
201: start = fDocument.getLineOffset(line - 1);
202: end = start;
203: } catch (BadLocationException x) {
204: }
205: }
206: }
207:
208: if (start > -1 && end > -1)
209: return new Position(start, end - start);
210:
211: return null;
212: }
213:
214: /**
215: * Creates an annotation for the given marker and adds it to this model.
216: * Does nothing if the marker is not acceptable to this model.
217: *
218: * @param marker the marker
219: * @see #isAcceptable(IMarker)
220: */
221: protected final void addMarkerAnnotation(IMarker marker) {
222:
223: if (isAcceptable(marker)) {
224: Position p = createPositionFromMarker(marker);
225: if (p != null)
226: try {
227: MarkerAnnotation annotation = createMarkerAnnotation(marker);
228: if (annotation != null)
229: addAnnotation(annotation, p, false);
230: } catch (BadLocationException e) {
231: // ignore invalid position
232: }
233: }
234: }
235:
236: /**
237: * Connects to the source of markers as marker change listener.
238: * @see AnnotationModel#connected()
239: */
240: protected void connected() {
241:
242: listenToMarkerChanges(true);
243:
244: try {
245: catchupWithMarkers();
246: } catch (CoreException x) {
247: if (x.getStatus().getCode() != IResourceStatus.RESOURCE_NOT_FOUND)
248: handleCoreException(
249: x,
250: TextEditorMessages.AbstractMarkerAnnotationModel_connected);
251: }
252:
253: fireModelChanged();
254: }
255:
256: /**
257: * Installs all marker updaters for this marker annotation model.
258: */
259: private void installMarkerUpdaters() {
260:
261: // initialize lists - indicates that the initialization happened
262: fMarkerUpdaterSpecifications = new ArrayList(2);
263: fInstantiatedMarkerUpdaters = new ArrayList(2);
264:
265: // populate list
266: IExtensionPoint extensionPoint = Platform
267: .getExtensionRegistry().getExtensionPoint(
268: EditorsUI.PLUGIN_ID, "markerUpdaters"); //$NON-NLS-1$
269: if (extensionPoint != null) {
270: IConfigurationElement[] elements = extensionPoint
271: .getConfigurationElements();
272: for (int i = 0; i < elements.length; i++)
273: fMarkerUpdaterSpecifications.add(elements[i]);
274: }
275: }
276:
277: /**
278: * Uninstalls all marker updaters.
279: */
280: private void uninstallMarkerUpdaters() {
281: if (fInstantiatedMarkerUpdaters != null) {
282: fInstantiatedMarkerUpdaters.clear();
283: fInstantiatedMarkerUpdaters = null;
284: }
285:
286: if (fMarkerUpdaterSpecifications != null) {
287: fMarkerUpdaterSpecifications.clear();
288: fMarkerUpdaterSpecifications = null;
289: }
290: }
291:
292: /**
293: * Removes the marker change listener.
294: * @see AnnotationModel#disconnected()
295: */
296: protected void disconnected() {
297: listenToMarkerChanges(false);
298: uninstallMarkerUpdaters();
299: }
300:
301: /**
302: * Returns the position known to this annotation model for the given marker.
303: *
304: * @param marker the marker
305: * @return the position, or <code>null</code> if none
306: */
307: public Position getMarkerPosition(IMarker marker) {
308: MarkerAnnotation a = getMarkerAnnotation(marker);
309: if (a != null) {
310: return (Position) getAnnotationMap().get(a);
311: }
312: return null;
313: }
314:
315: /**
316: * Updates the annotation corresponding to the given marker which has changed
317: * in some way.
318: * <p>
319: * Subclasses may override.</p>
320: *
321: * @param marker the marker
322: */
323: protected void modifyMarkerAnnotation(IMarker marker) {
324: MarkerAnnotation a = getMarkerAnnotation(marker);
325: if (a != null) {
326: Position p = createPositionFromMarker(marker);
327: if (p != null) {
328: a.update();
329: modifyAnnotationPosition(a, p, false);
330: }
331: } else
332: addMarkerAnnotation(marker);
333: }
334:
335: /*
336: * @see AnnotationModel#removeAnnotations(List, boolean, boolean)
337: */
338: protected void removeAnnotations(List annotations,
339: boolean fireModelChanged, boolean modelInitiated) {
340: if (annotations != null && annotations.size() > 0) {
341:
342: List markerAnnotations = new ArrayList();
343: for (Iterator e = annotations.iterator(); e.hasNext();) {
344: Annotation a = (Annotation) e.next();
345: if (a instanceof MarkerAnnotation)
346: markerAnnotations.add(a);
347:
348: // remove annotations from annotation model
349: removeAnnotation(a, false);
350: }
351:
352: if (markerAnnotations.size() > 0) {
353:
354: if (modelInitiated) {
355: // if model initiated also remove it from the marker manager
356:
357: listenToMarkerChanges(false);
358: try {
359:
360: IMarker[] m = new IMarker[markerAnnotations
361: .size()];
362: for (int i = 0; i < m.length; i++) {
363: MarkerAnnotation ma = (MarkerAnnotation) markerAnnotations
364: .get(i);
365: m[i] = ma.getMarker();
366: }
367: deleteMarkers(m);
368:
369: } catch (CoreException x) {
370: handleCoreException(
371: x,
372: TextEditorMessages.AbstractMarkerAnnotationModel_removeAnnotations);
373: }
374: listenToMarkerChanges(true);
375:
376: } else {
377: // remember deleted annotations in order to remove their markers later on
378: fDeletedAnnotations.addAll(markerAnnotations);
379: }
380: }
381:
382: if (fireModelChanged)
383: fireModelChanged();
384: }
385: }
386:
387: /**
388: * Removes the annotation corresponding to the given marker. Does nothing
389: * if there is no annotation for this marker.
390: *
391: * @param marker the marker
392: */
393: protected final void removeMarkerAnnotation(IMarker marker) {
394: MarkerAnnotation a = getMarkerAnnotation(marker);
395: if (a != null) {
396: removeAnnotation(a, false);
397: }
398: }
399:
400: /**
401: * Re-populates this model with annotations for all markers retrieved
402: * from the maker source via <code>retrieveMarkers</code>.
403: *
404: * @throws CoreException if there is a problem getting the markers
405: */
406: private void catchupWithMarkers() throws CoreException {
407:
408: for (Iterator e = getAnnotationIterator(false); e.hasNext();) {
409: Annotation a = (Annotation) e.next();
410: if (a instanceof MarkerAnnotation)
411: removeAnnotation(a, false);
412: }
413:
414: IMarker[] markers = retrieveMarkers();
415: if (markers != null) {
416: for (int i = 0; i < markers.length; i++)
417: addMarkerAnnotation(markers[i]);
418: }
419: }
420:
421: /**
422: * Returns this model's annotation for the given marker.
423: *
424: * @param marker the marker
425: * @return the annotation, or <code>null</code> if none
426: */
427: public final MarkerAnnotation getMarkerAnnotation(IMarker marker) {
428: Iterator e = getAnnotationIterator(false);
429: while (e.hasNext()) {
430: Object o = e.next();
431: if (o instanceof MarkerAnnotation) {
432: MarkerAnnotation a = (MarkerAnnotation) o;
433: if (marker.equals(a.getMarker())) {
434: return a;
435: }
436: }
437: }
438: return null;
439: }
440:
441: /**
442: * Creates a marker updater as specified in the given configuration element.
443: *
444: * @param element the configuration element
445: * @return the created marker updater or <code>null</code> if none could be created
446: */
447: private IMarkerUpdater createMarkerUpdater(
448: IConfigurationElement element) {
449: try {
450: return (IMarkerUpdater) element
451: .createExecutableExtension("class"); //$NON-NLS-1$
452: } catch (CoreException x) {
453: handleCoreException(
454: x,
455: TextEditorMessages.AbstractMarkerAnnotationModel_createMarkerUpdater);
456: }
457:
458: return null;
459: }
460:
461: /**
462: * Checks whether a marker updater is registered for the type of the
463: * given marker but not yet instantiated. If so, the method instantiates
464: * the marker updater and registers it with this model.
465: *
466: * @param marker the marker for which to look for an updater
467: * @since 2.0
468: */
469: private void checkMarkerUpdaters(IMarker marker) {
470: List toBeDeleted = new ArrayList();
471: for (int i = 0; i < fMarkerUpdaterSpecifications.size(); i++) {
472: IConfigurationElement spec = (IConfigurationElement) fMarkerUpdaterSpecifications
473: .get(i);
474: String markerType = spec.getAttribute("markerType"); //$NON-NLS-1$
475: if (markerType == null
476: || MarkerUtilities.isMarkerType(marker, markerType)) {
477: toBeDeleted.add(spec);
478: IMarkerUpdater updater = createMarkerUpdater(spec);
479: if (updater != null)
480: addMarkerUpdater(updater);
481: }
482: }
483:
484: for (int i = 0; i < toBeDeleted.size(); i++)
485: fMarkerUpdaterSpecifications.remove(toBeDeleted.get(i));
486: }
487:
488: /**
489: * Updates the given marker according to the given position in the given
490: * document. If the given position is <code>null</code>, the marker is
491: * assumed to carry the correct positional information. If it is detected
492: * that the marker is invalid and should thus be deleted, this method
493: * returns <code>false</code>.
494: * <p>
495: * <strong>Note:</strong> This implementation queries the registered
496: * {@linkplain IMarkerUpdater}s. If any of these updaters returns
497: * <code>false</code> this method also returns <code>false</code>.
498: * </p>
499: *
500: * @param marker the marker to be updated
501: * @param document the document into which the given position points
502: * @param position the current position of the marker inside the given document
503: * @return <code>false</code> if the marker is invalid
504: * @throws CoreException if there is a problem updating the marker
505: * @since 2.0
506: * @deprecated use <code>updateMarker(IDocument, IMarker, Position)</code> instead. This method will be changed to protected.
507: */
508: public boolean updateMarker(IMarker marker, IDocument document,
509: Position position) throws CoreException {
510:
511: if (fMarkerUpdaterSpecifications == null)
512: installMarkerUpdaters();
513:
514: if (!fMarkerUpdaterSpecifications.isEmpty())
515: checkMarkerUpdaters(marker);
516:
517: boolean isOK = true;
518:
519: for (int i = 0; i < fInstantiatedMarkerUpdaters.size(); i++) {
520: IMarkerUpdater updater = (IMarkerUpdater) fInstantiatedMarkerUpdaters
521: .get(i);
522: String markerType = updater.getMarkerType();
523: if (markerType == null
524: || MarkerUtilities.isMarkerType(marker, markerType)) {
525:
526: if (position == null) {
527: /* compatibility code */
528: position = createPositionFromMarker(marker);
529: }
530:
531: isOK = (isOK && updater.updateMarker(marker, document,
532: position));
533: }
534: }
535:
536: return isOK;
537: }
538:
539: /**
540: * Updates the given marker according to the given position in the given
541: * document. If the given position is <code>null</code>, the marker is
542: * assumed to carry the correct positional information. If it is detected
543: * that the marker is invalid and should thus be deleted, this method
544: * returns <code>false</code>.
545: *
546: * @param marker the marker to be updated
547: * @param document the document into which the given position points
548: * @param position the current position of the marker inside the given document
549: * @return <code>false</code> if the marker is invalid
550: * @throws CoreException if there is a problem updating the marker
551: * @since 3.0
552: */
553: public boolean updateMarker(IDocument document, IMarker marker,
554: Position position) throws CoreException {
555: listenToMarkerChanges(false);
556: try {
557: return updateMarker(marker, document, position);
558: } finally {
559: listenToMarkerChanges(true);
560: }
561: }
562:
563: /**
564: * Updates the markers managed by this annotation model by calling
565: * all registered marker updaters (<code>IMarkerUpdater</code>).
566: *
567: * @param document the document to which this model is currently connected
568: * @throws CoreException if there is a problem updating the markers
569: */
570: public void updateMarkers(IDocument document) throws CoreException {
571:
572: Assert.isTrue(fDocument == document);
573:
574: IAnnotationMap annotationMap = getAnnotationMap();
575:
576: if (annotationMap.size() == 0
577: && fDeletedAnnotations.size() == 0)
578: return;
579:
580: if (fMarkerUpdaterSpecifications == null)
581: installMarkerUpdaters();
582:
583: listenToMarkerChanges(false);
584:
585: try {
586:
587: // update all markers with the positions known by the annotation model
588: for (Iterator e = getAnnotationIterator(false); e.hasNext();) {
589: Object o = e.next();
590: if (o instanceof MarkerAnnotation) {
591: MarkerAnnotation a = (MarkerAnnotation) o;
592: IMarker marker = a.getMarker();
593: Position position = (Position) annotationMap.get(a);
594: if (!updateMarker(marker, document, position)) {
595: if (!fDeletedAnnotations.contains(a))
596: fDeletedAnnotations.add(a);
597: }
598: }
599: }
600:
601: if (!fDeletedAnnotations.isEmpty()) {
602: removeAnnotations(fDeletedAnnotations, true, true);
603: fDeletedAnnotations.clear();
604: }
605:
606: } finally {
607:
608: listenToMarkerChanges(true);
609:
610: }
611: }
612:
613: /**
614: * Resets all the markers to their original state.
615: */
616: public void resetMarkers() {
617:
618: // re-initializes the positions from the markers
619: for (Iterator e = getAnnotationIterator(false); e.hasNext();) {
620: Object o = e.next();
621: if (o instanceof MarkerAnnotation) {
622: MarkerAnnotation a = (MarkerAnnotation) o;
623: Position p = createPositionFromMarker(a.getMarker());
624: if (p != null) {
625: removeAnnotation(a, false);
626: try {
627: addAnnotation(a, p, false);
628: } catch (BadLocationException e1) {
629: // ignore invalid position
630: }
631: }
632: }
633: }
634:
635: // add the markers of deleted positions back to the annotation model
636: for (Iterator e = fDeletedAnnotations.iterator(); e.hasNext();) {
637: Object o = e.next();
638: if (o instanceof MarkerAnnotation) {
639: MarkerAnnotation a = (MarkerAnnotation) o;
640: Position p = createPositionFromMarker(a.getMarker());
641: if (p != null)
642: try {
643: addAnnotation(a, p, false);
644: } catch (BadLocationException e1) {
645: // ignore invalid position
646: }
647: }
648: }
649: fDeletedAnnotations.clear();
650:
651: // fire annotation model changed
652: fireModelChanged();
653: }
654:
655: /*
656: * @see org.eclipse.jface.text.source.IPersistableAnnotationModel#commit(org.eclipse.jface.text.IDocument)
657: */
658: public void commit(IDocument document) throws CoreException {
659: updateMarkers(document);
660: }
661:
662: /*
663: * @see org.eclipse.jface.text.source.IPersistableAnnotationModel#revert(org.eclipse.jface.text.IDocument)
664: */
665: public void revert(IDocument document) {
666: resetMarkers();
667: }
668:
669: /*
670: * @see org.eclipse.jface.text.source.IPersistableAnnotationModel#reinitialize(org.eclipse.jface.text.IDocument)
671: */
672: public void reinitialize(IDocument document) {
673: resetMarkers();
674: }
675: }
|