001/* ZoneView.java -- An effective BoxView subclass
002   Copyright (C) 2006 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text;
040
041import java.awt.Shape;
042import java.util.ArrayList;
043import java.util.LinkedList;
044
045import javax.swing.event.DocumentEvent;
046
047/**
048 * A View implementation that delays loading of sub views until they are
049 * needed for display or internal transformations. This can be used for
050 * editors that need to handle large documents more effectivly than the
051 * standard {@link BoxView}.
052 *
053 * @author Roman Kennke (kennke@aicas.com)
054 *
055 * @since 1.3
056 */
057public class ZoneView
058  extends BoxView
059{
060
061  /**
062   * The default zone view implementation. The specs suggest that this is
063   * a subclass of AsyncBoxView, so do we.
064   */
065  static class Zone
066    extends AsyncBoxView
067  {
068    /**
069     * The start position for this zone.
070     */
071    private Position p0;
072
073    /**
074     * The end position for this zone.
075     */
076    private Position p1;
077
078    /**
079     * Creates a new Zone for the specified element, start and end positions.
080     *
081     * @param el the element
082     * @param pos0 the start position
083     * @param pos1 the end position
084     * @param axis the major axis
085     */
086    Zone(Element el, Position pos0, Position pos1, int axis)
087    {
088      super(el, axis);
089      p0 = pos0;
090      p1 = pos1;
091    }
092
093    /**
094     * Returns the start offset of the zone.
095     *
096     * @return the start offset of the zone
097     */
098    public int getStartOffset()
099    {
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     */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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
347  public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
348  {
349    // TODO: Implement this.
350  }
351
352  public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
353  {
354    // TODO: Implement this.
355  }
356
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   */
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   */
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   */
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   */
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}