1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id: PropertyMaker.java 1617052 2014-08-10 06:55:01Z gadams $ */
19 
20 package org.apache.fop.fo.properties;
21 
22 import java.util.HashMap;
23 import java.util.Map;
24 
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 
28 import org.apache.fop.datatypes.CompoundDatatype;
29 import org.apache.fop.datatypes.LengthBase;
30 import org.apache.fop.datatypes.PercentBase;
31 import org.apache.fop.fo.Constants;
32 import org.apache.fop.fo.FOPropertyMapping;
33 import org.apache.fop.fo.FObj;
34 import org.apache.fop.fo.PropertyList;
35 import org.apache.fop.fo.expr.PropertyException;
36 import org.apache.fop.fo.expr.PropertyInfo;
37 import org.apache.fop.fo.expr.PropertyParser;
38 
39 
40 /**
41  * Base class for all property makers
42  */
43 public class PropertyMaker implements Cloneable {
44 
45     /** Logger instance */
46     private static final Log LOG = LogFactory.getLog(PropertyMaker.class);
47 
48     private static final boolean IS_LOG_TRACE_ENABLED = LOG.isTraceEnabled();
49 
50     /** the property ID */
51     protected int propId;
52     private boolean inherited = true;
53     private Map enums;
54     private Map keywords;
55     /** the default value for the maker */
56     protected String defaultValue;
57     /** Indicates whether the property is context-dependant and therefore can't be cached. */
58     protected boolean contextDep;
59     /** Indicates whether the property is set through a shorthand. */
60     protected boolean setByShorthand;
61     private int percentBase = -1;
62     private PropertyMaker[] shorthands;
63     private ShorthandParser datatypeParser;
64 
65     /** default property **/
66     protected Property defaultProperty;
67     /** Maker for 'corresponding' properties **/
68     protected CorrespondingPropertyMaker corresponding;
69 
70     /**
71      * @return the name of the property for this Maker
72      */
getPropId()73     public int getPropId() {
74         return propId;
75     }
76 
77     /**
78      * Construct an instance of a Property.Maker for the given property.
79      * @param propId The Constant ID of the property to be made.
80      */
PropertyMaker(int propId)81     public PropertyMaker(int propId) {
82         this.propId = propId;
83     }
84 
85     /**
86      * Copy all the values from the generic maker to this maker.
87      * @param generic a generic property maker.
88      */
useGeneric(PropertyMaker generic)89     public void useGeneric(PropertyMaker generic) {
90         contextDep = generic.contextDep;
91         inherited = generic.inherited;
92         defaultValue = generic.defaultValue;
93         percentBase = generic.percentBase;
94         if (generic.shorthands != null) {
95             shorthands = new PropertyMaker[generic.shorthands.length];
96             System.arraycopy(generic.shorthands, 0, shorthands, 0, shorthands.length);
97         }
98         if (generic.enums != null) {
99             enums = new HashMap(generic.enums);
100         }
101         if (generic.keywords != null) {
102             keywords = new HashMap(generic.keywords);
103         }
104     }
105 
106     /**
107      * Set the inherited flag.
108      * @param inherited true if this is an inherited property
109      */
setInherited(boolean inherited)110     public void setInherited(boolean inherited) {
111         this.inherited = inherited;
112     }
113 
114     /**
115      * Add a keyword-equiv to the maker.
116      * @param keyword the keyword
117      * @param value the value to be used when the keyword is specified
118      */
addKeyword(String keyword, String value)119     public void addKeyword(String keyword, String value) {
120         if (keywords == null) {
121             keywords = new HashMap();
122         }
123         keywords.put(keyword, value);
124     }
125 
126     /**
127      * Add a enum constant.
128      * @param constant the enum constant
129      * @param value the Property value to use when the constant is specified
130      */
addEnum(String constant, Property value)131     public void addEnum(String constant, Property value) {
132         if (enums == null) {
133             enums = new HashMap();
134         }
135         enums.put(constant, value);
136     }
137 
138     /**
139      * Add a subproperty to this maker.
140      * @param subproperty the PropertyMaker for the subproperty
141      */
addSubpropMaker(PropertyMaker subproperty)142     public void addSubpropMaker(PropertyMaker subproperty) {
143         throw new RuntimeException("Unable to add subproperties " + getClass());
144     }
145 
146     /**
147      * Return a subproperty maker for the subpropertyId.
148      * @param subpropertyId The subpropertyId of the maker.
149      * @return The subproperty maker.
150      */
getSubpropMaker(int subpropertyId)151     public PropertyMaker getSubpropMaker(int subpropertyId) {
152         throw new RuntimeException("Unable to add subproperties");
153     }
154 
155     /**
156      * Add a shorthand to this maker. Only an Integer is added to the
157      * shorthands list. Later the Integers are replaced with references
158      * to the actual shorthand property makers.
159      * @param shorthand a property maker thar is that is checked for
160      *        shorthand values.
161      */
addShorthand(PropertyMaker shorthand)162     public void addShorthand(PropertyMaker shorthand) {
163         if (shorthands == null) {
164             shorthands = new PropertyMaker[3];
165         }
166         for (int i = 0; i < shorthands.length; i++) {
167             if (shorthands[i] == null) {
168                 shorthands[i] = shorthand;
169                 break;
170             }
171         }
172     }
173 
174     /**
175      * Set the shorthand datatype parser.
176      * @param parser the shorthand parser
177      */
setDatatypeParser(ShorthandParser parser)178     public void setDatatypeParser(ShorthandParser parser) {
179         datatypeParser = parser;
180     }
181 
182     /**
183      * Set the default value for this maker.
184      * @param defaultValue the default value.
185      */
setDefault(String defaultValue)186     public void setDefault(String defaultValue) {
187         this.defaultValue = defaultValue;
188     }
189 
190     /**
191      * Set the default value for this maker.
192      * @param defaultValue the default value
193      * @param contextDep true when the value context dependent and
194      *        must not be cached.
195      */
setDefault(String defaultValue, boolean contextDep)196     public void setDefault(String defaultValue, boolean contextDep) {
197         this.defaultValue = defaultValue;
198         this.contextDep = contextDep;
199     }
200 
201     /**
202      * Set the percent base identifier for this maker.
203      * @param percentBase the percent base (ex. LengthBase.FONTSIZE)
204      */
setPercentBase(int percentBase)205     public void setPercentBase(int percentBase) {
206         this.percentBase = percentBase;
207     }
208 
209     /**
210      * Set the setByShorthand flag which only is applicable for subproperty
211      * makers. It should be true for the subproperties which must be
212      * assigned a value when the base property is assigned a attribute
213      * value directly.
214      * @param setByShorthand true if this subproperty must be set when the base property is set
215      */
setByShorthand(boolean setByShorthand)216     public void setByShorthand(boolean setByShorthand) {
217         this.setByShorthand = setByShorthand;
218     }
219 
220     /**
221      * Set the correspoding property information.
222      * @param corresponding a corresponding maker where the
223      *        isForcedCorresponding and compute methods are delegated to.
224      */
setCorresponding(CorrespondingPropertyMaker corresponding)225     public void setCorresponding(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      */
makeNewProperty()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      */
findProperty(PropertyList propertyList, boolean tryInherit)250     public Property findProperty(PropertyList propertyList,
251                                  boolean tryInherit)
252                 throws PropertyException {
253         Property p = null;
254 
255         if (IS_LOG_TRACE_ENABLED) {
256             LOG.trace("PropertyMaker.findProperty: "
257                   + FOPropertyMapping.getPropertyName(propId)
258                   + ", " + propertyList.getFObj().getName());
259         }
260 
261         if (corresponding != null && 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.getParentPropertyList();
275             if (parentPropertyList != null && isInherited()) {
276                 p = parentPropertyList.get(propId, true, false);
277             }
278         }
279         return p;
280     }
281 
282     /**
283      * Return the property on the current FlowObject. Depending on the passed flags,
284      * this will try to compute it based on other properties, or if it is
285      * inheritable, to return the inherited value. If all else fails, it returns
286      * the default value.
287      * @param subpropertyId  The subproperty id of the property being retrieved.
288      *        Is 0 when retrieving a base property.
289      * @param propertyList The PropertyList object being built for this FO.
290      * @param tryInherit true if inherited properties should be examined.
291      * @param tryDefault true if the default value should be returned.
292      * @return the property value
293      * @throws PropertyException if there is a problem evaluating the property
294      */
get(int subpropertyId, PropertyList propertyList, boolean tryInherit, boolean tryDefault)295     public Property get(int subpropertyId, PropertyList propertyList,
296                         boolean tryInherit, boolean tryDefault)
297                     throws PropertyException {
298         Property p = findProperty(propertyList, tryInherit);
299 
300         if (p == null && tryDefault) {    // default value for this FO!
301             p = make(propertyList);
302         }
303         return p;
304     }
305 
306     /**
307      * Default implementation of isInherited.
308      * @return A boolean indicating whether this property is inherited.
309      */
isInherited()310     public boolean isInherited() {
311         return inherited;
312     }
313 
314     /**
315      * This is used to handle properties specified as a percentage of
316      * some "base length", such as the content width of their containing
317      * box.
318      * Overridden by subclasses which allow percent specifications. See
319      * the documentation on properties.xsl for details.
320      * @param pl the PropertyList containing the property. (TODO: explain
321      * what this is used for, or remove it from the signature.)
322      * @return an object implementing the PercentBase interface.
323      * @throws PropertyException if there is a problem while evaluating the base property
324      */
getPercentBase(PropertyList pl)325     public PercentBase getPercentBase(PropertyList pl) throws PropertyException {
326         if (percentBase == -1) {
327             return null;
328         } else {
329             return new LengthBase(pl, percentBase);
330         }
331     }
332 
333     /**
334      * Return a property value for the given component of a compound
335      * property.
336      * @param p A property value for a compound property type such as
337      * SpaceProperty.
338      * @param subpropertyId the id of the component whose value is to be
339      * returned.
340      * NOTE: this is only to ease porting when calls are made to
341      * PropertyList.get() using a component name of a compound property,
342      * such as get("space.optimum"). The recommended technique is:
343      * get("space").getOptimum().
344      * Overridden by property maker subclasses which handle
345      * compound properties.
346      * @return the Property containing the subproperty
347      */
getSubprop(Property p, int subpropertyId)348     public Property getSubprop(Property p, int subpropertyId) {
349         CompoundDatatype val = (CompoundDatatype) p.getObject();
350         return val.getComponent(subpropertyId);
351     }
352 
353     /**
354      * Set a component in a compound property and return the modified
355      * compound property object.
356      * This default implementation returns the original base property
357      * without modifying it.
358      * It is overridden by property maker subclasses which handle
359      * compound properties.
360      * @param baseProperty The Property object representing the compound property,
361      * such as SpaceProperty.
362      * @param subpropertyId The ID of the component whose value is specified.
363      * @param subproperty A Property object holding the specified value of the
364      * component to be set.
365      * @return The modified compound property object.
366      */
setSubprop(Property baseProperty, int subpropertyId, Property subproperty)367     protected Property setSubprop(Property baseProperty, int subpropertyId,
368                                   Property subproperty) {
369         CompoundDatatype val = (CompoundDatatype) baseProperty.getObject();
370         val.setComponent(subpropertyId, subproperty, false);
371         return baseProperty;
372     }
373 
374     /**
375      * Return the default value.
376      * @param propertyList The PropertyList object being built for this FO.
377      * @return the Property object corresponding to the parameters
378      * @throws PropertyException for invalid or inconsisten FO input
379      */
make(PropertyList propertyList)380     public Property make(PropertyList propertyList) throws PropertyException {
381         if (defaultProperty != null) {
382             if (IS_LOG_TRACE_ENABLED) {
383                 LOG.trace("PropertyMaker.make: reusing defaultProperty, "
384                       + FOPropertyMapping.getPropertyName(propId));
385             }
386             return defaultProperty;
387         }
388         if (IS_LOG_TRACE_ENABLED) {
389             LOG.trace("PropertyMaker.make: making default property value, "
390                   + FOPropertyMapping.getPropertyName(propId)
391                   + ", " + propertyList.getFObj().getName());
392         }
393         Property p = make(propertyList, defaultValue, propertyList.getParentFObj());
394         if (!contextDep) {
395             defaultProperty = p;
396         }
397         return p;
398     }
399 
400     /**
401      * Create a Property object from an attribute specification.
402      * @param propertyList The PropertyList object being built for this FO.
403      * @param value The attribute value.
404      * @param fo The parent FO for the FO whose property is being made.
405      * @return The initialized Property object.
406      * @throws PropertyException for invalid or inconsistent FO input
407      */
make(PropertyList propertyList, String value, FObj fo)408      public Property make(PropertyList propertyList, String value,
409                          FObj fo) throws PropertyException {
410         try {
411             Property newProp = null;
412             String pvalue = value;
413             if ("inherit".equals(value)) {
414                 newProp = propertyList.getFromParent(this.propId & Constants.PROPERTY_MASK);
415                 if ((propId & Constants.COMPOUND_MASK) != 0) {
416                     newProp = getSubprop(newProp, propId & Constants.COMPOUND_MASK);
417                 }
418                 if (!isInherited() && LOG.isWarnEnabled()) {
419                     /* check whether explicit value is available on the parent
420                      * (for inherited properties, an inherited value will always
421                      *  be available)
422                      */
423                     Property parentExplicit = propertyList.getParentPropertyList()
424                                                 .getExplicit(getPropId());
425                     if (parentExplicit == null) {
426                         LOG.warn(FOPropertyMapping.getPropertyName(getPropId())
427                                 + "=\"inherit\" on " + propertyList.getFObj().getName()
428                                 + ", but no explicit value found on the parent FO.");
429                     }
430                 }
431             } else {
432                 // Check for keyword shorthand values to be substituted.
433                 pvalue = checkValueKeywords(pvalue.trim());
434                 newProp = checkEnumValues(pvalue);
435             }
436             if (newProp == null) {
437                 // Override parsePropertyValue in each subclass of Property.Maker
438                 newProp = PropertyParser.parse(pvalue,
439                                                   new PropertyInfo(this,
440                                                   propertyList));
441             }
442             if (newProp != null) {
443                 newProp = convertProperty(newProp, propertyList, fo);
444             }
445             if (newProp == null) {
446                 throw new PropertyException("No conversion defined " + pvalue);
447             }
448             return newProp;
449         } catch (PropertyException propEx) {
450             if (fo != null) {
451                 propEx.setLocator(fo.getLocator());
452             }
453             propEx.setPropertyName(getName());
454             throw propEx;
455         }
456     }
457 
458     /**
459      * Make a property value for a compound property. If the property
460      * value is already partially initialized, this method will modify it.
461      * @param baseProperty The Property object representing the compound property,
462      * for example: SpaceProperty.
463      * @param subpropertyId The Constants ID of the subproperty (component)
464      *        whose value is specified.
465      * @param propertyList The propertyList being built.
466      * @param fo The parent FO for the FO whose property is being made.
467      * @param value the value of the
468      * @return baseProperty (or if null, a new compound property object) with
469      * the new subproperty added
470      * @throws PropertyException for invalid or inconsistent FO input
471      */
make(Property baseProperty, int subpropertyId, PropertyList propertyList, String value, FObj fo)472     public Property make(Property baseProperty, int subpropertyId,
473                          PropertyList propertyList, String value,
474                          FObj fo) throws PropertyException {
475         //getLogger().error("compound property component "
476         //                       + partName + " unknown.");
477         return baseProperty;
478     }
479 
480     /**
481      * Converts a shorthand property
482      *
483      * @param propertyList  the propertyList for which to convert
484      * @param prop          the shorthand property
485      * @param fo            ...
486      * @return  the converted property
487      * @throws PropertyException ...
488      */
convertShorthandProperty(PropertyList propertyList, Property prop, FObj fo)489     public Property convertShorthandProperty(PropertyList propertyList,
490                                              Property prop, FObj fo)
491         throws PropertyException {
492         Property pret = convertProperty(prop, propertyList, fo);
493         if (pret == null) {
494             // If value is a name token, may be keyword or Enum
495             String sval = prop.getNCname();
496             if (sval != null) {
497                 //log.debug("Convert shorthand ncname " + sval);
498                 pret = checkEnumValues(sval);
499                 if (pret == null) {
500                     /* Check for keyword shorthand values to be substituted. */
501                     String pvalue = checkValueKeywords(sval);
502                     if (!pvalue.equals(sval)) {
503                         //log.debug("Convert shorthand keyword" + pvalue);
504                         // Substituted a value: must parse it
505                         Property p = PropertyParser.parse(pvalue,
506                                                  new PropertyInfo(this,
507                                                                   propertyList));
508                         pret = convertProperty(p, propertyList, fo);
509                     }
510                 }
511             }
512         }
513         return pret;
514     }
515 
516     /**
517      * For properties that contain enumerated values.
518      * This method should be overridden by subclasses.
519      * @param value the string containing the property value
520      * @return the Property encapsulating the enumerated equivalent of the
521      * input value
522      */
checkEnumValues(String value)523     protected Property checkEnumValues(String value) {
524         if (enums != null) {
525             Property p = (Property) enums.get(value);
526             return p;
527         }
528         return null;
529     }
530 
531     /**
532      * Return a String to be parsed if the passed value corresponds to
533      * a keyword which can be parsed and used to initialize the property.
534      * For example, the border-width family of properties can have the
535      * initializers "thin", "medium", or "thick". The FOPropertyMapping
536      * file specifies a length value equivalent for these keywords,
537      * such as "0.5pt" for "thin".
538      * @param keyword the string value of property attribute.
539      * @return a String containing a parseable equivalent or null if
540      * the passed value isn't a keyword initializer for this Property
541      */
checkValueKeywords(String keyword)542     protected String checkValueKeywords(String keyword) {
543         if (keywords != null) {
544             String value = (String)keywords.get(keyword);
545             if (value != null) {
546                 return value;
547             }
548         }
549         // TODO: should return null here?
550         return keyword;
551     }
552 
553     /**
554      * Return a Property object based on the passed Property object.
555      * This method is called if the Property object built by the parser
556      * isn't the right type for this property.
557      * It is overridden by subclasses.
558      * @param p The Property object return by the expression parser
559      * @param propertyList The PropertyList object being built for this FO.
560      * @param fo The parent FO for the FO whose property is being made.
561      * @return A Property of the correct type or null if the parsed value
562      * can't be converted to the correct type.
563      * @throws PropertyException for invalid or inconsistent FO input
564      */
convertProperty(Property p, PropertyList propertyList, FObj fo)565     protected Property convertProperty(Property p,
566                                     PropertyList propertyList,
567                                     FObj fo) throws PropertyException {
568         return null;
569     }
570 
571     /**
572      * For properties that have more than one legal way to be specified,
573      * this routine should be overridden to attempt to set them based upon
574      * the other methods. For example, colors may be specified using an RGB
575      * model, or they may be specified using an NCname.
576      * @param p property whose datatype should be converted
577      * @param propertyList collection of properties. (TODO: explain why
578      * this is needed, or remove it from the signature.)
579      * @param fo The parent FO for the FO whose property is being made.
580      * why this is needed, or remove it from the signature).
581      * @return an Property with the appropriate datatype used
582      * @throws PropertyException for invalid or inconsistent input
583      */
convertPropertyDatatype(Property p, PropertyList propertyList, FObj fo)584     protected Property convertPropertyDatatype(Property p,
585                                                PropertyList propertyList,
586                                                FObj fo) throws PropertyException {
587         return null;
588     }
589 
590     /**
591      * Return a Property object representing the value of this property,
592      * based on other property values for this FO.
593      * A special case is properties which inherit the specified value,
594      * rather than the computed value.
595      * @param propertyList The PropertyList for the FO.
596      * @return Property A computed Property value or null if no rules
597      * are specified to compute the value.
598      * @throws PropertyException for invalid or inconsistent FO input
599      */
compute(PropertyList propertyList)600     protected Property compute(PropertyList propertyList)
601             throws PropertyException {
602         if (corresponding != null) {
603             return corresponding.compute(propertyList);
604         }
605         return null;    // standard
606     }
607 
608     /**
609      * For properties that can be set by shorthand properties, this method
610      * should return the Property, if any, that is parsed from any
611      * shorthand properties that affect this property.
612      * This method expects to be overridden by subclasses.
613      * For example, the border-right-width property could be set implicitly
614      * from the border shorthand property, the border-width shorthand
615      * property, or the border-right shorthand property. This method should
616      * be overridden in the appropriate subclass to check each of these, and
617      * return an appropriate border-right-width Property object.
618      * @param propertyList the collection of properties to be considered
619      * @return the Property, if found, the correspons, otherwise, null
620      * @throws PropertyException if there is a problem while evaluating the shorthand
621      */
getShorthand(PropertyList propertyList)622     public Property getShorthand(PropertyList propertyList)
623                 throws PropertyException {
624         if (shorthands == null) {
625             return null;
626         }
627         Property prop;
628         int n = shorthands.length;
629         for (int i = 0; i < n && shorthands[i] != null; i++) {
630             PropertyMaker shorthand = shorthands[i];
631             prop = propertyList.getExplicit(shorthand.propId);
632             if (prop != null) {
633                 ShorthandParser parser = shorthand.datatypeParser;
634                 Property p = parser.getValueForProperty(getPropId(),
635                                         prop, this, propertyList);
636                 if (p != null) {
637                     return p;
638                 }
639             }
640         }
641         return null;
642     }
643 
644     /** @return the name of the property this maker is used for. */
getName()645     public String getName() {
646         return FOPropertyMapping.getPropertyName(propId);
647     }
648 
649     /**
650      * Return a clone of the makers. Used by useGeneric() to clone the
651      * subproperty makers of the generic compound makers.
652      * {@inheritDoc}
653      */
654     @Override
clone()655     public Object clone() {
656         try {
657             return super.clone();
658         } catch (CloneNotSupportedException exc) {
659             return null;
660         }
661     }
662 }
663