001    /* ZoneView.java -- An effective BoxView subclass
002       Copyright (C) 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text;
040    
041    import java.awt.Shape;
042    import java.util.ArrayList;
043    import java.util.LinkedList;
044    
045    import 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     */
057    public 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    }