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 }