blob: e61632a0b94be81bf308ea014c79810abfc27789 [file] [log] [blame]
/*
* 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;
}
}
}