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    }