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.Collection;
019 import java.util.Collections;
020 import java.util.HashMap;
021 import java.util.HashSet;
022 import java.util.Iterator;
023 import java.util.List;
024 import java.util.Map;
025 import java.util.Set;
026
027 import org.apache.hivemind.ApplicationRuntimeException;
028 import org.apache.hivemind.HiveMind;
029 import org.apache.hivemind.Resource;
030 import org.apache.hivemind.util.ToStringBuilder;
031
032 /**
033 * A specification for a component, as read from an XML specification file.
034 * <p>
035 * A specification consists of
036 * <ul>
037 * <li>An implementing class
038 * <li>An optional description
039 * <li>A set of contained components
040 * <li>Bindings for the properties of each contained component
041 * <li>A set of named assets
042 * <li>Definitions for managed beans
043 * <li>Any reserved names (used for HTML attributes)
044 * <li>Declared properties
045 * <li>Property injections
046 * </ul>
047 * <p>
048 * From this information, an actual component may be instantiated and initialized. Instantiating a
049 * component is usually a recursive process, since to initialize a container component, it is
050 * necessary to instantiate and initialize its contained components as well.
051 *
052 * @see org.apache.tapestry.IComponent
053 * @see IContainedComponent
054 * @see org.apache.tapestry.engine.IPageLoader
055 * @author Howard Lewis Ship
056 */
057
058 public class ComponentSpecification extends LocatablePropertyHolder implements
059 IComponentSpecification
060 {
061 private String _componentClassName;
062
063 /** @since 1.0.9 * */
064
065 private String _description;
066
067 /**
068 * Keyed on component id, value is {@link IContainedComponent}.
069 */
070
071 protected Map _components;
072
073 /**
074 * Keyed on asset name, value is {@link IAssetSpecification}.
075 */
076
077 protected Map _assets;
078
079 /**
080 * Defines all formal parameters. Keyed on parameter name, value is
081 * {@link IParameterSpecification}.
082 */
083
084 protected Map _parameters;
085
086 /**
087 * Defines all helper beans. Keyed on name, value is {@link IBeanSpecification}.
088 *
089 * @since 1.0.4
090 */
091
092 protected Map _beans;
093
094 /**
095 * The names of all reserved informal parameter names (as lower-case). This allows the page
096 * loader to filter out any informal parameters during page load, rather than during render.
097 *
098 * @since 1.0.5
099 */
100
101 protected Set _reservedParameterNames;
102
103 /**
104 * Is the component allowed to have a body (that is, wrap other elements?).
105 */
106
107 private boolean _allowBody = true;
108
109 /**
110 * Is the component allow to have informal parameter specified.
111 */
112
113 private boolean _allowInformalParameters = true;
114
115 /**
116 * The XML Public Id used when the page or component specification was read (if applicable).
117 *
118 * @since 2.2
119 */
120
121 private String _publicId;
122
123 /**
124 * Indicates that the specification is for a page, not a component.
125 *
126 * @since 2.2
127 */
128
129 private boolean _pageSpecification;
130
131 /**
132 * The location from which the specification was obtained.
133 *
134 * @since 3.0
135 */
136
137 private Resource _specificationLocation;
138
139 /**
140 * A Map of {@link IPropertySpecification}keyed on the name of the property.
141 *
142 * @since 3.0
143 */
144
145 private Map _propertySpecifications;
146
147 /**
148 * List of {@link InjectSpecification}.
149 *
150 * @since 4.0
151 */
152
153 private List _injectSpecifications;
154
155 /**
156 * Keyed on property name, value is some other object (such as an IAssetSpecification) that has
157 * claimed a property of the page.
158 *
159 * @since 4.0
160 */
161
162 private Map _claimedProperties;
163
164 /**
165 * @since 4.0
166 */
167
168 private boolean _deprecated = false;
169
170 /**
171 * @throws ApplicationRuntimeException
172 * if the name already exists.
173 */
174
175 public void addAsset(String name, IAssetSpecification asset)
176 {
177 if (_assets == null)
178 _assets = new HashMap();
179
180 IAssetSpecification existing = (IAssetSpecification) _assets.get(name);
181
182 if (existing != null)
183 throw new ApplicationRuntimeException(SpecMessages.duplicateAsset(name, existing),
184 asset.getLocation(), null);
185
186 claimProperty(asset.getPropertyName(), asset);
187
188 _assets.put(name, asset);
189
190 }
191
192 /**
193 * @throws ApplicationRuntimeException
194 * if the id is already defined.
195 */
196
197 public void addComponent(String id, IContainedComponent component)
198 {
199 if (_components == null)
200 _components = new HashMap();
201
202 IContainedComponent existing = (IContainedComponent) _components.get(id);
203
204 if (existing != null)
205 throw new ApplicationRuntimeException(SpecMessages.duplicateComponent(id, existing),
206 component.getLocation(), null);
207
208 _components.put(id, component);
209
210 claimProperty(component.getPropertyName(), component);
211 }
212
213 /**
214 * Adds the parameter. The name is added as a reserved name.
215 *
216 * @throws ApplicationRuntimeException
217 * if the name already exists.
218 */
219
220 public void addParameter(IParameterSpecification spec)
221 {
222 if (_parameters == null)
223 _parameters = new HashMap();
224
225 String name = spec.getParameterName();
226
227 addParameterByName(name, spec);
228
229 Iterator i = spec.getAliasNames().iterator();
230 while (i.hasNext())
231 {
232 String alias = (String) i.next();
233
234 addParameterByName(alias, spec);
235 }
236
237 claimProperty(spec.getPropertyName(), spec);
238 }
239
240 private void addParameterByName(String name, IParameterSpecification spec)
241 {
242 IParameterSpecification existing = (IParameterSpecification) _parameters.get(name);
243
244 if (existing != null)
245 throw new ApplicationRuntimeException(SpecMessages.duplicateParameter(name, existing),
246 spec.getLocation(), null);
247
248 _parameters.put(name, spec);
249
250 addReservedParameterName(name);
251 }
252
253 /**
254 * Returns true if the component is allowed to wrap other elements (static HTML or other
255 * components). The default is true.
256 *
257 * @see #setAllowBody(boolean)
258 */
259
260 public boolean getAllowBody()
261 {
262 return _allowBody;
263 }
264
265 /**
266 * Returns true if the component allows informal parameters (parameters not formally defined).
267 * Informal parameters are generally used to create additional HTML attributes for an HTML tag
268 * rendered by the component. This is often used to specify JavaScript event handlers or the
269 * class of the component (for Cascarding Style Sheets).
270 * <p>
271 * The default value is true.
272 *
273 * @see #setAllowInformalParameters(boolean)
274 */
275
276 public boolean getAllowInformalParameters()
277 {
278 return _allowInformalParameters;
279 }
280
281 /**
282 * Returns the {@link IAssetSpecification}with the given name, or null if no such specification
283 * exists.
284 *
285 * @see #addAsset(String,IAssetSpecification)
286 */
287
288 public IAssetSpecification getAsset(String name)
289 {
290
291 return (IAssetSpecification) get(_assets, name);
292 }
293
294 /**
295 * Returns a <code>List</code> of the String names of all assets, in alphabetical order
296 */
297
298 public List getAssetNames()
299 {
300 return sortedKeys(_assets);
301 }
302
303 /**
304 * Returns the specification of a contained component with the given id, or null if no such
305 * contained component exists.
306 *
307 * @see #addComponent(String, IContainedComponent)
308 */
309
310 public IContainedComponent getComponent(String id)
311 {
312 return (IContainedComponent) get(_components, id);
313 }
314
315 public String getComponentClassName()
316 {
317 return _componentClassName;
318 }
319
320 /**
321 * Returns an <code>List</code> of the String names of the {@link IContainedComponent}s for
322 * this component.
323 *
324 * @see #addComponent(String, IContainedComponent)
325 */
326
327 public List getComponentIds()
328 {
329 return sortedKeys(_components);
330 }
331
332 /**
333 * Returns the specification of a parameter with the given name, or null if no such parameter
334 * exists.
335 *
336 * @see #addParameter(String, IParameterSpecification)
337 */
338
339 public IParameterSpecification getParameter(String name)
340 {
341 return (IParameterSpecification) get(_parameters, name);
342 }
343
344 public Collection getRequiredParameters()
345 {
346 if (_parameters == null)
347 return Collections.EMPTY_LIST;
348
349 Collection result = new ArrayList();
350
351 Iterator i = _parameters.entrySet().iterator();
352 while (i.hasNext())
353 {
354 Map.Entry entry = (Map.Entry) i.next();
355 String name = (String) entry.getKey();
356 IParameterSpecification spec = (IParameterSpecification) entry.getValue();
357
358 if (!spec.isRequired())
359 continue;
360
361 if (!name.equals(spec.getParameterName()))
362 continue;
363
364 result.add(spec);
365 }
366
367 return result;
368 }
369
370 /**
371 * Returns a List of of String names of all parameters. This list is in alphabetical order.
372 *
373 * @see #addParameter(String, IParameterSpecification)
374 */
375
376 public List getParameterNames()
377 {
378 return sortedKeys(_parameters);
379 }
380
381 public void setAllowBody(boolean value)
382 {
383 _allowBody = value;
384 }
385
386 public void setAllowInformalParameters(boolean value)
387 {
388 _allowInformalParameters = value;
389 }
390
391 public void setComponentClassName(String value)
392 {
393 _componentClassName = value;
394 }
395
396 /**
397 * @since 1.0.4
398 * @throws ApplicationRuntimeException
399 * if the bean already has a specification.
400 */
401
402 public void addBeanSpecification(String name, IBeanSpecification specification)
403 {
404 if (_beans == null)
405 _beans = new HashMap();
406
407 IBeanSpecification existing = (IBeanSpecification) _beans.get(name);
408
409 if (existing != null)
410 throw new ApplicationRuntimeException(SpecMessages.duplicateBean(name, existing),
411 specification.getLocation(), null);
412
413 claimProperty(specification.getPropertyName(), specification);
414
415 _beans.put(name, specification);
416 }
417
418 /**
419 * Returns the {@link IBeanSpecification}for the given name, or null if not such specification
420 * exists.
421 *
422 * @since 1.0.4
423 */
424
425 public IBeanSpecification getBeanSpecification(String name)
426 {
427 return (IBeanSpecification) get(_beans, name);
428 }
429
430 /**
431 * Returns an unmodifiable collection of the names of all beans.
432 */
433
434 public Collection getBeanNames()
435 {
436 if (_beans == null)
437 return Collections.EMPTY_LIST;
438
439 return Collections.unmodifiableCollection(_beans.keySet());
440 }
441
442 /**
443 * Adds the value as a reserved name. Reserved names are not allowed as the names of informal
444 * parameters. Since the comparison is caseless, the value is converted to lowercase before
445 * being stored.
446 *
447 * @since 1.0.5
448 */
449
450 public void addReservedParameterName(String value)
451 {
452 if (_reservedParameterNames == null)
453 _reservedParameterNames = new HashSet();
454
455 _reservedParameterNames.add(value.toLowerCase());
456 }
457
458 /**
459 * Returns true if the value specified is in the reserved name list. The comparison is caseless.
460 * All formal parameters are automatically in the reserved name list, as well as any additional
461 * reserved names specified in the component specification. The latter refer to HTML attributes
462 * generated directly by the component.
463 *
464 * @since 1.0.5
465 */
466
467 public boolean isReservedParameterName(String value)
468 {
469 if (_reservedParameterNames == null)
470 return false;
471
472 return _reservedParameterNames.contains(value.toLowerCase());
473 }
474
475 public String toString()
476 {
477 ToStringBuilder builder = new ToStringBuilder(this);
478
479 builder.append("componentClassName", _componentClassName);
480 builder.append("pageSpecification", _pageSpecification);
481 builder.append("specificationLocation", _specificationLocation);
482 builder.append("allowBody", _allowBody);
483 builder.append("allowInformalParameter", _allowInformalParameters);
484
485 return builder.toString();
486 }
487
488 /**
489 * Returns the documentation for this component.
490 *
491 * @since 1.0.9
492 */
493
494 public String getDescription()
495 {
496 return _description;
497 }
498
499 /**
500 * Sets the documentation for this component.
501 *
502 * @since 1.0.9
503 */
504
505 public void setDescription(String description)
506 {
507 _description = description;
508 }
509
510 /**
511 * Returns the XML Public Id for the specification file, or null if not applicable.
512 * <p>
513 * This method exists as a convienience for the Spindle plugin. A previous method used an
514 * arbitrary version string, the public id is more useful and less ambiguous.
515 *
516 * @since 2.2
517 */
518
519 public String getPublicId()
520 {
521 return _publicId;
522 }
523
524 /** @since 2.2 * */
525
526 public void setPublicId(String publicId)
527 {
528 _publicId = publicId;
529 }
530
531 /**
532 * Returns true if the specification is known to be a page specification and not a component
533 * specification. Earlier versions of the framework did not distinguish between the two, but
534 * starting in 2.2, there are seperate XML entities for pages and components. Pages omit several
535 * attributes and entities related to parameters, as parameters only make sense for components.
536 *
537 * @since 2.2
538 */
539
540 public boolean isPageSpecification()
541 {
542 return _pageSpecification;
543 }
544
545 /** @since 2.2 * */
546
547 public void setPageSpecification(boolean pageSpecification)
548 {
549 _pageSpecification = pageSpecification;
550 }
551
552 /** @since 2.2 * */
553
554 private List sortedKeys(Map input)
555 {
556 if (input == null)
557 return Collections.EMPTY_LIST;
558
559 List result = new ArrayList(input.keySet());
560
561 Collections.sort(result);
562
563 return result;
564 }
565
566 /** @since 2.2 * */
567
568 private Object get(Map map, Object key)
569 {
570 if (map == null)
571 return null;
572
573 return map.get(key);
574 }
575
576 /** @since 3.0 * */
577
578 public Resource getSpecificationLocation()
579 {
580 return _specificationLocation;
581 }
582
583 /** @since 3.0 * */
584
585 public void setSpecificationLocation(Resource specificationLocation)
586 {
587 _specificationLocation = specificationLocation;
588 }
589
590 /**
591 * Adds a new property specification. The name of the property must not already be defined (and
592 * must not change after being added).
593 *
594 * @since 3.0
595 */
596
597 public void addPropertySpecification(IPropertySpecification spec)
598 {
599 if (_propertySpecifications == null)
600 _propertySpecifications = new HashMap();
601
602 String name = spec.getName();
603 IPropertySpecification existing = (IPropertySpecification) _propertySpecifications
604 .get(name);
605
606 if (existing != null)
607 throw new ApplicationRuntimeException(SpecMessages.duplicateProperty(name, existing),
608 spec.getLocation(), null);
609
610 claimProperty(name, spec);
611
612 _propertySpecifications.put(name, spec);
613 }
614
615 /**
616 * Returns a sorted, immutable list of the names of all
617 * {@link org.apache.tapestry.spec.IPropertySpecification}s.
618 *
619 * @since 3.0
620 */
621
622 public List getPropertySpecificationNames()
623 {
624 return sortedKeys(_propertySpecifications);
625 }
626
627 /**
628 * Returns the named {@link org.apache.tapestry.spec.IPropertySpecification}, or null if no
629 * such specification exist.
630 *
631 * @since 3.0
632 * @see #addPropertySpecification(IPropertySpecification)
633 */
634
635 public IPropertySpecification getPropertySpecification(String name)
636 {
637 return (IPropertySpecification) get(_propertySpecifications, name);
638 }
639
640 public void addInjectSpecification(InjectSpecification spec)
641 {
642 if (_injectSpecifications == null)
643 _injectSpecifications = new ArrayList();
644
645 claimProperty(spec.getProperty(), spec);
646
647 _injectSpecifications.add(spec);
648 }
649
650 public List getInjectSpecifications()
651 {
652 return safeList(_injectSpecifications);
653 }
654
655 private List safeList(List input)
656 {
657 if (input == null)
658 return Collections.EMPTY_LIST;
659
660 return Collections.unmodifiableList(input);
661 }
662
663 private void claimProperty(String propertyName, Object subSpecification)
664 {
665 if (propertyName == null)
666 return;
667
668 if (_claimedProperties == null)
669 _claimedProperties = new HashMap();
670
671 Object existing = _claimedProperties.get(propertyName);
672
673 if (existing != null)
674 throw new ApplicationRuntimeException(SpecMessages.claimedProperty(
675 propertyName,
676 existing), HiveMind.getLocation(subSpecification), null);
677
678 _claimedProperties.put(propertyName, subSpecification);
679 }
680
681 /** @since 4.0 */
682 public boolean isDeprecated()
683 {
684 return _deprecated;
685 }
686
687 /** @since 4.0 */
688 public void setDeprecated(boolean deprecated)
689 {
690 _deprecated = deprecated;
691 }
692
693 public Set getReservedParameterNames()
694 {
695 if (_reservedParameterNames == null)
696 return Collections.EMPTY_SET;
697
698 return Collections.unmodifiableSet(_reservedParameterNames);
699 }
700 }