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