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: LayoutControllerUtil.java,v 1.15 2007/06/19 07:22:53 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 java.util.Iterator;
035 import java.util.Map;
036
037 import org.jfree.layouting.input.style.CSSDeclarationRule;
038 import org.jfree.layouting.input.style.CSSStyleRule;
039 import org.jfree.layouting.input.style.StyleKey;
040 import org.jfree.layouting.input.style.StyleKeyRegistry;
041 import org.jfree.layouting.input.style.StyleRule;
042 import org.jfree.layouting.input.style.values.CSSValue;
043 import org.jfree.layouting.namespace.NamespaceDefinition;
044 import org.jfree.layouting.namespace.Namespaces;
045 import org.jfree.layouting.util.AttributeMap;
046 import org.jfree.report.DataSourceException;
047 import org.jfree.report.EmptyReportData;
048 import org.jfree.report.JFreeReportInfo;
049 import org.jfree.report.ReportDataFactoryException;
050 import org.jfree.report.ReportProcessingException;
051 import org.jfree.report.data.GlobalMasterRow;
052 import org.jfree.report.data.PrecomputeNode;
053 import org.jfree.report.data.PrecomputeNodeKey;
054 import org.jfree.report.data.PrecomputedValueRegistry;
055 import org.jfree.report.data.ReportDataRow;
056 import org.jfree.report.data.StaticExpressionRuntimeData;
057 import org.jfree.report.expressions.Expression;
058 import org.jfree.report.expressions.ExpressionRuntime;
059 import org.jfree.report.flow.EmptyReportTarget;
060 import org.jfree.report.flow.FlowControlOperation;
061 import org.jfree.report.flow.FlowController;
062 import org.jfree.report.flow.LayoutExpressionRuntime;
063 import org.jfree.report.flow.ReportContext;
064 import org.jfree.report.flow.ReportJob;
065 import org.jfree.report.flow.ReportStructureRoot;
066 import org.jfree.report.flow.ReportTarget;
067 import org.jfree.report.structure.Element;
068 import org.jfree.report.structure.Group;
069 import org.jfree.report.structure.Node;
070 import org.jfree.report.structure.Section;
071 import org.jfree.resourceloader.Resource;
072 import org.jfree.resourceloader.ResourceKey;
073 import org.jfree.resourceloader.ResourceManager;
074
075 /**
076 * Creation-Date: 24.11.2006, 15:01:22
077 *
078 * @author Thomas Morgner
079 */
080 public class LayoutControllerUtil
081 {
082 public static final EmptyReportData EMPTY_REPORT_DATA = new EmptyReportData();
083
084 private LayoutControllerUtil()
085 {
086 }
087
088 public static int findNodeInParent(final Section parentSection,
089 final Node n)
090 {
091 final Node[] nodes = parentSection.getNodeArray();
092 for (int i = 0; i < nodes.length; i++)
093 {
094 final Node node = nodes[i];
095 if (node == n)
096 {
097 return i;
098 }
099 }
100 return -1;
101 }
102
103 public static StaticExpressionRuntimeData getStaticExpressionRuntime
104 (final FlowController fc,
105 final Object declaringParent)
106 {
107 final GlobalMasterRow dataRow = fc.getMasterRow();
108 final ReportJob reportJob = fc.getReportJob();
109 final StaticExpressionRuntimeData sdd = new StaticExpressionRuntimeData();
110 sdd.setData(dataRow.getReportDataRow().getReportData());
111 sdd.setDeclaringParent(declaringParent);
112 sdd.setConfiguration(reportJob.getConfiguration());
113 sdd.setReportContext(fc.getReportContext());
114 return sdd;
115 }
116
117
118 public static LayoutExpressionRuntime getExpressionRuntime
119 (final FlowController fc, final Object node)
120 {
121 final LayoutExpressionRuntime ler = new LayoutExpressionRuntime();
122 ler.setConfiguration(fc.getReportJob().getConfiguration());
123 ler.setReportContext(fc.getReportContext());
124
125 final GlobalMasterRow masterRow = fc.getMasterRow();
126 ler.setDataRow(masterRow.getGlobalView());
127
128 final ReportDataRow reportDataRow = masterRow.getReportDataRow();
129 if (reportDataRow == null)
130 {
131 ler.setData(EMPTY_REPORT_DATA);
132 ler.setCurrentRow(-1);
133 }
134 else
135 {
136 ler.setData(reportDataRow.getReportData());
137 ler.setCurrentRow(reportDataRow.getCursor());
138 }
139
140 ler.setDeclaringParent(node);
141 return ler;
142 }
143
144
145 public static FlowController processFlowOperations(FlowController fc,
146 final FlowControlOperation[] ops)
147 throws DataSourceException
148 {
149 for (int i = 0; i < ops.length; i++)
150 {
151 final FlowControlOperation op = ops[i];
152 fc = fc.performOperation(op);
153 }
154 return fc;
155 }
156
157
158 /**
159 * Checks, whether the current group should continue. If there is no group, we assume that we should continue. (This
160 * emulates the control-break-algorithm's default behaviour if testing an empty set of arguments.)
161 *
162 * @param fc the current flow controller holding the data
163 * @param node the current node.
164 * @return true, if the group is finished and we should stop reiterating it, false if the group is not finished and we
165 * can start iterating it again.
166 * @throws org.jfree.report.DataSourceException
167 *
168 */
169 public static boolean isGroupFinished(final FlowController fc,
170 final Node node)
171 throws DataSourceException
172 {
173 final Node nodeParent = node.getParent();
174 if (nodeParent == null)
175 {
176 return false;
177 }
178 Group group = nodeParent.getGroup();
179 if (group == null)
180 {
181 return false;
182 }
183
184 // maybe we can move this state into the layoutstate itself so that
185 // we do not have to rebuild that crap all the time.
186 LayoutExpressionRuntime ler = null;
187
188 // OK, now we are almost complete.
189 while (group != null)
190 {
191 if (ler == null)
192 {
193 ler = getExpressionRuntime(fc, node);
194 }
195
196 ler.setDeclaringParent(group);
197
198 final Expression groupingExpression = group.getGroupingExpression();
199 if (groupingExpression != null)
200 {
201 groupingExpression.setRuntime(ler);
202 final Object groupFinished;
203 try
204 {
205 groupFinished = groupingExpression.computeValue();
206 }
207 finally
208 {
209 groupingExpression.setRuntime(null);
210 }
211
212 if (Boolean.TRUE.equals(groupFinished))
213 {
214 // If the group expression returns true, we should pack our belongings
215 // and stop with that process. The group is finished.
216
217 // In Cobol, this would mean that one of the group-fields has changed.
218 return true;
219 }
220 }
221
222 final Node parent = group.getParent();
223 if (parent == null)
224 {
225 group = null;
226 }
227 else
228 {
229 group = parent.getGroup();
230 }
231 }
232 return false;
233 }
234
235
236 private static void mergeDeclarationRule(final CSSDeclarationRule target,
237 final CSSDeclarationRule source)
238 {
239 final Iterator it = source.getPropertyKeys();
240 while (it.hasNext())
241 {
242 final StyleKey key = (StyleKey) it.next();
243 final CSSValue value = source.getPropertyCSSValue(key);
244 final boolean sourceImportant = source.isImportant(key);
245 final boolean targetImportant = target.isImportant(key);
246 if (targetImportant)
247 {
248 continue;
249 }
250 target.setPropertyValue(key, value);
251 target.setImportant(key, sourceImportant);
252 }
253 }
254
255 private static CSSDeclarationRule processStyleAttribute
256 (final Object styleAttributeValue,
257 final Element node,
258 final ExpressionRuntime runtime,
259 CSSDeclarationRule targetRule)
260 throws DataSourceException
261 {
262 if (targetRule == null)
263 {
264 try
265 {
266 targetRule = (CSSDeclarationRule) node.getStyle().clone();
267 }
268 catch (CloneNotSupportedException e)
269 {
270 targetRule = new CSSStyleRule(null, null);
271 }
272 }
273
274
275 if (styleAttributeValue instanceof String)
276 {
277 // ugly, we have to parse that thing. Cant think of nothing
278 // worse than that.
279 final String styleText = (String) styleAttributeValue;
280 try
281 {
282 final ReportContext reportContext = runtime.getReportContext();
283 final ReportStructureRoot root = reportContext.getReportStructureRoot();
284 final ResourceKey baseResource = root.getBaseResource();
285 final ResourceManager resourceManager = root.getResourceManager();
286
287 final byte[] bytes = styleText.getBytes("UTF-8");
288 final ResourceKey key = resourceManager.createKey(bytes);
289 final Resource resource = resourceManager.create
290 (key, baseResource, StyleRule.class);
291
292 final CSSDeclarationRule parsedRule =
293 (CSSDeclarationRule) resource.getResource();
294 mergeDeclarationRule(targetRule, parsedRule);
295 }
296 catch (Exception e)
297 {
298 // ignore ..
299 e.printStackTrace();
300 }
301 }
302 else if (styleAttributeValue instanceof CSSStyleRule)
303 {
304 final CSSStyleRule styleRule =
305 (CSSStyleRule) styleAttributeValue;
306 mergeDeclarationRule(targetRule, styleRule);
307 }
308
309 // ok, not lets fill in the stuff from the style expressions ..
310 final Map styleExpressions = node.getStyleExpressions();
311 final Iterator styleExIt = styleExpressions.entrySet().iterator();
312
313 while (styleExIt.hasNext())
314 {
315 final Map.Entry entry = (Map.Entry) styleExIt.next();
316 final String name = (String) entry.getKey();
317 final Expression expression = (Expression) entry.getValue();
318 try
319 {
320 expression.setRuntime(runtime);
321 final Object value = expression.computeValue();
322 if (value instanceof CSSValue)
323 {
324 final CSSValue cssvalue = (CSSValue) value;
325 final StyleKey keyByName =
326 StyleKeyRegistry.getRegistry().findKeyByName(name);
327 if (keyByName != null)
328 {
329 targetRule.setPropertyValue(keyByName, cssvalue);
330 }
331 else
332 {
333 targetRule.setPropertyValueAsString(name, cssvalue.getCSSText());
334 }
335 }
336 else if (value != null)
337 {
338 targetRule.setPropertyValueAsString(name, String.valueOf(value));
339 }
340 }
341 finally
342 {
343 expression.setRuntime(null);
344 }
345 }
346 return targetRule;
347 }
348
349 private static AttributeMap collectAttributes(final Element node,
350 final ExpressionRuntime runtime)
351 throws DataSourceException
352 {
353 final AttributeMap attributes = node.getAttributeMap();
354 final AttributeMap attributeExpressions = node.getAttributeExpressionMap();
355 final String[] namespaces = attributeExpressions.getNameSpaces();
356 for (int i = 0; i < namespaces.length; i++)
357 {
358 final String namespace = namespaces[i];
359 final Map attrEx = attributeExpressions.getAttributes(namespace);
360
361 final Iterator attributeExIt = attrEx.entrySet().iterator();
362 while (attributeExIt.hasNext())
363 {
364 final Map.Entry entry = (Map.Entry) attributeExIt.next();
365 final String name = (String) entry.getKey();
366 final Expression expression = (Expression) entry.getValue();
367 try
368 {
369 expression.setRuntime(runtime);
370 final Object value = expression.computeValue();
371 attributes.setAttribute(namespace, name, value);
372 }
373 finally
374 {
375 expression.setRuntime(null);
376 }
377 }
378 }
379 return attributes;
380 }
381
382 public static AttributeMap processAttributes(final Element node,
383 final ReportTarget target,
384 final ExpressionRuntime runtime)
385 throws DataSourceException
386 {
387 final AttributeMap attributes = collectAttributes(node, runtime);
388 CSSDeclarationRule rule = null;
389
390 final AttributeMap retval = new AttributeMap();
391
392 final String[] attrNamespaces = attributes.getNameSpaces();
393 for (int i = 0; i < attrNamespaces.length; i++)
394 {
395 final String namespace = attrNamespaces[i];
396 final Map attributeMap = attributes.getAttributes(namespace);
397 if (attributeMap == null)
398 {
399 continue;
400 }
401
402 final NamespaceDefinition nsDef = target.getNamespaceByUri(namespace);
403 final Iterator attributeIt = attributeMap.entrySet().iterator();
404 while (attributeIt.hasNext())
405 {
406 final Map.Entry entry = (Map.Entry) attributeIt.next();
407 final String key = (String) entry.getKey();
408 if (isStyleAttribute(nsDef, node.getType(), key))
409 {
410 final Object styleAttributeValue = entry.getValue();
411 rule = processStyleAttribute(styleAttributeValue, node, runtime,
412 rule);
413 }
414 else
415 {
416 retval.setAttribute(namespace, key, entry.getValue());
417 }
418 }
419 }
420
421 // Just in case there was no style-attribute but there are style-expressions
422 if (rule == null)
423 {
424 rule = processStyleAttribute(null, node, runtime, rule);
425 }
426
427 if (rule != null && rule.getSize() > 0)
428 {
429 retval.setAttribute(Namespaces.LIBLAYOUT_NAMESPACE, "style", rule);
430 }
431
432 return retval;
433 }
434
435 private static boolean isStyleAttribute(final NamespaceDefinition def,
436 final String elementName,
437 final String attrName)
438 {
439 if (def == null)
440 {
441 return false;
442 }
443
444 final String[] styleAttr = def.getStyleAttribute(elementName);
445 for (int i = 0; i < styleAttr.length; i++)
446 {
447 final String styleAttrib = styleAttr[i];
448 if (attrName.equals(styleAttrib))
449 {
450 return true;
451 }
452 }
453 return false;
454 }
455
456 public static AttributeMap createEmptyMap(final String namespace,
457 final String tagName)
458 {
459 final AttributeMap map = new AttributeMap();
460 map.setAttribute(JFreeReportInfo.REPORT_NAMESPACE,
461 Element.NAMESPACE_ATTRIBUTE, namespace);
462 map.setAttribute(JFreeReportInfo.REPORT_NAMESPACE,
463 Element.TYPE_ATTRIBUTE, tagName);
464 return map;
465 }
466
467
468 public static Object performPrecompute(final int expressionPosition,
469 final PrecomputeNodeKey nodeKey,
470 final LayoutController layoutController,
471 final FlowController flowController)
472 throws ReportProcessingException, ReportDataFactoryException,
473 DataSourceException
474 {
475 final FlowController fc = flowController.createPrecomputeInstance();
476 final PrecomputedValueRegistry pcvr = fc.getPrecomputedValueRegistry();
477
478 pcvr.startElementPrecomputation(nodeKey);
479 //final PrecomputeNode startNode = pcvr.currentNode();
480 final LayoutController rootLc = layoutController.createPrecomputeInstance(fc);
481 final LayoutController rootParent = rootLc.getParent();
482 final ReportTarget target = new EmptyReportTarget(fc.getReportJob(), fc.getExportDescriptor());
483
484 LayoutController lc = rootLc;
485 while (lc.isAdvanceable())
486 {
487 lc = lc.advance(target);
488 while (lc.isAdvanceable() == false && lc.getParent() != null)
489 {
490 final LayoutController parent = lc.getParent();
491 lc = parent.join(lc.getFlowController());
492
493 if (parent == rootParent)
494 {
495 target.commit();
496 final PrecomputeNode precomputeNode = pcvr.currentNode();
497 final Object functionResult = precomputeNode.getFunctionResult(expressionPosition);
498 pcvr.finishElementPrecomputation(nodeKey);
499 return functionResult;
500 }
501 }
502 }
503
504 throw new IllegalStateException
505 ("Ups - we did not get to the root parent again. This is awful and we cannot continue.");
506 }
507
508
509 public static LayoutController skipInvisibleElement(final LayoutController layoutController)
510 throws ReportProcessingException, ReportDataFactoryException, DataSourceException
511 {
512 final FlowController fc = layoutController.getFlowController();
513 final ReportTarget target = new EmptyReportTarget(fc.getReportJob(), fc.getExportDescriptor());
514 final LayoutController rootParent = layoutController.getParent();
515
516 // Now start to iterate until the derived layout controller 'lc' that has this given parent
517 // wants to join.
518 LayoutController lc = layoutController;
519 while (lc.isAdvanceable())
520 {
521 lc = lc.advance(target);
522 while (lc.isAdvanceable() == false && lc.getParent() != null)
523 {
524 final LayoutController parent = lc.getParent();
525 lc = parent.join(lc.getFlowController());
526 if (parent == rootParent)
527 {
528 target.commit();
529 return lc;
530 }
531 }
532 }
533 target.commit();
534 throw new IllegalStateException
535 ("Ups - we did not get to the root parent again. This is awful and we cannot continue.");
536 // return lc;
537 }
538
539 public static Object evaluateExpression(final FlowController flowController,
540 final Object declaringParent,
541 final Expression expression)
542 throws DataSourceException
543 {
544 final ExpressionRuntime runtime =
545 getExpressionRuntime(flowController, declaringParent);
546
547 try
548 {
549 expression.setRuntime(runtime);
550 return expression.computeValue();
551 }
552 catch (DataSourceException dse)
553 {
554 throw dse;
555 }
556 catch (Exception e)
557 {
558 throw new DataSourceException("Failed to evaluate expression", e);
559 }
560 finally
561 {
562 expression.setRuntime(null);
563 }
564 }
565 }