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    }