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 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.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    
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        }
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    //    throw new IllegalStateException
501    //        ("Ups - we did not get to the root parent again. This is awful and we cannot continue.");
502      }
503    
504    
505      public static LayoutController skipInvisibleElement(final LayoutController layoutController)
506          throws ReportProcessingException, ReportDataFactoryException, DataSourceException
507      {
508        final FlowController fc = layoutController.getFlowController();
509        final ReportTarget target = new EmptyReportTarget(fc.getReportJob(), fc.getExportDescriptor());
510        final LayoutController rootParent = layoutController.getParent();
511    
512        // Now start to iterate until the derived layout controller 'lc' that has this given parent
513        // wants to join.
514        LayoutController lc = layoutController;
515        while (lc.isAdvanceable())
516        {
517          lc = lc.advance(target);
518          while (lc.isAdvanceable() == false && lc.getParent() != null)
519          {
520            final LayoutController parent = lc.getParent();
521            lc = parent.join(lc.getFlowController());
522            if (parent == rootParent)
523            {
524              target.commit();
525              return lc;
526            }
527          }
528        }
529        target.commit();
530        throw new IllegalStateException
531            ("Ups - we did not get to the root parent again. This is awful and we cannot continue.");
532    //    return lc;
533      }
534    
535      public static Object evaluateExpression(final FlowController flowController,
536                                              final Object declaringParent,
537                                              final Expression expression)
538          throws DataSourceException
539      {
540        final ExpressionRuntime runtime =
541            getExpressionRuntime(flowController, declaringParent);
542    
543        try
544        {
545          expression.setRuntime(runtime);
546          return expression.computeValue();
547        }
548        catch (DataSourceException dse)
549        {
550          throw dse;
551        }
552        catch (Exception e)
553        {
554          throw new DataSourceException("Failed to evaluate expression", e);
555        }
556        finally
557        {
558          expression.setRuntime(null);
559        }
560      }
561    }