001: /*
002: * $Id: ComponentTag.java 461655 2006-07-30 12:37:49Z jdonnerstag $
003: * $Revision: 461655 $ $Date: 2006-07-30 14:37:49 +0200 (Sun, 30 Jul 2006) $
004: *
005: * ==============================================================================
006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
007: * use this file except in compliance with the License. You may obtain a copy of
008: * the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015: * License for the specific language governing permissions and limitations under
016: * the License.
017: */
018: package wicket.markup;
019:
020: import java.util.Iterator;
021: import java.util.Map;
022:
023: import wicket.Response;
024: import wicket.markup.parser.XmlTag;
025: import wicket.markup.parser.XmlTag.Type;
026: import wicket.markup.parser.filter.HtmlHandler;
027: import wicket.util.string.AppendingStringBuffer;
028: import wicket.util.string.StringValue;
029: import wicket.util.string.Strings;
030: import wicket.util.value.ValueMap;
031:
032: /**
033: * A subclass of MarkupElement which represents a "significant" markup tag, such
034: * as a component open tag. Insignificant markup tags (those which are merely
035: * concerned with markup formatting operations and do not denote components or
036: * component nesting) are coalesced into instances of RawMarkup (also a subclass
037: * of MarkupElement).
038: *
039: * @author Jonathan Locke
040: */
041: public class ComponentTag extends MarkupElement {
042: /**
043: * Standard component id attribute always available for components
044: * regardless of user ApplicationSettings for id attribute; value ==
045: * 'wicket'.
046: */
047: public static final String DEFAULT_WICKET_NAMESPACE = "wicket";
048:
049: /**
050: * Assuming this is a open (or open-close) tag, 'closes' refers to the
051: * ComponentTag which closes it.
052: */
053: protected ComponentTag closes;
054:
055: /** The underlying xml tag */
056: protected final XmlTag xmlTag;
057:
058: /** True if a href attribute is available and autolinking is on */
059: private boolean autolink = false;
060:
061: /** The component's id identified by wicket:id="xxx" */
062: private String id;
063:
064: /** The component's path in the markup */
065: private String path;
066:
067: /** True, if attributes have been modified or added */
068: private boolean modified = false;
069:
070: /**
071: * In case of inherited markup, the base and the extended markups are merged
072: * and the information about the tags origin is lost. In some cases like
073: * wicket:head and wicket:link this information however is required.
074: */
075: private Class markupClass;
076:
077: /**
078: * Tags which are detected to have only an open tag, which is allowed with
079: * some HTML tags like 'br' for example
080: */
081: private boolean hasNoCloseTag = false;
082:
083: /**
084: * True if the tag has not wicket namespace and no wicket:id. E.g. <head> or
085: * <body>
086: */
087: private boolean internalTag = false;
088:
089: /**
090: * Automatically create a XmlTag, assign the name and the type, and
091: * construct a ComponentTag based on this XmlTag.
092: *
093: * @param name
094: * The name of html tag
095: * @param type
096: * The type of tag
097: */
098: public ComponentTag(final String name, final XmlTag.Type type) {
099: final XmlTag tag = new XmlTag();
100: tag.setName(name);
101: tag.setType(type);
102: xmlTag = tag;
103: }
104:
105: /**
106: * Construct.
107: *
108: * @param tag
109: * The underlying xml tag
110: */
111: public ComponentTag(final XmlTag tag) {
112: super ();
113: xmlTag = tag;
114: }
115:
116: /**
117: * Gets whether this tag closes the provided open tag.
118: *
119: * @param open
120: * The open tag
121: * @return True if this tag closes the given open tag
122: */
123: public final boolean closes(final MarkupElement open) {
124: if (open instanceof ComponentTag) {
125: return (closes == open)
126: || getXmlTag().closes(
127: ((ComponentTag) open).getXmlTag());
128: }
129:
130: return false;
131: }
132:
133: /**
134: * If autolink is set to true, href attributes will automatically be
135: * converted into Wicket bookmarkable URLs.
136: *
137: * @param autolink
138: * enable/disable automatic href conversion
139: */
140: public final void enableAutolink(final boolean autolink) {
141: this .autolink = autolink;
142: }
143:
144: /**
145: * @see wicket.markup.parser.XmlTag#getAttributes()
146: * @return The tag#s attributes
147: */
148: public final ValueMap getAttributes() {
149: return xmlTag.getAttributes();
150: }
151:
152: /**
153: * Get the tag's component id
154: *
155: * @return The component id attribute of this tag
156: */
157: public final String getId() {
158: return id;
159: }
160:
161: /**
162: * Gets the length of the tag in characters.
163: *
164: * @return The tag's length
165: */
166: public final int getLength() {
167: return xmlTag.getLength();
168: }
169:
170: /**
171: * @see wicket.markup.parser.XmlTag#getName()
172: * @return The tag's name
173: */
174: public final String getName() {
175: return xmlTag.getName();
176: }
177:
178: /**
179: * @see wicket.markup.parser.XmlTag#getNameChanged()
180: * @return Returns true if the name of this component tag was changed
181: */
182: public final boolean getNameChanged() {
183: return xmlTag.getNameChanged();
184: }
185:
186: /**
187: * @see wicket.markup.parser.XmlTag#getNamespace()
188: * @return The tag's namespace
189: */
190: public final String getNamespace() {
191: return xmlTag.getNamespace();
192: }
193:
194: /**
195: * If set, return the corresponding open tag (ComponentTag).
196: *
197: * @return The corresponding open tag
198: */
199: public final ComponentTag getOpenTag() {
200: return closes;
201: }
202:
203: /**
204: * @see wicket.markup.parser.XmlTag#getPos()
205: * @return Tag location (index in input string)
206: */
207: public final int getPos() {
208: return xmlTag.getPos();
209: }
210:
211: /**
212: * @see wicket.markup.parser.XmlTag#getString(String)
213: * @param key
214: * The key
215: * @return The string value
216: */
217: public final CharSequence getString(String key) {
218: return xmlTag.getString(key);
219: }
220:
221: /**
222: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
223: * <p>
224: *
225: * @see wicket.markup.parser.XmlTag#getType()
226: * @return the tag type (OPEN, CLOSE or OPEN_CLOSE).
227: */
228: public final Type getType() {
229: return xmlTag.getType();
230: }
231:
232: /**
233: * True if autolink is enabled and the tag contains a href attribute.
234: *
235: * @return True, if the href contained should automatically be converted
236: */
237: public final boolean isAutolinkEnabled() {
238: return this .autolink;
239: }
240:
241: /**
242: * @see wicket.markup.parser.XmlTag#isClose()
243: * @return True if this tag is a close tag
244: */
245: public final boolean isClose() {
246: return xmlTag.isClose();
247: }
248:
249: /**
250: * @see wicket.markup.parser.XmlTag#isOpen()
251: * @return True if this tag is an open tag
252: */
253: public final boolean isOpen() {
254: return xmlTag.isOpen();
255: }
256:
257: /**
258: * @param id
259: * Required component id
260: * @return True if this tag is an open tag with the given component name
261: * @see wicket.markup.parser.XmlTag#isOpen()
262: */
263: public final boolean isOpen(String id) {
264: return xmlTag.isOpen() && this .id.equals(id);
265: }
266:
267: /**
268: * @see wicket.markup.parser.XmlTag#isOpenClose()
269: * @return True if this tag is an open and a close tag
270: */
271: public final boolean isOpenClose() {
272: return xmlTag.isOpenClose();
273: }
274:
275: /**
276: * @param id
277: * Required component id
278: * @return True if this tag is an openclose tag with the given component id
279: * @see wicket.markup.parser.XmlTag#isOpenClose()
280: */
281: public final boolean isOpenClose(String id) {
282: return xmlTag.isOpenClose() && this .id.equals(id);
283: }
284:
285: /**
286: * Compare tag name including namespace
287: *
288: * @param tag
289: * @return true if name and namespace are equal
290: */
291: public boolean hasEqualTagName(final ComponentTag tag) {
292: return xmlTag.hasEqualTagName(tag.getXmlTag());
293: }
294:
295: /**
296: * Makes this tag object immutable by making the attribute map unmodifiable.
297: * Immutable tags cannot be made mutable again. They can only be copied into
298: * new mutable tag objects.
299: */
300: public final void makeImmutable() {
301: xmlTag.makeImmutable();
302: }
303:
304: /**
305: * Gets this tag if it is already mutable, or a mutable copy of this tag if
306: * it is immutable.
307: *
308: * @return This tag if it is already mutable, or a mutable copy of this tag
309: * if it is immutable.
310: */
311: public ComponentTag mutable() {
312: if (xmlTag.isMutable()) {
313: return this ;
314: } else {
315: final ComponentTag tag = new ComponentTag(xmlTag.mutable());
316: tag.id = id;
317: tag.setMarkupClass(this .markupClass);
318: tag.setHasNoCloseTag(this .hasNoCloseTag);
319: tag.setPath(this .path);
320: return tag;
321: }
322: }
323:
324: /**
325: * @see wicket.markup.parser.XmlTag#put(String, boolean)
326: * @param key
327: * The key
328: * @param value
329: * The value
330: */
331: public final void put(String key, boolean value) {
332: xmlTag.put(key, value);
333: }
334:
335: /**
336: * @see wicket.markup.parser.XmlTag#put(String, int)
337: * @param key
338: * The key
339: * @param value
340: * The value
341: */
342: public final void put(String key, int value) {
343: xmlTag.put(key, value);
344: }
345:
346: /**
347: * @see wicket.markup.parser.XmlTag#put(String, String)
348: * @param key
349: * The key
350: * @param value
351: * The value
352: */
353: public final void put(String key, CharSequence value) {
354: xmlTag.put(key, value);
355: }
356:
357: /**
358: * @see wicket.markup.parser.XmlTag#put(String, StringValue)
359: * @param key
360: * The key
361: * @param value
362: * The value
363: */
364: public final void put(String key, StringValue value) {
365: xmlTag.put(key, value);
366: }
367:
368: /**
369: * @see wicket.markup.parser.XmlTag#putAll(Map)
370: * @param map
371: * a key/value map
372: */
373: public final void putAll(final Map map) {
374: xmlTag.putAll(map);
375: }
376:
377: /**
378: * @see wicket.markup.parser.XmlTag#remove(String)
379: * @param key
380: * The key to remove
381: */
382: public final void remove(String key) {
383: xmlTag.remove(key);
384: }
385:
386: /**
387: * Gets whether this tag does not require a closing tag.
388: *
389: * @return True if this tag does not require a closing tag
390: */
391: public final boolean requiresCloseTag() {
392: if (getNamespace() == null) {
393: return HtmlHandler.requiresCloseTag(getName());
394: } else {
395: return HtmlHandler.requiresCloseTag(getNamespace() + ":"
396: + getName());
397: }
398: }
399:
400: /**
401: * Set the component's id. The value is usually taken from the tag's id
402: * attribute, e.g. wicket:id="componentId".
403: *
404: * @param id
405: * The component's id assigned to the tag.
406: */
407: public final void setId(final String id) {
408: this .id = id;
409: }
410:
411: /**
412: * @param flag
413: * True if the tag has not wicket namespace and no wicket:id.
414: * E.g. <head> or <body>
415: */
416: public void setInternalTag(final boolean flag) {
417: this .internalTag = flag;
418: }
419:
420: /**
421: * @see wicket.markup.parser.XmlTag#setName(String)
422: * @param name
423: * New tag name
424: */
425: public final void setName(String name) {
426: xmlTag.setName(name);
427: }
428:
429: /**
430: * @see wicket.markup.parser.XmlTag#setNamespace(String)
431: * @param namespace
432: * New tag name namespace
433: */
434: public final void setNamespace(String namespace) {
435: xmlTag.setNamespace(namespace);
436: }
437:
438: /**
439: * Assuming this is a close tag, assign it's corresponding open tag.
440: *
441: * @param tag
442: * the open-tag
443: * @throws RuntimeException
444: * if 'this' is not a close tag
445: */
446: public final void setOpenTag(final ComponentTag tag) {
447: this .closes = tag;
448: getXmlTag().setOpenTag(tag.getXmlTag());
449: }
450:
451: /**
452: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
453: *
454: * @param type
455: * The new type
456: */
457: public final void setType(final Type type) {
458: xmlTag.setType(type);
459: }
460:
461: /**
462: * @return A synthetic close tag for this tag
463: */
464: public final CharSequence syntheticCloseTagString() {
465: AppendingStringBuffer buf = new AppendingStringBuffer();
466: buf.append("</");
467: if (getNamespace() != null) {
468: buf.append(getNamespace()).append(":");
469: }
470: buf.append(getName()).append(">");
471:
472: return buf;
473: }
474:
475: /**
476: * @see wicket.markup.MarkupElement#toCharSequence()
477: */
478: public CharSequence toCharSequence() {
479: return xmlTag.toCharSequence();
480: }
481:
482: /**
483: * Converts this object to a string representation.
484: *
485: * @return String version of this object
486: */
487: public final String toString() {
488: return toCharSequence().toString();
489: }
490:
491: /**
492: * Write the tag to the response
493: *
494: * @param response
495: * The response to write to
496: * @param stripWicketAttributes
497: * if true, wicket:id are removed from output
498: * @param namespace
499: * Wicket's namespace to use
500: */
501: public final void writeOutput(final Response response,
502: final boolean stripWicketAttributes, final String namespace) {
503: response.write("<");
504:
505: if (getType() == XmlTag.CLOSE) {
506: response.write("/");
507: }
508:
509: if (getNamespace() != null) {
510: response.write(getNamespace());
511: response.write(":");
512: }
513:
514: response.write(getName());
515:
516: String namespacePrefix = null;
517: if (stripWicketAttributes == true) {
518: namespacePrefix = namespace + ":";
519: }
520:
521: if (getAttributes().size() > 0) {
522: final Iterator iterator = getAttributes().keySet()
523: .iterator();
524: while (iterator.hasNext()) {
525: final String key = (String) iterator.next();
526: if (key == null) {
527: continue;
528: }
529:
530: if ((namespacePrefix == null)
531: || (key.startsWith(namespacePrefix) == false)) {
532: response.write(" ");
533: response.write(key);
534: CharSequence value = getString(key);
535:
536: // attributes without values are possible, e.g. 'disabled'
537: if (value != null) {
538: response.write("=\"");
539: value = Strings.replaceAll(value, "\"", "\\\"");
540: response.write(value);
541: response.write("\"");
542: }
543: }
544: }
545: }
546:
547: if (getType() == XmlTag.OPEN_CLOSE) {
548: response.write("/");
549: }
550:
551: response.write(">");
552: }
553:
554: /**
555: * Converts this object to a string representation including useful
556: * information for debugging
557: *
558: * @return String version of this object
559: */
560: public final String toUserDebugString() {
561: return xmlTag.toUserDebugString();
562: }
563:
564: /**
565: * @return Returns the underlying xml tag.
566: */
567: final XmlTag getXmlTag() {
568: return xmlTag;
569: }
570:
571: /**
572: * Manually mark the ComponentTag being modified. Flagging the tag being
573: * modified does not happen automatically.
574: *
575: * @param modified
576: */
577: public final void setModified(final boolean modified) {
578: this .modified = modified;
579: }
580:
581: /**
582: *
583: * @return True, if the component tag has been marked modified
584: */
585: public final boolean isModified() {
586: return this .modified;
587: }
588:
589: /**
590: * Gets the component path of wicket elements
591: *
592: * @return path
593: */
594: public String getPath() {
595: return path;
596: }
597:
598: /**
599: * Sets the component path of wicket elements
600: *
601: * @param path
602: * path
603: */
604: void setPath(final String path) {
605: this .path = path;
606: }
607:
608: /**
609: *
610: * @return True if the HTML tag (e.g. br) has no close tag
611: */
612: public boolean hasNoCloseTag() {
613: return hasNoCloseTag;
614: }
615:
616: /**
617: * True if the HTML tag (e.g. br) has no close tag
618: *
619: * @param hasNoCloseTag
620: */
621: public void setHasNoCloseTag(boolean hasNoCloseTag) {
622: this .hasNoCloseTag = hasNoCloseTag;
623: }
624:
625: /**
626: * In case of inherited markup, the base and the extended markups are merged
627: * and the information about the tags origin is lost. In some cases like
628: * wicket:head and wicket:link this information however is required.
629: *
630: * @return wicketHeaderClass
631: */
632: public Class getMarkupClass() {
633: return markupClass;
634: }
635:
636: /**
637: * Set the class of wicket component which contains the wicket:head tag.
638: *
639: * @param wicketHeaderClass
640: * wicketHeaderClass
641: */
642: public void setMarkupClass(Class wicketHeaderClass) {
643: this.markupClass = wicketHeaderClass;
644: }
645: }
|