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 015 package org.apache.tapestry.spec; 016 017 import org.apache.hivemind.ApplicationRuntimeException; 018 import org.apache.hivemind.Location; 019 import org.apache.hivemind.Resource; 020 import org.apache.hivemind.util.ToStringBuilder; 021 import org.apache.tapestry.Tapestry; 022 023 import java.util.*; 024 025 /** 026 * Specification for a library. 027 * {@link org.apache.tapestry.spec.ApplicationSpecification}is a specialized 028 * kind of library. 029 * 030 * @author Howard Lewis Ship 031 * @since 2.2bv 032 */ 033 034 public class LibrarySpecification extends LocatablePropertyHolder implements ILibrarySpecification 035 { 036 037 /** 038 * Map of page name to page specification path. 039 */ 040 041 private Map _pages; 042 043 /** 044 * Map of component alias to component specification path. 045 */ 046 private Map _components; 047 048 /** 049 * Map of library id to library specification path. 050 */ 051 052 private Map _libraries; 053 054 private String _description; 055 056 /** 057 * Map of extension name to {@link IExtensionSpecification}. 058 */ 059 060 private Map _extensions; 061 062 /** 063 * Map of extension name to Object for instantiated extensions. 064 */ 065 066 private Map _instantiatedExtensions; 067 068 /** 069 * The XML Public Id used when the library specification was read (if 070 * applicable). 071 * 072 * @since 2.2 073 */ 074 075 private String _publicId; 076 077 /** 078 * The location of the specification. 079 */ 080 081 private Resource _specificationLocation; 082 083 public String getLibrarySpecificationPath(String id) 084 { 085 return (String) get(_libraries, id); 086 } 087 088 /** 089 * Sets the specification path for an embedded library. 090 * 091 * @throws IllegalArgumentException 092 * if a library with the given id already exists 093 */ 094 095 public void setLibrarySpecificationPath(String id, String path) 096 { 097 if (_libraries == null) _libraries = new HashMap(); 098 099 if (_libraries.containsKey(id)) 100 throw new IllegalArgumentException(Tapestry.format("LibrarySpecification.duplicate-child-namespace-id", id)); 101 102 _libraries.put(id, path); 103 } 104 105 public List getLibraryIds() 106 { 107 return sortedKeys(_libraries); 108 } 109 110 public String getPageSpecificationPath(String name) 111 { 112 return (String) get(_pages, name); 113 } 114 115 public void setPageSpecificationPath(String name, String path) 116 { 117 if (_pages == null) 118 _pages = new HashMap(); 119 120 if (_pages.containsKey(name)) 121 throw new IllegalArgumentException(Tapestry.format("LibrarySpecification.duplicate-page-name", name)); 122 123 _pages.put(name, path); 124 } 125 126 public List getPageNames() 127 { 128 return sortedKeys(_pages); 129 } 130 131 public void setComponentSpecificationPath(String alias, String path) 132 { 133 if (_components == null) 134 _components = new HashMap(); 135 136 if (_components.containsKey(alias)) 137 throw new IllegalArgumentException(Tapestry.format("LibrarySpecification.duplicate-component-alias", alias)); 138 139 _components.put(alias, path); 140 } 141 142 public String getComponentSpecificationPath(String alias) 143 { 144 return (String) get(_components, alias); 145 } 146 147 /** 148 * @since 3.0 149 */ 150 151 public List getComponentTypes() 152 { 153 return sortedKeys(_components); 154 } 155 156 private List sortedKeys(Map map) 157 { 158 if (map == null) 159 return Collections.EMPTY_LIST; 160 161 List result = new ArrayList(map.keySet()); 162 163 Collections.sort(result); 164 165 return result; 166 } 167 168 private Object get(Map map, Object key) 169 { 170 if (map == null) 171 return null; 172 173 return map.get(key); 174 } 175 176 /** 177 * Returns the documentation for this library.. 178 */ 179 180 public String getDescription() 181 { 182 return _description; 183 } 184 185 /** 186 * Sets the documentation for this library. 187 */ 188 189 public void setDescription(String description) 190 { 191 _description = description; 192 } 193 194 /** 195 * Returns a Map of extensions; key is extension name, value is 196 * {@link org.apache.tapestry.spec.IExtensionSpecification}. May return 197 * null. The returned Map is immutable. 198 */ 199 200 public Map getExtensionSpecifications() 201 { 202 if (_extensions == null) 203 return null; 204 205 return Collections.unmodifiableMap(_extensions); 206 } 207 208 /** 209 * Adds another extension specification. 210 * 211 * @throws IllegalArgumentException 212 * if an extension with the given name already exists. 213 */ 214 public void addExtensionSpecification(String name, IExtensionSpecification extension) 215 { 216 if (_extensions == null) 217 _extensions = new HashMap(); 218 219 if (_extensions.containsKey(name)) 220 throw new IllegalArgumentException(Tapestry.format("LibrarySpecification.duplicate-extension-name", 221 this, name)); 222 223 _extensions.put(name, extension); 224 } 225 226 /** 227 * Returns a sorted List of the names of all extensions. May return the 228 * empty list, but won't return null. 229 */ 230 231 public synchronized List getExtensionNames() 232 { 233 return sortedKeys(_instantiatedExtensions); 234 } 235 236 /** 237 * Returns the named IExtensionSpecification, or null if it doesn't exist. 238 */ 239 240 public IExtensionSpecification getExtensionSpecification(String name) 241 { 242 if (_extensions == null) 243 return null; 244 245 return (IExtensionSpecification) _extensions.get(name); 246 } 247 248 /** 249 * Returns true if this library specification has a specification for the 250 * named extension. 251 */ 252 253 public boolean checkExtension(String name) 254 { 255 if (_extensions == null) 256 return false; 257 258 return _extensions.containsKey(name); 259 } 260 261 /** 262 * Returns an instantiated extension. Extensions are created as needed and 263 * cached for later use. 264 * 265 * @throws IllegalArgumentException 266 * if no extension specification exists for the given name. 267 */ 268 269 public synchronized Object getExtension(String name) 270 { 271 return getExtension(name, null); 272 } 273 274 /** @since 3.0 * */ 275 276 public synchronized Object getExtension(String name, Class typeConstraint) 277 { 278 if (_instantiatedExtensions == null) 279 _instantiatedExtensions = new HashMap(); 280 281 Object result = _instantiatedExtensions.get(name); 282 IExtensionSpecification spec = getExtensionSpecification(name); 283 284 if (spec == null) 285 throw new IllegalArgumentException(Tapestry.format("LibrarySpecification.no-such-extension", name)); 286 287 if (result == null) 288 { 289 290 result = spec.instantiateExtension(); 291 292 _instantiatedExtensions.put(name, result); 293 } 294 295 if (typeConstraint != null) 296 applyTypeConstraint(name, result, typeConstraint, spec.getLocation()); 297 298 return result; 299 } 300 301 /** 302 * Checks that an extension conforms to the supplied type constraint. 303 * 304 * @param name 305 * Name of the extension to apply constraint check to. 306 * @param extension 307 * Object extension. 308 * @param typeConstraint 309 * Constraint to check. 310 * @param location 311 * Location of specified extension. 312 * 313 * @throws IllegalArgumentException 314 * if the extension fails the check. 315 * @since 3.0 316 */ 317 318 protected void applyTypeConstraint(String name, Object extension, 319 Class typeConstraint, Location location) 320 { 321 Class extensionClass = extension.getClass(); 322 323 // Can you assign an instance of the extension to a variable 324 // of type typeContraint legally? 325 326 if (typeConstraint.isAssignableFrom(extensionClass)) 327 return; 328 329 String key = typeConstraint.isInterface() 330 ? "LibrarySpecification.extension-does-not-implement-interface" 331 : "LibrarySpecification.extension-not-a-subclass"; 332 333 throw new ApplicationRuntimeException( 334 Tapestry.format(key, name, extensionClass.getName(), typeConstraint.getName()), location, null); 335 } 336 337 /** 338 * Invoked after the entire specification has been constructed to 339 * instantiate any extensions marked immediate. 340 */ 341 342 public synchronized void instantiateImmediateExtensions() 343 { 344 if (_extensions == null) 345 return; 346 347 Iterator i = _extensions.entrySet().iterator(); 348 349 while(i.hasNext()) 350 { 351 Map.Entry entry = (Map.Entry) i.next(); 352 353 IExtensionSpecification spec = (IExtensionSpecification) entry.getValue(); 354 355 if (!spec.isImmediate()) 356 continue; 357 358 String name = (String) entry.getKey(); 359 360 getExtension(name); 361 } 362 363 } 364 365 /** 366 * Returns the extensions map. 367 * 368 * @return Map of objects. 369 */ 370 371 protected Map getExtensions() 372 { 373 return _extensions; 374 } 375 376 /** 377 * Updates the extension map. 378 * 379 * @param extension 380 * A Map of extension specification paths keyed on extension id. 381 * <p> 382 * The map is retained, not copied. 383 */ 384 385 protected void setExtensions(Map extension) 386 { 387 _extensions = extension; 388 } 389 390 /** 391 * Returns the libraries map. 392 * 393 * @return Map of {@link LibrarySpecification}. 394 */ 395 396 protected Map getLibraries() 397 { 398 return _libraries; 399 } 400 401 /** 402 * Updates the library map. 403 * 404 * @param libraries 405 * A Map of library specification paths keyed on library id. 406 * <p> 407 * The map is retained, not copied. 408 */ 409 410 protected void setLibraries(Map libraries) 411 { 412 _libraries = libraries; 413 } 414 415 /** 416 * Returns the pages map. 417 * 418 * @return Map of {@link IComponentSpecification}. 419 */ 420 421 protected Map getPages() 422 { 423 return _pages; 424 } 425 426 /** 427 * Updates the page map. 428 * 429 * @param pages 430 * A Map of page specification paths keyed on page id. 431 * <p> 432 * The map is retained, not copied. 433 */ 434 435 protected void setPages(Map pages) 436 { 437 _pages = pages; 438 } 439 440 /** 441 * Returns the components map. 442 * 443 * @return Map of {@link IContainedComponent}. 444 */ 445 446 protected Map getComponents() 447 { 448 return _components; 449 } 450 451 /** 452 * Updates the components map. 453 * 454 * @param components 455 * A Map of {@link IContainedComponent}keyed on component id. 456 * The map is retained, not copied. 457 */ 458 459 protected void setComponents(Map components) 460 { 461 _components = components; 462 } 463 464 /** 465 * Returns the XML Public Id for the library file, or null if not 466 * applicable. 467 * <p> 468 * This method exists as a convienience for the Spindle plugin. A previous 469 * method used an arbitrary version string, the public id is more useful and 470 * less ambiguous. 471 */ 472 473 public String getPublicId() 474 { 475 return _publicId; 476 } 477 478 public void setPublicId(String publicId) 479 { 480 _publicId = publicId; 481 } 482 483 /** @since 3.0 * */ 484 485 public Resource getSpecificationLocation() 486 { 487 return _specificationLocation; 488 } 489 490 /** @since 3.0 * */ 491 492 public void setSpecificationLocation(Resource specificationLocation) 493 { 494 _specificationLocation = specificationLocation; 495 } 496 497 /** @since 3.0 * */ 498 499 public synchronized String toString() 500 { 501 ToStringBuilder builder = new ToStringBuilder(this); 502 503 builder.append("components", _components); 504 builder.append("description", _description); 505 builder.append("instantiatedExtensions", _instantiatedExtensions); 506 builder.append("libraries", _libraries); 507 builder.append("pages", _pages); 508 builder.append("publicId", _publicId); 509 builder.append("specificationLocation", _specificationLocation); 510 511 extendDescription(builder); 512 513 return builder.toString(); 514 } 515 516 /** 517 * Does nothing, subclasses may override to add additional description. 518 * 519 * @see #toString() 520 * @since 3.0 521 */ 522 523 protected void extendDescription(ToStringBuilder builder) 524 { 525 } 526 527 }