1 /* ZoneView.java -- An effective BoxView subclass
2    Copyright (C) 2006 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package javax.swing.text;
40 
41 import java.awt.Shape;
42 import java.util.ArrayList;
43 import java.util.LinkedList;
44 
45 import javax.swing.event.DocumentEvent;
46 
47 /**
48  * A View implementation that delays loading of sub views until they are
49  * needed for display or internal transformations. This can be used for
50  * editors that need to handle large documents more effectivly than the
51  * standard {@link BoxView}.
52  *
53  * @author Roman Kennke (kennke@aicas.com)
54  *
55  * @since 1.3
56  */
57 public class ZoneView
58   extends BoxView
59 {
60 
61   /**
62    * The default zone view implementation. The specs suggest that this is
63    * a subclass of AsyncBoxView, so do we.
64    */
65   static class Zone
66     extends AsyncBoxView
67   {
68     /**
69      * The start position for this zone.
70      */
71     private Position p0;
72 
73     /**
74      * The end position for this zone.
75      */
76     private Position p1;
77 
78     /**
79      * Creates a new Zone for the specified element, start and end positions.
80      *
81      * @param el the element
82      * @param pos0 the start position
83      * @param pos1 the end position
84      * @param axis the major axis
85      */
Zone(Element el, Position pos0, Position pos1, int axis)86     Zone(Element el, Position pos0, Position pos1, int axis)
87     {
88       super(el, axis);
89       p0 = pos0;
90       p1 = pos1;
91     }
92 
93     /**
94      * Returns the start offset of the zone.
95      *
96      * @return the start offset of the zone
97      */
getStartOffset()98     public int getStartOffset()
99     {
100       return p0.getOffset();
101     }
102 
103     /**
104      * Returns the end offset of the zone.
105      *
106      * @return the end offset of the zone
107      */
getEndOffset()108     public int getEndOffset()
109     {
110       return p1.getOffset();
111     }
112   }
113 
114   /**
115    * The maximumZoneSize.
116    */
117   private int maximumZoneSize;
118 
119   /**
120    * The maximum number of loaded zones.
121    */
122   private int maxZonesLoaded;
123 
124   /**
125    * A queue of loaded zones. When the number of loaded zones exceeds the
126    * maximum number of zones, the oldest zone(s) get unloaded.
127    */
128   private LinkedList loadedZones;
129 
130   /**
131    * Creates a new ZoneView for the specified element and axis.
132    *
133    * @param element the element for which to create a ZoneView
134    * @param axis the major layout axis for the box
135    */
ZoneView(Element element, int axis)136   public ZoneView(Element element, int axis)
137   {
138     super(element, axis);
139     maximumZoneSize = 8192;
140     maxZonesLoaded = 3;
141     loadedZones = new LinkedList();
142   }
143 
144   /**
145    * Sets the maximum zone size. Note that zones might still become larger
146    * then the size specified when a singe child view is larger for itself,
147    * because zones are formed on child view boundaries.
148    *
149    * @param size the maximum zone size to set
150    *
151    * @see #getMaximumZoneSize()
152    */
setMaximumZoneSize(int size)153   public void setMaximumZoneSize(int size)
154   {
155     maximumZoneSize = size;
156   }
157 
158   /**
159    * Returns the maximum zone size. Note that zones might still become larger
160    * then the size specified when a singe child view is larger for itself,
161    * because zones are formed on child view boundaries.
162    *
163    * @return the maximum zone size
164    *
165    * @see #setMaximumZoneSize(int)
166    */
getMaximumZoneSize()167   public int getMaximumZoneSize()
168   {
169     return maximumZoneSize;
170   }
171 
172   /**
173    * Sets the maximum number of zones that are allowed to be loaded at the
174    * same time. If the new number of allowed zones is smaller then the
175    * previous settings, this unloads all zones the aren't allowed to be
176    * loaded anymore.
177    *
178    * @param num the number of zones allowed to be loaded at the same time
179    *
180    * @throws IllegalArgumentException if <code>num &lt;= 0</code>
181    *
182    * @see #getMaxZonesLoaded()
183    */
setMaxZonesLoaded(int num)184   public void setMaxZonesLoaded(int num)
185   {
186     if (num < 1)
187       throw new IllegalArgumentException("Illegal number of zones");
188     maxZonesLoaded = num;
189     unloadOldestZones();
190   }
191 
192   /**
193    * Returns the number of zones that are allowed to be loaded.
194    *
195    * @return the number of zones that are allowed to be loaded
196    *
197    * @see #setMaxZonesLoaded(int)
198    */
getMaxZonesLoaded()199   public int getMaxZonesLoaded()
200   {
201     return maxZonesLoaded;
202   }
203 
204   /**
205    * Gets called after a zone has been loaded. This unloads the oldest zone(s)
206    * when the maximum number of zones is reached.
207    *
208    * @param zone the zone that has been loaded
209    */
zoneWasLoaded(View zone)210   protected void zoneWasLoaded(View zone)
211   {
212     loadedZones.addLast(zone);
213     unloadOldestZones();
214   }
215 
216   /**
217    * This unloads the specified zone. This is implemented to simply remove
218    * all child views from that zone.
219    *
220    * @param zone the zone to be unloaded
221    */
unloadZone(View zone)222   protected void unloadZone(View zone)
223   {
224     zone.removeAll();
225   }
226 
227   /**
228    * Returns <code>true</code> when the specified zone is loaded,
229    * <code>false</code> otherwise. The default implementation checks if
230    * the zone view has child elements.
231    *
232    * @param zone the zone view to check
233    *
234    * @return <code>true</code> when the specified zone is loaded,
235    *         <code>false</code> otherwise
236    */
isZoneLoaded(View zone)237   protected boolean isZoneLoaded(View zone)
238   {
239     return zone.getViewCount() > 0;
240   }
241 
242   /**
243    * Creates a zone for the specified range. Subclasses can override this
244    * to provide a custom implementation for the zones.
245    *
246    * @param p0 the start of the range
247    * @param p1 the end of the range
248    *
249    * @return the zone
250    */
createZone(int p0, int p1)251   protected View createZone(int p0, int p1)
252   {
253     Document doc = getDocument();
254     Position pos0 = null;
255     Position pos1 = null;
256     try
257       {
258         pos0 = doc.createPosition(p0);
259         pos1 = doc.createPosition(p1);
260       }
261     catch (BadLocationException ex)
262       {
263         assert false : "Must not happen";
264       }
265     Zone zone = new Zone(getElement(), pos0, pos1, getAxis());
266     return zone;
267   }
268 
269   // --------------------------------------------------------------------------
270   // CompositeView methods.
271   // --------------------------------------------------------------------------
272 
273   /**
274    * Overridden to not load all the child views. This methods creates
275    * initial zones without actually loading them.
276    *
277    * @param vf not used
278    */
loadChildren(ViewFactory vf)279   protected void loadChildren(ViewFactory vf)
280   {
281     int p0 = getStartOffset();
282     int p1 = getEndOffset();
283     append(createZone(p0, p1));
284     checkZoneAt(p0);
285   }
286 
287   /**
288    * Returns the index of the child view at the document position
289    * <code>pos</code>.
290    *
291    * This overrides the CompositeView implementation because the ZoneView does
292    * not provide a one to one mapping from Elements to Views.
293    *
294    * @param pos the document position
295    *
296    * @return the index of the child view at the document position
297    *         <code>pos</code>
298    */
getViewIndexAtPosition(int pos)299   protected int getViewIndexAtPosition(int pos)
300   {
301     int index = -1;
302     boolean found = false;
303     if (pos >= getStartOffset() && pos <= getEndOffset())
304       {
305         int upper = getViewCount() - 1;
306         int lower = 0;
307         index = (upper - lower) / 2 + lower;
308         int bias = 0;
309         do
310           {
311             View child = getView(index);
312             int childStart = child.getStartOffset();
313             int childEnd = child.getEndOffset();
314             if (pos >= childStart && pos < childEnd)
315               found = true;
316             else if (pos < childStart)
317               {
318                 upper = index;
319                 bias = -1;
320               }
321             else if (pos >= childEnd)
322               {
323                 lower = index;
324                 bias = 1;
325               }
326             if (! found)
327               {
328                 int newIndex = (upper - lower) / 2 + lower;
329                 if (newIndex == index)
330                   index = newIndex + bias;
331                 else
332                   index = newIndex;
333               }
334           } while (upper != lower && ! found);
335       }
336     // If no child view actually covers the specified offset, reset index to
337     // -1.
338     if (! found)
339       index = -1;
340     return index;
341   }
342 
343   // --------------------------------------------------------------------------
344   // View methods.
345   // --------------------------------------------------------------------------
346 
insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)347   public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
348   {
349     // TODO: Implement this.
350   }
351 
removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)352   public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
353   {
354     // TODO: Implement this.
355   }
356 
updateChildren(DocumentEvent.ElementChange ec, DocumentEvent e, ViewFactory vf)357   protected boolean updateChildren(DocumentEvent.ElementChange ec,
358                                    DocumentEvent e, ViewFactory vf)
359   {
360     // TODO: Implement this.
361     return false;
362   }
363 
364   // --------------------------------------------------------------------------
365   // Internal helper methods.
366   // --------------------------------------------------------------------------
367 
368   /**
369    * A helper method to unload the oldest zones when there are more loaded
370    * zones then allowed.
371    */
unloadOldestZones()372   private void unloadOldestZones()
373   {
374     int maxZones = getMaxZonesLoaded();
375     while (loadedZones.size() > maxZones)
376       {
377         View zone = (View) loadedZones.removeFirst();
378         unloadZone(zone);
379       }
380   }
381 
382   /**
383    * Checks if the zone view at position <code>pos</code> should be split
384    * (its size is greater than maximumZoneSize) and tries to split it.
385    *
386    * @param pos the document position to check
387    */
checkZoneAt(int pos)388   private void checkZoneAt(int pos)
389   {
390     int viewIndex = getViewIndexAtPosition(pos); //, Position.Bias.Forward);
391     View view = getView(viewIndex);
392     int p0 = view.getStartOffset();
393     int p1 = view.getEndOffset();
394     if (p1 - p0 > maximumZoneSize)
395       splitZone(viewIndex, p0, p1);
396   }
397 
398   /**
399    * Tries to break the view at the specified index and inside the specified
400    * range into pieces that are acceptable with respect to the maximum zone
401    * size.
402    *
403    * @param index the index of the view to split
404    * @param p0 the start offset
405    * @param p1 the end offset
406    */
splitZone(int index, int p0, int p1)407   private void splitZone(int index, int p0, int p1)
408   {
409     ArrayList newZones = new ArrayList();
410     int p = p0;
411     do
412       {
413         p0 = p;
414         p = Math.min(getPreferredZoneEnd(p0), p1);
415         newZones.add(createZone(p0, p));
416       } while (p < p1);
417     View[] newViews = new View[newZones.size()];
418     newViews = (View[]) newZones.toArray(newViews);
419     replace(index, 1, newViews);
420   }
421 
422   /**
423    * Calculates the positions at which a zone split is performed. This
424    * tries to create zones sized close to half the maximum zone size.
425    *
426    * @param start the start offset
427    *
428    * @return the preferred end offset
429    */
getPreferredZoneEnd(int start)430   private int getPreferredZoneEnd(int start)
431   {
432     Element el = getElement();
433     int index = el.getElementIndex(start + (maximumZoneSize / 2));
434     Element child = el.getElement(index);
435     int p0 = child.getStartOffset();
436     int p1 = child.getEndOffset();
437     int end = p1;
438     if (p0 - start > maximumZoneSize && p0 > start)
439       end = p0;
440     return end;
441   }
442 }
443