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,v 1.11 2007/05/14 08:55:52 taqua Exp $
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 public 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 }