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 services.
437 *
438 * @return Map of service class names.
439 * @deprecated To be removed in release 4.1.
440 */
441
442 protected Map getServices()
443 {
444 return Collections.EMPTY_MAP;
445 }
446
447 /**
448 * Updates the services map.
449 *
450 * @param services
451 * A Map of the fully qualified names of classes which implement
452 * {@link org.apache.tapestry.engine.IEngineService}keyed on
453 * service id.
454 * <p>
455 * The map is retained, not copied.
456 * @deprecated To be removed in release 4.1.
457 */
458
459 protected void setServices(Map services)
460 {
461 }
462
463 /**
464 * Returns the components map.
465 *
466 * @return Map of {@link IContainedComponent}.
467 */
468
469 protected Map getComponents()
470 {
471 return _components;
472 }
473
474 /**
475 * Updates the components map.
476 *
477 * @param components
478 * A Map of {@link IContainedComponent}keyed on component id.
479 * The map is retained, not copied.
480 */
481
482 protected void setComponents(Map components)
483 {
484 _components = components;
485 }
486
487 /**
488 * Returns the XML Public Id for the library file, or null if not
489 * applicable.
490 * <p>
491 * This method exists as a convienience for the Spindle plugin. A previous
492 * method used an arbitrary version string, the public id is more useful and
493 * less ambiguous.
494 */
495
496 public String getPublicId()
497 {
498 return _publicId;
499 }
500
501 public void setPublicId(String publicId)
502 {
503 _publicId = publicId;
504 }
505
506 /** @since 3.0 * */
507
508 public Resource getSpecificationLocation()
509 {
510 return _specificationLocation;
511 }
512
513 /** @since 3.0 * */
514
515 public void setSpecificationLocation(Resource specificationLocation)
516 {
517 _specificationLocation = specificationLocation;
518 }
519
520 /** @since 3.0 * */
521
522 public synchronized String toString()
523 {
524 ToStringBuilder builder = new ToStringBuilder(this);
525
526 builder.append("components", _components);
527 builder.append("description", _description);
528 builder.append("instantiatedExtensions", _instantiatedExtensions);
529 builder.append("libraries", _libraries);
530 builder.append("pages", _pages);
531 builder.append("publicId", _publicId);
532 builder.append("specificationLocation", _specificationLocation);
533
534 extendDescription(builder);
535
536 return builder.toString();
537 }
538
539 /**
540 * Does nothing, subclasses may override to add additional description.
541 *
542 * @see #toString()
543 * @since 3.0
544 */
545
546 protected void extendDescription(ToStringBuilder builder)
547 {
548 }
549
550 }