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.util.ArrayList;
020: import java.util.Collections;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.regex.Matcher;
026: import java.util.regex.Pattern;
027:
028: import org.apache.wicket.util.string.AppendingStringBuffer;
029: import org.slf4j.Logger;
030: import org.slf4j.LoggerFactory;
031:
032: /**
033: * A list of markup elements associated with a Markup. Might be all elements of
034: * a markup resource, might be just the elements associated with a specific tag.
035: *
036: * @see org.apache.wicket.markup.MarkupResourceData
037: * @see org.apache.wicket.markup.MarkupElement
038: * @see org.apache.wicket.markup.ComponentTag
039: * @see org.apache.wicket.markup.RawMarkup
040: *
041: * @author Juergen Donnerstag
042: */
043: public class Markup {
044: private static final Logger log = LoggerFactory
045: .getLogger(Markup.class);
046:
047: /** Placeholder that indicates no markup */
048: public static final Markup NO_MARKUP = new Markup(
049: MarkupResourceData.NO_MARKUP_RESOURCE_DATA);
050:
051: /** The list of markup elements */
052: private/* final */List markupElements;
053:
054: /** The associated markup */
055: private final MarkupResourceData markupResourceData;
056:
057: /**
058: * A cache which maps (componentPath + id) to the componentTags index in the
059: * markup
060: */
061: private Map componentMap;
062:
063: /**
064: * Used at markup load time to maintain the current component path (not id)
065: * while adding markup elements to this Markup instance
066: */
067: private StringBuffer currentPath;
068:
069: /**
070: * Constructor
071: *
072: * @param markupResourceData
073: * The associated Markup
074: */
075: Markup(final MarkupResourceData markupResourceData) {
076: this .markupResourceData = markupResourceData;
077: markupElements = new ArrayList();
078: }
079:
080: /**
081: * For Wicket it would be sufficient for this method to be package
082: * protected. However to allow wicket-bench easy access to the information
083: * ...
084: *
085: * @param index
086: * Index into markup list
087: * @return Markup element
088: */
089: public final MarkupElement get(final int index) {
090: return (MarkupElement) markupElements.get(index);
091: }
092:
093: /**
094: * Gets the associate markup
095: *
096: * @return The associated markup
097: */
098: public final MarkupResourceData getMarkupResourceData() {
099: return markupResourceData;
100: }
101:
102: /**
103: * For Wicket it would be sufficient for this method to be package
104: * protected. However to allow wicket-bench easy access to the information
105: * ...
106: *
107: * @return Number of markup elements
108: */
109: public int size() {
110: return markupElements.size();
111: }
112:
113: /**
114: * Add a MarkupElement
115: *
116: * @param markupElement
117: */
118: final public void addMarkupElement(final MarkupElement markupElement) {
119: markupElements.add(markupElement);
120: }
121:
122: /**
123: * Add a MarkupElement
124: *
125: * @param pos
126: * @param markupElement
127: */
128: final public void addMarkupElement(final int pos,
129: final MarkupElement markupElement) {
130: markupElements.add(pos, markupElement);
131: }
132:
133: /**
134: * Make all tags immutable and the list of elements unmodifable.
135: */
136: final void makeImmutable() {
137: for (int i = 0; i < markupElements.size(); i++) {
138: MarkupElement elem = (MarkupElement) markupElements.get(i);
139: if (elem instanceof ComponentTag) {
140: // Make the tag immutable
141: ((ComponentTag) elem).makeImmutable();
142: }
143: }
144:
145: markupElements = Collections.unmodifiableList(markupElements);
146: initialize();
147: }
148:
149: /**
150: * Add the tag to the local cache if open or open-close and if wicket:id is
151: * present
152: *
153: * @param index
154: * @param tag
155: */
156: private void addToCache(final int index, final ComponentTag tag) {
157: // Only if the tag has wicket:id="xx" and open or open-close
158: if ((tag.isOpen() || tag.isOpenClose())
159: && tag.getAttributes().containsKey(
160: getMarkupResourceData().getWicketId())) {
161: // Add the tag to the cache
162: if (componentMap == null) {
163: componentMap = new HashMap();
164: }
165:
166: /*
167: * XXX cleanup - this fragment check probably needs to be in
168: * componenttag.isWantToBeDirectMarkupChild() or something similar
169: * instead of being here
170: */
171: final boolean fragment = (tag instanceof WicketTag && ((WicketTag) tag)
172: .isFragementTag());
173:
174: final String key;
175:
176: if (tag.getPath() != null && !fragment/* WICKET-404 */) {
177: key = tag.getPath() + ":" + tag.getId();
178: } else {
179: key = tag.getId();
180: }
181: componentMap.put(key, new Integer(index));
182: }
183: }
184:
185: /**
186: * Set the components path within the markup and add the component tag to
187: * the local cache
188: *
189: * @param componentPath
190: * @param tag
191: * @return componentPath
192: */
193: private StringBuffer setComponentPathForTag(
194: final StringBuffer componentPath, final ComponentTag tag) {
195: // Only if the tag has wicket:id="xx" and open or open-close
196: if ((tag.isOpen() || tag.isOpenClose())
197: && tag.getAttributes().containsKey(
198: markupResourceData.getWicketId())) {
199: // With open-close the path does not change. It can/will not have
200: // children. The same is true for HTML tags like <br> or <img>
201: // which might not have close tags.
202: if (tag.isOpenClose() || tag.hasNoCloseTag()) {
203: // Set the components path.
204: if ((currentPath != null) && (currentPath.length() > 0)) {
205: tag.setPath(currentPath.toString());
206: }
207: } else {
208: // Set the components path.
209: if (currentPath == null) {
210: currentPath = new StringBuffer(100);
211: } else if (currentPath.length() > 0) {
212: tag.setPath(currentPath.toString());
213: currentPath.append(':');
214: }
215:
216: // .. and append the tags id to the component path for the
217: // children to come
218: currentPath.append(tag.getId());
219: }
220: } else if (tag.isClose() && (currentPath != null)) {
221: // For example <wicket:message> does not have an id
222: if ((tag.getOpenTag() == null)
223: || tag.getOpenTag().getAttributes().containsKey(
224: markupResourceData.getWicketId())) {
225: // Remove the last element from the component path
226: int index = currentPath.lastIndexOf(":");
227: if (index != -1) {
228: currentPath.setLength(index);
229: } else {
230: currentPath.setLength(0);
231: }
232: }
233: }
234:
235: return currentPath;
236: }
237:
238: /**
239: * Find the markup element index of the component with 'path'
240: *
241: * @param path
242: * The component path expression
243: * @param id
244: * The component's id to search for
245: * @return -1, if not found
246: */
247: public int findComponentIndex(final String path, final String id) {
248: if ((id == null) || (id.length() == 0)) {
249: throw new IllegalArgumentException(
250: "Parameter 'id' must not be null");
251: }
252:
253: // TODO Post 1.2: A component path e.g. "panel:label" does not match 1:1
254: // with the markup in case of ListView, where the path contains a number
255: // for each list item. E.g. list:0:label. What we currently do is simply
256: // remove the number from the path and hope that no user uses an integer
257: // for a component id. This is a hack only. A much better solution would
258: // delegate to the various components recursivly to search within there
259: // realm only for the components markup. ListItems could then simply
260: // do nothing and delegate to their parents.
261: String completePath = (path == null || path.length() == 0 ? id
262: : path + ":" + id);
263:
264: // s/:\d+//g
265: Pattern re = Pattern.compile(":\\d+");
266: Matcher matcher = re.matcher(completePath);
267: completePath = matcher.replaceAll("");
268:
269: // All component tags are registered with the cache
270: if (componentMap == null) {
271: // not found
272: return -1;
273: }
274:
275: final Integer value = (Integer) componentMap.get(completePath);
276: if (value == null) {
277: // not found
278: return -1;
279: }
280:
281: // return the components position in the markup stream
282: return value.intValue();
283: }
284:
285: /**
286: * @param that
287: * The markup to compare with
288: * @return True if the two markups are equal
289: */
290: public boolean equalTo(final Markup that) {
291: final MarkupStream this Stream = new MarkupStream(this );
292: final MarkupStream thatStream = new MarkupStream(that);
293:
294: // Compare the streams
295: return this Stream.equalTo(thatStream);
296: }
297:
298: /**
299: * Initialize the index where wicket tags can be found
300: */
301: protected void initialize() {
302: // Reset
303: componentMap = null;
304:
305: if (markupElements != null) {
306: // HTML tags like <img> may not have a close tag. But because that
307: // can only be detected until later on in the sequential markup
308: // reading loop, we only can do it now.
309: StringBuffer componentPath = null;
310: for (int i = 0; i < size(); i++) {
311: MarkupElement elem = get(i);
312: if (elem instanceof ComponentTag) {
313: ComponentTag tag = (ComponentTag) elem;
314:
315: // Set the tags components path
316: componentPath = setComponentPathForTag(
317: componentPath, tag);
318:
319: // and add it to the local cache to be found fast if
320: // required
321: addToCache(i, tag);
322: }
323: }
324: }
325:
326: // The variable is only needed while adding markup elements.
327: // initialize() is invoked after all elements have been added.
328: currentPath = null;
329: }
330:
331: /**
332: * @return String representation of markup list
333: */
334: public final String toString() {
335: final AppendingStringBuffer buf = new AppendingStringBuffer(400);
336: buf.append(markupResourceData.toString());
337: buf.append("\n");
338:
339: final Iterator iter = markupElements.iterator();
340: while (iter.hasNext()) {
341: buf.append(iter.next());
342: }
343:
344: return buf.toString();
345: }
346: }
|