001: /**
002: * ===========================================
003: * JFreeReport : a free Java reporting library
004: * ===========================================
005: *
006: * Project Info: http://reporting.pentaho.org/
007: *
008: * (C) Copyright 2001-2007, by Object Refinery Ltd, 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: * Element.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report;
030:
031: import java.awt.geom.Dimension2D;
032: import java.util.Collections;
033: import java.util.HashMap;
034: import java.util.Map;
035:
036: import org.jfree.report.filter.DataSource;
037: import org.jfree.report.filter.DataTarget;
038: import org.jfree.report.filter.EmptyDataSource;
039: import org.jfree.report.function.Expression;
040: import org.jfree.report.function.ExpressionRuntime;
041: import org.jfree.report.style.ElementDefaultStyleSheet;
042: import org.jfree.report.style.ElementStyleKeys;
043: import org.jfree.report.style.ElementStyleSheet;
044: import org.jfree.report.style.StyleKey;
045: import org.jfree.report.style.StyleSheetCarrier;
046: import org.jfree.report.util.InstanceID;
047:
048: /**
049: * Base class for all report elements (displays items that can appear within a report band).
050: * <p/>
051: * Elements can either be Bands (which are container classes that group elements) or content-elements. Content elements
052: * produce printable values when the {@link org.jfree.report.Element#getValue(org.jfree.report.function.ExpressionRuntime)}
053: * method is called.
054: * <p/>
055: * All elements have a non-null name and have a private style sheet defined. The style sheet is used to store and access
056: * all element properties that can be used to control the layout of the element or affect the elements appeareance in
057: * the generated content.
058: * <p/>
059: * Elements can inherit all style information from its parent. A style value is inherited whenever the element's
060: * stylesheet does not define an own value for the corresponding key. Some style-keys cannot be inherited at all, in
061: * that case the default-style-sheets value is used as fall-back value. In addition to the bands stylesheet, elements
062: * may also inherit from stylesheets assigned to the current report definition's StyleSheetCollection. Foreign
063: * stylesheet will be lost after the local cloning is complete.
064: * <p/>
065: * Use the following code to create and assign a global stylesheet to an element:
066: * <pre>
067: * JFreeReport report = .. // created elsewhere
068: * ElementStyleSheet globalStyle =
069: * report.getStyleSheetCollection().createStyleSheet ("a name for the global
070: * style");
071: * <p/>
072: * Element element = .. // created elsewhere
073: * element.getStyleSheet().addParent(globalStyle);
074: * report.getItemBand().addElement (element);
075: * </pre>
076: * <p/>
077: * Global stylesheets will always be queried before the parent's stylesheet gets queried. The order of the add-operation
078: * does matter, StyleSheets which are added last will be preferred over previously added stylesheets.
079: *
080: * @author David Gilbert
081: * @author Thomas Morgner
082: * @noinspection ClassReferencesSubclass
083: */
084: public class Element implements DataTarget {
085: /**
086: * A empty unmodifiable map stored here for performance reasons.
087: */
088: private static final Map EMPTY_MAP = Collections
089: .unmodifiableMap(new HashMap());
090:
091: /**
092: * A helper class to preserve a recoverable reference to the elements stylesheet.
093: */
094: private static class InternalElementStyleSheetCarrier implements
095: StyleSheetCarrier {
096: /**
097: * An inherited stylesheet of the element.
098: */
099: private transient ElementStyleSheet styleSheet;
100: /**
101: * The private stylesheet of the element.
102: */
103: private InternalElementStyleSheet self;
104: /**
105: * The stylesheet id of the inherited stylesheet.
106: */
107: private InstanceID styleSheetID;
108:
109: /**
110: * Creates a new stylesheet carrier for the given internal stylesheet and the given inherited stylesheet.
111: *
112: * @param parent the internal stylesheet
113: * @param styleSheet the stylesheet
114: */
115: protected InternalElementStyleSheetCarrier(
116: final InternalElementStyleSheet parent,
117: final ElementStyleSheet styleSheet) {
118: if (parent == null) {
119: throw new NullPointerException(
120: "Internal stylesheet must not be null.");
121: }
122: if (styleSheet == null) {
123: throw new NullPointerException(
124: "Inherited stylesheet must not be null.");
125: }
126: this .self = parent;
127: this .styleSheet = styleSheet;
128: this .styleSheetID = styleSheet.getId();
129: }
130:
131: /**
132: * An internal helper method that gets called to update the reference to the element's internal stylesheet.
133: *
134: * @param self the new reference to the element's stylesheet
135: * @throws NullPointerException if the given self reference is null
136: */
137: protected void updateParentReference(
138: final InternalElementStyleSheet self) {
139: if (self == null) {
140: throw new NullPointerException(
141: "Invalid implementation: Self reference cannot be null after cloning.");
142: }
143: this .self = self;
144: }
145:
146: /**
147: * Clones this reference. During cloning the stylesheet is removed. The stylesheets ID is preserved to allow to
148: * recover the stylesheet later.
149: *
150: * @return the clone.
151: * @throws CloneNotSupportedException if cloning failed for some reason.
152: */
153: public Object clone() throws CloneNotSupportedException {
154: final InternalElementStyleSheetCarrier ic = (InternalElementStyleSheetCarrier) super
155: .clone();
156: ic.self = null;
157: ic.styleSheet = null;
158: return ic;
159: }
160:
161: /**
162: * Returns the referenced stylesheet (and recovers the stylesheet if necessary).
163: *
164: * @return the stylesheet
165: * @throws IllegalStateException if the stylesheet could not be recovered.
166: */
167: public ElementStyleSheet getStyleSheet() {
168: if (styleSheet != null) {
169: return styleSheet;
170: }
171: if (self == null) {
172: // should not happen in a sane environment ..
173: throw new IllegalStateException(
174: "Stylesheet was not valid after restore operation.");
175: }
176: final Element element = self.getElement();
177: if (element == null) {
178: throw new IllegalStateException();
179: }
180: final ReportDefinition reportDefinition = element
181: .getReportDefinition();
182: if (reportDefinition == null) {
183: // should not happen in a sane environment ..
184: throw new IllegalStateException(
185: "Stylesheet was not valid after restore operation.");
186: }
187: styleSheet = reportDefinition.getStyleSheetCollection()
188: .getStyleSheetByID(styleSheetID);
189: return styleSheet;
190: }
191:
192: /**
193: * Invalidates the stylesheet reference. Recovery is started on the next call to <code>getStylesheet()</code>
194: */
195: public void invalidate() {
196: this .styleSheet = null;
197: }
198:
199: /**
200: * Checks, whether the given stylesheet is the same as the referenced stylesheet in this object.
201: *
202: * @param style the stylesheet
203: * @return true, if both stylesheets share the same instance ID, false otherwise.
204: */
205: public boolean isSame(final ElementStyleSheet style) {
206: return style.getId().equals(styleSheetID);
207: }
208: }
209:
210: /**
211: * An private implementation of a stylesheet.
212: * <p/>
213: * Using that stylesheet outside the element class will not work, cloning an element's private stylesheet without
214: * cloning the element will produce <code>IllegalStateException</code>s later.
215: */
216: private static class InternalElementStyleSheet extends
217: ElementStyleSheet {
218: /**
219: * The element that contains this stylesheet.
220: */
221: private Element element;
222: /**
223: * The parent of the element.
224: */
225: private Band parent;
226:
227: /**
228: * Creates a new internal stylesheet for the given element.
229: *
230: * @param element the element
231: * @throws NullPointerException if the element given is null.
232: */
233: protected InternalElementStyleSheet(final Element element) {
234: super (element.getName());
235: this .parent = element.getParent();
236: this .element = element;
237: setGlobalDefaultStyleSheet(element
238: .createGlobalDefaultStyle());
239: setAllowCaching(true);
240: }
241:
242: /**
243: * Returns the element for this stylesheet.
244: *
245: * @return the element.
246: */
247: public Element getElement() {
248: return element;
249: }
250:
251: /**
252: * Creates and returns a copy of this object. After the cloning, the new StyleSheet is no longer registered with its
253: * parents.
254: *
255: * @return a clone of this instance.
256: * @throws CloneNotSupportedException if cloning the element failed.
257: * @see Cloneable
258: */
259: public Object clone() throws CloneNotSupportedException {
260: final InternalElementStyleSheet es = (InternalElementStyleSheet) super
261: .clone();
262: es.parent = null;
263: es.element = null;
264:
265: final StyleSheetCarrier[] sheets = es.getParentReferences();
266: final int length = sheets.length;
267: for (int i = 0; i < length; i++) {
268: final InternalElementStyleSheetCarrier esc = (InternalElementStyleSheetCarrier) sheets[i];
269: esc.updateParentReference(es);
270: }
271: return es;
272: }
273:
274: /**
275: * A callback method used by the element to inform that the element's parent changed.
276: */
277: public void parentChanged() {
278: if (parent != null) {
279: setCascadeStyleSheet(null);
280: }
281: this .parent = element.getParent();
282: if (parent != null) {
283: setCascadeStyleSheet(parent.getStyle());
284: }
285: }
286:
287: /**
288: * Updates the reference to the element after the cloning.
289: *
290: * @param e the element that contains this stylesheet.
291: */
292: protected void updateElementReference(final Element e) {
293: if (e == null) {
294: throw new NullPointerException(
295: "Invalid implementation: Self reference cannot be null after cloning.");
296: }
297: this .element = e;
298: }
299:
300: /**
301: * Creates a stylesheet carrier to reference inherited stylesheets in a secure way.
302: *
303: * @param styleSheet the stylesheet for which the carrier should be created.
304: * @return the stylesheet carrier.
305: */
306: protected StyleSheetCarrier createCarrier(
307: final ElementStyleSheet styleSheet) {
308: return new InternalElementStyleSheetCarrier(this ,
309: styleSheet);
310: }
311: }
312:
313: /**
314: * The internal constant to mark anonymous element names.
315: */
316: public static final String ANONYMOUS_ELEMENT_PREFIX = "anonymousElement@";
317:
318: /**
319: * A null datasource. This class is immutable and shared across all elements.
320: */
321: private static final DataSource NULL_DATASOURCE = new EmptyDataSource();
322:
323: /**
324: * The head of the data source chain.
325: */
326: private DataSource datasource;
327:
328: /**
329: * The name of the element.
330: */
331: private String name;
332:
333: /**
334: * The stylesheet defines global appearance for elements.
335: */
336: private InternalElementStyleSheet style;
337:
338: /**
339: * the parent for the element (the band where the element is contained in).
340: */
341: private Band parent;
342:
343: /**
344: * The tree lock to identify the element. This object is shared among all clones and can be used to identify elements
345: * with the same anchestor.
346: */
347: private final InstanceID treeLock;
348:
349: /**
350: * The assigned report definition for this element.
351: */
352: private ReportDefinition reportDefinition;
353:
354: /**
355: * The map of style-expressions keyed by the style-key.
356: */
357: private HashMap styleExpressions;
358:
359: /**
360: * Constructs an element.
361: * <p/>
362: * The element inherits the element's defined default ElementStyleSheet to provide reasonable default values for
363: * common stylekeys. When the element is added to the band, the bands stylesheet is set as parent to the element's
364: * stylesheet.
365: * <p/>
366: * A datasource is assigned with this element is set to a default source, which always returns null.
367: */
368: public Element() {
369: setName(Element.ANONYMOUS_ELEMENT_PREFIX
370: + System.identityHashCode(this ));
371: treeLock = new InstanceID();
372: datasource = Element.NULL_DATASOURCE;
373: style = new InternalElementStyleSheet(this );
374: }
375:
376: /**
377: * Return the parent of the Element. You can use this to explore the component tree.
378: *
379: * @return the parent of the self.
380: */
381: public final Band getParent() {
382: return parent;
383: }
384:
385: /**
386: * Defines the parent of the Element. Only a band should call this method.
387: *
388: * @param parent (null allowed).
389: */
390: protected final void setParent(final Band parent) {
391: this .parent = parent;
392: if (this .parent == null) {
393: setReportDefinition(null);
394: } else {
395: setReportDefinition(parent.getReportDefinition());
396: }
397: this .style.parentChanged();
398: }
399:
400: /**
401: * Defines the name for this Element. The name must not be empty, or a NullPointerException is thrown.
402: * <p/>
403: * Names can be used to lookup an element within a band. There is no requirement for element names to be unique.
404: *
405: * @param name the name of this self (null not permitted)
406: * @throws NullPointerException if the given name is null.
407: */
408: public void setName(final String name) {
409: if (name == null) {
410: throw new NullPointerException(
411: "Element.setName(...): name is null.");
412: }
413: this .name = name;
414: }
415:
416: /**
417: * Returns the name of the Element. The name of the Element is never null.
418: *
419: * @return the name.
420: */
421: public String getName() {
422: return this .name;
423: }
424:
425: /**
426: * Returns the datasource for this Element. You cannot override this function as the Element needs always to be the
427: * last consumer in the chain of filters. This function must never return null.
428: *
429: * @return the assigned datasource.
430: */
431: public final DataSource getDataSource() {
432: return datasource;
433: }
434:
435: /**
436: * Sets the data source for this Element. The data source is used to produce or query the element's display value.
437: *
438: * @param ds the datasource (<code>null</code> not permitted).
439: * @throws NullPointerException if the given data source is null.
440: */
441: public void setDataSource(final DataSource ds) {
442: if (ds == null) {
443: throw new NullPointerException(
444: "Element.setDataSource(...) : null data source.");
445: }
446: this .datasource = ds;
447: }
448:
449: /**
450: * Queries this Element's datasource for a value.
451: *
452: * @param runtime the expression runtime for evaluating inline expression.
453: * @return the value of the datasource, which can be null.
454: */
455: public Object getValue(final ExpressionRuntime runtime) {
456: final DataSource ds = getDataSource();
457: return ds.getValue(runtime);
458: }
459:
460: /**
461: * Defines whether this Element should be painted. The detailed implementation is up to the outputtarget.
462: *
463: * @return the current visiblity state.
464: */
465: public boolean isVisible() {
466: final Boolean b = (Boolean) getStyle().getStyleProperty(
467: ElementStyleKeys.VISIBLE, Boolean.FALSE);
468: return b.booleanValue();
469: }
470:
471: /**
472: * Defines, whether this Element should be visible in the output. The interpretation of this flag is up to the content
473: * processor.
474: *
475: * @param b the new visibility state
476: */
477: public void setVisible(final boolean b) {
478: if (b) {
479: getStyle().setStyleProperty(ElementStyleKeys.VISIBLE,
480: Boolean.TRUE);
481: } else {
482: getStyle().setStyleProperty(ElementStyleKeys.VISIBLE,
483: Boolean.FALSE);
484: }
485: }
486:
487: /**
488: * Clones this Element, the datasource and the private stylesheet of this Element. The clone does no longer have a
489: * parent, as the old parent would not recognize that new object anymore.
490: *
491: * @return a clone of this Element.
492: * @throws CloneNotSupportedException should never happen.
493: */
494: public Object clone() throws CloneNotSupportedException {
495: final Element e = (Element) super .clone();
496: e.style = (InternalElementStyleSheet) style.getCopy();
497: e.datasource = (DataSource) datasource.clone();
498: e.parent = null;
499: e.style.updateElementReference(e);
500: e.reportDefinition = null;
501: if (styleExpressions != null) {
502: e.styleExpressions = (HashMap) styleExpressions.clone();
503: }
504: return e;
505: }
506:
507: /**
508: * Returns this elements private stylesheet. This sheet can be used to override the default values set in one of the
509: * parent-stylesheets.
510: *
511: * @return the Element's stylesheet
512: */
513: public ElementStyleSheet getStyle() {
514: return style;
515: }
516:
517: /**
518: * Defines the content-type for this Element. The content-type is used as a hint, how to process the contents of this
519: * Element. An Element implementation should restrict itself to the content-type set here, or the report processing
520: * may fail or the Element may not be printed.
521: * <p/>
522: * An Element is not allowed to change its content-type after the report processing has started.
523: * <p/>
524: * If an content-type is unknown to the output-target, the processor must ignore the content.
525: *
526: * @return the content-type as string.
527: * @deprecated we no longer use the content-type.
528: */
529: public String getContentType() {
530: return "object";
531: }
532:
533: /**
534: * Returns the tree lock object for the self tree. If the element is part of a content hierarchy, the parent's tree
535: * lock is returned.
536: *
537: * @return the treelock object.
538: */
539: public final Object getTreeLock() {
540: final Band parent = getParent();
541: if (parent != null) {
542: return parent.getTreeLock();
543: }
544: return treeLock;
545: }
546:
547: /**
548: * Returns a unique identifier for the given instance. The identifier can be used to recognize cloned instance which
549: * have the same anchestor. The identifier is unique as long as the element remains in the JVM, it does not guarantee
550: * uniqueness or the ability to recognize clones, after the element has been serialized.
551: *
552: * @return the object identifier.
553: */
554: public final Object getObjectID() {
555: return treeLock;
556: }
557:
558: /**
559: * Checks, whether the layout of this element is dynamic and adjusts to the element's printable content. If set to
560: * false, the element's minimum-size will be also used as maximum size.
561: *
562: * @return true, if the Element's layout is dynamic, false otherwise.
563: */
564: public boolean isDynamicContent() {
565: return getStyle().getBooleanStyleProperty(
566: ElementStyleKeys.DYNAMIC_HEIGHT);
567: }
568:
569: /**
570: * Defines the stylesheet property for the dynamic attribute. Calling this function with either parameter will
571: * override any previously defined value for the dynamic attribute. The value can no longer be inherited from parent
572: * stylesheets.
573: *
574: * @param dynamicContent the new state of the dynamic flag.
575: */
576: public void setDynamicContent(final boolean dynamicContent) {
577: getStyle().setBooleanStyleProperty(
578: ElementStyleKeys.DYNAMIC_HEIGHT, dynamicContent);
579: }
580:
581: /**
582: * Returns whether the layout of this Element is cacheable. No matter what's defined here, dynamic elements cannot be
583: * cachable at all.
584: *
585: * @return returns always false, as this property is no longer used.
586: * @deprecated this property is no longer used.
587: */
588: public boolean isLayoutCacheable() {
589: return false;
590: }
591:
592: /**
593: * Defines whether the layout of this Element can be cached.
594: * <p/>
595: * This method has no effect.
596: *
597: * @param layoutCacheable true, if the layout is cacheable, false otherwise.
598: * @deprecated this property is no longer used.
599: */
600: public void setLayoutCacheable(final boolean layoutCacheable) {
601: }
602:
603: /**
604: * Returns the minimum size of this Element, if defined.
605: *
606: * @return the minimum size
607: */
608: public Dimension2D getMinimumSize() {
609: return (Dimension2D) getStyle().getStyleProperty(
610: ElementStyleKeys.MINIMUMSIZE);
611: }
612:
613: /**
614: * Defines the stylesheet property for the minimum Element size.
615: *
616: * @param minimumSize the new minimum size or null, if the value should be inherited.
617: */
618: public void setMinimumSize(final Dimension2D minimumSize) {
619: getStyle().setStyleProperty(ElementStyleKeys.MINIMUMSIZE,
620: minimumSize);
621: }
622:
623: /**
624: * Returns the maximum size of this Element, if defined.
625: *
626: * @return the maximum size
627: */
628: public Dimension2D getMaximumSize() {
629: return (Dimension2D) getStyle().getStyleProperty(
630: ElementStyleKeys.MAXIMUMSIZE);
631: }
632:
633: /**
634: * Defines the stylesheet property for the maximum Element size.
635: *
636: * @param maximumSize the new maximum size or null, if the value should be inherited.
637: */
638: public void setMaximumSize(final Dimension2D maximumSize) {
639: getStyle().setStyleProperty(ElementStyleKeys.MAXIMUMSIZE,
640: maximumSize);
641: }
642:
643: /**
644: * Returns the preferred size of this Element, if defined.
645: *
646: * @return the preferred size
647: */
648: public Dimension2D getPreferredSize() {
649: return (Dimension2D) getStyle().getStyleProperty(
650: ElementStyleKeys.PREFERREDSIZE);
651: }
652:
653: /**
654: * Defines the stylesheet property for the preferred Element size.
655: *
656: * @param preferredSize the new preferred size or null, if the value is not defined.
657: */
658: public void setPreferredSize(final Dimension2D preferredSize) {
659: getStyle().setStyleProperty(ElementStyleKeys.PREFERREDSIZE,
660: preferredSize);
661: }
662:
663: /**
664: * Assigns the given report definition to the Element. If the reportdefinition is null, the Element is not part of a
665: * report definition at all.
666: *
667: * @param reportDefinition the report definition (maybe null).
668: */
669: protected void setReportDefinition(
670: final ReportDefinition reportDefinition) {
671: this .reportDefinition = reportDefinition;
672: }
673:
674: /**
675: * Returns the currently assigned report definition.
676: *
677: * @return the report definition or null, if no report has been assigned.
678: */
679: public ReportDefinition getReportDefinition() {
680: return reportDefinition;
681: }
682:
683: /**
684: * Redefines the link target for this element.
685: *
686: * @param target the target
687: */
688: public void setHRefTarget(final String target) {
689: getStyle().setStyleProperty(ElementStyleKeys.HREF_TARGET,
690: target);
691: }
692:
693: /**
694: * Returns the currently set link target for this element.
695: *
696: * @return the link target.
697: */
698: public String getHRefTarget() {
699: return (String) getStyle().getStyleProperty(
700: ElementStyleKeys.HREF_TARGET);
701: }
702:
703: /**
704: * Creates the global stylesheet for this element type. The global stylesheet is an immutable stylesheet that provides
705: * reasonable default values for some of the style keys.
706: * <p/>
707: * The global default stylesheet is always the last stylesheet that will be queried for values.
708: *
709: * @return the global stylesheet.
710: */
711: protected ElementDefaultStyleSheet createGlobalDefaultStyle() {
712: return ElementDefaultStyleSheet.getDefaultStyle();
713: }
714:
715: /**
716: * Adds a function to the report's collection of expressions.
717: *
718: * @param property the stylekey that will be modified by this element.
719: * @param function the function.
720: */
721: public void setStyleExpression(final StyleKey property,
722: final Expression function) {
723: if (styleExpressions == null) {
724: if (function == null) {
725: return;
726: }
727:
728: styleExpressions = new HashMap();
729: }
730:
731: if (function == null) {
732: styleExpressions.remove(property);
733: } else {
734: styleExpressions.put(property, function);
735: }
736: }
737:
738: /**
739: * Returns the expressions for the report.
740: *
741: * @param property the stylekey for which an style-expression is returned.
742: * @return the expressions.
743: */
744: public Expression getStyleExpression(final StyleKey property) {
745: if (styleExpressions == null) {
746: return null;
747: }
748: return (Expression) styleExpressions.get(property);
749: }
750:
751: /**
752: * Returns a map of all style expressions attached to this element. The map is keyed by an StyleKey and contains
753: * Expression instances.
754: *
755: * @return the expression.
756: */
757: public Map getStyleExpressions() {
758: if (styleExpressions != null) {
759: return styleExpressions;
760: }
761: return Element.EMPTY_MAP;
762: }
763:
764: }
|