001// Copyright 2004, 2005 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry.spec; 016 017import java.util.ArrayList; 018import java.util.Collection; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.hivemind.ApplicationRuntimeException; 028import org.apache.hivemind.HiveMind; 029import org.apache.hivemind.Resource; 030import org.apache.hivemind.util.ToStringBuilder; 031 032/** 033 * A specification for a component, as read from an XML specification file. 034 * <p> 035 * A specification consists of 036 * <ul> 037 * <li>An implementing class 038 * <li>An optional description 039 * <li>A set of contained components 040 * <li>Bindings for the properties of each contained component 041 * <li>A set of named assets 042 * <li>Definitions for managed beans 043 * <li>Any reserved names (used for HTML attributes) 044 * <li>Declared properties 045 * <li>Property injections 046 * </ul> 047 * <p> 048 * From this information, an actual component may be instantiated and initialized. Instantiating a 049 * component is usually a recursive process, since to initialize a container component, it is 050 * necessary to instantiate and initialize its contained components as well. 051 * 052 * @see org.apache.tapestry.IComponent 053 * @see IContainedComponent 054 * @see org.apache.tapestry.engine.IPageLoader 055 * @author Howard Lewis Ship 056 */ 057 058public class ComponentSpecification extends LocatablePropertyHolder implements 059 IComponentSpecification 060{ 061 private String _componentClassName; 062 063 /** @since 1.0.9 * */ 064 065 private String _description; 066 067 /** 068 * Keyed on component id, value is {@link IContainedComponent}. 069 */ 070 071 protected Map _components; 072 073 /** 074 * Keyed on asset name, value is {@link IAssetSpecification}. 075 */ 076 077 protected Map _assets; 078 079 /** 080 * Defines all formal parameters. Keyed on parameter name, value is 081 * {@link IParameterSpecification}. 082 */ 083 084 protected Map _parameters; 085 086 /** 087 * Defines all helper beans. Keyed on name, value is {@link IBeanSpecification}. 088 * 089 * @since 1.0.4 090 */ 091 092 protected Map _beans; 093 094 /** 095 * The names of all reserved informal parameter names (as lower-case). This allows the page 096 * loader to filter out any informal parameters during page load, rather than during render. 097 * 098 * @since 1.0.5 099 */ 100 101 protected Set _reservedParameterNames; 102 103 /** 104 * Is the component allowed to have a body (that is, wrap other elements?). 105 */ 106 107 private boolean _allowBody = true; 108 109 /** 110 * Is the component allow to have informal parameter specified. 111 */ 112 113 private boolean _allowInformalParameters = true; 114 115 /** 116 * The XML Public Id used when the page or component specification was read (if applicable). 117 * 118 * @since 2.2 119 */ 120 121 private String _publicId; 122 123 /** 124 * Indicates that the specification is for a page, not a component. 125 * 126 * @since 2.2 127 */ 128 129 private boolean _pageSpecification; 130 131 /** 132 * The location from which the specification was obtained. 133 * 134 * @since 3.0 135 */ 136 137 private Resource _specificationLocation; 138 139 /** 140 * A Map of {@link IPropertySpecification}keyed on the name of the property. 141 * 142 * @since 3.0 143 */ 144 145 private Map _propertySpecifications; 146 147 /** 148 * List of {@link InjectSpecification}. 149 * 150 * @since 4.0 151 */ 152 153 private List _injectSpecifications; 154 155 /** 156 * Keyed on property name, value is some other object (such as an IAssetSpecification) that has 157 * claimed a property of the page. 158 * 159 * @since 4.0 160 */ 161 162 private Map _claimedProperties; 163 164 /** 165 * @since 4.0 166 */ 167 168 private boolean _deprecated = false; 169 170 /** 171 * @throws ApplicationRuntimeException 172 * if the name already exists. 173 */ 174 175 public void addAsset(String name, IAssetSpecification asset) 176 { 177 if (_assets == null) 178 _assets = new HashMap(); 179 180 IAssetSpecification existing = (IAssetSpecification) _assets.get(name); 181 182 if (existing != null) 183 throw new ApplicationRuntimeException(SpecMessages.duplicateAsset(name, existing), 184 asset.getLocation(), null); 185 186 claimProperty(asset.getPropertyName(), asset); 187 188 _assets.put(name, asset); 189 190 } 191 192 /** 193 * @throws ApplicationRuntimeException 194 * if the id is already defined. 195 */ 196 197 public void addComponent(String id, IContainedComponent component) 198 { 199 if (_components == null) 200 _components = new HashMap(); 201 202 IContainedComponent existing = (IContainedComponent) _components.get(id); 203 204 if (existing != null) 205 throw new ApplicationRuntimeException(SpecMessages.duplicateComponent(id, existing), 206 component.getLocation(), null); 207 208 _components.put(id, component); 209 210 claimProperty(component.getPropertyName(), component); 211 } 212 213 /** 214 * Adds the parameter. The name is added as a reserved name. 215 * 216 * @throws ApplicationRuntimeException 217 * if the name already exists. 218 */ 219 220 public void addParameter(IParameterSpecification spec) 221 { 222 if (_parameters == null) 223 _parameters = new HashMap(); 224 225 String name = spec.getParameterName(); 226 227 addParameterByName(name, spec); 228 229 Iterator i = spec.getAliasNames().iterator(); 230 while (i.hasNext()) 231 { 232 String alias = (String) i.next(); 233 234 addParameterByName(alias, spec); 235 } 236 237 claimProperty(spec.getPropertyName(), spec); 238 } 239 240 private void addParameterByName(String name, IParameterSpecification spec) 241 { 242 IParameterSpecification existing = (IParameterSpecification) _parameters.get(name); 243 244 if (existing != null) 245 throw new ApplicationRuntimeException(SpecMessages.duplicateParameter(name, existing), 246 spec.getLocation(), null); 247 248 _parameters.put(name, spec); 249 250 addReservedParameterName(name); 251 } 252 253 /** 254 * Returns true if the component is allowed to wrap other elements (static HTML or other 255 * components). The default is true. 256 * 257 * @see #setAllowBody(boolean) 258 */ 259 260 public boolean getAllowBody() 261 { 262 return _allowBody; 263 } 264 265 /** 266 * Returns true if the component allows informal parameters (parameters not formally defined). 267 * Informal parameters are generally used to create additional HTML attributes for an HTML tag 268 * rendered by the component. This is often used to specify JavaScript event handlers or the 269 * class of the component (for Cascarding Style Sheets). 270 * <p> 271 * The default value is true. 272 * 273 * @see #setAllowInformalParameters(boolean) 274 */ 275 276 public boolean getAllowInformalParameters() 277 { 278 return _allowInformalParameters; 279 } 280 281 /** 282 * Returns the {@link IAssetSpecification}with the given name, or null if no such specification 283 * exists. 284 * 285 * @see #addAsset(String,IAssetSpecification) 286 */ 287 288 public IAssetSpecification getAsset(String name) 289 { 290 291 return (IAssetSpecification) get(_assets, name); 292 } 293 294 /** 295 * Returns a <code>List</code> of the String names of all assets, in alphabetical order 296 */ 297 298 public List getAssetNames() 299 { 300 return sortedKeys(_assets); 301 } 302 303 /** 304 * Returns the specification of a contained component with the given id, or null if no such 305 * contained component exists. 306 * 307 * @see #addComponent(String, IContainedComponent) 308 */ 309 310 public IContainedComponent getComponent(String id) 311 { 312 return (IContainedComponent) get(_components, id); 313 } 314 315 public String getComponentClassName() 316 { 317 return _componentClassName; 318 } 319 320 /** 321 * Returns an <code>List</code> of the String names of the {@link IContainedComponent}s for 322 * this component. 323 * 324 * @see #addComponent(String, IContainedComponent) 325 */ 326 327 public List getComponentIds() 328 { 329 return sortedKeys(_components); 330 } 331 332 /** 333 * Returns the specification of a parameter with the given name, or null if no such parameter 334 * exists. 335 * 336 * @see #addParameter(String, IParameterSpecification) 337 */ 338 339 public IParameterSpecification getParameter(String name) 340 { 341 return (IParameterSpecification) get(_parameters, name); 342 } 343 344 public Collection getRequiredParameters() 345 { 346 if (_parameters == null) 347 return Collections.EMPTY_LIST; 348 349 Collection result = new ArrayList(); 350 351 Iterator i = _parameters.entrySet().iterator(); 352 while (i.hasNext()) 353 { 354 Map.Entry entry = (Map.Entry) i.next(); 355 String name = (String) entry.getKey(); 356 IParameterSpecification spec = (IParameterSpecification) entry.getValue(); 357 358 if (!spec.isRequired()) 359 continue; 360 361 if (!name.equals(spec.getParameterName())) 362 continue; 363 364 result.add(spec); 365 } 366 367 return result; 368 } 369 370 /** 371 * Returns a List of of String names of all parameters. This list is in alphabetical order. 372 * 373 * @see #addParameter(String, IParameterSpecification) 374 */ 375 376 public List getParameterNames() 377 { 378 return sortedKeys(_parameters); 379 } 380 381 public void setAllowBody(boolean value) 382 { 383 _allowBody = value; 384 } 385 386 public void setAllowInformalParameters(boolean value) 387 { 388 _allowInformalParameters = value; 389 } 390 391 public void setComponentClassName(String value) 392 { 393 _componentClassName = value; 394 } 395 396 /** 397 * @since 1.0.4 398 * @throws ApplicationRuntimeException 399 * if the bean already has a specification. 400 */ 401 402 public void addBeanSpecification(String name, IBeanSpecification specification) 403 { 404 if (_beans == null) 405 _beans = new HashMap(); 406 407 IBeanSpecification existing = (IBeanSpecification) _beans.get(name); 408 409 if (existing != null) 410 throw new ApplicationRuntimeException(SpecMessages.duplicateBean(name, existing), 411 specification.getLocation(), null); 412 413 claimProperty(specification.getPropertyName(), specification); 414 415 _beans.put(name, specification); 416 } 417 418 /** 419 * Returns the {@link IBeanSpecification}for the given name, or null if not such specification 420 * exists. 421 * 422 * @since 1.0.4 423 */ 424 425 public IBeanSpecification getBeanSpecification(String name) 426 { 427 return (IBeanSpecification) get(_beans, name); 428 } 429 430 /** 431 * Returns an unmodifiable collection of the names of all beans. 432 */ 433 434 public Collection getBeanNames() 435 { 436 if (_beans == null) 437 return Collections.EMPTY_LIST; 438 439 return Collections.unmodifiableCollection(_beans.keySet()); 440 } 441 442 /** 443 * Adds the value as a reserved name. Reserved names are not allowed as the names of informal 444 * parameters. Since the comparison is caseless, the value is converted to lowercase before 445 * being stored. 446 * 447 * @since 1.0.5 448 */ 449 450 public void addReservedParameterName(String value) 451 { 452 if (_reservedParameterNames == null) 453 _reservedParameterNames = new HashSet(); 454 455 _reservedParameterNames.add(value.toLowerCase()); 456 } 457 458 /** 459 * Returns true if the value specified is in the reserved name list. The comparison is caseless. 460 * All formal parameters are automatically in the reserved name list, as well as any additional 461 * reserved names specified in the component specification. The latter refer to HTML attributes 462 * generated directly by the component. 463 * 464 * @since 1.0.5 465 */ 466 467 public boolean isReservedParameterName(String value) 468 { 469 if (_reservedParameterNames == null) 470 return false; 471 472 return _reservedParameterNames.contains(value.toLowerCase()); 473 } 474 475 public String toString() 476 { 477 ToStringBuilder builder = new ToStringBuilder(this); 478 479 builder.append("componentClassName", _componentClassName); 480 builder.append("pageSpecification", _pageSpecification); 481 builder.append("specificationLocation", _specificationLocation); 482 builder.append("allowBody", _allowBody); 483 builder.append("allowInformalParameter", _allowInformalParameters); 484 485 return builder.toString(); 486 } 487 488 /** 489 * Returns the documentation for this component. 490 * 491 * @since 1.0.9 492 */ 493 494 public String getDescription() 495 { 496 return _description; 497 } 498 499 /** 500 * Sets the documentation for this component. 501 * 502 * @since 1.0.9 503 */ 504 505 public void setDescription(String description) 506 { 507 _description = description; 508 } 509 510 /** 511 * Returns the XML Public Id for the specification file, or null if not applicable. 512 * <p> 513 * This method exists as a convienience for the Spindle plugin. A previous method used an 514 * arbitrary version string, the public id is more useful and less ambiguous. 515 * 516 * @since 2.2 517 */ 518 519 public String getPublicId() 520 { 521 return _publicId; 522 } 523 524 /** @since 2.2 * */ 525 526 public void setPublicId(String publicId) 527 { 528 _publicId = publicId; 529 } 530 531 /** 532 * Returns true if the specification is known to be a page specification and not a component 533 * specification. Earlier versions of the framework did not distinguish between the two, but 534 * starting in 2.2, there are seperate XML entities for pages and components. Pages omit several 535 * attributes and entities related to parameters, as parameters only make sense for components. 536 * 537 * @since 2.2 538 */ 539 540 public boolean isPageSpecification() 541 { 542 return _pageSpecification; 543 } 544 545 /** @since 2.2 * */ 546 547 public void setPageSpecification(boolean pageSpecification) 548 { 549 _pageSpecification = pageSpecification; 550 } 551 552 /** @since 2.2 * */ 553 554 private List sortedKeys(Map input) 555 { 556 if (input == null) 557 return Collections.EMPTY_LIST; 558 559 List result = new ArrayList(input.keySet()); 560 561 Collections.sort(result); 562 563 return result; 564 } 565 566 /** @since 2.2 * */ 567 568 private Object get(Map map, Object key) 569 { 570 if (map == null) 571 return null; 572 573 return map.get(key); 574 } 575 576 /** @since 3.0 * */ 577 578 public Resource getSpecificationLocation() 579 { 580 return _specificationLocation; 581 } 582 583 /** @since 3.0 * */ 584 585 public void setSpecificationLocation(Resource specificationLocation) 586 { 587 _specificationLocation = specificationLocation; 588 } 589 590 /** 591 * Adds a new property specification. The name of the property must not already be defined (and 592 * must not change after being added). 593 * 594 * @since 3.0 595 */ 596 597 public void addPropertySpecification(IPropertySpecification spec) 598 { 599 if (_propertySpecifications == null) 600 _propertySpecifications = new HashMap(); 601 602 String name = spec.getName(); 603 IPropertySpecification existing = (IPropertySpecification) _propertySpecifications 604 .get(name); 605 606 if (existing != null) 607 throw new ApplicationRuntimeException(SpecMessages.duplicateProperty(name, existing), 608 spec.getLocation(), null); 609 610 claimProperty(name, spec); 611 612 _propertySpecifications.put(name, spec); 613 } 614 615 /** 616 * Returns a sorted, immutable list of the names of all 617 * {@link org.apache.tapestry.spec.IPropertySpecification}s. 618 * 619 * @since 3.0 620 */ 621 622 public List getPropertySpecificationNames() 623 { 624 return sortedKeys(_propertySpecifications); 625 } 626 627 /** 628 * Returns the named {@link org.apache.tapestry.spec.IPropertySpecification}, or null if no 629 * such specification exist. 630 * 631 * @since 3.0 632 * @see #addPropertySpecification(IPropertySpecification) 633 */ 634 635 public IPropertySpecification getPropertySpecification(String name) 636 { 637 return (IPropertySpecification) get(_propertySpecifications, name); 638 } 639 640 public void addInjectSpecification(InjectSpecification spec) 641 { 642 if (_injectSpecifications == null) 643 _injectSpecifications = new ArrayList(); 644 645 claimProperty(spec.getProperty(), spec); 646 647 _injectSpecifications.add(spec); 648 } 649 650 public List getInjectSpecifications() 651 { 652 return safeList(_injectSpecifications); 653 } 654 655 private List safeList(List input) 656 { 657 if (input == null) 658 return Collections.EMPTY_LIST; 659 660 return Collections.unmodifiableList(input); 661 } 662 663 private void claimProperty(String propertyName, Object subSpecification) 664 { 665 if (propertyName == null) 666 return; 667 668 if (_claimedProperties == null) 669 _claimedProperties = new HashMap(); 670 671 Object existing = _claimedProperties.get(propertyName); 672 673 if (existing != null) 674 throw new ApplicationRuntimeException(SpecMessages.claimedProperty( 675 propertyName, 676 existing), HiveMind.getLocation(subSpecification), null); 677 678 _claimedProperties.put(propertyName, subSpecification); 679 } 680 681 /** @since 4.0 */ 682 public boolean isDeprecated() 683 { 684 return _deprecated; 685 } 686 687 /** @since 4.0 */ 688 public void setDeprecated(boolean deprecated) 689 { 690 _deprecated = deprecated; 691 } 692 693 public Set getReservedParameterNames() 694 { 695 if (_reservedParameterNames == null) 696 return Collections.EMPTY_SET; 697 698 return Collections.unmodifiableSet(_reservedParameterNames); 699 } 700}