001: package com.opensymphony.module.sitemesh.html;
002:
003: import java.util.Arrays;
004:
005: import com.opensymphony.module.sitemesh.html.util.CharArray;
006: import com.opensymphony.module.sitemesh.html.tokenizer.Parser;
007:
008: /**
009: * A CustomTag provides a mechanism to manipulate the contents of a Tag. The standard Tag implementations
010: * are immutable, however CustomTag allows a copy to be taken of an immutable Tag that can then be manipulated.
011: *
012: * @see Tag
013: *
014: * @author Joe Walnes
015: */
016: public class CustomTag implements Tag {
017:
018: private String[] attributes = new String[10]; // name1, value1, name2, value2...
019: private int attributeCount = 0;
020: private String name;
021: private int type;
022:
023: /**
024: * Type of tag: <br/>
025: * <blah> - Tag.OPEN<br/>
026: * </blah> - Tag.CLOSE<br/>
027: * <blah/> - Tag.EMPTY<br/>
028: */
029: public CustomTag(String name, int type) {
030: setName(name);
031: setType(type);
032: }
033:
034: /**
035: * Create a CustomTag based on an existing Tag - this takes a copy of the Tag.
036: */
037: public CustomTag(Tag tag) {
038: setName(tag.getName());
039: setType(tag.getType());
040: if (tag instanceof Parser.ReusableToken) {
041: Parser.ReusableToken orig = (Parser.ReusableToken) tag;
042: attributeCount = orig.attributeCount;
043: attributes = new String[attributeCount];
044: System.arraycopy(orig.attributes, 0, attributes, 0,
045: attributeCount);
046: } else if (tag instanceof CustomTag) {
047: CustomTag orig = (CustomTag) tag;
048: attributeCount = orig.attributeCount;
049: attributes = new String[attributeCount];
050: System.arraycopy(orig.attributes, 0, attributes, 0,
051: attributeCount);
052: } else {
053: int c = tag.getAttributeCount();
054: attributes = new String[c * 2];
055: for (int i = 0; i < c; i++) {
056: attributes[attributeCount++] = tag.getAttributeName(i);
057: attributes[attributeCount++] = tag.getAttributeValue(i);
058: }
059: }
060: }
061:
062: public String getContents() {
063: CharArray c = new CharArray(64);
064: writeTo(c);
065: return c.toString();
066: }
067:
068: public void writeTo(CharArray out) {
069: if (type == Tag.CLOSE) {
070: out.append("</");
071: } else {
072: out.append('<');
073: }
074:
075: out.append(name);
076: final int len = attributeCount;
077:
078: for (int i = 0; i < len; i += 2) {
079: final String name = attributes[i];
080: final String value = attributes[i + 1];
081: if (value == null) {
082: out.append(' ').append(name);
083: } else {
084: out.append(' ').append(name).append("=\"")
085: .append(value).append("\"");
086: }
087: }
088:
089: if (type == Tag.EMPTY) {
090: out.append("/>");
091: } else {
092: out.append('>');
093: }
094: }
095:
096: public boolean equals(Object o) {
097: if (this == o)
098: return true;
099: if (!(o instanceof CustomTag))
100: return false;
101:
102: final CustomTag customTag = (CustomTag) o;
103:
104: if (type != customTag.type)
105: return false;
106: if (attributes != null ? !Arrays.equals(attributes,
107: customTag.attributes) : customTag.attributes != null)
108: return false;
109: if (name != null ? !name.equals(customTag.name)
110: : customTag.name != null)
111: return false;
112:
113: return true;
114: }
115:
116: public int hashCode() {
117: int result = (attributes != null ? attributes.hashCode() : 0);
118: result = 29 * result + (name != null ? name.hashCode() : 0);
119: result = 29 * result + type;
120: return result;
121: }
122:
123: public String toString() {
124: return getContents();
125: }
126:
127: // ---------- Standard methods to implement Tag interface ------
128:
129: public int getAttributeCount() {
130: return attributeCount / 2;
131: }
132:
133: public int getAttributeIndex(String name, boolean caseSensitive) {
134: if (attributes == null) {
135: return -1;
136: }
137: final int len = attributeCount;
138: for (int i = 0; i < len; i += 2) {
139: final String current = attributes[i];
140: if (caseSensitive ? name.equals(current) : name
141: .equalsIgnoreCase(current)) {
142: return i / 2;
143: }
144: }
145: return -1;
146: }
147:
148: public String getAttributeName(int index) {
149: return attributes[index * 2];
150: }
151:
152: public String getAttributeValue(int index) {
153: return attributes[index * 2 + 1];
154: }
155:
156: public String getAttributeValue(String name, boolean caseSensitive) {
157: int attributeIndex = getAttributeIndex(name, caseSensitive);
158: if (attributeIndex == -1) {
159: return null;
160: } else {
161: return attributes[attributeIndex * 2 + 1];
162: }
163: }
164:
165: public boolean hasAttribute(String name, boolean caseSensitive) {
166: return getAttributeIndex(name, caseSensitive) > -1;
167: }
168:
169: public String getName() {
170: return name;
171: }
172:
173: /**
174: * Type of tag: <br/>
175: * <blah> - Tag.OPEN<br/>
176: * </blah> - Tag.CLOSE<br/>
177: * <blah/> - Tag.EMPTY<br/>
178: */
179: public int getType() {
180: return type;
181: }
182:
183: // ----------- Additional methods for changing a tag -----------
184:
185: /**
186: * Change the name of the attribute.
187: */
188: public void setName(String name) {
189: if (name == null || name.length() == 0) {
190: throw new IllegalArgumentException(
191: "CustomTag requires a name");
192: } else {
193: this .name = name;
194: }
195: }
196:
197: /**
198: * Change the type of the tag.
199: *
200: * Type of tag: <br/>
201: * <blah> - Tag.OPEN<br/>
202: * </blah> - Tag.CLOSE<br/>
203: * <blah/> - Tag.EMPTY<br/>
204: */
205: public void setType(int type) {
206: if (type == Tag.OPEN || type == Tag.CLOSE || type == Tag.EMPTY) {
207: this .type = type;
208: } else {
209: throw new IllegalArgumentException(
210: "CustomTag must be of type Tag.OPEN, Tag.CLOSE or Tag.EMPTY - was "
211: + type);
212: }
213: }
214:
215: private void growAttributes() {
216: int newSize = attributes.length == 0 ? 4
217: : attributes.length * 2;
218: String[] newAttributes = new String[newSize];
219: System.arraycopy(attributes, 0, newAttributes, 0,
220: attributes.length);
221: attributes = newAttributes;
222: }
223:
224: /**
225: * Add a new attribute. This does not check for the existence of an attribute with the same name,
226: * thus allowing duplicate attributes.
227: *
228: * @param name Name of attribute to change.
229: * @param value New value of attribute or null for an HTML style empty attribute.
230: * @return Index of new attribute.
231: */
232: public int addAttribute(String name, String value) {
233: if (attributeCount == attributes.length) {
234: growAttributes();
235: }
236: attributes[attributeCount++] = name;
237: attributes[attributeCount++] = value;
238: return (attributeCount / 2) - 1;
239: }
240:
241: /**
242: * Change the value of an attribute, or add an attribute if it does not already exist.
243: *
244: * @param name Name of attribute to change.
245: * @param caseSensitive Whether the name should be treated as case sensitive when searching for an existing value.
246: * @param value New value of attribute or null for an HTML style empty attribute.
247: */
248: public void setAttributeValue(String name, boolean caseSensitive,
249: String value) {
250: int attributeIndex = getAttributeIndex(name, caseSensitive);
251: if (attributeIndex == -1) {
252: addAttribute(name, value);
253: } else {
254: attributes[attributeIndex * 2 + 1] = value;
255: }
256: }
257:
258: /**
259: * Change the name of an existing attribute.
260: */
261: public void setAttributeName(int attributeIndex, String name) {
262: attributes[attributeIndex * 2] = name;
263: }
264:
265: /**
266: * Change the value of an existing attribute. The value may be null for an HTML style empty attribute.
267: */
268: public void setAttributeValue(int attributeIndex, String value) {
269: attributes[(attributeIndex * 2) + 1] = value;
270: }
271:
272: /**
273: * Remove an attribute.
274: */
275: public void removeAttribute(int attributeIndex) {
276: if (attributeIndex > attributeCount / 2) {
277: throw new ArrayIndexOutOfBoundsException(
278: "Cannot remove attribute at index "
279: + attributeIndex + ", max index is "
280: + attributeCount / 2);
281: }
282: //shift everything down one and null the last two
283: String[] newAttributes = new String[attributes.length - 2];
284: System.arraycopy(attributes, 0, newAttributes, 0,
285: attributeIndex * 2);
286: int next = (attributeIndex * 2) + 2;
287: System.arraycopy(attributes, next, newAttributes,
288: attributeIndex * 2, attributes.length - next);
289: attributeCount = attributeCount - 2;
290: attributes = newAttributes;
291: }
292:
293: /**
294: * Change the value of an attribute, or add an attribute if it does not already exist.
295: *
296: * @param name Name of attribute to remove.
297: * @param caseSensitive Whether the name should be treated as case sensitive.
298: */
299: public void removeAttribute(String name, boolean caseSensitive) {
300: int attributeIndex = getAttributeIndex(name, caseSensitive);
301: if (attributeIndex == -1) {
302: throw new IllegalArgumentException("Attribute " + name
303: + " not found");
304: } else {
305: removeAttribute(attributeIndex);
306: }
307: }
308: }
|