001    /**
002     * ========================================
003     * JFreeReport : a free Java report library
004     * ========================================
005     *
006     * Project Info:  http://reporting.pentaho.org/
007     *
008     * (C) Copyright 2000-2007, by Object Refinery Limited, Pentaho Corporation and Contributors.
009     *
010     * This library is free software; you can redistribute it and/or modify it under the terms
011     * of the GNU Lesser General Public License as published by the Free Software Foundation;
012     * either version 2.1 of the License, or (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016     * See the GNU Lesser General Public License for more details.
017     *
018     * You should have received a copy of the GNU Lesser General Public License along with this
019     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020     * Boston, MA 02111-1307, USA.
021     *
022     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023     * in the United States and other countries.]
024     *
025     * ------------
026     * $Id: Section.java 3048 2007-07-28 18:02:42Z tmorgner $
027     * ------------
028     * (C) Copyright 2000-2005, by Object Refinery Limited.
029     * (C) Copyright 2005-2007, by Pentaho Corporation.
030     */
031    
032    package org.jfree.report.structure;
033    
034    import java.util.ArrayList;
035    import java.util.Collection;
036    import java.util.Collections;
037    import java.util.Iterator;
038    import java.util.List;
039    
040    import org.jfree.report.flow.FlowControlOperation;
041    import org.jfree.util.ObjectUtilities;
042    
043    /**
044     * A report section is a collection of other elements and sections.
045     * <p/>
046     * This implementation is not synchronized, to take care that you externally
047     * synchronize it when using multiple threads to modify instances of this
048     * class.
049     * <p/>
050     * Trying to add a parent of an band as child to the band, will result in an
051     * exception.
052     * <p/>
053     * The attribute and style expressions added to the element are considered
054     * unnamed and stateless. To define a named, statefull state expression, one
055     * would create an ordinary named expression or function and would then
056     * reference that expression from within a style or attribute expression.
057     *
058     * @author Thomas Morgner
059     */
060    public class Section extends Element
061    {
062      /**
063       * An empty array to prevent object creation.
064       */
065      private static final Node[] EMPTY_ARRAY = new Node[0];
066      private static final FlowControlOperation[] EMPTY_FLOWCONTROL = new FlowControlOperation[0];
067      /**
068       * All the elements for this band, stored by name.
069       */
070      private ArrayList allElements;
071    
072      /**
073       * Cached elements.
074       */
075      private transient Node[] allElementsCached;
076    
077      private ArrayList operationsBefore;
078      private ArrayList operationsAfter;
079      private transient FlowControlOperation[] operationsBeforeCached;
080      private transient FlowControlOperation[] operationsAfterCached;
081      private boolean repeat;
082    
083      /**
084       * Constructs a new band (initially empty).
085       */
086      public Section()
087      {
088        setType("section");
089        allElements = new ArrayList();
090    
091      }
092    
093      /**
094       * Adds a report element to the band.
095       *
096       * @param element the element that should be added
097       * @throws NullPointerException     if the given element is null
098       * @throws IllegalArgumentException if the position is invalid, either
099       *                                  negative or greater than the number of
100       *                                  elements in this band or if the given
101       *                                  element is a parent of this element.
102       */
103      public void addNode(final Node element)
104      {
105        addNode(allElements.size(), element);
106      }
107    
108      /**
109       * Adds a report element to the band. The element will be inserted at the
110       * specified position.
111       *
112       * @param position the position where to insert the element
113       * @param element  the element that should be added
114       * @throws NullPointerException     if the given element is null
115       * @throws IllegalArgumentException if the position is invalid, either
116       *                                  negative or greater than the number of
117       *                                  elements in this band or if the given
118       *                                  element is a parent of this element.
119       */
120      public void addNode(final int position, final Node element)
121      {
122        if (position < 0)
123        {
124          throw new IllegalArgumentException("Position < 0");
125        }
126        if (position > allElements.size())
127        {
128          throw new IllegalArgumentException("Position < 0");
129        }
130        if (element == null)
131        {
132          throw new NullPointerException("Band.addElement(...): element is null.");
133        }
134    
135        // check for component loops ...
136        if (element instanceof Section)
137        {
138          Node band = this;
139          while (band != null)
140          {
141            if (band == element)
142            {
143              throw new IllegalArgumentException(
144                  "adding container's parent to itself");
145            }
146            band = band.getParent();
147          }
148        }
149    
150        // remove the element from its old parent ..
151        // this is the default AWT behaviour when adding Components to Container
152        final Node parent = element.getParent();
153        if (parent != null)
154        {
155          if (parent == this)
156          {
157            // already a child, wont add twice ...
158            return;
159          }
160    
161          if (parent instanceof Section)
162          {
163            final Section section = (Section) parent;
164            section.removeNode(element);
165          }
166          else
167          {
168            element.setParent(null);
169          }
170        }
171    
172        // add the element, update the childs Parent and the childs stylesheet.
173        allElements.add(position, element);
174        allElementsCached = null;
175    
176        // then add the parents, or the band's parent will be unregistered ..
177        element.setParent(this);
178      }
179    
180      /**
181       * Adds a collection of elements to the band.
182       *
183       * @param elements the element collection.
184       * @throws NullPointerException     if one of the given elements is null
185       * @throws IllegalArgumentException if one of the given element is a parent of
186       *                                  this element.
187       */
188      public void addNodes(final Collection elements)
189      {
190        if (elements == null)
191        {
192          throw new NullPointerException(
193              "Band.addElements(...): collection is null.");
194        }
195    
196        final Iterator iterator = elements.iterator();
197        while (iterator.hasNext())
198        {
199          final Element element = (Element) iterator.next();
200          addNode(element);
201        }
202      }
203    
204      /**
205       * Returns the first element in the list that is known by the given name.
206       *
207       * @param name the element name.
208       * @return the first element with the specified name, or <code>null</code> if
209       *         there is no such element.
210       *
211       * @throws NullPointerException if the given name is null.
212       */
213      public Element getElementByName(final String name)
214      {
215        if (name == null)
216        {
217          throw new NullPointerException("Band.getElement(...): name is null.");
218        }
219    
220        final Node[] elements = getNodeArray();
221        final int elementsSize = elements.length;
222        for (int i = 0; i < elementsSize; i++)
223        {
224          final Node e = elements[i];
225          if (e instanceof Element == false)
226          {
227            continue;
228          }
229          final Element element = (Element) e;
230          final String elementName = element.getName();
231          if (elementName != null)
232          {
233            if (elementName.equals(name))
234            {
235              return element;
236            }
237          }
238        }
239        return null;
240      }
241    
242      /**
243       * Removes an element from the band.
244       *
245       * @param e the element to be removed.
246       * @throws NullPointerException if the given element is null.
247       */
248      public void removeNode(final Node e)
249      {
250        if (e == null)
251        {
252          throw new NullPointerException();
253        }
254        if (e.getParent() != this)
255        {
256          // this is none of my childs, ignore the request ...
257          return;
258        }
259    
260        e.setParent(null);
261        allElements.remove(e);
262        allElementsCached = null;
263      }
264    
265      /**
266       * Returns all child-elements of this band as immutable list.
267       *
268       * @return an immutable list of all registered elements for this band.
269       *
270       * @deprecated use <code>getElementArray()</code> instead.
271       */
272      public List getNodes()
273      {
274        return Collections.unmodifiableList(allElements);
275      }
276    
277      /**
278       * Returns the number of elements in this band.
279       *
280       * @return the number of elements of this band.
281       */
282      public int getNodeCount()
283      {
284        return allElements.size();
285      }
286    
287      /**
288       * Returns an array of the elements in the band. If the band is empty, an
289       * empty array is returned.
290       * <p/>
291       * For performance reasons, a shared cached instance is returned. Do not
292       * modify the returned array or live with the consquences.
293       *
294       * @return the elements.
295       */
296      public Node[] getNodeArray()
297      {
298        if (allElementsCached == null)
299        {
300          if (allElements.isEmpty())
301          {
302            allElementsCached = Section.EMPTY_ARRAY;
303          }
304          else
305          {
306            Node[] elements = new Node[allElements.size()];
307            elements = (Node[]) allElements.toArray(elements);
308            allElementsCached = elements;
309          }
310        }
311        return allElementsCached;
312      }
313    
314      /**
315       * Returns the element stored add the given index.
316       *
317       * @param index the element position within this band
318       * @return the element
319       *
320       * @throws IndexOutOfBoundsException if the index is invalid.
321       */
322      public Node getNode(final int index)
323      {
324        if (allElementsCached == null)
325        {
326          if (allElements.isEmpty())
327          {
328            allElementsCached = Section.EMPTY_ARRAY;
329          }
330          else
331          {
332            Node[] elements = new Node[allElements.size()];
333            elements = (Node[]) allElements.toArray(elements);
334            allElementsCached = elements;
335          }
336        }
337        return allElementsCached[index];
338      }
339    
340      /**
341       * Returns a string representation of the band and all the elements it
342       * contains, useful mainly for debugging purposes.
343       *
344       * @return a string representation of this band.
345       */
346      public String toString()
347      {
348        final StringBuffer b = new StringBuffer();
349        b.append(this.getClass().getName());
350        b.append("={name=\"");
351        b.append(getName());
352        b.append("\", namespace=\"");
353        b.append(getNamespace());
354        b.append("\", type=\"");
355        b.append(getType());
356        b.append("\", size=\"");
357        b.append(allElements.size());
358        b.append("\"}");
359        return b.toString();
360      }
361    
362      public FlowControlOperation[] getOperationBefore()
363      {
364        if (operationsBefore == null)
365        {
366          return Section.EMPTY_FLOWCONTROL;
367        }
368        if (operationsBeforeCached == null)
369        {
370          operationsBeforeCached = (FlowControlOperation[])
371              operationsBefore.toArray(Section.EMPTY_FLOWCONTROL);
372        }
373        return operationsBeforeCached;
374      }
375    
376      public FlowControlOperation[] getOperationAfter()
377      {
378        if (operationsAfter == null)
379        {
380          return Section.EMPTY_FLOWCONTROL;
381        }
382        if (operationsAfterCached == null)
383        {
384          operationsAfterCached = (FlowControlOperation[])
385              operationsAfter.toArray(Section.EMPTY_FLOWCONTROL);
386        }
387        return operationsAfterCached;
388      }
389    
390      public void setOperationBefore(final FlowControlOperation[] before)
391      {
392        if (operationsBefore == null)
393        {
394          operationsBefore = new ArrayList(before.length);
395        }
396        else
397        {
398          operationsBefore.clear();
399          operationsBefore.ensureCapacity(before.length);
400        }
401        for (int i = 0; i < before.length; i++)
402        {
403          operationsBefore.add(before[i]);
404        }
405    
406        operationsBeforeCached =
407            (FlowControlOperation[]) before.clone();
408      }
409    
410      public void setOperationAfter(final FlowControlOperation[] ops)
411      {
412        if (operationsAfter == null)
413        {
414          operationsAfter = new ArrayList(ops.length);
415        }
416        else
417        {
418          operationsAfter.clear();
419          operationsAfter.ensureCapacity(ops.length);
420        }
421        for (int i = 0; i < ops.length; i++)
422        {
423          operationsAfter.add(ops[i]);
424        }
425    
426        operationsAfterCached =
427            (FlowControlOperation[]) ops.clone();
428      }
429    
430      public void addOperationAfter(final FlowControlOperation op)
431      {
432        if (operationsAfter == null)
433        {
434          operationsAfter = new ArrayList();
435        }
436        operationsAfter.add(op);
437        operationsAfterCached = null;
438      }
439    
440      public void addOperationBefore(final FlowControlOperation op)
441      {
442        if (operationsBefore == null)
443        {
444          operationsBefore = new ArrayList();
445        }
446        operationsBefore.add(op);
447        operationsBeforeCached = null;
448      }
449    
450      public boolean isRepeat()
451      {
452        return repeat;
453      }
454    
455      public void setRepeat(final boolean repeat)
456      {
457        this.repeat = repeat;
458      }
459    
460      public Element findFirstChild (final String uri, final String tagName)
461      {
462        final Node[] nodes = getNodeArray();
463        for (int i = 0; i < nodes.length; i++)
464        {
465          final Node node = nodes[i];
466          if (node instanceof Element == false)
467          {
468            continue;
469          }
470          final Element e = (Element) node;
471          if (ObjectUtilities.equal(uri, e.getNamespace()) &&
472              ObjectUtilities.equal(tagName, e.getType()))
473          {
474            return e;
475          }
476        }
477        return null;
478      }
479    
480      public Object clone()
481          throws CloneNotSupportedException
482      {
483        final Section section = (Section) super.clone();
484        if (operationsAfter != null)
485        {
486          section.operationsAfter = (ArrayList) operationsAfter.clone();
487        }
488        if (operationsBefore != null)
489        {
490          section.operationsBefore = (ArrayList) operationsBefore.clone();
491        }
492        section.allElements = (ArrayList) allElements.clone();
493        section.allElements.clear();
494        final int elementSize = allElements.size();
495        if (allElementsCached != null)
496        {
497          section.allElementsCached = (Node[]) allElementsCached.clone();
498          for (int i = 0; i < allElementsCached.length; i++)
499          {
500            final Node eC = (Node) allElementsCached[i].clone();
501            section.allElements.add(eC);
502            section.allElementsCached[i] = eC;
503            eC.setParent(section);
504          }
505        }
506        else
507        {
508          for (int i = 0; i < elementSize; i++)
509          {
510            final Node e = (Node) allElements.get(i);
511            final Node eC = (Node) e.clone();
512            section.allElements.add(eC);
513            eC.setParent(section);
514          }
515        }
516        return section;
517      }
518    }