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: ElementLayoutController.java 3525 2007-10-16 11:43:48Z 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.flow.layoutprocessor;
033    
034    import org.jfree.report.DataSourceException;
035    import org.jfree.report.ReportDataFactoryException;
036    import org.jfree.report.ReportProcessingException;
037    import org.jfree.report.data.ExpressionSlot;
038    import org.jfree.report.data.PrecomputeNodeKey;
039    import org.jfree.report.data.PrecomputedExpressionSlot;
040    import org.jfree.report.data.PrecomputedValueRegistry;
041    import org.jfree.report.data.RunningExpressionSlot;
042    import org.jfree.report.data.StaticExpressionRuntimeData;
043    import org.jfree.report.expressions.Expression;
044    import org.jfree.report.flow.FlowControlOperation;
045    import org.jfree.report.flow.FlowController;
046    import org.jfree.report.flow.ReportTarget;
047    import org.jfree.report.flow.LayoutExpressionRuntime;
048    import org.jfree.report.structure.Element;
049    import org.jfree.util.Log;
050    import org.jfree.layouting.util.AttributeMap;
051    
052    /**
053     * Creation-Date: 24.11.2006, 13:56:30
054     *
055     * @author Thomas Morgner
056     */
057    public abstract class ElementLayoutController
058        implements LayoutController
059    {
060      protected static class ElementPrecomputeKey implements PrecomputeNodeKey
061      {
062        private String name;
063        private String id;
064        private String namespace;
065        private String tagName;
066    
067        protected ElementPrecomputeKey(final Element element)
068        {
069          this.name = element.getName();
070          this.tagName = element.getType();
071          this.namespace = element.getNamespace();
072          this.id = element.getId();
073        }
074    
075        public boolean equals(final Object obj)
076        {
077          if (this == obj)
078          {
079            return true;
080          }
081          if (obj == null || getClass() != obj.getClass())
082          {
083            return false;
084          }
085    
086          final ElementPrecomputeKey that = (ElementPrecomputeKey) obj;
087    
088          if (id != null ? !id.equals(that.id) : that.id != null)
089          {
090            return false;
091          }
092          if (name != null ? !name.equals(that.name) : that.name != null)
093          {
094            return false;
095          }
096          if (namespace != null ? !namespace.equals(
097              that.namespace) : that.namespace != null)
098          {
099            return false;
100          }
101          if (tagName != null ? !tagName.equals(
102              that.tagName) : that.tagName != null)
103          {
104            return false;
105          }
106    
107          return true;
108        }
109    
110        public int hashCode()
111        {
112          int result = (name != null ? name.hashCode() : 0);
113          result = 29 * result + (id != null ? id.hashCode() : 0);
114          result = 29 * result + (namespace != null ? namespace.hashCode() : 0);
115          result = 29 * result + (tagName != null ? tagName.hashCode() : 0);
116          return result;
117        }
118    
119        public boolean equals(final PrecomputeNodeKey otherKey)
120        {
121          return false;
122        }
123      }
124    
125      public static final int NOT_STARTED = 0;
126      public static final int OPENED = 1;
127      public static final int WAITING_FOR_JOIN = 2;
128      public static final int FINISHING = 3;
129      //public static final int JOINING = 4;
130      public static final int FINISHED = 4;
131    
132      private int processingState;
133      private FlowController flowController;
134      private Element element;
135      private LayoutController parent;
136      private boolean precomputing;
137      private AttributeMap attributeMap;
138      private int expressionsCount;
139      private int iterationCount;
140    
141      protected ElementLayoutController()
142      {
143        this.processingState = ElementLayoutController.NOT_STARTED;
144      }
145    
146    
147      public String toString()
148      {
149        return "ElementLayoutController{" +
150            "processingState=" + processingState +
151            ", element=" + element +
152            ", precomputing=" + precomputing +
153            ", expressionsCount=" + expressionsCount +
154            ", iterationCount=" + iterationCount +
155            '}';
156      }
157    
158      /**
159       * Retrieves the parent of this layout controller. This allows childs to query
160       * their context.
161       *
162       * @return the layout controller's parent to <code>null</code> if there is no
163       * parent.
164       */
165      public LayoutController getParent()
166      {
167        return parent;
168      }
169    
170    
171      /**
172       * Initializes the layout controller. This method is called exactly once. It
173       * is the creators responsibility to call this method.
174       * <p/>
175       * Calling initialize after the first advance must result in a
176       * IllegalStateException.
177       *
178       * @param node           the currently processed object or layout node.
179       * @param flowController the current flow controller.
180       * @param parent         the parent layout controller that was responsible for
181       *                       instantiating this controller.
182       * @throws DataSourceException        if there was a problem reading data from
183       *                                    the datasource.
184       * @throws ReportProcessingException  if there was a general problem during
185       *                                    the report processing.
186       * @throws ReportDataFactoryException if a query failed.
187       */
188      public void initialize(final Object node,
189                             final FlowController flowController,
190                             final LayoutController parent)
191          throws DataSourceException, ReportDataFactoryException,
192          ReportProcessingException
193      {
194    
195        if (processingState != ElementLayoutController.NOT_STARTED)
196        {
197          throw new IllegalStateException();
198        }
199    
200        this.element = (Element) node;
201        this.flowController = flowController;
202        this.parent = parent;
203        this.iterationCount = -1;
204      }
205    
206      /**
207       * Advances the layout controller to the next state. This method delegates the
208       * call to one of the following methods: <ul> <li>{@link
209       * #startElement(org.jfree.report.flow.ReportTarget)} <li>{@link
210       * #processContent(org.jfree.report.flow.ReportTarget)} <li>{@link
211       * #finishElement(org.jfree.report.flow.ReportTarget)} </ul>
212       *
213       * @param target the report target that receives generated events.
214       * @return the new layout controller instance representing the new state.
215       *
216       * @throws DataSourceException        if there was a problem reading data from
217       *                                    the datasource.
218       * @throws ReportProcessingException  if there was a general problem during
219       *                                    the report processing.
220       * @throws ReportDataFactoryException if a query failed.
221       */
222      public final LayoutController advance(final ReportTarget target)
223          throws DataSourceException, ReportProcessingException,
224          ReportDataFactoryException
225      {
226        final int processingState = getProcessingState();
227        switch (processingState)
228        {
229          case ElementLayoutController.NOT_STARTED:
230            return startElement(target);
231          case ElementLayoutController.OPENED:
232            return processContent(target);
233          case ElementLayoutController.FINISHING:
234            return finishElement(target);
235    //      case ElementLayoutController.JOINING:
236    //        return joinWithParent();
237          default:
238            throw new IllegalStateException();
239        }
240      }
241    
242      /**
243       * This method is called for each newly instantiated layout controller. The
244       * returned layout controller instance should have a processing state of
245       * either 'OPEN' or 'FINISHING' depending on whether there is any content or
246       * any child nodes to process.
247       *
248       * @param target the report target that receives generated events.
249       * @return the new layout controller instance representing the new state.
250       *
251       * @throws DataSourceException        if there was a problem reading data from
252       *                                    the datasource.
253       * @throws ReportProcessingException  if there was a general problem during
254       *                                    the report processing.
255       * @throws ReportDataFactoryException if a query failed.
256       */
257      protected LayoutController startElement(final ReportTarget target)
258          throws DataSourceException, ReportProcessingException,
259          ReportDataFactoryException
260      {
261        final Element s = getElement();
262    
263        FlowController fc = getFlowController();
264        // Step 3: Add the expressions. Any expressions defined for the subreport
265        // will work on the queried dataset.
266        fc = startData(target, fc);
267    
268        final Expression[] expressions = s.getExpressions();
269        fc = performElementPrecomputation(expressions, fc);
270    
271        if (s.isVirtual() == false)
272        {
273          attributeMap = computeAttributes(fc, s, target);
274          target.startElement(attributeMap);
275        }
276    
277        final ElementLayoutController derived = (ElementLayoutController) clone();
278        derived.setProcessingState(ElementLayoutController.OPENED);
279        derived.setFlowController(fc);
280        derived.expressionsCount = expressions.length;
281        derived.attributeMap = attributeMap;
282        derived.iterationCount += 1;
283        return derived;
284      }
285    
286      public AttributeMap getAttributeMap()
287      {
288        return attributeMap;
289      }
290    
291      public int getExpressionsCount()
292      {
293        return expressionsCount;
294      }
295    
296      public int getIterationCount()
297      {
298        return iterationCount;
299      }
300    
301    
302      protected FlowController startData(final ReportTarget target,
303                                         final FlowController fc)
304          throws DataSourceException, ReportProcessingException,
305          ReportDataFactoryException
306      {
307        return fc;
308      }
309    
310      protected AttributeMap computeAttributes(final FlowController fc,
311                                               final Element element,
312                                               final ReportTarget target)
313          throws DataSourceException
314      {
315        final LayoutExpressionRuntime ler =
316            LayoutControllerUtil.getExpressionRuntime(fc, element);
317        return LayoutControllerUtil.processAttributes(element, target, ler);
318      }
319    
320    
321      /**
322       * Processes any content in this element. This method is called when the
323       * processing state is 'OPENED'. The returned layout controller will retain
324       * the 'OPENED' state as long as there is more content available. Once all
325       * content has been processed, the returned layout controller should carry a
326       * 'FINISHED' state.
327       *
328       * @param target the report target that receives generated events.
329       * @return the new layout controller instance representing the new state.
330       *
331       * @throws DataSourceException        if there was a problem reading data from
332       *                                    the datasource.
333       * @throws ReportProcessingException  if there was a general problem during
334       *                                    the report processing.
335       * @throws ReportDataFactoryException if a query failed.
336       */
337      protected abstract LayoutController processContent(final ReportTarget target)
338          throws DataSourceException, ReportProcessingException,
339          ReportDataFactoryException;
340    
341      /**
342       * Finishes the processing of this element. This method is called when the
343       * processing state is 'FINISHING'. The element should be closed now and all
344       * privatly owned resources should be freed. If the element has a parent, it
345       * would be time to join up with the parent now, else the processing state
346       * should be set to 'FINISHED'.
347       *
348       * @param target the report target that receives generated events.
349       * @return the new layout controller instance representing the new state.
350       *
351       * @throws DataSourceException       if there was a problem reading data from
352       *                                   the datasource.
353       * @throws ReportProcessingException if there was a general problem during the
354       *                                   report processing.
355       * @throws ReportDataFactoryException if there was an error trying query data.
356       */
357      protected LayoutController finishElement(final ReportTarget target)
358          throws ReportProcessingException, DataSourceException,
359          ReportDataFactoryException
360      {
361        final FlowController fc = handleDefaultEndElement(target);
362        final ElementLayoutController derived = (ElementLayoutController) clone();
363        derived.setProcessingState(ElementLayoutController.FINISHED);
364        derived.setFlowController(fc);
365        return derived;
366      }
367    
368      protected FlowController handleDefaultEndElement (final ReportTarget target)
369          throws ReportProcessingException, DataSourceException,
370          ReportDataFactoryException
371      {
372        final Element e = getElement();
373        // Step 1: call End Element
374        if (e.isVirtual() == false)
375        {
376          target.endElement(getAttributeMap());
377        }
378    
379        FlowController fc = getFlowController();
380        final PrecomputedValueRegistry pcvr =
381            fc.getPrecomputedValueRegistry();
382        // Step 2: Remove the expressions of this element
383        final int expressionsCount = getExpressionsCount();
384        if (expressionsCount != 0)
385        {
386          final ExpressionSlot[] activeExpressions = fc.getActiveExpressions();
387          for (int i = activeExpressions.length - expressionsCount; i < activeExpressions.length; i++)
388          {
389            final ExpressionSlot slot = activeExpressions[i];
390            pcvr.addFunction(slot.getName(), slot.getValue());
391          }
392          fc = fc.deactivateExpressions();
393        }
394    
395        if (isPrecomputing() == false)
396        {
397          pcvr.finishElement(new ElementPrecomputeKey(e));
398        }
399    
400        return fc;
401      }
402    //
403    //  /**
404    //   * Joins the layout controller with the parent. This simply calls
405    //   * {@link #join(org.jfree.report.flow.FlowController)} on the parent. A join
406    //   * operation is necessary to propagate changes in the flow-controller to the
407    //   * parent for further processing.
408    //   *
409    //   * @return the joined parent.
410    //   * @throws IllegalStateException if this layout controller has no parent.
411    //   */
412    //  protected LayoutController joinWithParent()
413    //      throws ReportProcessingException, ReportDataFactoryException,
414    //      DataSourceException
415    //  {
416    //    final LayoutController parent = getParent();
417    //    if (parent == null)
418    //    {
419    //      // skip to the next step ..
420    //      throw new IllegalStateException("There is no parent to join with. " +
421    //                                      "This should not happen in a sane environment!");
422    //    }
423    //
424    //    return parent.join(getFlowController());
425    //  }
426    
427      public boolean isAdvanceable()
428      {
429        return processingState != ElementLayoutController.FINISHED;
430      }
431    
432      public Element getElement()
433      {
434        return element;
435      }
436    
437      public FlowController getFlowController()
438      {
439        return flowController;
440      }
441    
442      public int getProcessingState()
443      {
444        return processingState;
445      }
446    
447      public void setProcessingState(final int processingState)
448      {
449        this.processingState = processingState;
450      }
451    
452      public void setFlowController(final FlowController flowController)
453      {
454        this.flowController = flowController;
455      }
456    
457      public void setParent(final LayoutController parent)
458      {
459        this.parent = parent;
460      }
461    
462      public Object clone()
463      {
464        try
465        {
466          return super.clone();
467        }
468        catch (CloneNotSupportedException e)
469        {
470          Log.error("Clone not supported: ", e);
471          throw new IllegalStateException("Clone must be supported.");
472        }
473      }
474    
475      public boolean isPrecomputing()
476      {
477        return precomputing;
478      }
479    
480      protected FlowController performElementPrecomputation(
481          final Expression[] expressions,
482          FlowController fc)
483          throws ReportProcessingException, ReportDataFactoryException,
484          DataSourceException
485      {
486        final Element element = getElement();
487        final PrecomputedValueRegistry pcvr = fc.getPrecomputedValueRegistry();
488        if (isPrecomputing() == false)
489        {
490          pcvr.startElement(new ElementPrecomputeKey(element));
491        }
492    
493        if (expressions.length > 0)
494        {
495          final ExpressionSlot[] slots = new ExpressionSlot[expressions.length];
496          final StaticExpressionRuntimeData runtimeData =
497              LayoutControllerUtil.getStaticExpressionRuntime(fc, element);
498    
499          for (int i = 0; i < expressions.length; i++)
500          {
501            final Expression expression = expressions[i];
502            if (isPrecomputing() == false && expression.isPrecompute())
503            {
504              // ok, we have to precompute the expression's value. For that
505              // we fork a new layout process, compute the value and then come
506              // back with the result.
507              final Object value = LayoutControllerUtil.performPrecompute
508                  (i, new ElementPrecomputeKey(element),
509                      this, getFlowController());
510              slots[i] = new PrecomputedExpressionSlot(expression.getName(), value,
511                  expression.isPreserve());
512            }
513            else
514            {
515              // thats a bit easier; we dont have to do anything special ..
516              slots[i] = new RunningExpressionSlot(expression, runtimeData,
517                  pcvr.currentNode());
518            }
519          }
520    
521          fc = fc.activateExpressions(slots);
522        }
523        return fc;
524      }
525    
526    
527      protected FlowController tryRepeatingCommit(final FlowController fc)
528          throws DataSourceException
529      {
530        if (isPrecomputing() == false)
531        {
532          // ok, the user wanted us to repeat. So we repeat if the group in which
533          // we are in, is not closed (and at least one advance has been fired
534          // since the last repeat request [to prevent infinite loops]) ...
535          final boolean advanceRequested = fc.isAdvanceRequested();
536          final boolean advanceable = fc.getMasterRow().isAdvanceable();
537          if (advanceable && advanceRequested)
538          {
539            // we check against the commited target; But we will not use the
540            // commited target if the group is no longer active...
541            final FlowController cfc =
542                fc.performOperation(FlowControlOperation.COMMIT);
543            final boolean groupFinished =
544                LayoutControllerUtil.isGroupFinished(cfc, getElement());
545            if (groupFinished == false)
546            {
547              return cfc;
548            }
549          }
550        }
551        return null;
552      }
553    
554    
555      /**
556       * Derives a copy of this controller that is suitable to perform a
557       * precomputation.
558       *
559       * @param fc
560       * @return
561       */
562      public LayoutController createPrecomputeInstance(final FlowController fc)
563      {
564        final ElementLayoutController lc = (ElementLayoutController) clone();
565        lc.setFlowController(fc);
566        lc.setParent(null);
567        lc.precomputing = true;
568        return lc;
569      }
570    
571    
572      public Object getNode()
573      {
574        return getElement();
575      }
576    }