001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.markup;
018:
019: import java.lang.ref.WeakReference;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.Collections;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027:
028: import org.apache.wicket.Response;
029: import org.apache.wicket.behavior.IBehavior;
030: import org.apache.wicket.markup.parser.XmlTag;
031: import org.apache.wicket.markup.parser.XmlTag.Type;
032: import org.apache.wicket.markup.parser.filter.HtmlHandler;
033: import org.apache.wicket.util.string.AppendingStringBuffer;
034: import org.apache.wicket.util.string.StringValue;
035: import org.apache.wicket.util.string.Strings;
036: import org.apache.wicket.util.value.IValueMap;
037:
038: /**
039: * A subclass of MarkupElement which represents a "significant" markup tag, such
040: * as a component open tag. Insignificant markup tags (those which are merely
041: * concerned with markup formatting operations and do not denote components or
042: * component nesting) are coalesced into instances of RawMarkup (also a subclass
043: * of MarkupElement).
044: *
045: * @author Jonathan Locke
046: */
047: public class ComponentTag extends MarkupElement {
048: /**
049: * Standard component id attribute always available for components
050: * regardless of user ApplicationSettings for id attribute; value ==
051: * 'wicket'.
052: */
053: public static final String DEFAULT_WICKET_NAMESPACE = "wicket";
054:
055: /** an empty list */
056: private static final List EMPTY_LIST = new ArrayList();
057:
058: /**
059: * Assuming this is a open (or open-close) tag, 'closes' refers to the
060: * ComponentTag which closes it.
061: */
062: private ComponentTag closes;
063:
064: /** The underlying xml tag */
065: protected final XmlTag xmlTag;
066:
067: /** True if a href attribute is available and autolinking is on */
068: private boolean autolink = false;
069:
070: /**
071: * By default this is equal to the wicket:id="xxx" attribute value, but may
072: * be provided e.g. for auto-tags
073: */
074: private String id;
075:
076: /** The component's path in the markup */
077: private String path;
078:
079: /** True, if attributes have been modified or added */
080: private boolean modified = false;
081:
082: /**
083: * If true, than the MarkupParser will ignore (remove) it. Temporary working
084: * variable
085: */
086: private transient boolean ignore = false;
087:
088: /** If true, than the tag contain an automatically created wicket id */
089: private boolean autoComponent = false;
090:
091: /**
092: * In case of inherited markup, the base and the extended markups are merged
093: * and the information about the tags origin is lost. In some cases like
094: * wicket:head and wicket:link this information however is required.
095: */
096: private WeakReference/* <Class> */markupClassRef = null;
097:
098: /**
099: * Tags which are detected to have only an open tag, which is allowed with
100: * some HTML tags like 'br' for example
101: */
102: private boolean hasNoCloseTag = false;
103:
104: /** added behaviors */
105: private List behaviors;
106:
107: /** Filters and Handlers may add their own attributes to the tag */
108: private Map userData;
109:
110: /**
111: * Automatically create a XmlTag, assign the name and the type, and
112: * construct a ComponentTag based on this XmlTag.
113: *
114: * @param name
115: * The name of html tag
116: * @param type
117: * The type of tag
118: */
119: public ComponentTag(final String name, final XmlTag.Type type) {
120: final XmlTag tag = new XmlTag();
121: tag.setName(name);
122: tag.setType(type);
123: xmlTag = tag;
124: }
125:
126: /**
127: * Construct.
128: *
129: * @param tag
130: * The underlying xml tag
131: */
132: public ComponentTag(final XmlTag tag) {
133: super ();
134: xmlTag = tag;
135: }
136:
137: /**
138: * Adds a behavior to this component tag.
139: *
140: * @param behavior
141: */
142: public final void addBehavior(final IBehavior behavior) {
143: if (behavior == null) {
144: throw new IllegalArgumentException(
145: "Argument [behavior] cannot be null");
146: }
147:
148: if (behaviors == null) {
149: behaviors = new ArrayList();
150: }
151: behaviors.add(behavior);
152: }
153:
154: /**
155: * @return true if this tag has any behaviors added, false otherwise
156: */
157: public final boolean hasBehaviors() {
158: return behaviors != null;
159: }
160:
161: /**
162: * @return read only iterator over added behaviors
163: */
164: public final Iterator getBehaviors() {
165: if (behaviors == null) {
166: List empty = EMPTY_LIST;
167: return empty.iterator();
168: }
169:
170: Collection locked = Collections
171: .unmodifiableCollection(behaviors);
172: return locked.iterator();
173: }
174:
175: /**
176: * Gets whether this tag closes the provided open tag.
177: *
178: * @param open
179: * The open tag
180: * @return True if this tag closes the given open tag
181: */
182: public final boolean closes(final MarkupElement open) {
183: if (open instanceof ComponentTag) {
184: return (closes == open)
185: || getXmlTag().closes(
186: ((ComponentTag) open).getXmlTag());
187: }
188:
189: return false;
190: }
191:
192: /**
193: * If autolink is set to true, href attributes will automatically be
194: * converted into Wicket bookmarkable URLs.
195: *
196: * @param autolink
197: * enable/disable automatic href conversion
198: */
199: public final void enableAutolink(final boolean autolink) {
200: this .autolink = autolink;
201: }
202:
203: /**
204: * @see org.apache.wicket.markup.parser.XmlTag#getAttributes()
205: * @return The tag#s attributes
206: */
207: public final IValueMap getAttributes() {
208: return xmlTag.getAttributes();
209: }
210:
211: /**
212: * Get the tag's component id
213: *
214: * @return The component id attribute of this tag
215: */
216: public final String getId() {
217: return id;
218: }
219:
220: /**
221: * Gets the length of the tag in characters.
222: *
223: * @return The tag's length
224: */
225: public final int getLength() {
226: return xmlTag.getLength();
227: }
228:
229: /**
230: * @see org.apache.wicket.markup.parser.XmlTag#getName()
231: * @return The tag's name
232: */
233: public final String getName() {
234: return xmlTag.getName();
235: }
236:
237: /**
238: * @see org.apache.wicket.markup.parser.XmlTag#getNameChanged()
239: * @return Returns true if the name of this component tag was changed
240: */
241: public final boolean getNameChanged() {
242: return xmlTag.getNameChanged();
243: }
244:
245: /**
246: * @see org.apache.wicket.markup.parser.XmlTag#getNamespace()
247: * @return The tag's namespace
248: */
249: public final String getNamespace() {
250: return xmlTag.getNamespace();
251: }
252:
253: /**
254: * If set, return the corresponding open tag (ComponentTag).
255: *
256: * @return The corresponding open tag
257: */
258: public final ComponentTag getOpenTag() {
259: return closes;
260: }
261:
262: /**
263: * @see org.apache.wicket.markup.parser.XmlTag#getPos()
264: * @return Tag location (index in input string)
265: */
266: public final int getPos() {
267: return xmlTag.getPos();
268: }
269:
270: /**
271: * @see org.apache.wicket.markup.parser.XmlTag#getString(String)
272: * @param key
273: * The key
274: * @return The string value
275: */
276: public final CharSequence getString(String key) {
277: return xmlTag.getString(key);
278: }
279:
280: /**
281: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
282: * <p>
283: *
284: * @see org.apache.wicket.markup.parser.XmlTag#getType()
285: * @return the tag type (OPEN, CLOSE or OPEN_CLOSE).
286: */
287: public final Type getType() {
288: return xmlTag.getType();
289: }
290:
291: /**
292: * True if autolink is enabled and the tag contains a href attribute.
293: *
294: * @return True, if the href contained should automatically be converted
295: */
296: public final boolean isAutolinkEnabled() {
297: return autolink;
298: }
299:
300: /**
301: * @see org.apache.wicket.markup.parser.XmlTag#isClose()
302: * @return True if this tag is a close tag
303: */
304: public final boolean isClose() {
305: return xmlTag.isClose();
306: }
307:
308: /**
309: * @see org.apache.wicket.markup.parser.XmlTag#isOpen()
310: * @return True if this tag is an open tag
311: */
312: public final boolean isOpen() {
313: return xmlTag.isOpen();
314: }
315:
316: /**
317: * @param id
318: * Required component id
319: * @return True if this tag is an open tag with the given component name
320: * @see org.apache.wicket.markup.parser.XmlTag#isOpen()
321: */
322: public final boolean isOpen(String id) {
323: return xmlTag.isOpen() && this .id.equals(id);
324: }
325:
326: /**
327: * @see org.apache.wicket.markup.parser.XmlTag#isOpenClose()
328: * @return True if this tag is an open and a close tag
329: */
330: public final boolean isOpenClose() {
331: return xmlTag.isOpenClose();
332: }
333:
334: /**
335: * @param id
336: * Required component id
337: * @return True if this tag is an openclose tag with the given component id
338: * @see org.apache.wicket.markup.parser.XmlTag#isOpenClose()
339: */
340: public final boolean isOpenClose(String id) {
341: return xmlTag.isOpenClose() && this .id.equals(id);
342: }
343:
344: /**
345: * Compare tag name including namespace
346: *
347: * @param tag
348: * @return true if name and namespace are equal
349: */
350: public boolean hasEqualTagName(final ComponentTag tag) {
351: return xmlTag.hasEqualTagName(tag.getXmlTag());
352: }
353:
354: /**
355: * Makes this tag object immutable by making the attribute map unmodifiable.
356: * Immutable tags cannot be made mutable again. They can only be copied into
357: * new mutable tag objects.
358: */
359: public final void makeImmutable() {
360: xmlTag.makeImmutable();
361: }
362:
363: /**
364: * Gets this tag if it is already mutable, or a mutable copy of this tag if
365: * it is immutable.
366: *
367: * @return This tag if it is already mutable, or a mutable copy of this tag
368: * if it is immutable.
369: */
370: public ComponentTag mutable() {
371: if (xmlTag.isMutable()) {
372: return this ;
373: } else {
374: final ComponentTag tag = new ComponentTag(xmlTag.mutable());
375: copyPropertiesTo(tag);
376: return tag;
377: }
378: }
379:
380: /**
381: * Copies all internal properties from this tag to <code>dest</code>.
382: * This is basically cloning without instance creation.
383: *
384: * @param dest
385: * tag whose properties will be set
386: */
387: void copyPropertiesTo(final ComponentTag dest) {
388: dest.id = id;
389: dest.setHasNoCloseTag(hasNoCloseTag);
390: dest.setPath(path);
391: dest.setAutoComponentTag(autoComponent);
392: if (markupClassRef != null) {
393: dest.setMarkupClass((Class) markupClassRef.get());
394: }
395: if (behaviors != null) {
396: dest.behaviors = new ArrayList(behaviors.size());
397: dest.behaviors.addAll(behaviors);
398: }
399: }
400:
401: /**
402: * @see org.apache.wicket.markup.parser.XmlTag#put(String, boolean)
403: * @param key
404: * The key
405: * @param value
406: * The value
407: */
408: public final void put(final String key, final boolean value) {
409: xmlTag.put(key, value);
410: }
411:
412: /**
413: * @see org.apache.wicket.markup.parser.XmlTag#put(String, int)
414: * @param key
415: * The key
416: * @param value
417: * The value
418: */
419: public final void put(final String key, final int value) {
420: xmlTag.put(key, value);
421: }
422:
423: /**
424: * @see org.apache.wicket.markup.parser.XmlTag#put(String, String)
425: * @param key
426: * The key
427: * @param value
428: * The value
429: */
430: public final void put(String key, CharSequence value) {
431: xmlTag.put(key, value);
432: }
433:
434: /**
435: * @see org.apache.wicket.markup.parser.XmlTag#put(String, StringValue)
436: * @param key
437: * The key
438: * @param value
439: * The value
440: */
441: public final void put(String key, StringValue value) {
442: xmlTag.put(key, value);
443: }
444:
445: /**
446: * @see org.apache.wicket.markup.parser.XmlTag#putAll(Map)
447: * @param map
448: * a key/value map
449: */
450: public final void putAll(final Map map) {
451: xmlTag.putAll(map);
452: }
453:
454: /**
455: * @see org.apache.wicket.markup.parser.XmlTag#remove(String)
456: * @param key
457: * The key to remove
458: */
459: public final void remove(String key) {
460: xmlTag.remove(key);
461: }
462:
463: /**
464: * Gets whether this tag does not require a closing tag.
465: *
466: * @return True if this tag does not require a closing tag
467: */
468: public final boolean requiresCloseTag() {
469: if (getNamespace() == null) {
470: return HtmlHandler.requiresCloseTag(getName());
471: } else {
472: return HtmlHandler.requiresCloseTag(getNamespace() + ":"
473: + getName());
474: }
475: }
476:
477: /**
478: * Set the component's id. The value is usually taken from the tag's id
479: * attribute, e.g. wicket:id="componentId".
480: *
481: * @param id
482: * The component's id assigned to the tag.
483: */
484: public final void setId(final String id) {
485: this .id = id;
486: }
487:
488: /**
489: * @see org.apache.wicket.markup.parser.XmlTag#setName(String)
490: * @param name
491: * New tag name
492: */
493: public final void setName(String name) {
494: xmlTag.setName(name);
495: }
496:
497: /**
498: * @see org.apache.wicket.markup.parser.XmlTag#setNamespace(String)
499: * @param namespace
500: * New tag name namespace
501: */
502: public final void setNamespace(String namespace) {
503: xmlTag.setNamespace(namespace);
504: }
505:
506: /**
507: * Assuming this is a close tag, assign it's corresponding open tag.
508: *
509: * @param tag
510: * the open-tag
511: * @throws RuntimeException
512: * if 'this' is not a close tag
513: */
514: public final void setOpenTag(final ComponentTag tag) {
515: closes = tag;
516: getXmlTag().setOpenTag(tag.getXmlTag());
517: }
518:
519: /**
520: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
521: *
522: * @param type
523: * The new type
524: */
525: public final void setType(final Type type) {
526: xmlTag.setType(type);
527: }
528:
529: /**
530: * @return A synthetic close tag for this tag
531: */
532: public final CharSequence syntheticCloseTagString() {
533: AppendingStringBuffer buf = new AppendingStringBuffer();
534: buf.append("</");
535: if (getNamespace() != null) {
536: buf.append(getNamespace()).append(":");
537: }
538: buf.append(getName()).append(">");
539:
540: return buf;
541: }
542:
543: /**
544: * @see org.apache.wicket.markup.MarkupElement#toCharSequence()
545: */
546: public CharSequence toCharSequence() {
547: return xmlTag.toCharSequence();
548: }
549:
550: /**
551: * Converts this object to a string representation.
552: *
553: * @return String version of this object
554: */
555: public final String toString() {
556: return toCharSequence().toString();
557: }
558:
559: /**
560: * Write the tag to the response
561: *
562: * @param response
563: * The response to write to
564: * @param stripWicketAttributes
565: * if true, wicket:id are removed from output
566: * @param namespace
567: * Wicket's namespace to use
568: */
569: public final void writeOutput(final Response response,
570: final boolean stripWicketAttributes, final String namespace) {
571: response.write("<");
572:
573: if (getType() == XmlTag.CLOSE) {
574: response.write("/");
575: }
576:
577: if (getNamespace() != null) {
578: response.write(getNamespace());
579: response.write(":");
580: }
581:
582: response.write(getName());
583:
584: String namespacePrefix = null;
585: if (stripWicketAttributes == true) {
586: namespacePrefix = namespace + ":";
587: }
588:
589: if (getAttributes().size() > 0) {
590: final Iterator iterator = getAttributes().keySet()
591: .iterator();
592: while (iterator.hasNext()) {
593: final String key = (String) iterator.next();
594: if (key == null) {
595: continue;
596: }
597:
598: if ((namespacePrefix == null)
599: || (key.startsWith(namespacePrefix) == false)) {
600: response.write(" ");
601: response.write(key);
602: CharSequence value = getString(key);
603:
604: // attributes without values are possible, e.g. 'disabled'
605: if (value != null) {
606: response.write("=\"");
607: value = Strings
608: .replaceAll(value, "\"", """);
609: response.write(value);
610: response.write("\"");
611: }
612: }
613: }
614: }
615:
616: if (getType() == XmlTag.OPEN_CLOSE) {
617: response.write("/");
618: }
619:
620: response.write(">");
621: }
622:
623: /**
624: * Converts this object to a string representation including useful
625: * information for debugging
626: *
627: * @return String version of this object
628: */
629: public final String toUserDebugString() {
630: return xmlTag.toUserDebugString();
631: }
632:
633: /**
634: * @return Returns the underlying xml tag.
635: */
636: final XmlTag getXmlTag() {
637: return xmlTag;
638: }
639:
640: /**
641: * Manually mark the ComponentTag being modified. Flagging the tag being
642: * modified does not happen automatically.
643: *
644: * @param modified
645: */
646: public final void setModified(final boolean modified) {
647: this .modified = modified;
648: }
649:
650: /**
651: *
652: * @return True, if the component tag has been marked modified
653: */
654: public final boolean isModified() {
655: return modified;
656: }
657:
658: /**
659: * Gets the component path of wicket elements
660: *
661: * @return path
662: */
663: public String getPath() {
664: return path;
665: }
666:
667: /**
668: * Sets the component path of wicket elements
669: *
670: * @param path
671: * path
672: */
673: void setPath(final String path) {
674: this .path = path;
675: }
676:
677: /**
678: *
679: * @return True if the HTML tag (e.g. br) has no close tag
680: */
681: public boolean hasNoCloseTag() {
682: return hasNoCloseTag;
683: }
684:
685: /**
686: * True if the HTML tag (e.g. br) has no close tag
687: *
688: * @param hasNoCloseTag
689: */
690: public void setHasNoCloseTag(boolean hasNoCloseTag) {
691: this .hasNoCloseTag = hasNoCloseTag;
692: }
693:
694: /**
695: * In case of inherited markup, the base and the extended markups are merged
696: * and the information about the tags origin is lost. In some cases like
697: * wicket:head and wicket:link this information however is required.
698: *
699: * @return wicketHeaderClass
700: */
701: public Class getMarkupClass() {
702: return (markupClassRef == null ? null : (Class) markupClassRef
703: .get());
704: }
705:
706: /**
707: * Set the class of wicket component which contains the wicket:head tag.
708: *
709: * @param wicketHeaderClass
710: * wicketHeaderClass
711: */
712: public void setMarkupClass(Class wicketHeaderClass) {
713: if (wicketHeaderClass == null) {
714: markupClassRef = null;
715: } else {
716: markupClassRef = new WeakReference(wicketHeaderClass);
717: }
718: }
719:
720: /**
721: * @see org.apache.wicket.markup.MarkupElement#equalTo(org.apache.wicket.markup.MarkupElement)
722: */
723: public boolean equalTo(final MarkupElement element) {
724: if (element instanceof ComponentTag) {
725: final ComponentTag that = (ComponentTag) element;
726: return getXmlTag().equalTo(that.getXmlTag());
727: }
728: return false;
729: }
730:
731: /**
732: * Gets ignore.
733: *
734: * @return If true than MarkupParser will remove it from the markup
735: */
736: public boolean isIgnore() {
737: return ignore;
738: }
739:
740: /**
741: * Sets ignore.
742: *
743: * @param ignore
744: * If true than MarkupParser will remove it from the markup
745: */
746: public void setIgnore(boolean ignore) {
747: this .ignore = ignore;
748: }
749:
750: /**
751: * @return True, if wicket:id has been automatically created (internal
752: * component)
753: */
754: public boolean isAutoComponentTag() {
755: return autoComponent;
756: }
757:
758: /**
759: * @param auto
760: * True, if wicket:id has been automatically created (internal
761: * component)
762: */
763: public void setAutoComponentTag(boolean auto) {
764: autoComponent = auto;
765: }
766:
767: /**
768: * Gets userData.
769: *
770: * @param key
771: * The key to store and retrieve the value
772: * @return userData
773: */
774: public Object getUserData(final String key) {
775: if (userData == null) {
776: return null;
777: }
778:
779: return userData.get(key);
780: }
781:
782: /**
783: * Sets userData.
784: *
785: * @param key
786: * The key to store and retrieve the value
787: * @param value
788: * The user specific value to store
789: */
790: public void setUserData(final String key, final Object value) {
791: if (userData == null) {
792: userData = new HashMap();
793: }
794: userData.put(key, value);
795: }
796: }
|