KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > link > LinkedPositionGroup


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 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.link;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.HashMap JavaDoc;
15 import java.util.Iterator JavaDoc;
16 import java.util.LinkedList JavaDoc;
17 import java.util.List JavaDoc;
18 import java.util.Map JavaDoc;
19
20 import org.eclipse.core.runtime.Assert;
21
22 import org.eclipse.text.edits.MalformedTreeException;
23 import org.eclipse.text.edits.MultiTextEdit;
24 import org.eclipse.text.edits.ReplaceEdit;
25 import org.eclipse.text.edits.TextEdit;
26
27 import org.eclipse.jface.text.BadLocationException;
28 import org.eclipse.jface.text.DocumentEvent;
29 import org.eclipse.jface.text.IDocument;
30 import org.eclipse.jface.text.IRegion;
31 import org.eclipse.jface.text.Position;
32 import org.eclipse.jface.text.Region;
33
34 /**
35  * A group of positions in multiple documents that are simultaneously modified -
36  * if one gets edited, all other positions in a group are edited the same way.
37  * All linked positions in a group have the same content.
38  * <p>
39  * Normally, new positions are given a tab stop weight which can be used by
40  * clients, e.g. the UI. If no weight is given, a position will not be visited.
41  * If no weights are used at all, the first position in a document is taken as
42  * the only stop as to comply with the behavior of the old linked position
43  * infrastructure.
44  * </p>
45  * <p>
46  * Clients may instantiate this class.
47  * </p>
48  *
49  * @since 3.0
50  */

51 public class LinkedPositionGroup {
52
53     /** Sequence constant declaring that a position should not be stopped by. */
54     public static final int NO_STOP= -1;
55
56     /* members */
57
58     /** The linked positions of this group. */
59     private final List JavaDoc fPositions= new LinkedList JavaDoc();
60     /** Whether we are sealed or not. */
61     private boolean fIsSealed= false;
62     /**
63      * <code>true</code> if there are custom iteration weights. For backward
64      * compatibility.
65      */

66     private boolean fHasCustomIteration= false;
67
68     /*
69      * iteration variables, set to communicate state between isLegalEvent and
70      * handleEvent
71      */

72     /** The position including the most recent <code>DocumentEvent</code>. */
73     private LinkedPosition fLastPosition;
74     /** The region covered by <code>fLastPosition</code> before the document
75      * change.
76      */

77     private IRegion fLastRegion;
78
79     /**
80      * Adds a position to this group. The document region defined by the
81      * position must contain the same content (and thus have the same length) as
82      * any of the other positions already in this group. Additionally, all
83      * positions added must be disjoint; otherwise a
84      * <code>BadLocationException</code> is thrown.
85      * <p>
86      * Positions added using this method are owned by this group afterwards and
87      * may not be updated or modified thereafter.
88      * </p>
89      * <p>
90      * Once a group has been added to a <code>LinkedModeModel</code>, it
91      * becomes <em>sealed</em> and no positions may be added any more.
92      * </p>
93      *
94      * @param position the position to add
95      * @throws BadLocationException if the position is invalid or conflicts with
96      * other positions in the group
97      * @throws IllegalStateException if the group has already been added to a
98      * model
99      */

100     public void addPosition(LinkedPosition position) throws BadLocationException {
101         /*
102          * Enforces constraints and sets the custom iteration flag. If the
103          * position is already in this group, nothing happens.
104          */

105         Assert.isNotNull(position);
106         if (fIsSealed)
107             throw new IllegalStateException JavaDoc("cannot add positions after the group is added to an model"); //$NON-NLS-1$
108

109         if (!fPositions.contains(position)) {
110             enforceDisjoint(position);
111             enforceEqualContent(position);
112             fPositions.add(position);
113             fHasCustomIteration |= position.getSequenceNumber() != LinkedPositionGroup.NO_STOP;
114         } else
115             return; // nothing happens
116
}
117
118     /**
119      * Enforces the invariant that all positions must contain the same string.
120      *
121      * @param position the position to check
122      * @throws BadLocationException if the equal content check fails
123      */

124     private void enforceEqualContent(LinkedPosition position) throws BadLocationException {
125         if (fPositions.size() > 0) {
126             LinkedPosition groupPosition= (LinkedPosition) fPositions.get(0);
127             String JavaDoc groupContent= groupPosition.getContent();
128             String JavaDoc positionContent= position.getContent();
129             if (!groupContent.equals(positionContent))
130                 throw new BadLocationException(
131                         "First position: '" + groupContent + "' at " + groupPosition.getOffset() + //$NON-NLS-1$ //$NON-NLS-2$
132
", this position: '" + positionContent + "' at " + position.getOffset()); //$NON-NLS-1$ //$NON-NLS-2$
133
}
134     }
135
136     /**
137      * Enforces the invariant that all positions must be disjoint.
138      *
139      * @param position the position to check
140      * @throws BadLocationException if the disjointness check fails
141      */

142     private void enforceDisjoint(LinkedPosition position) throws BadLocationException {
143         for (Iterator JavaDoc it= fPositions.iterator(); it.hasNext(); ) {
144             LinkedPosition p= (LinkedPosition) it.next();
145             if (p.overlapsWith(position))
146                 throw new BadLocationException();
147         }
148     }
149
150     /**
151      * Enforces the disjointness for another group
152      *
153      * @param group the group to check
154      * @throws BadLocationException if the disjointness check fails
155      */

156     void enforceDisjoint(LinkedPositionGroup group) throws BadLocationException {
157         Assert.isNotNull(group);
158         for (Iterator JavaDoc it= group.fPositions.iterator(); it.hasNext(); ) {
159             LinkedPosition p= (LinkedPosition) it.next();
160             enforceDisjoint(p);
161         }
162     }
163
164     /**
165      * Checks whether <code>event</code> is a legal event for this group. An
166      * event is legal if it touches at most one position contained within this
167      * group.
168      *
169      * @param event the document event to check
170      * @return <code>true</code> if <code>event</code> is legal
171      */

172     boolean isLegalEvent(DocumentEvent event) {
173         fLastPosition= null;
174         fLastRegion= null;
175
176         for (Iterator JavaDoc it= fPositions.iterator(); it.hasNext(); ) {
177             LinkedPosition pos= (LinkedPosition) it.next();
178             if (overlapsOrTouches(pos, event)) {
179                 if (fLastPosition != null) {
180                     fLastPosition= null;
181                     fLastRegion= null;
182                     return false;
183                 }
184
185                 fLastPosition= pos;
186                 fLastRegion= new Region(pos.getOffset(), pos.getLength());
187             }
188         }
189
190         return true;
191     }
192
193     /**
194      * Checks whether the given event touches the given position. To touch means
195      * to overlap or come up to the borders of the position.
196      *
197      * @param position the position
198      * @param event the event
199      * @return <code>true</code> if <code>position</code> and
200      * <code>event</code> are not absolutely disjoint
201      * @since 3.1
202      */

203     private boolean overlapsOrTouches(LinkedPosition position, DocumentEvent event) {
204         return position.getDocument().equals(event.getDocument()) && position.getOffset() <= event.getOffset() + event.getLength() && position.getOffset() + position.getLength() >= event.getOffset();
205     }
206
207     /**
208      * Creates an edition of a document change that will forward any
209      * modification in one position to all linked siblings. The return value is
210      * a map from <code>IDocument</code> to <code>TextEdit</code>.
211      *
212      * @param event the document event to check
213      * @return a map of edits, grouped by edited document, or <code>null</code>
214      * if there are no edits
215      */

216     Map JavaDoc handleEvent(DocumentEvent event) {
217
218         if (fLastPosition != null) {
219
220             Map JavaDoc map= new HashMap JavaDoc();
221
222
223             int relativeOffset= event.getOffset() - fLastRegion.getOffset();
224             if (relativeOffset < 0) {
225                 relativeOffset= 0;
226             }
227
228             int eventEnd= event.getOffset() + event.getLength();
229             int lastEnd= fLastRegion.getOffset() + fLastRegion.getLength();
230             int length;
231             if (eventEnd > lastEnd)
232                 length= lastEnd - relativeOffset - fLastRegion.getOffset();
233             else
234                 length= eventEnd - relativeOffset - fLastRegion.getOffset();
235
236             String JavaDoc text= event.getText();
237             if (text == null)
238                 text= ""; //$NON-NLS-1$
239

240             for (Iterator JavaDoc it= fPositions.iterator(); it.hasNext(); ) {
241                 LinkedPosition p= (LinkedPosition) it.next();
242                 if (p == fLastPosition || p.isDeleted())
243                     continue; // don't re-update the origin of the change
244

245                 List JavaDoc edits= (List JavaDoc) map.get(p.getDocument());
246                 if (edits == null) {
247                     edits= new ArrayList JavaDoc();
248                     map.put(p.getDocument(), edits);
249                 }
250
251                 edits.add(new ReplaceEdit(p.getOffset() + relativeOffset, length, text));
252             }
253
254             try {
255                 for (Iterator JavaDoc it= map.keySet().iterator(); it.hasNext();) {
256                     IDocument d= (IDocument) it.next();
257                     TextEdit edit= new MultiTextEdit(0, d.getLength());
258                     edit.addChildren((TextEdit[]) ((List JavaDoc) map.get(d)).toArray(new TextEdit[0]));
259                     map.put(d, edit);
260                 }
261
262                 return map;
263             } catch (MalformedTreeException x) {
264                 // may happen during undo, as LinkedModeModel does not know
265
// that the changes technically originate from a parent environment
266
// if this happens, post notification changes are not accepted anyway and
267
// we can simply return null - any changes will be undone by the undo
268
// manager
269
return null;
270             }
271
272         }
273
274         return null;
275     }
276
277     /**
278      * Sets the model of this group. Once a model has been set, no
279      * more positions can be added and the model cannot be changed.
280      */

281     void seal() {
282         Assert.isTrue(!fIsSealed);
283         fIsSealed= true;
284
285         if (fHasCustomIteration == false && fPositions.size() > 0) {
286             ((LinkedPosition) fPositions.get(0)).setSequenceNumber(0);
287         }
288     }
289
290     IDocument[] getDocuments() {
291         IDocument[] docs= new IDocument[fPositions.size()];
292         int i= 0;
293         for (Iterator JavaDoc it= fPositions.iterator(); it.hasNext(); i++) {
294             LinkedPosition pos= (LinkedPosition) it.next();
295             docs[i]= pos.getDocument();
296         }
297         return docs;
298     }
299
300     void register(LinkedModeModel model) throws BadLocationException {
301         for (Iterator JavaDoc it= fPositions.iterator(); it.hasNext(); ) {
302             LinkedPosition pos= (LinkedPosition) it.next();
303             model.register(pos);
304         }
305     }
306
307     /**
308      * Returns the position in this group that encompasses all positions in
309      * <code>group</code>.
310      *
311      * @param group the group to be adopted
312      * @return a position in the receiver that contains all positions in <code>group</code>,
313      * or <code>null</code> if none can be found
314      * @throws BadLocationException if more than one position are affected by
315      * <code>group</code>
316      */

317     LinkedPosition adopt(LinkedPositionGroup group) throws BadLocationException {
318         LinkedPosition found= null;
319         for (Iterator JavaDoc it= group.fPositions.iterator(); it.hasNext(); ) {
320             LinkedPosition pos= (LinkedPosition) it.next();
321             LinkedPosition localFound= null;
322             for (Iterator JavaDoc it2= fPositions.iterator(); it2.hasNext(); ) {
323                 LinkedPosition myPos= (LinkedPosition) it2.next();
324                 if (myPos.includes(pos)) {
325                     if (found == null)
326                         found= myPos;
327                     else if (found != myPos)
328                         throw new BadLocationException();
329                     if (localFound == null)
330                         localFound= myPos;
331                 }
332             }
333
334             if (localFound != found)
335                 throw new BadLocationException();
336         }
337         return found;
338     }
339
340     /**
341      * Finds the closest position to <code>toFind</code>.
342      *
343      * @param toFind the linked position for which to find the closest position
344      * @return the closest position to <code>toFind</code>.
345      */

346     LinkedPosition getPosition(LinkedPosition toFind) {
347         for (Iterator JavaDoc it= fPositions.iterator(); it.hasNext(); ) {
348             LinkedPosition p= (LinkedPosition) it.next();
349             if (p.includes(toFind))
350                 return p;
351         }
352         return null;
353     }
354
355     /**
356      * Returns <code>true</code> if <code>offset</code> is contained in any
357      * position in this group.
358      *
359      * @param offset the offset to check
360      * @return <code>true</code> if offset is contained by this group
361      */

362     boolean contains(int offset) {
363         for (Iterator JavaDoc it= fPositions.iterator(); it.hasNext(); ) {
364             LinkedPosition pos= (LinkedPosition) it.next();
365             if (pos.includes(offset)) {
366                 return true;
367             }
368         }
369         return false;
370     }
371
372     /**
373      * Returns whether this group contains any positions.
374      *
375      * @return <code>true</code> if this group is empty, <code>false</code> otherwise
376      * @since 3.1
377      */

378     public boolean isEmpty() {
379         return fPositions.size() == 0;
380     }
381     
382     /**
383      * Returns whether this group contains any positions.
384      *
385      * @return <code>true</code> if this group is empty, <code>false</code> otherwise
386      * @deprecated As of 3.1, replaced by {@link #isEmpty()}
387      */

388     public boolean isEmtpy() {
389         return isEmpty();
390     }
391
392     /**
393      * Returns the positions contained in the receiver as an array. The
394      * positions are the actual positions and must not be modified; the array
395      * is a copy of internal structures.
396      *
397      * @return the positions of this group in no particular order
398      */

399     public LinkedPosition[] getPositions() {
400         return (LinkedPosition[]) fPositions.toArray(new LinkedPosition[0]);
401     }
402
403     /**
404      * Returns <code>true</code> if the receiver contains <code>position</code>.
405      *
406      * @param position the position to check
407      * @return <code>true</code> if the receiver contains <code>position</code>
408      */

409     boolean contains(Position position) {
410         for (Iterator JavaDoc it= fPositions.iterator(); it.hasNext(); ) {
411             LinkedPosition p= (LinkedPosition) it.next();
412             if (position.equals(p))
413                 return true;
414         }
415         return false;
416     }
417 }
418
Popular Tags