001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: PropertyMaker.java 495371 2007-01-11 21:03:07Z adelmelle $ */
019:
020: package org.apache.fop.fo.properties;
021:
022: import java.util.HashMap;
023: import java.util.Map;
024:
025: import org.apache.commons.logging.Log;
026: import org.apache.commons.logging.LogFactory;
027:
028: import org.apache.fop.datatypes.CompoundDatatype;
029: import org.apache.fop.datatypes.LengthBase;
030: import org.apache.fop.datatypes.PercentBase;
031: import org.apache.fop.fo.Constants;
032: import org.apache.fop.fo.FOPropertyMapping;
033: import org.apache.fop.fo.FObj;
034: import org.apache.fop.fo.PropertyList;
035: import org.apache.fop.fo.expr.PropertyException;
036: import org.apache.fop.fo.expr.PropertyInfo;
037: import org.apache.fop.fo.expr.PropertyParser;
038:
039: /**
040: * Base class for all property makers
041: */
042: public class PropertyMaker implements Cloneable {
043:
044: /** Logger instance */
045: private static Log log = LogFactory.getLog(PropertyMaker.class);
046:
047: /** the property ID */
048: protected int propId;
049: private boolean inherited = true;
050: private Map enums = null;
051: private Map keywords = null;
052: /** the default value for the maker */
053: protected String defaultValue = null;
054: /** Indicates whether the property is context-dependant and therefore can't be cached. */
055: protected boolean contextDep = false;
056: /** Indicates whether the property is set through a shorthand. */
057: protected boolean setByShorthand = false;
058: private int percentBase = -1;
059: private PropertyMaker[] shorthands = null;
060: private ShorthandParser datatypeParser;
061:
062: /** default property **/
063: protected Property defaultProperty;
064: /** Maker for 'corresponding' properties **/
065: protected CorrespondingPropertyMaker corresponding;
066:
067: /**
068: * @return the name of the property for this Maker
069: */
070: public int getPropId() {
071: return propId;
072: }
073:
074: /**
075: * Construct an instance of a Property.Maker for the given property.
076: * @param propId The Constant ID of the property to be made.
077: */
078: public PropertyMaker(int propId) {
079: this .propId = propId;
080: }
081:
082: /**
083: * Copy all the values from the generic maker to this maker.
084: * @param generic a generic property maker.
085: */
086: public void useGeneric(PropertyMaker generic) {
087: contextDep = generic.contextDep;
088: inherited = generic.inherited;
089: defaultValue = generic.defaultValue;
090: percentBase = generic.percentBase;
091: if (generic.shorthands != null) {
092: shorthands = new PropertyMaker[generic.shorthands.length];
093: System.arraycopy(generic.shorthands, 0, shorthands, 0,
094: shorthands.length);
095: }
096: if (generic.enums != null) {
097: enums = new HashMap(generic.enums);
098: }
099: if (generic.keywords != null) {
100: keywords = new HashMap(generic.keywords);
101: }
102: }
103:
104: /**
105: * Set the inherited flag.
106: * @param inherited true if this is an inherited property
107: */
108: public void setInherited(boolean inherited) {
109: this .inherited = inherited;
110: }
111:
112: /**
113: * Add a keyword-equiv to the maker.
114: * @param keyword the keyword
115: * @param value the value to be used when the keyword is specified
116: */
117: public void addKeyword(String keyword, String value) {
118: if (keywords == null) {
119: keywords = new HashMap();
120: }
121: keywords.put(keyword, value);
122: }
123:
124: /**
125: * Add a enum constant.
126: * @param constant the enum constant
127: * @param value the Property value to use when the constant is specified
128: */
129: public void addEnum(String constant, Property value) {
130: if (enums == null) {
131: enums = new HashMap();
132: }
133: enums.put(constant, value);
134: }
135:
136: /**
137: * Add a subproperty to this maker.
138: * @param subproperty the PropertyMaker for the subproperty
139: */
140: public void addSubpropMaker(PropertyMaker subproperty) {
141: throw new RuntimeException("Unable to add subproperties "
142: + getClass());
143: }
144:
145: /**
146: * Return a subproperty maker for the subpropertyId.
147: * @param subpropertyId The subpropertyId of the maker.
148: * @return The subproperty maker.
149: */
150: public PropertyMaker getSubpropMaker(int subpropertyId) {
151: throw new RuntimeException("Unable to add subproperties");
152: }
153:
154: /**
155: * Add a shorthand to this maker. Only an Integer is added to the
156: * shorthands list. Later the Integers are replaced with references
157: * to the actual shorthand property makers.
158: * @param shorthand a property maker thar is that is checked for
159: * shorthand values.
160: */
161: public void addShorthand(PropertyMaker shorthand) {
162: if (shorthands == null) {
163: shorthands = new PropertyMaker[3];
164: }
165: for (int i = 0; i < shorthands.length; i++) {
166: if (shorthands[i] == null) {
167: shorthands[i] = shorthand;
168: break;
169: }
170: }
171: }
172:
173: /**
174: * Set the shorthand datatype parser.
175: * @param parser the shorthand parser
176: */
177: public void setDatatypeParser(ShorthandParser parser) {
178: datatypeParser = parser;
179: }
180:
181: /**
182: * Set the default value for this maker.
183: * @param defaultValue the default value.
184: */
185: public void setDefault(String defaultValue) {
186: this .defaultValue = defaultValue;
187: }
188:
189: /**
190: * Set the default value for this maker.
191: * @param defaultValue the default value
192: * @param contextDep true when the value context dependent and
193: * must not be cached.
194: */
195: public void setDefault(String defaultValue, boolean contextDep) {
196: this .defaultValue = defaultValue;
197: this .contextDep = contextDep;
198: }
199:
200: /**
201: * Set the percent base identifier for this maker.
202: * @param percentBase the percent base (ex. LengthBase.FONTSIZE)
203: */
204: public void setPercentBase(int percentBase) {
205: this .percentBase = percentBase;
206: }
207:
208: /**
209: * Set the setByShorthand flag which only is applicable for subproperty
210: * makers. It should be true for the subproperties which must be
211: * assigned a value when the base property is assigned a attribute
212: * value directly.
213: * @param setByShorthand true if this subproperty must be set when the base property is set
214: */
215: public void setByShorthand(boolean setByShorthand) {
216: this .setByShorthand = setByShorthand;
217: }
218:
219: /**
220: * Set the correspoding property information.
221: * @param corresponding a corresponding maker where the
222: * isForcedCorresponding and compute methods are delegated to.
223: */
224: public void setCorresponding(
225: CorrespondingPropertyMaker corresponding) {
226: this .corresponding = corresponding;
227: }
228:
229: /**
230: * Create a new empty property. Must be overriden in compound
231: * subclasses.
232: * @return a new instance of the Property for which this is a maker.
233: */
234: public Property makeNewProperty() {
235: return null;
236: }
237:
238: /**
239: * If the property is a relative property with a corresponding absolute
240: * value specified, the absolute value is used. This is also true of
241: * the inheritance priority (I think...)
242: * If the property is an "absolute" property and it isn't specified, then
243: * we try to compute it from the corresponding relative property: this
244: * happens in computeProperty.
245: * @param propertyList the applicable property list
246: * @param tryInherit true if inherited properties should be examined.
247: * @return the property value
248: * @throws PropertyException if there is a problem evaluating the property
249: */
250: public Property findProperty(PropertyList propertyList,
251: boolean tryInherit) throws PropertyException {
252: Property p = null;
253:
254: if (log.isTraceEnabled()) {
255: log.trace("PropertyMaker.findProperty: "
256: + FOPropertyMapping.getPropertyName(propId) + ", "
257: + propertyList.getFObj().getName());
258: }
259:
260: if (corresponding != null
261: && corresponding.isCorrespondingForced(propertyList)) {
262: p = corresponding.compute(propertyList);
263: } else {
264: p = propertyList.getExplicit(propId);
265: if (p == null) { // check for shorthand specification
266: p = getShorthand(propertyList);
267: }
268: if (p == null) {
269: p = this .compute(propertyList);
270: }
271: }
272: if (p == null && tryInherit) {
273: // else inherit (if has parent and is inheritable)
274: PropertyList parentPropertyList = propertyList
275: .getParentPropertyList();
276: if (parentPropertyList != null && isInherited()) {
277: p = parentPropertyList.get(propId, true, false);
278: }
279: }
280: return p;
281: }
282:
283: /**
284: * Return the property on the current FlowObject. Depending on the passed flags,
285: * this will try to compute it based on other properties, or if it is
286: * inheritable, to return the inherited value. If all else fails, it returns
287: * the default value.
288: * @param subpropertyId The subproperty id of the property being retrieved.
289: * Is 0 when retriving a base property.
290: * @param propertyList The PropertyList object being built for this FO.
291: * @param tryInherit true if inherited properties should be examined.
292: * @param tryDefault true if the default value should be returned.
293: * @return the property value
294: * @throws PropertyException if there is a problem evaluating the property
295: */
296: public Property get(int subpropertyId, PropertyList propertyList,
297: boolean tryInherit, boolean tryDefault)
298: throws PropertyException {
299: Property p = findProperty(propertyList, tryInherit);
300:
301: if (p == null && tryDefault) { // default value for this FO!
302: p = make(propertyList);
303: }
304: return p;
305: }
306:
307: /**
308: * Default implementation of isInherited.
309: * @return A boolean indicating whether this property is inherited.
310: */
311: public boolean isInherited() {
312: return inherited;
313: }
314:
315: /**
316: * This is used to handle properties specified as a percentage of
317: * some "base length", such as the content width of their containing
318: * box.
319: * Overridden by subclasses which allow percent specifications. See
320: * the documentation on properties.xsl for details.
321: * @param fo the FObj containing the PercentBase
322: * @param pl the PropertyList containing the property. (TODO: explain
323: * what this is used for, or remove it from the signature.)
324: * @return an object implementing the PercentBase interface.
325: * @throws PropertyException if there is a problem while evaluating the base property
326: */
327: public PercentBase getPercentBase(PropertyList pl)
328: throws PropertyException {
329: if (percentBase == -1) {
330: return null;
331: } else {
332: return new LengthBase(pl, percentBase);
333: }
334: }
335:
336: /**
337: * Return a property value for the given component of a compound
338: * property.
339: * @param p A property value for a compound property type such as
340: * SpaceProperty.
341: * @param subpropertyId the id of the component whose value is to be
342: * returned.
343: * NOTE: this is only to ease porting when calls are made to
344: * PropertyList.get() using a component name of a compound property,
345: * such as get("space.optimum"). The recommended technique is:
346: * get("space").getOptimum().
347: * Overridden by property maker subclasses which handle
348: * compound properties.
349: * @return the Property containing the subproperty
350: */
351: public Property getSubprop(Property p, int subpropertyId) {
352: CompoundDatatype val = (CompoundDatatype) p.getObject();
353: return val.getComponent(subpropertyId);
354: }
355:
356: /**
357: * Set a component in a compound property and return the modified
358: * compound property object.
359: * This default implementation returns the original base property
360: * without modifying it.
361: * It is overridden by property maker subclasses which handle
362: * compound properties.
363: * @param baseProperty The Property object representing the compound property,
364: * such as SpaceProperty.
365: * @param subpropertyId The ID of the component whose value is specified.
366: * @param subproperty A Property object holding the specified value of the
367: * component to be set.
368: * @return The modified compound property object.
369: */
370: protected Property setSubprop(Property baseProperty,
371: int subpropertyId, Property subproperty) {
372: CompoundDatatype val = (CompoundDatatype) baseProperty
373: .getObject();
374: val.setComponent(subpropertyId, subproperty, false);
375: return baseProperty;
376: }
377:
378: /**
379: * Return the default value.
380: * @param propertyList The PropertyList object being built for this FO.
381: * @return the Property object corresponding to the parameters
382: * @throws PropertyException for invalid or inconsisten FO input
383: */
384: public Property make(PropertyList propertyList)
385: throws PropertyException {
386: if (defaultProperty != null) {
387: if (log.isTraceEnabled()) {
388: log
389: .trace("PropertyMaker.make: reusing defaultProperty, "
390: + FOPropertyMapping
391: .getPropertyName(propId));
392: }
393: return defaultProperty;
394: }
395: if (log.isTraceEnabled()) {
396: log
397: .trace("PropertyMaker.make: making default property value, "
398: + FOPropertyMapping.getPropertyName(propId)
399: + ", " + propertyList.getFObj().getName());
400: }
401: Property p = make(propertyList, defaultValue, propertyList
402: .getParentFObj());
403: if (!contextDep) {
404: defaultProperty = p;
405: }
406: return p;
407: }
408:
409: /**
410: * Create a Property object from an attribute specification.
411: * @param propertyList The PropertyList object being built for this FO.
412: * @param value The attribute value.
413: * @param fo The parent FO for the FO whose property is being made.
414: * @return The initialized Property object.
415: * @throws PropertyException for invalid or inconsistent FO input
416: */
417: public Property make(PropertyList propertyList, String value,
418: FObj fo) throws PropertyException {
419: try {
420: Property newProp = null;
421: String pvalue = value;
422: if ("inherit".equals(value)) {
423: newProp = propertyList.getFromParent(this .propId
424: & Constants.PROPERTY_MASK);
425: if ((propId & Constants.COMPOUND_MASK) != 0) {
426: newProp = getSubprop(newProp, propId
427: & Constants.COMPOUND_MASK);
428: }
429: if (!isInherited() && log.isWarnEnabled()) {
430: /* check whether explicit value is available on the parent
431: * (for inherited properties, an inherited value will always
432: * be available)
433: */
434: Property parentExplicit = propertyList
435: .getParentPropertyList().getExplicit(
436: getPropId());
437: if (parentExplicit == null) {
438: log
439: .warn(FOPropertyMapping
440: .getPropertyName(getPropId())
441: + "=\"inherit\" on "
442: + propertyList.getFObj()
443: .getName()
444: + ", but no explicit value found on the parent FO.");
445: }
446: }
447: } else {
448: // Check for keyword shorthand values to be substituted.
449: pvalue = checkValueKeywords(pvalue);
450: newProp = checkEnumValues(pvalue);
451: }
452: if (newProp == null) {
453: // Override parsePropertyValue in each subclass of Property.Maker
454: newProp = PropertyParser.parse(pvalue,
455: new PropertyInfo(this , propertyList));
456: }
457: if (newProp != null) {
458: newProp = convertProperty(newProp, propertyList, fo);
459: }
460: if (newProp == null) {
461: throw new PropertyException("No conversion defined "
462: + pvalue);
463: }
464: return newProp;
465: } catch (PropertyException propEx) {
466: propEx.setLocator(fo.getLocator());
467: propEx.setPropertyName(getName());
468: throw propEx;
469: }
470: }
471:
472: /**
473: * Make a property value for a compound property. If the property
474: * value is already partially initialized, this method will modify it.
475: * @param baseProperty The Property object representing the compound property,
476: * for example: SpaceProperty.
477: * @param subpropertyId The Constants ID of the subproperty (component)
478: * whose value is specified.
479: * @param propertyList The propertyList being built.
480: * @param fo The parent FO for the FO whose property is being made.
481: * @param value the value of the
482: * @return baseProperty (or if null, a new compound property object) with
483: * the new subproperty added
484: * @throws PropertyException for invalid or inconsistent FO input
485: */
486: public Property make(Property baseProperty, int subpropertyId,
487: PropertyList propertyList, String value, FObj fo)
488: throws PropertyException {
489: //getLogger().error("compound property component "
490: // + partName + " unknown.");
491: return baseProperty;
492: }
493:
494: /**
495: * Converts a shorthand property
496: *
497: * @param propertyList the propertyList for which to convert
498: * @param prop the shorthand property
499: * @param fo ...
500: * @return the converted property
501: * @throws PropertyException ...
502: */
503: public Property convertShorthandProperty(PropertyList propertyList,
504: Property prop, FObj fo) throws PropertyException {
505: Property pret = convertProperty(prop, propertyList, fo);
506: if (pret == null) {
507: // If value is a name token, may be keyword or Enum
508: String sval = prop.getNCname();
509: if (sval != null) {
510: //log.debug("Convert shorthand ncname " + sval);
511: pret = checkEnumValues(sval);
512: if (pret == null) {
513: /* Check for keyword shorthand values to be substituted. */
514: String pvalue = checkValueKeywords(sval);
515: if (!pvalue.equals(sval)) {
516: //log.debug("Convert shorthand keyword" + pvalue);
517: // Substituted a value: must parse it
518: Property p = PropertyParser.parse(pvalue,
519: new PropertyInfo(this , propertyList));
520: pret = convertProperty(p, propertyList, fo);
521: }
522: }
523: }
524: }
525: if (pret != null) {
526: /*
527: * log.debug("Return shorthand value " + pret.getString() +
528: * " for " + getPropName());
529: */
530: }
531: return pret;
532: }
533:
534: /**
535: * For properties that contain enumerated values.
536: * This method should be overridden by subclasses.
537: * @param value the string containing the property value
538: * @return the Property encapsulating the enumerated equivalent of the
539: * input value
540: */
541: protected Property checkEnumValues(String value) {
542: if (enums != null) {
543: return (Property) enums.get(value);
544: }
545: return null;
546: }
547:
548: /**
549: * Return a String to be parsed if the passed value corresponds to
550: * a keyword which can be parsed and used to initialize the property.
551: * For example, the border-width family of properties can have the
552: * initializers "thin", "medium", or "thick". The FOPropertyMapping
553: * file specifies a length value equivalent for these keywords,
554: * such as "0.5pt" for "thin".
555: * @param keyword the string value of property attribute.
556: * @return a String containing a parseable equivalent or null if
557: * the passed value isn't a keyword initializer for this Property
558: */
559: protected String checkValueKeywords(String keyword) {
560: if (keywords != null) {
561: String value = (String) keywords.get(keyword);
562: if (value != null) {
563: return value;
564: }
565: }
566: // TODO: should return null here?
567: return keyword;
568: }
569:
570: /**
571: * Return a Property object based on the passed Property object.
572: * This method is called if the Property object built by the parser
573: * isn't the right type for this property.
574: * It is overridden by subclasses.
575: * @param p The Property object return by the expression parser
576: * @param propertyList The PropertyList object being built for this FO.
577: * @param fo The parent FO for the FO whose property is being made.
578: * @return A Property of the correct type or null if the parsed value
579: * can't be converted to the correct type.
580: * @throws PropertyException for invalid or inconsistent FO input
581: */
582: protected Property convertProperty(Property p,
583: PropertyList propertyList, FObj fo)
584: throws PropertyException {
585: return null;
586: }
587:
588: /**
589: * For properties that have more than one legal way to be specified,
590: * this routine should be overridden to attempt to set them based upon
591: * the other methods. For example, colors may be specified using an RGB
592: * model, or they may be specified using an NCname.
593: * @param p property whose datatype should be converted
594: * @param propertyList collection of properties. (TODO: explain why
595: * this is needed, or remove it from the signature.)
596: * @param fo The parent FO for the FO whose property is being made.
597: * why this is needed, or remove it from the signature).
598: * @return an Property with the appropriate datatype used
599: * @throws PropertyException for invalid or inconsistent input
600: */
601: protected Property convertPropertyDatatype(Property p,
602: PropertyList propertyList, FObj fo)
603: throws PropertyException {
604: return null;
605: }
606:
607: /**
608: * Return a Property object representing the value of this property,
609: * based on other property values for this FO.
610: * A special case is properties which inherit the specified value,
611: * rather than the computed value.
612: * @param propertyList The PropertyList for the FO.
613: * @return Property A computed Property value or null if no rules
614: * are specified to compute the value.
615: * @throws PropertyException for invalid or inconsistent FO input
616: */
617: protected Property compute(PropertyList propertyList)
618: throws PropertyException {
619: if (corresponding != null) {
620: return corresponding.compute(propertyList);
621: }
622: return null; // standard
623: }
624:
625: /**
626: * For properties that can be set by shorthand properties, this method
627: * should return the Property, if any, that is parsed from any
628: * shorthand properties that affect this property.
629: * This method expects to be overridden by subclasses.
630: * For example, the border-right-width property could be set implicitly
631: * from the border shorthand property, the border-width shorthand
632: * property, or the border-right shorthand property. This method should
633: * be overridden in the appropriate subclass to check each of these, and
634: * return an appropriate border-right-width Property object.
635: * @param propertyList the collection of properties to be considered
636: * @return the Property, if found, the correspons, otherwise, null
637: * @throws PropertyException if there is a problem while evaluating the shorthand
638: */
639: public Property getShorthand(PropertyList propertyList)
640: throws PropertyException {
641: if (shorthands == null) {
642: return null;
643: }
644: Property prop;
645: int n = shorthands.length;
646: for (int i = 0; i < n && shorthands[i] != null; i++) {
647: PropertyMaker shorthand = shorthands[i];
648: prop = propertyList.getExplicit(shorthand.propId);
649: if (prop != null) {
650: ShorthandParser parser = shorthand.datatypeParser;
651: Property p = parser.getValueForProperty(getPropId(),
652: prop, this , propertyList);
653: if (p != null) {
654: return p;
655: }
656: }
657: }
658: return null;
659: }
660:
661: /** @return the name of the property this maker is used for. */
662: public String getName() {
663: return FOPropertyMapping.getPropertyName(propId);
664: }
665:
666: /**
667: * Return a clone of the makers. Used by useGeneric() to clone the
668: * subproperty makers of the generic compound makers.
669: * @see java.lang.Object#clone()
670: */
671: public Object clone() {
672: try {
673: return super .clone();
674: } catch (CloneNotSupportedException exc) {
675: return null;
676: }
677: }
678: }
|