| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.fo.properties; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.fop.datatypes.CompoundDatatype; |
| import org.apache.fop.datatypes.LengthBase; |
| import org.apache.fop.datatypes.PercentBase; |
| import org.apache.fop.fo.Constants; |
| import org.apache.fop.fo.FOPropertyMapping; |
| import org.apache.fop.fo.FObj; |
| import org.apache.fop.fo.PropertyList; |
| import org.apache.fop.fo.expr.PropertyException; |
| import org.apache.fop.fo.expr.PropertyInfo; |
| import org.apache.fop.fo.expr.PropertyParser; |
| |
| |
| /** |
| * Base class for all property makers |
| */ |
| public class PropertyMaker implements Cloneable { |
| |
| /** Logger instance */ |
| private static final Log LOG = LogFactory.getLog(PropertyMaker.class); |
| |
| private static final boolean IS_LOG_TRACE_ENABLED = LOG.isTraceEnabled(); |
| |
| /** the property ID */ |
| protected int propId; |
| private boolean inherited = true; |
| private Map enums; |
| private Map keywords; |
| /** the default value for the maker */ |
| protected String defaultValue; |
| /** Indicates whether the property is context-dependant and therefore can't be cached. */ |
| protected boolean contextDep; |
| /** Indicates whether the property is set through a shorthand. */ |
| protected boolean setByShorthand; |
| private int percentBase = -1; |
| private PropertyMaker[] shorthands; |
| private ShorthandParser datatypeParser; |
| |
| /** default property **/ |
| protected Property defaultProperty; |
| /** Maker for 'corresponding' properties **/ |
| protected CorrespondingPropertyMaker corresponding; |
| |
| /** |
| * @return the name of the property for this Maker |
| */ |
| public int getPropId() { |
| return propId; |
| } |
| |
| /** |
| * Construct an instance of a Property.Maker for the given property. |
| * @param propId The Constant ID of the property to be made. |
| */ |
| public PropertyMaker(int propId) { |
| this.propId = propId; |
| } |
| |
| /** |
| * Copy all the values from the generic maker to this maker. |
| * @param generic a generic property maker. |
| */ |
| public void useGeneric(PropertyMaker generic) { |
| contextDep = generic.contextDep; |
| inherited = generic.inherited; |
| defaultValue = generic.defaultValue; |
| percentBase = generic.percentBase; |
| if (generic.shorthands != null) { |
| shorthands = new PropertyMaker[generic.shorthands.length]; |
| System.arraycopy(generic.shorthands, 0, shorthands, 0, shorthands.length); |
| } |
| if (generic.enums != null) { |
| enums = new HashMap(generic.enums); |
| } |
| if (generic.keywords != null) { |
| keywords = new HashMap(generic.keywords); |
| } |
| } |
| |
| /** |
| * Set the inherited flag. |
| * @param inherited true if this is an inherited property |
| */ |
| public void setInherited(boolean inherited) { |
| this.inherited = inherited; |
| } |
| |
| /** |
| * Add a keyword-equiv to the maker. |
| * @param keyword the keyword |
| * @param value the value to be used when the keyword is specified |
| */ |
| public void addKeyword(String keyword, String value) { |
| if (keywords == null) { |
| keywords = new HashMap(); |
| } |
| keywords.put(keyword, value); |
| } |
| |
| /** |
| * Add a enum constant. |
| * @param constant the enum constant |
| * @param value the Property value to use when the constant is specified |
| */ |
| public void addEnum(String constant, Property value) { |
| if (enums == null) { |
| enums = new HashMap(); |
| } |
| enums.put(constant, value); |
| } |
| |
| /** |
| * Add a subproperty to this maker. |
| * @param subproperty the PropertyMaker for the subproperty |
| */ |
| public void addSubpropMaker(PropertyMaker subproperty) { |
| throw new RuntimeException("Unable to add subproperties " + getClass()); |
| } |
| |
| /** |
| * Return a subproperty maker for the subpropertyId. |
| * @param subpropertyId The subpropertyId of the maker. |
| * @return The subproperty maker. |
| */ |
| public PropertyMaker getSubpropMaker(int subpropertyId) { |
| throw new RuntimeException("Unable to add subproperties"); |
| } |
| |
| /** |
| * Add a shorthand to this maker. Only an Integer is added to the |
| * shorthands list. Later the Integers are replaced with references |
| * to the actual shorthand property makers. |
| * @param shorthand a property maker thar is that is checked for |
| * shorthand values. |
| */ |
| public void addShorthand(PropertyMaker shorthand) { |
| if (shorthands == null) { |
| shorthands = new PropertyMaker[3]; |
| } |
| for (int i = 0; i < shorthands.length; i++) { |
| if (shorthands[i] == null) { |
| shorthands[i] = shorthand; |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Set the shorthand datatype parser. |
| * @param parser the shorthand parser |
| */ |
| public void setDatatypeParser(ShorthandParser parser) { |
| datatypeParser = parser; |
| } |
| |
| /** |
| * Set the default value for this maker. |
| * @param defaultValue the default value. |
| */ |
| public void setDefault(String defaultValue) { |
| this.defaultValue = defaultValue; |
| } |
| |
| /** |
| * Set the default value for this maker. |
| * @param defaultValue the default value |
| * @param contextDep true when the value context dependent and |
| * must not be cached. |
| */ |
| public void setDefault(String defaultValue, boolean contextDep) { |
| this.defaultValue = defaultValue; |
| this.contextDep = contextDep; |
| } |
| |
| /** |
| * Set the percent base identifier for this maker. |
| * @param percentBase the percent base (ex. LengthBase.FONTSIZE) |
| */ |
| public void setPercentBase(int percentBase) { |
| this.percentBase = percentBase; |
| } |
| |
| /** |
| * Set the setByShorthand flag which only is applicable for subproperty |
| * makers. It should be true for the subproperties which must be |
| * assigned a value when the base property is assigned a attribute |
| * value directly. |
| * @param setByShorthand true if this subproperty must be set when the base property is set |
| */ |
| public void setByShorthand(boolean setByShorthand) { |
| this.setByShorthand = setByShorthand; |
| } |
| |
| /** |
| * Set the correspoding property information. |
| * @param corresponding a corresponding maker where the |
| * isForcedCorresponding and compute methods are delegated to. |
| */ |
| public void setCorresponding(CorrespondingPropertyMaker corresponding) { |
| this.corresponding = corresponding; |
| } |
| |
| /** |
| * Create a new empty property. Must be overriden in compound |
| * subclasses. |
| * @return a new instance of the Property for which this is a maker. |
| */ |
| public Property makeNewProperty() { |
| return null; |
| } |
| |
| /** |
| * If the property is a relative property with a corresponding absolute |
| * value specified, the absolute value is used. This is also true of |
| * the inheritance priority (I think...) |
| * If the property is an "absolute" property and it isn't specified, then |
| * we try to compute it from the corresponding relative property: this |
| * happens in computeProperty. |
| * @param propertyList the applicable property list |
| * @param tryInherit true if inherited properties should be examined. |
| * @return the property value |
| * @throws PropertyException if there is a problem evaluating the property |
| */ |
| public Property findProperty(PropertyList propertyList, |
| boolean tryInherit) |
| throws PropertyException { |
| Property p = null; |
| |
| if (IS_LOG_TRACE_ENABLED) { |
| LOG.trace("PropertyMaker.findProperty: " |
| + FOPropertyMapping.getPropertyName(propId) |
| + ", " + propertyList.getFObj().getName()); |
| } |
| |
| if (corresponding != null && corresponding.isCorrespondingForced(propertyList)) { |
| p = corresponding.compute(propertyList); |
| } else { |
| p = propertyList.getExplicit(propId); |
| if (p == null) { // check for shorthand specification |
| p = getShorthand(propertyList); |
| } |
| if (p == null) { |
| p = this.compute(propertyList); |
| } |
| } |
| if (p == null && tryInherit) { |
| // else inherit (if has parent and is inheritable) |
| PropertyList parentPropertyList = propertyList.getParentPropertyList(); |
| if (parentPropertyList != null && isInherited()) { |
| p = parentPropertyList.get(propId, true, false); |
| } |
| } |
| return p; |
| } |
| |
| /** |
| * Return the property on the current FlowObject. Depending on the passed flags, |
| * this will try to compute it based on other properties, or if it is |
| * inheritable, to return the inherited value. If all else fails, it returns |
| * the default value. |
| * @param subpropertyId The subproperty id of the property being retrieved. |
| * Is 0 when retrieving a base property. |
| * @param propertyList The PropertyList object being built for this FO. |
| * @param tryInherit true if inherited properties should be examined. |
| * @param tryDefault true if the default value should be returned. |
| * @return the property value |
| * @throws PropertyException if there is a problem evaluating the property |
| */ |
| public Property get(int subpropertyId, PropertyList propertyList, |
| boolean tryInherit, boolean tryDefault) |
| throws PropertyException { |
| Property p = findProperty(propertyList, tryInherit); |
| |
| if (p == null && tryDefault) { // default value for this FO! |
| p = make(propertyList); |
| } |
| return p; |
| } |
| |
| /** |
| * Default implementation of isInherited. |
| * @return A boolean indicating whether this property is inherited. |
| */ |
| public boolean isInherited() { |
| return inherited; |
| } |
| |
| /** |
| * This is used to handle properties specified as a percentage of |
| * some "base length", such as the content width of their containing |
| * box. |
| * Overridden by subclasses which allow percent specifications. See |
| * the documentation on properties.xsl for details. |
| * @param pl the PropertyList containing the property. (TODO: explain |
| * what this is used for, or remove it from the signature.) |
| * @return an object implementing the PercentBase interface. |
| * @throws PropertyException if there is a problem while evaluating the base property |
| */ |
| public PercentBase getPercentBase(PropertyList pl) throws PropertyException { |
| if (percentBase == -1) { |
| return null; |
| } else { |
| return new LengthBase(pl, percentBase); |
| } |
| } |
| |
| /** |
| * Return a property value for the given component of a compound |
| * property. |
| * @param p A property value for a compound property type such as |
| * SpaceProperty. |
| * @param subpropertyId the id of the component whose value is to be |
| * returned. |
| * NOTE: this is only to ease porting when calls are made to |
| * PropertyList.get() using a component name of a compound property, |
| * such as get("space.optimum"). The recommended technique is: |
| * get("space").getOptimum(). |
| * Overridden by property maker subclasses which handle |
| * compound properties. |
| * @return the Property containing the subproperty |
| */ |
| public Property getSubprop(Property p, int subpropertyId) { |
| CompoundDatatype val = (CompoundDatatype) p.getObject(); |
| return val.getComponent(subpropertyId); |
| } |
| |
| /** |
| * Set a component in a compound property and return the modified |
| * compound property object. |
| * This default implementation returns the original base property |
| * without modifying it. |
| * It is overridden by property maker subclasses which handle |
| * compound properties. |
| * @param baseProperty The Property object representing the compound property, |
| * such as SpaceProperty. |
| * @param subpropertyId The ID of the component whose value is specified. |
| * @param subproperty A Property object holding the specified value of the |
| * component to be set. |
| * @return The modified compound property object. |
| */ |
| protected Property setSubprop(Property baseProperty, int subpropertyId, |
| Property subproperty) { |
| CompoundDatatype val = (CompoundDatatype) baseProperty.getObject(); |
| val.setComponent(subpropertyId, subproperty, false); |
| return baseProperty; |
| } |
| |
| /** |
| * Return the default value. |
| * @param propertyList The PropertyList object being built for this FO. |
| * @return the Property object corresponding to the parameters |
| * @throws PropertyException for invalid or inconsisten FO input |
| */ |
| public Property make(PropertyList propertyList) throws PropertyException { |
| if (defaultProperty != null) { |
| if (IS_LOG_TRACE_ENABLED) { |
| LOG.trace("PropertyMaker.make: reusing defaultProperty, " |
| + FOPropertyMapping.getPropertyName(propId)); |
| } |
| return defaultProperty; |
| } |
| if (IS_LOG_TRACE_ENABLED) { |
| LOG.trace("PropertyMaker.make: making default property value, " |
| + FOPropertyMapping.getPropertyName(propId) |
| + ", " + propertyList.getFObj().getName()); |
| } |
| Property p = make(propertyList, defaultValue, propertyList.getParentFObj()); |
| if (!contextDep) { |
| defaultProperty = p; |
| } |
| return p; |
| } |
| |
| /** |
| * Create a Property object from an attribute specification. |
| * @param propertyList The PropertyList object being built for this FO. |
| * @param value The attribute value. |
| * @param fo The parent FO for the FO whose property is being made. |
| * @return The initialized Property object. |
| * @throws PropertyException for invalid or inconsistent FO input |
| */ |
| public Property make(PropertyList propertyList, String value, |
| FObj fo) throws PropertyException { |
| try { |
| Property newProp = null; |
| String pvalue = value; |
| if ("inherit".equals(value)) { |
| newProp = propertyList.getFromParent(this.propId & Constants.PROPERTY_MASK); |
| if ((propId & Constants.COMPOUND_MASK) != 0) { |
| newProp = getSubprop(newProp, propId & Constants.COMPOUND_MASK); |
| } |
| if (!isInherited() && LOG.isWarnEnabled()) { |
| /* check whether explicit value is available on the parent |
| * (for inherited properties, an inherited value will always |
| * be available) |
| */ |
| Property parentExplicit = propertyList.getParentPropertyList() |
| .getExplicit(getPropId()); |
| if (parentExplicit == null) { |
| LOG.warn(FOPropertyMapping.getPropertyName(getPropId()) |
| + "=\"inherit\" on " + propertyList.getFObj().getName() |
| + ", but no explicit value found on the parent FO."); |
| } |
| } |
| } else { |
| // Check for keyword shorthand values to be substituted. |
| pvalue = checkValueKeywords(pvalue.trim()); |
| newProp = checkEnumValues(pvalue); |
| } |
| if (newProp == null) { |
| // Override parsePropertyValue in each subclass of Property.Maker |
| newProp = PropertyParser.parse(pvalue, |
| new PropertyInfo(this, |
| propertyList)); |
| } |
| if (newProp != null) { |
| newProp = convertProperty(newProp, propertyList, fo); |
| } |
| if (newProp == null) { |
| throw new PropertyException("No conversion defined " + pvalue); |
| } |
| return newProp; |
| } catch (PropertyException propEx) { |
| if (fo != null) { |
| propEx.setLocator(fo.getLocator()); |
| } |
| propEx.setPropertyName(getName()); |
| throw propEx; |
| } |
| } |
| |
| /** |
| * Make a property value for a compound property. If the property |
| * value is already partially initialized, this method will modify it. |
| * @param baseProperty The Property object representing the compound property, |
| * for example: SpaceProperty. |
| * @param subpropertyId The Constants ID of the subproperty (component) |
| * whose value is specified. |
| * @param propertyList The propertyList being built. |
| * @param fo The parent FO for the FO whose property is being made. |
| * @param value the value of the |
| * @return baseProperty (or if null, a new compound property object) with |
| * the new subproperty added |
| * @throws PropertyException for invalid or inconsistent FO input |
| */ |
| public Property make(Property baseProperty, int subpropertyId, |
| PropertyList propertyList, String value, |
| FObj fo) throws PropertyException { |
| //getLogger().error("compound property component " |
| // + partName + " unknown."); |
| return baseProperty; |
| } |
| |
| /** |
| * Converts a shorthand property |
| * |
| * @param propertyList the propertyList for which to convert |
| * @param prop the shorthand property |
| * @param fo ... |
| * @return the converted property |
| * @throws PropertyException ... |
| */ |
| public Property convertShorthandProperty(PropertyList propertyList, |
| Property prop, FObj fo) |
| throws PropertyException { |
| Property pret = convertProperty(prop, propertyList, fo); |
| if (pret == null) { |
| // If value is a name token, may be keyword or Enum |
| String sval = prop.getNCname(); |
| if (sval != null) { |
| //log.debug("Convert shorthand ncname " + sval); |
| pret = checkEnumValues(sval); |
| if (pret == null) { |
| /* Check for keyword shorthand values to be substituted. */ |
| String pvalue = checkValueKeywords(sval); |
| if (!pvalue.equals(sval)) { |
| //log.debug("Convert shorthand keyword" + pvalue); |
| // Substituted a value: must parse it |
| Property p = PropertyParser.parse(pvalue, |
| new PropertyInfo(this, |
| propertyList)); |
| pret = convertProperty(p, propertyList, fo); |
| } |
| } |
| } |
| } |
| return pret; |
| } |
| |
| /** |
| * For properties that contain enumerated values. |
| * This method should be overridden by subclasses. |
| * @param value the string containing the property value |
| * @return the Property encapsulating the enumerated equivalent of the |
| * input value |
| */ |
| protected Property checkEnumValues(String value) { |
| if (enums != null) { |
| Property p = (Property) enums.get(value); |
| return p; |
| } |
| return null; |
| } |
| |
| /** |
| * Return a String to be parsed if the passed value corresponds to |
| * a keyword which can be parsed and used to initialize the property. |
| * For example, the border-width family of properties can have the |
| * initializers "thin", "medium", or "thick". The FOPropertyMapping |
| * file specifies a length value equivalent for these keywords, |
| * such as "0.5pt" for "thin". |
| * @param keyword the string value of property attribute. |
| * @return a String containing a parseable equivalent or null if |
| * the passed value isn't a keyword initializer for this Property |
| */ |
| public String checkValueKeywords(String keyword) { |
| if (keywords != null) { |
| String value = (String)keywords.get(keyword); |
| if (value != null) { |
| return value; |
| } |
| } |
| // TODO: should return null here? |
| return keyword; |
| } |
| |
| /** |
| * Return a Property object based on the passed Property object. |
| * This method is called if the Property object built by the parser |
| * isn't the right type for this property. |
| * It is overridden by subclasses. |
| * @param p The Property object return by the expression parser |
| * @param propertyList The PropertyList object being built for this FO. |
| * @param fo The parent FO for the FO whose property is being made. |
| * @return A Property of the correct type or null if the parsed value |
| * can't be converted to the correct type. |
| * @throws PropertyException for invalid or inconsistent FO input |
| */ |
| protected Property convertProperty(Property p, |
| PropertyList propertyList, |
| FObj fo) throws PropertyException { |
| return null; |
| } |
| |
| /** |
| * For properties that have more than one legal way to be specified, |
| * this routine should be overridden to attempt to set them based upon |
| * the other methods. For example, colors may be specified using an RGB |
| * model, or they may be specified using an NCname. |
| * @param p property whose datatype should be converted |
| * @param propertyList collection of properties. (TODO: explain why |
| * this is needed, or remove it from the signature.) |
| * @param fo The parent FO for the FO whose property is being made. |
| * why this is needed, or remove it from the signature). |
| * @return an Property with the appropriate datatype used |
| * @throws PropertyException for invalid or inconsistent input |
| */ |
| protected Property convertPropertyDatatype(Property p, |
| PropertyList propertyList, |
| FObj fo) throws PropertyException { |
| return null; |
| } |
| |
| /** |
| * Return a Property object representing the value of this property, |
| * based on other property values for this FO. |
| * A special case is properties which inherit the specified value, |
| * rather than the computed value. |
| * @param propertyList The PropertyList for the FO. |
| * @return Property A computed Property value or null if no rules |
| * are specified to compute the value. |
| * @throws PropertyException for invalid or inconsistent FO input |
| */ |
| protected Property compute(PropertyList propertyList) |
| throws PropertyException { |
| if (corresponding != null) { |
| return corresponding.compute(propertyList); |
| } |
| return null; // standard |
| } |
| |
| /** |
| * For properties that can be set by shorthand properties, this method |
| * should return the Property, if any, that is parsed from any |
| * shorthand properties that affect this property. |
| * This method expects to be overridden by subclasses. |
| * For example, the border-right-width property could be set implicitly |
| * from the border shorthand property, the border-width shorthand |
| * property, or the border-right shorthand property. This method should |
| * be overridden in the appropriate subclass to check each of these, and |
| * return an appropriate border-right-width Property object. |
| * @param propertyList the collection of properties to be considered |
| * @return the Property, if found, the correspons, otherwise, null |
| * @throws PropertyException if there is a problem while evaluating the shorthand |
| */ |
| public Property getShorthand(PropertyList propertyList) |
| throws PropertyException { |
| if (shorthands == null) { |
| return null; |
| } |
| Property prop; |
| int n = shorthands.length; |
| for (int i = 0; i < n && shorthands[i] != null; i++) { |
| PropertyMaker shorthand = shorthands[i]; |
| prop = propertyList.getExplicit(shorthand.propId); |
| if (prop != null) { |
| ShorthandParser parser = shorthand.datatypeParser; |
| Property p = parser.getValueForProperty(getPropId(), |
| prop, this, propertyList); |
| if (p != null) { |
| return p; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** @return the name of the property this maker is used for. */ |
| public String getName() { |
| return FOPropertyMapping.getPropertyName(propId); |
| } |
| |
| /** |
| * Return a clone of the makers. Used by useGeneric() to clone the |
| * subproperty makers of the generic compound makers. |
| * {@inheritDoc} |
| */ |
| @Override |
| public Object clone() { |
| try { |
| return super.clone(); |
| } catch (CloneNotSupportedException exc) { |
| return null; |
| } |
| } |
| } |