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