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.link;
011:
012: import java.util.ArrayList;
013: import java.util.Arrays;
014: import java.util.HashSet;
015: import java.util.Iterator;
016: import java.util.List;
017: import java.util.Map;
018: import java.util.Set;
019:
020: import org.eclipse.core.runtime.Assert;
021:
022: import org.eclipse.text.edits.MalformedTreeException;
023: import org.eclipse.text.edits.TextEdit;
024:
025: import org.eclipse.jface.text.BadLocationException;
026: import org.eclipse.jface.text.BadPositionCategoryException;
027: import org.eclipse.jface.text.DocumentEvent;
028: import org.eclipse.jface.text.IDocument;
029: import org.eclipse.jface.text.IDocumentExtension;
030: import org.eclipse.jface.text.IDocumentListener;
031: import org.eclipse.jface.text.IPositionUpdater;
032: import org.eclipse.jface.text.Position;
033: import org.eclipse.jface.text.IDocumentExtension.IReplace;
034:
035: /**
036: * The model for linked mode, umbrellas several
037: * {@link LinkedPositionGroup}s. Once installed, the model
038: * propagates any changes to a position to all its siblings in the same position
039: * group.
040: * <p>
041: * Setting up a model consists of first adding
042: * <code>LinkedPositionGroup</code>s to it, and then installing the
043: * model by either calling {@link #forceInstall()} or
044: * {@link #tryInstall()}. After installing the model, it becomes
045: * <em>sealed</em> and no more groups may be added.
046: * </p>
047: * <p>
048: * If a document change occurs that would modify more than one position
049: * group or that would invalidate the disjointness requirement of the positions,
050: * the model is torn down and all positions are deleted. The same happens
051: * upon calling {@link #exit(int)}.
052: * </p>
053: * <h4>Nesting</h4>
054: * <p>
055: * A <code>LinkedModeModel</code> may be nested into another model. This
056: * happens when installing a model the positions of which all fit into a
057: * single position in a parent model that has previously been installed on
058: * the same document(s).
059: * </p>
060: * <p>
061: * Clients may instantiate instances of this class.
062: * </p>
063: *
064: * @since 3.0
065: */
066: public class LinkedModeModel {
067:
068: /**
069: * Checks whether there is already a model installed on <code>document</code>.
070: *
071: * @param document the <code>IDocument</code> of interest
072: * @return <code>true</code> if there is an existing model, <code>false</code>
073: * otherwise
074: */
075: public static boolean hasInstalledModel(IDocument document) {
076: // if there is a manager, there also is a model
077: return LinkedModeManager.hasManager(document);
078: }
079:
080: /**
081: * Checks whether there is already a linked mode model installed on any of
082: * the <code>documents</code>.
083: *
084: * @param documents the <code>IDocument</code>s of interest
085: * @return <code>true</code> if there is an existing model, <code>false</code>
086: * otherwise
087: */
088: public static boolean hasInstalledModel(IDocument[] documents) {
089: // if there is a manager, there also is a model
090: return LinkedModeManager.hasManager(documents);
091: }
092:
093: /**
094: * Cancels any linked mode model on the specified document. If there is no
095: * model, nothing happens.
096: *
097: * @param document the document whose <code>LinkedModeModel</code> should
098: * be canceled
099: */
100: public static void closeAllModels(IDocument document) {
101: LinkedModeManager.cancelManager(document);
102: }
103:
104: /**
105: * Returns the model currently active on <code>document</code> at
106: * <code>offset</code>, or <code>null</code> if there is none.
107: *
108: * @param document the document for which the caller asks for a
109: * model
110: * @param offset the offset into <code>document</code>, as there may be
111: * several models on a document
112: * @return the model currently active on <code>document</code>, or
113: * <code>null</code>
114: */
115: public static LinkedModeModel getModel(IDocument document,
116: int offset) {
117: if (!hasInstalledModel(document))
118: return null;
119:
120: LinkedModeManager mgr = LinkedModeManager.getLinkedManager(
121: new IDocument[] { document }, false);
122: if (mgr != null)
123: return mgr.getTopEnvironment();
124: return null;
125: }
126:
127: /**
128: * Encapsulates the edition triggered by a change to a linking position. Can
129: * be applied to a document as a whole.
130: */
131: private class Replace implements IReplace {
132:
133: /** The edition to apply on a document. */
134: private TextEdit fEdit;
135:
136: /**
137: * Creates a new instance.
138: *
139: * @param edit the edition to apply to a document.
140: */
141: public Replace(TextEdit edit) {
142: fEdit = edit;
143: }
144:
145: /*
146: * @see org.eclipse.jface.text.IDocumentExtension.IReplace#perform(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocumentListener)
147: */
148: public void perform(IDocument document, IDocumentListener owner)
149: throws RuntimeException, MalformedTreeException {
150: document.removeDocumentListener(owner);
151: fIsChanging = true;
152: try {
153: fEdit.apply(document, TextEdit.UPDATE_REGIONS
154: | TextEdit.CREATE_UNDO);
155: } catch (BadLocationException e) {
156: /* XXX: perform should really throw a BadLocationException
157: * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=52950
158: */
159: throw new RuntimeException(e);
160: } finally {
161: document.addDocumentListener(owner);
162: fIsChanging = false;
163: }
164: }
165:
166: }
167:
168: /**
169: * The document listener triggering the linked updating of positions
170: * managed by this model.
171: */
172: private class DocumentListener implements IDocumentListener {
173:
174: private boolean fExit = false;
175:
176: /**
177: * Checks whether <code>event</code> occurs within any of the positions
178: * managed by this model. If not, the linked mode is left.
179: *
180: * @param event {@inheritDoc}
181: */
182: public void documentAboutToBeChanged(DocumentEvent event) {
183: // don't react on changes executed by the parent model
184: if (fParentEnvironment != null
185: && fParentEnvironment.isChanging())
186: return;
187:
188: for (Iterator it = fGroups.iterator(); it.hasNext();) {
189: LinkedPositionGroup group = (LinkedPositionGroup) it
190: .next();
191: if (!group.isLegalEvent(event)) {
192: fExit = true;
193: return;
194: }
195: }
196: }
197:
198: /**
199: * Propagates a change to a linked position to all its sibling positions.
200: *
201: * @param event {@inheritDoc}
202: */
203: public void documentChanged(DocumentEvent event) {
204: if (fExit) {
205: LinkedModeModel.this
206: .exit(ILinkedModeListener.EXTERNAL_MODIFICATION);
207: return;
208: }
209: fExit = false;
210:
211: // don't react on changes executed by the parent model
212: if (fParentEnvironment != null
213: && fParentEnvironment.isChanging())
214: return;
215:
216: // collect all results
217: Map result = null;
218: for (Iterator it = fGroups.iterator(); it.hasNext();) {
219: LinkedPositionGroup group = (LinkedPositionGroup) it
220: .next();
221:
222: Map map = group.handleEvent(event);
223: if (result != null && map != null) {
224: // exit if more than one position was changed
225: LinkedModeModel.this
226: .exit(ILinkedModeListener.EXTERNAL_MODIFICATION);
227: return;
228: }
229: if (map != null)
230: result = map;
231: }
232:
233: if (result != null) {
234: // edit all documents
235: for (Iterator it2 = result.keySet().iterator(); it2
236: .hasNext();) {
237: IDocument doc = (IDocument) it2.next();
238: TextEdit edit = (TextEdit) result.get(doc);
239: Replace replace = new Replace(edit);
240:
241: // apply the edition, either as post notification replace
242: // on the calling document or directly on any other
243: // document
244: if (doc == event.getDocument()) {
245: if (doc instanceof IDocumentExtension) {
246: ((IDocumentExtension) doc)
247: .registerPostNotificationReplace(
248: this , replace);
249: } else {
250: // ignore - there is no way we can log from JFace text...
251: }
252: } else {
253: replace.perform(doc, this );
254: }
255: }
256: }
257: }
258:
259: }
260:
261: /** The set of linked position groups. */
262: private final List fGroups = new ArrayList();
263: /** The set of documents spanned by this group. */
264: private final Set fDocuments = new HashSet();
265: /** The position updater for linked positions. */
266: private final IPositionUpdater fUpdater = new InclusivePositionUpdater(
267: getCategory());
268: /** The document listener on the documents affected by this model. */
269: private final DocumentListener fDocumentListener = new DocumentListener();
270: /** The parent model for a hierarchical set up, or <code>null</code>. */
271: private LinkedModeModel fParentEnvironment;
272: /**
273: * The position in <code>fParentEnvironment</code> that includes all
274: * positions in this object, or <code>null</code> if there is no parent
275: * model.
276: */
277: private LinkedPosition fParentPosition = null;
278: /**
279: * A model is sealed once it has children - no more positions can be
280: * added.
281: */
282: private boolean fIsSealed = false;
283: /** <code>true</code> when this model is changing documents. */
284: private boolean fIsChanging = false;
285: /** The linked listeners. */
286: private final List fListeners = new ArrayList();
287: /** Flag telling whether we have exited: */
288: private boolean fIsActive = true;
289: /**
290: * The sequence of document positions as we are going to iterate through
291: * them.
292: */
293: private List fPositionSequence = new ArrayList();
294:
295: /**
296: * Whether we are in the process of editing documents (set by <code>Replace</code>,
297: * read by <code>DocumentListener</code>.
298: *
299: * @return <code>true</code> if we are in the process of editing a
300: * document, <code>false</code> otherwise
301: */
302: private boolean isChanging() {
303: return fIsChanging || fParentEnvironment != null
304: && fParentEnvironment.isChanging();
305: }
306:
307: /**
308: * Throws a <code>BadLocationException</code> if <code>group</code>
309: * conflicts with this model's groups.
310: *
311: * @param group the group being checked
312: * @throws BadLocationException if <code>group</code> conflicts with this
313: * model's groups
314: */
315: private void enforceDisjoint(LinkedPositionGroup group)
316: throws BadLocationException {
317: for (Iterator it = fGroups.iterator(); it.hasNext();) {
318: LinkedPositionGroup g = (LinkedPositionGroup) it.next();
319: g.enforceDisjoint(group);
320: }
321: }
322:
323: /**
324: * Causes this model to exit. Called either if an illegal document change
325: * is detected, or by the UI.
326: *
327: * @param flags the exit flags as defined in {@link ILinkedModeListener}
328: */
329: public void exit(int flags) {
330: if (!fIsActive)
331: return;
332:
333: fIsActive = false;
334:
335: for (Iterator it = fDocuments.iterator(); it.hasNext();) {
336: IDocument doc = (IDocument) it.next();
337: try {
338: doc.removePositionCategory(getCategory());
339: } catch (BadPositionCategoryException e) {
340: // won't happen
341: Assert.isTrue(false);
342: }
343: doc.removePositionUpdater(fUpdater);
344: doc.removeDocumentListener(fDocumentListener);
345: }
346:
347: fDocuments.clear();
348: fGroups.clear();
349:
350: List listeners = new ArrayList(fListeners);
351: fListeners.clear();
352: for (Iterator it = listeners.iterator(); it.hasNext();) {
353: ILinkedModeListener listener = (ILinkedModeListener) it
354: .next();
355: listener.left(this , flags);
356: }
357:
358: if (fParentEnvironment != null)
359: fParentEnvironment.resume(flags);
360: }
361:
362: /**
363: * Causes this model to stop forwarding updates. The positions are not
364: * unregistered however, which will only happen when <code>exit</code>
365: * is called, or after the next document change.
366: *
367: * @param flags the exit flags as defined in {@link ILinkedModeListener}
368: * @since 3.1
369: */
370: public void stopForwarding(int flags) {
371: fDocumentListener.fExit = true;
372: }
373:
374: /**
375: * Puts <code>document</code> into the set of managed documents. This
376: * involves registering the document listener and adding our position
377: * category.
378: *
379: * @param document the new document
380: */
381: private void manageDocument(IDocument document) {
382: if (!fDocuments.contains(document)) {
383: fDocuments.add(document);
384: document.addPositionCategory(getCategory());
385: document.addPositionUpdater(fUpdater);
386: document.addDocumentListener(fDocumentListener);
387: }
388:
389: }
390:
391: /**
392: * Returns the position category used by this model.
393: *
394: * @return the position category used by this model
395: */
396: private String getCategory() {
397: return toString();
398: }
399:
400: /**
401: * Adds a position group to this <code>LinkedModeModel</code>. This
402: * method may not be called if the model has been installed. Also, if
403: * a UI has been set up for this model, it may not pick up groups
404: * added afterwards.
405: * <p>
406: * If the positions in <code>group</code> conflict with any other group in
407: * this model, a <code>BadLocationException</code> is thrown. Also,
408: * if this model is nested inside another one, all positions in all
409: * groups of the child model have to reside within a single position in the
410: * parent model, otherwise a <code>BadLocationException</code> is thrown.
411: * </p>
412: * <p>
413: * If <code>group</code> already exists, nothing happens.
414: * </p>
415: *
416: * @param group the group to be added to this model
417: * @throws BadLocationException if the group conflicts with the other groups
418: * in this model or violates the nesting requirements.
419: * @throws IllegalStateException if the method is called when the
420: * model is already sealed
421: */
422: public void addGroup(LinkedPositionGroup group)
423: throws BadLocationException {
424: if (group == null)
425: throw new IllegalArgumentException("group may not be null"); //$NON-NLS-1$
426: if (fIsSealed)
427: throw new IllegalStateException(
428: "model is already installed"); //$NON-NLS-1$
429: if (fGroups.contains(group))
430: // nothing happens
431: return;
432:
433: enforceDisjoint(group);
434: group.seal();
435: fGroups.add(group);
436: }
437:
438: /**
439: * Creates a new model.
440: * @since 3.1
441: */
442: public LinkedModeModel() {
443: }
444:
445: /**
446: * Installs this model, which includes registering as document
447: * listener on all involved documents and storing global information about
448: * this model. Any conflicting model already present will be
449: * closed.
450: * <p>
451: * If an exception is thrown, the installation failed and
452: * the model is unusable.
453: * </p>
454: *
455: * @throws BadLocationException if some of the positions of this model
456: * were not valid positions on their respective documents
457: */
458: public void forceInstall() throws BadLocationException {
459: if (!install(true))
460: Assert.isTrue(false);
461: }
462:
463: /**
464: * Installs this model, which includes registering as document
465: * listener on all involved documents and storing global information about
466: * this model. If there is another model installed on the
467: * document(s) targeted by the receiver that conflicts with it, installation
468: * may fail.
469: * <p>
470: * The return value states whether installation was
471: * successful; if not, the model is not installed and will not work.
472: * </p>
473: *
474: * @return <code>true</code> if installation was successful,
475: * <code>false</code> otherwise
476: * @throws BadLocationException if some of the positions of this model
477: * were not valid positions on their respective documents
478: */
479: public boolean tryInstall() throws BadLocationException {
480: return install(false);
481: }
482:
483: /**
484: * Installs this model, which includes registering as document
485: * listener on all involved documents and storing global information about
486: * this model. The return value states whether installation was
487: * successful; if not, the model is not installed and will not work.
488: * The return value can only then become <code>false</code> if
489: * <code>force</code> was set to <code>false</code> as well.
490: *
491: * @param force if <code>true</code>, any other model that cannot
492: * coexist with this one is canceled; if <code>false</code>,
493: * install will fail when conflicts occur and return false
494: * @return <code>true</code> if installation was successful,
495: * <code>false</code> otherwise
496: * @throws BadLocationException if some of the positions of this model
497: * were not valid positions on their respective documents
498: */
499: private boolean install(boolean force) throws BadLocationException {
500: if (fIsSealed)
501: throw new IllegalStateException(
502: "model is already installed"); //$NON-NLS-1$
503: enforceNotEmpty();
504:
505: IDocument[] documents = getDocuments();
506: LinkedModeManager manager = LinkedModeManager.getLinkedManager(
507: documents, force);
508: // if we force creation, we require a valid manager
509: Assert.isTrue(!(force && manager == null));
510: if (manager == null)
511: return false;
512:
513: if (!manager.nestEnvironment(this , force))
514: if (force)
515: Assert.isTrue(false);
516: else
517: return false;
518:
519: // we set up successfully. After this point, exit has to be called to
520: // remove registered listeners...
521: fIsSealed = true;
522: if (fParentEnvironment != null)
523: fParentEnvironment.suspend();
524:
525: // register positions
526: try {
527: for (Iterator it = fGroups.iterator(); it.hasNext();) {
528: LinkedPositionGroup group = (LinkedPositionGroup) it
529: .next();
530: group.register(this );
531: }
532: return true;
533: } catch (BadLocationException e) {
534: // if we fail to add, make sure to release all listeners again
535: exit(ILinkedModeListener.NONE);
536: throw e;
537: }
538: }
539:
540: /**
541: * Asserts that there is at least one linked position in this linked mode
542: * model, throws an IllegalStateException otherwise.
543: */
544: private void enforceNotEmpty() {
545: boolean hasPosition = false;
546: for (Iterator it = fGroups.iterator(); it.hasNext();)
547: if (!((LinkedPositionGroup) it.next()).isEmpty()) {
548: hasPosition = true;
549: break;
550: }
551: if (!hasPosition)
552: throw new IllegalStateException(
553: "must specify at least one linked position"); //$NON-NLS-1$
554:
555: }
556:
557: /**
558: * Collects all the documents that contained positions are set upon.
559: * @return the set of documents affected by this model
560: */
561: private IDocument[] getDocuments() {
562: Set docs = new HashSet();
563: for (Iterator it = fGroups.iterator(); it.hasNext();) {
564: LinkedPositionGroup group = (LinkedPositionGroup) it.next();
565: docs.addAll(Arrays.asList(group.getDocuments()));
566: }
567: return (IDocument[]) docs.toArray(new IDocument[docs.size()]);
568: }
569:
570: /**
571: * Returns whether the receiver can be nested into the given <code>parent</code>
572: * model. If yes, the parent model and its position that the receiver
573: * fits in are remembered.
574: *
575: * @param parent the parent model candidate
576: * @return <code>true</code> if the receiver can be nested into <code>parent</code>, <code>false</code> otherwise
577: */
578: boolean canNestInto(LinkedModeModel parent) {
579: for (Iterator it = fGroups.iterator(); it.hasNext();) {
580: LinkedPositionGroup group = (LinkedPositionGroup) it.next();
581: if (!enforceNestability(group, parent)) {
582: fParentPosition = null;
583: return false;
584: }
585: }
586:
587: Assert.isNotNull(fParentPosition);
588: fParentEnvironment = parent;
589: return true;
590: }
591:
592: /**
593: * Called by nested models when a group is added to them. All
594: * positions in all groups of a nested model have to fit inside a
595: * single position in the parent model.
596: *
597: * @param group the group of the nested model to be adopted.
598: * @param model the model to check against
599: * @return <code>false</code> if it failed to enforce nestability
600: */
601: private boolean enforceNestability(LinkedPositionGroup group,
602: LinkedModeModel model) {
603: Assert.isNotNull(model);
604: Assert.isNotNull(group);
605:
606: try {
607: for (Iterator it = model.fGroups.iterator(); it.hasNext();) {
608: LinkedPositionGroup pg = (LinkedPositionGroup) it
609: .next();
610: LinkedPosition pos;
611: pos = pg.adopt(group);
612: if (pos != null && fParentPosition != null
613: && fParentPosition != pos)
614: return false; // group does not fit into one parent position, which is illegal
615: else if (fParentPosition == null && pos != null)
616: fParentPosition = pos;
617: }
618: } catch (BadLocationException e) {
619: return false;
620: }
621:
622: // group must fit into exactly one of the parent's positions
623: return fParentPosition != null;
624: }
625:
626: /**
627: * Returns whether this model is nested.
628: *
629: * <p>
630: * This method is part of the private protocol between
631: * <code>LinkedModeUI</code> and <code>LinkedModeModel</code>.
632: * </p>
633: *
634: * @return <code>true</code> if this model is nested,
635: * <code>false</code> otherwise
636: */
637: public boolean isNested() {
638: return fParentEnvironment != null;
639: }
640:
641: /**
642: * Returns the positions in this model that have a tab stop, in the
643: * order they were added.
644: *
645: * <p>
646: * This method is part of the private protocol between
647: * <code>LinkedModeUI</code> and <code>LinkedModeModel</code>.
648: * </p>
649: *
650: * @return the positions in this model that have a tab stop, in the
651: * order they were added
652: */
653: public List getTabStopSequence() {
654: return fPositionSequence;
655: }
656:
657: /**
658: * Adds <code>listener</code> to the set of listeners that are informed
659: * upon state changes.
660: *
661: * @param listener the new listener
662: */
663: public void addLinkingListener(ILinkedModeListener listener) {
664: Assert.isNotNull(listener);
665: if (!fListeners.contains(listener))
666: fListeners.add(listener);
667: }
668:
669: /**
670: * Removes <code>listener</code> from the set of listeners that are
671: * informed upon state changes.
672: *
673: * @param listener the new listener
674: */
675: public void removeLinkingListener(ILinkedModeListener listener) {
676: fListeners.remove(listener);
677: }
678:
679: /**
680: * Finds the position in this model that is closest after
681: * <code>toFind</code>. <code>toFind</code> needs not be a position in
682: * this model and serves merely as an offset.
683: *
684: * <p>
685: * This method part of the private protocol between
686: * <code>LinkedModeUI</code> and <code>LinkedModeModel</code>.
687: * </p>
688: *
689: * @param toFind the position to search from
690: * @return the closest position in the same document as <code>toFind</code>
691: * after the offset of <code>toFind</code>, or <code>null</code>
692: */
693: public LinkedPosition findPosition(LinkedPosition toFind) {
694: LinkedPosition position = null;
695: for (Iterator it = fGroups.iterator(); it.hasNext();) {
696: LinkedPositionGroup group = (LinkedPositionGroup) it.next();
697: position = group.getPosition(toFind);
698: if (position != null)
699: break;
700: }
701: return position;
702: }
703:
704: /**
705: * Registers a <code>LinkedPosition</code> with this model. Called
706: * by <code>PositionGroup</code>.
707: *
708: * @param position the position to register
709: * @throws BadLocationException if the position cannot be added to its
710: * document
711: */
712: void register(LinkedPosition position) throws BadLocationException {
713: Assert.isNotNull(position);
714:
715: IDocument document = position.getDocument();
716: manageDocument(document);
717: try {
718: document.addPosition(getCategory(), position);
719: } catch (BadPositionCategoryException e) {
720: // won't happen as the category has been added by manageDocument()
721: Assert.isTrue(false);
722: }
723: int seqNr = position.getSequenceNumber();
724: if (seqNr != LinkedPositionGroup.NO_STOP) {
725: fPositionSequence.add(position);
726: }
727: }
728:
729: /**
730: * Suspends this model.
731: */
732: private void suspend() {
733: List l = new ArrayList(fListeners);
734: for (Iterator it = l.iterator(); it.hasNext();) {
735: ILinkedModeListener listener = (ILinkedModeListener) it
736: .next();
737: listener.suspend(this );
738: }
739: }
740:
741: /**
742: * Resumes this model. <code>flags</code> can be <code>NONE</code>
743: * or <code>SELECT</code>.
744: *
745: * @param flags <code>NONE</code> or <code>SELECT</code>
746: */
747: private void resume(int flags) {
748: List l = new ArrayList(fListeners);
749: for (Iterator it = l.iterator(); it.hasNext();) {
750: ILinkedModeListener listener = (ILinkedModeListener) it
751: .next();
752: listener.resume(this , flags);
753: }
754: }
755:
756: /**
757: * Returns whether an offset is contained by any position in this
758: * model.
759: *
760: * @param offset the offset to check
761: * @return <code>true</code> if <code>offset</code> is included by any
762: * position (see {@link LinkedPosition#includes(int)}) in this
763: * model, <code>false</code> otherwise
764: */
765: public boolean anyPositionContains(int offset) {
766: for (Iterator it = fGroups.iterator(); it.hasNext();) {
767: LinkedPositionGroup group = (LinkedPositionGroup) it.next();
768: if (group.contains(offset))
769: // take the first hit - exclusion is guaranteed by enforcing
770: // disjointness when adding positions
771: return true;
772: }
773: return false;
774: }
775:
776: /**
777: * Returns the linked position group that contains <code>position</code>,
778: * or <code>null</code> if <code>position</code> is not contained in any
779: * group within this model. Group containment is tested by calling
780: * <code>group.contains(position)</code> for every <code>group</code> in
781: * this model.
782: *
783: * <p>
784: * This method part of the private protocol between
785: * <code>LinkedModeUI</code> and <code>LinkedModeModel</code>.
786: * </p>
787: *
788: * @param position the position the group of which is requested
789: * @return the first group in this model for which
790: * <code>group.contains(position)</code> returns <code>true</code>,
791: * or <code>null</code> if no group contains <code>position</code>
792: */
793: public LinkedPositionGroup getGroupForPosition(Position position) {
794: for (Iterator it = fGroups.iterator(); it.hasNext();) {
795: LinkedPositionGroup group = (LinkedPositionGroup) it.next();
796: if (group.contains(position))
797: return group;
798: }
799: return null;
800: }
801: }
|