KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > editor > lib2 > highlighting > CompoundHighlightsContainer


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.editor.lib2.highlighting;
21
22 import java.lang.ref.WeakReference JavaDoc;
23 import java.util.ConcurrentModificationException JavaDoc;
24 import java.util.logging.Level JavaDoc;
25 import java.util.logging.Logger JavaDoc;
26 import javax.swing.text.AttributeSet JavaDoc;
27 import javax.swing.text.BadLocationException JavaDoc;
28 import javax.swing.text.Document JavaDoc;
29 import javax.swing.text.Position JavaDoc;
30 import org.netbeans.spi.editor.highlighting.HighlightsChangeEvent;
31 import org.netbeans.spi.editor.highlighting.HighlightsChangeListener;
32 import org.netbeans.spi.editor.highlighting.HighlightsContainer;
33 import org.netbeans.spi.editor.highlighting.HighlightsSequence;
34 import org.netbeans.spi.editor.highlighting.support.AbstractHighlightsContainer;
35 import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
36
37 /**
38  *
39  * @author Vita Stejskal, Miloslav Metelka
40  */

41 public final class CompoundHighlightsContainer extends AbstractHighlightsContainer {
42
43     private static final Logger JavaDoc LOG = Logger.getLogger(CompoundHighlightsContainer.class.getName());
44     
45     private static final Position JavaDoc MAX_POSITION = new Position JavaDoc() {
46         public int getOffset() {
47             return Integer.MAX_VALUE;
48         }
49     };
50
51     private static final int MIN_CACHE_SIZE = 128;
52     
53     private Document JavaDoc doc;
54     private HighlightsContainer[] layers;
55     private long version = 0;
56
57     private final String JavaDoc LOCK = new String JavaDoc("CompoundHighlightsContainer.LOCK"); //NOI18N
58
private final LayerListener listener = new LayerListener(this);
59
60     private OffsetsBag cache;
61     private Position JavaDoc cacheLowestPos;
62     private Position JavaDoc cacheHighestPos;
63     
64     public CompoundHighlightsContainer() {
65         this(null, null);
66     }
67     
68     public CompoundHighlightsContainer(Document JavaDoc doc, HighlightsContainer[] layers) {
69         setLayers(doc, layers);
70     }
71     
72     /**
73      * Gets the list of <code>Highlight</code>s from this layer in the specified
74      * area. The highlights are obtained as a merge of the highlights from all the
75      * delegate layers. The following rules must hold true for the parameters
76      * passed in:
77      *
78      * <ul>
79      * <li>0 <= <code>startOffset</code> <= <code>endOffset</code></li>
80      * <li>0 <= <code>endOffset</code> <= <code>document.getLength() - 1<code></li>
81      * <li>Optionally, <code>endOffset</code> can be equal to Integer.MAX_VALUE
82      * in which case all available highlights will be returned.</li>
83      * </ul>
84      *
85      * @param startOffset The beginning of the area.
86      * @param endOffset The end of the area.
87      *
88      * @return The <code>Highlight</code>s in the area between <code>startOffset</code>
89      * and <code>endOffset</code>.
90      */

91     public HighlightsSequence getHighlights(int startOffset, int endOffset) {
92         assert 0 <= startOffset : "offsets must be greater than or equal to zero"; //NOI18N
93
assert startOffset <= endOffset : "startOffset must be less than or equal to endOffset; " + //NOI18N
94
"startOffset = " + startOffset + " endOffset = " + endOffset; //NOI18N
95

96         synchronized (LOCK) {
97             if (doc == null || layers == null || layers.length == 0 || startOffset == endOffset) {
98                 return HighlightsSequence.EMPTY;
99             }
100
101             int [] update = null;
102             
103             int lowest = cacheLowestPos == null ? -1 : cacheLowestPos.getOffset();
104             int highest = cacheHighestPos == null ? -1 : cacheHighestPos.getOffset();
105
106             if (lowest == -1 || highest == -1) {
107                 // not sure what is cached -> reset the cache
108
cache = null;
109             } else {
110                 int maxDistance = Math.max(MIN_CACHE_SIZE, highest - lowest);
111                 if (endOffset > lowest - maxDistance && endOffset <= highest && startOffset < lowest) {
112                     // below the cached area, but close enough
113
update = new int [] { startOffset, lowest };
114                 } else if (startOffset < highest + maxDistance && startOffset >= lowest && endOffset > highest) {
115                     // above the cached area, but close enough
116
update = new int [] { highest, endOffset };
117                 } else if (startOffset < lowest && endOffset > highest) {
118                     // extends the cached area on both sides
119
update = new int [] { startOffset, lowest, highest, endOffset };
120                 } else if (startOffset >= lowest && endOffset <= highest) {
121                     // inside the cached area
122
} else {
123                     // completely off the area and too far
124
cache = null;
125                 }
126             }
127             
128             if (cache == null) {
129                 cache = new OffsetsBag(doc, true);
130                 lowest = highest = -1;
131                 update = new int [] { startOffset, endOffset };
132             }
133             
134             if (update != null) {
135                 for (int i = 0; i < update.length / 2; i++) {
136                     if (update[2 * i + 1] - update[2 * i] < MIN_CACHE_SIZE) {
137                         update[2 * i + 1] = update[2 * i] + MIN_CACHE_SIZE;
138                         if (update[2 * i + 1] >= doc.getLength()) {
139                             update[2 * i + 1] = Integer.MAX_VALUE;
140                         }
141                     }
142                     
143                     updateCache(update[2 * i], update[2 * i + 1]);
144                     
145                     if (update[2 * i + 1] == Integer.MAX_VALUE) {
146                         break;
147                     }
148                 }
149                 
150                 if (lowest == -1 || highest == -1) {
151                     cacheLowestPos = createPosition(update[0]);
152                     cacheHighestPos = createPosition(update[update.length - 1]);
153                 } else {
154                     cacheLowestPos = createPosition(Math.min(lowest, update[0]));
155                     cacheHighestPos = createPosition(Math.max(highest, update[update.length - 1]));
156                 }
157                 
158                 if (LOG.isLoggable(Level.FINE)) {
159                     LOG.fine("Cache boundaries: " + //NOI18N
160
"<" + (cacheLowestPos == null ? "-" : cacheLowestPos.getOffset()) + //NOI18N
161
", " + (cacheHighestPos == null ? "-" : cacheHighestPos.getOffset()) + "> " + //NOI18N
162
"when asked for <" + startOffset + ", " + endOffset + ">"); //NOI18N
163
}
164             }
165             
166             return new Seq(version, cache.getHighlights(startOffset, endOffset));
167         }
168     }
169
170     /**
171      * Gets the delegate layers.
172      *
173      * @return The layers, which this proxy layer delegates to.
174      */

175     public HighlightsContainer[] getLayers() {
176         synchronized (LOCK) {
177             return layers;
178         }
179     }
180     
181     /**
182      * Sets the delegate layers. The layers are merged in the same order in which
183      * they appear in the array passed into this method. That means that the first
184      * layer in the array is the less important (i.e. the bottom of the z-order) and
185      * the last layer in the array is the most visible one (i.e. the top of the z-order).
186      *
187      * <p>If you want the layers to be merged according to their real z-order sort
188      * the array first by using <code>ZOrder.sort()</code>.
189      *
190      * @param layers The new delegate layers. Can be <code>null</code>.
191      * @see org.netbeans.api.editor.view.ZOrder#sort(HighlightLayer [])
192      */

193     public void setLayers(Document JavaDoc doc, HighlightsContainer[] layers) {
194         Document JavaDoc docForEvents = null;
195         
196         synchronized (LOCK) {
197             if (doc == null) {
198                 assert layers == null : "If doc is null the layers must be null too."; //NOI18N
199
}
200         
201             docForEvents = doc != null ? doc : this.doc;
202             
203             // Remove the listener from the current layers
204
if (this.layers != null) {
205                 for (int i = 0; i < this.layers.length; i++) {
206                     this.layers[i].removeHighlightsChangeListener(listener);
207                 }
208             }
209     
210             this.doc = doc;
211             this.layers = layers;
212             cache = null;
213             version++;
214
215             // Add the listener to the new layers
216
if (this.layers != null) {
217                 for (int i = 0; i < this.layers.length; i++) {
218                     this.layers[i].addHighlightsChangeListener(listener);
219                 }
220             }
221         }
222
223         if (docForEvents != null) {
224             docForEvents.render(new Runnable JavaDoc() {
225                 public void run() {
226                     fireHighlightsChange(0, Integer.MAX_VALUE);
227                 }
228             });
229         }
230     }
231
232     public void resetCache() {
233         layerChanged(null, 0, Integer.MAX_VALUE);
234     }
235     
236     // ----------------------------------------------------------------------
237
// Private implementation
238
// ----------------------------------------------------------------------
239

240     private void layerChanged(HighlightsContainer layer, final int changeStartOffset, final int changeEndOffset) {
241         Document JavaDoc docForEvents = null;
242
243         synchronized (LOCK) {
244             // XXX: Perhaps we could do something more efficient.
245
cache = null;
246             version++;
247             
248             docForEvents = doc;
249         }
250         
251         // Fire an event
252
if (docForEvents != null) {
253             docForEvents.render(new Runnable JavaDoc() {
254                 public void run() {
255                     fireHighlightsChange(changeStartOffset, changeEndOffset);
256                 }
257             });
258         }
259     }
260
261     private void updateCache(int startOffset, int endOffset) {
262         if (LOG.isLoggable(Level.FINE)) {
263             LOG.fine("Updating cache: <" + startOffset + ", " + endOffset + ">"); //NOI18N
264
}
265         
266         for (HighlightsContainer layer : layers) {
267             HighlightsSequence seq = layer.getHighlights(startOffset, endOffset);
268             cache.addAllHighlights(seq);
269         }
270     }
271     
272     private Position JavaDoc createPosition(int offset) {
273         try {
274             if (offset == Integer.MAX_VALUE) {
275                 return MAX_POSITION;
276             } else {
277                 return doc.createPosition(offset);
278             }
279         } catch (BadLocationException JavaDoc e) {
280             LOG.log(Level.WARNING, "Can't create document position: offset = " + offset + //NOI18N
281
", document.lenght = " + doc.getLength(), e); //NOI18N
282
return null;
283         }
284     }
285     
286     private static final class LayerListener implements HighlightsChangeListener {
287         
288         private WeakReference JavaDoc<CompoundHighlightsContainer> ref;
289         
290         public LayerListener(CompoundHighlightsContainer container) {
291             ref = new WeakReference JavaDoc<CompoundHighlightsContainer>(container);
292         }
293         
294         public void highlightChanged(HighlightsChangeEvent event) {
295             CompoundHighlightsContainer container = ref.get();
296             if (container != null) {
297                 container.layerChanged(
298                     (HighlightsContainer)event.getSource(),
299                     event.getStartOffset(),
300                     event.getEndOffset());
301             }
302         }
303     } // End of Listener class
304

305     private final class Seq implements HighlightsSequence {
306         
307         private HighlightsSequence seq;
308         private long version;
309         
310         public Seq(long version, HighlightsSequence seq) {
311             this.version = version;
312             this.seq = seq;
313         }
314         
315         public boolean moveNext() {
316             synchronized (CompoundHighlightsContainer.this.LOCK) {
317                 checkVersion();
318                 
319                 return seq.moveNext();
320             }
321         }
322
323         public int getStartOffset() {
324             synchronized (CompoundHighlightsContainer.this.LOCK) {
325                 checkVersion();
326                 
327                 return seq.getStartOffset();
328             }
329         }
330
331         public int getEndOffset() {
332             synchronized (CompoundHighlightsContainer.this.LOCK) {
333                 checkVersion();
334                 
335                 return seq.getEndOffset();
336             }
337         }
338
339         public AttributeSet JavaDoc getAttributes() {
340             synchronized (CompoundHighlightsContainer.this.LOCK) {
341                 checkVersion();
342                 
343                 return seq.getAttributes();
344             }
345         }
346
347         private void checkVersion() {
348             if (this.version != CompoundHighlightsContainer.this.version) {
349                 throw new ConcurrentModificationException JavaDoc();
350             }
351         }
352     } // End of Seq class
353
}
354
Popular Tags