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 }