001: /* AbstractGroup.java
002:
003: {{IS_NOTE
004:
005: Purpose:
006: Description:
007: History:
008: 2001/10/21 16:31:30, Create, Tom M. Yeh.
009: }}IS_NOTE
010:
011: Copyright (C) 2001 Potix Corporation. All Rights Reserved.
012:
013: {{IS_RIGHT
014: This program is distributed under GPL Version 2.0 in the hope that
015: it will be useful, but WITHOUT ANY WARRANTY.
016: }}IS_RIGHT
017: */
018: package org.zkoss.idom.impl;
019:
020: import java.util.List;
021: import java.util.ArrayList;
022: import java.util.LinkedList;
023: import java.util.Set;
024: import java.util.LinkedHashSet;
025: import java.util.Iterator;
026: import java.util.ListIterator;
027: import java.util.regex.Pattern;
028: import java.util.Map;
029: import java.util.LinkedHashMap;
030: import java.util.Collection;
031: import java.util.AbstractCollection;
032: import java.util.Collections;
033:
034: import org.w3c.dom.Node;
035: import org.w3c.dom.NodeList;
036:
037: import org.zkoss.util.CheckableTreeArray;
038: import org.zkoss.xml.FacadeNodeList;
039: import org.zkoss.idom.*;
040:
041: /**
042: * A semi-implemented item for group. A group is a item that has child items.
043: *
044: * <p>The default implementation of newChildren is for the sematic of
045: * Element. A deriving class has to re-implement it, if it is not applicable.
046: * Example, Document.
047: *
048: * @author tomyeh
049: * @see Item
050: */
051: public abstract class AbstractGroup extends AbstractItem implements
052: Group {
053: /** The list of the children. Never null.
054: */
055: protected List _children;
056:
057: /** A helper map to enhance the searching speed with tag name.
058: * If any deriving class don't contain this helper map, they should
059: * apply the basic sequential search.
060: */
061: private transient ElementMap _elemMap;
062:
063: /** Constructor.
064: */
065: protected AbstractGroup() {
066: _children = newChildren();
067: }
068:
069: //-- deriving to override --//
070: /** Creates a list to hold child vertices.
071: * Note: the list must be able to protect itself from adding
072: * unexpected child -- read-only, wrong type, undetached...
073: *
074: * <p>The default implementation obeys the sematic of Element,
075: * i.e., it doen't allow any child that cannot be a child of Element.
076: *
077: * <p>For performance issue, we introduced a map to improve the search
078: * speed for Element node associated with a tag name.
079: */
080: protected List newChildren() {
081: return new ChildArray();
082: }
083:
084: //-- Group --//
085: public void clearModified(boolean includingDescendant) {
086: if (includingDescendant) {
087: for (final Iterator it = _children.iterator(); it.hasNext();)
088: ((Item) it.next()).clearModified(true);
089: }
090: super .clearModified(includingDescendant);
091: }
092:
093: public Item clone(boolean preserveModified) {
094: AbstractGroup group = (AbstractGroup) super
095: .clone(preserveModified);
096:
097: group._children = group.newChildren();
098: for (final Iterator it = _children.iterator(); it.hasNext();) {
099: Item v = ((Item) it.next()).clone(preserveModified);
100: boolean bClearModified = !preserveModified
101: || !v.isModified();
102:
103: group._children.add(v); //v becomes modified (v.setParent is called)
104:
105: if (bClearModified)
106: v.clearModified(false);
107: }
108:
109: if (group._children instanceof ChildArray)
110: ((ChildArray) group._children).afterClone();
111:
112: group._modified = preserveModified && _modified;
113: return group;
114: }
115:
116: public final List getChildren() {
117: return _children;
118: }
119:
120: public final List detachChildren() {
121: List list = new ArrayList(_children); //make a copy first
122:
123: for (Iterator it = _children.iterator(); it.hasNext();) {
124: it.next();
125: it.remove(); //and detach
126: }
127:
128: return list;
129: }
130:
131: public final boolean anyElement() {
132: if (_elemMap != null)
133: return _elemMap.any();
134:
135: for (Iterator it = _children.iterator(); it.hasNext();) {
136: final Object o = it.next();
137: if (o instanceof Element)
138: return true;
139: }
140: return false;
141: }
142:
143: public final Set getElementNames() {
144: if (_elemMap != null)
145: return _elemMap.names();
146:
147: final Set set = new LinkedHashSet();
148: for (final Iterator it = _children.iterator(); it.hasNext();) {
149: final Object o = it.next();
150: if (o instanceof Element)
151: set.add(((Element) o).getName());
152: }
153: return set;
154: }
155:
156: public final List getElements() {
157: final List lst = new LinkedList();
158: for (final Iterator it = _children.iterator(); it.hasNext();) {
159: final Object o = it.next();
160: if (o instanceof Element)
161: lst.add(o);
162: }
163: return lst;
164: }
165:
166: public final int getElementIndex(int indexFrom, String namespace,
167: String name, int mode) {
168: if (indexFrom < 0 || indexFrom >= _children.size())
169: return -1;
170:
171: final Pattern ptn = (mode & FIND_BY_REGEX) != 0 ? Pattern
172: .compile(name) : null;
173:
174: final Iterator it = _children.listIterator(indexFrom);
175: for (int j = indexFrom; it.hasNext(); ++j) {
176: final Object o = it.next();
177: if ((o instanceof Element)
178: && match((Element) o, namespace, name, ptn, mode))
179: return j;
180: }
181: return -1;
182: }
183:
184: public final int getElementIndex(int indexFrom, String tname) {
185: return getElementIndex(indexFrom, null, tname, FIND_BY_TAGNAME);
186: }
187:
188: public final Element getElement(String namespace, String name,
189: int mode) {
190: if (_elemMap != null && namespace == null
191: && mode == FIND_BY_TAGNAME)
192: return getElement(name); //use the speed version
193:
194: int j = getElementIndex(0, namespace, name, mode);
195: if (j >= 0)
196: return (Element) _children.get(j);
197:
198: if ((mode & FIND_RECURSIVE) != 0) {
199: for (Iterator it = _children.iterator(); it.hasNext();) {
200: Object o = it.next();
201: if (o instanceof Group) {
202: Element elem = ((Group) o).getElement(namespace,
203: name, mode);
204: if (elem != null)
205: return elem;
206: }
207: }
208: }
209: return null;
210: }
211:
212: public final Element getElement(String tname) {
213: if (_elemMap != null)
214: return _elemMap.get(tname);
215:
216: int j = getElementIndex(0, tname);
217: return j >= 0 ? (Element) _children.get(j) : null;
218: }
219:
220: public final List getElements(String namespace, String name,
221: int mode) {
222: if (_elemMap != null && namespace == null
223: && mode == FIND_BY_TAGNAME)
224: return getElements(name); //use the speed version
225:
226: final Pattern ptn = (mode & FIND_BY_REGEX) != 0 ? Pattern
227: .compile(name) : null;
228:
229: final List list = new LinkedList();
230: for (final Iterator it = _children.iterator(); it.hasNext();) {
231: Object o = it.next();
232: if ((o instanceof Element)
233: && match((Element) o, namespace, name, ptn, mode))
234: list.add(o);
235: }
236:
237: if ((mode & FIND_RECURSIVE) != 0) {
238: for (final Iterator it = _children.iterator(); it.hasNext();) {
239: Object o = it.next();
240: if (o instanceof Group)
241: list.addAll(((Group) o).getElements(namespace,
242: name, mode));
243: }
244: }
245: return list;
246: }
247:
248: public final List getElements(String tname) {
249: if (_elemMap != null)
250: return _elemMap.getAll(tname);
251:
252: return getElements(null, tname, FIND_BY_TAGNAME);
253: }
254:
255: public final String getElementValue(String namespace, String name,
256: int mode, boolean trim) {
257: Element child = getElement(namespace, name, mode);
258: return child != null ? child.getText(trim) : null;
259: }
260:
261: public final String getElementValue(String tname, boolean trim) {
262: final Element child = getElement(tname);
263: return child != null ? child.getText(trim) : null;
264: }
265:
266: public final int coalesce(boolean recursive) {
267: int count = 0;
268: Item found = null;
269: StringBuffer sb = new StringBuffer();
270: for (final Iterator it = _children.iterator(); it.hasNext();) {
271: Object o = it.next();
272: Item newFound = (o instanceof Textual)
273: && ((Textual) o).isCoalesceable() ? (Item) o : null;
274:
275: if (newFound != null && found != null
276: && found.getClass().equals(o.getClass())) {
277: if (sb.length() == 0)
278: sb.append(found.getText());
279: sb.append(((Item) o).getText()); //coalesce text
280: it.remove(); //remove this node
281: ++count; //# being coalesced and removed
282: } else {
283: if (sb.length() > 0) { //coalesced before?
284: found.setText(sb.toString());
285: sb.setLength(0);
286: }
287: found = newFound;
288: }
289: }
290: if (sb.length() > 0)
291: found.setText(sb.toString());
292: sb = null; //no longer useful
293:
294: if (recursive) {
295: for (final Iterator it = _children.iterator(); it.hasNext();) {
296: final Object o = it.next();
297: if (o instanceof Group)
298: count += ((Group) o).coalesce(recursive);
299: }
300: }
301: return count;
302: }
303:
304: //-- Node --//
305: public final NodeList getChildNodes() {
306: return new FacadeNodeList(_children);
307: }
308:
309: public final Node getFirstChild() {
310: return _children.isEmpty() ? null : (Node) _children.get(0);
311: }
312:
313: public final Node getLastChild() {
314: int sz = _children.size();
315: return sz == 0 ? null : (Node) _children.get(sz - 1);
316: }
317:
318: public final boolean hasChildNodes() {
319: return !_children.isEmpty();
320: }
321:
322: //No need to call checkWritable here because _children is smart enough
323: public final Node insertBefore(Node newChild, Node refChild) {
324: if (refChild == null)
325: return appendChild(newChild);
326:
327: int j = _children.indexOf(refChild);
328: if (j < 0)
329: throw new DOMException(DOMException.NOT_FOUND_ERR,
330: getLocator());
331: _children.add(j, newChild);
332: return newChild;
333: }
334:
335: public final Node replaceChild(Node newChild, Node oldChild) {
336: int j = _children.indexOf(oldChild);
337: if (j < 0)
338: throw new DOMException(DOMException.NOT_FOUND_ERR,
339: getLocator());
340: return (Node) _children.set(j, newChild);
341: }
342:
343: public final Node removeChild(Node oldChild) {
344: int j = _children.indexOf(oldChild);
345: if (j < 0)
346: throw new DOMException(DOMException.NOT_FOUND_ERR,
347: getLocator());
348: return (Node) _children.remove(j);
349: }
350:
351: public final Node appendChild(Node newChild) {
352: _children.add(newChild);
353: return newChild;
354: }
355:
356: //-- Serializable --//
357: //NOTE: they must be declared as private
358: private synchronized void readObject(java.io.ObjectInputStream s)
359: throws java.io.IOException, ClassNotFoundException {
360: s.defaultReadObject();
361:
362: if (_children instanceof ChildArray)
363: ((ChildArray) _children).afterUnmarshal();
364: }
365:
366: //-- ElementMap
367: /** Stores a 'cached' map of child elements to speed up the access.
368: */
369: protected static class ElementMap {
370: /** the map of (String elemName, List of Elements). */
371: private final Map _map = new LinkedHashMap();
372:
373: protected ElementMap() {
374: }
375:
376: /**
377: * Put an element into the map.
378: * If the "following" argument is assocaied the same name, we will
379: * add the element before the "following".
380: */
381: public final void put(Element e, Element following) {
382: final String name = e.getName();
383: List valueList = (List) _map.get(name);
384: if (valueList == null) {
385: valueList = new LinkedList();
386: _map.put(name, valueList);
387: }
388:
389: if (following != null && name.equals(following.getName())) {
390: //add into list before the following
391: for (ListIterator it = valueList.listIterator(); it
392: .hasNext();) {
393: if (it.next() == following) { //no need to use equals
394: it.previous();
395: it.add(e);
396: return;
397: }
398: }
399: }
400:
401: valueList.add(e); //add into list
402: }
403:
404: /**
405: * Get the element with name. If you have many values associalted with
406: * the same key, it returned the head for you.
407: */
408: public final Element get(String name) {
409: final List vals = (List) _map.get(name);
410: return vals != null && !vals.isEmpty() ? (Element) vals
411: .get(0) : null;
412: }
413:
414: /**
415: * Get a readonly list of all elements with name.
416: */
417: public final List getAll(String name) {
418: final List vals = (List) _map.get(name);
419: return vals != null ? Collections.unmodifiableList(vals)
420: : Collections.EMPTY_LIST;
421: }
422:
423: /**
424: * Remove e from the map.
425: */
426: public final void remove(Element e) {
427: final List vals = (List) _map.get(e.getName());
428: vals.remove(e);
429: if (vals.isEmpty())
430: _map.remove(e.getName());
431: }
432:
433: /** Returns true if any element.
434: */
435: public final boolean any() {
436: return !_map.isEmpty();
437: }
438:
439: /** Returns a readonly set of names of all elements.
440: */
441: public final Set names() {
442: return _map.keySet();
443: }
444:
445: /** Returns the number of elements.
446: */
447: public final int size() {
448: int sz = 0;
449: for (Iterator it = _map.values().iterator(); it.hasNext();) {
450: sz += ((List) it.next()).size();
451: }
452: return sz;
453: }
454: }
455:
456: //-- ChildArray --//
457: /** The array to hold children.
458: */
459: protected class ChildArray extends CheckableTreeArray {
460: protected ChildArray() {
461: _elemMap = new ElementMap();
462: }
463:
464: /** Called after unmarshalling back the AbstractGroup instance
465: * that owns this object.
466: */
467: private void afterUnmarshal() {
468: _elemMap = new ElementMap();
469:
470: for (Iterator it = this .iterator(); it.hasNext();) {
471: final Object o = it.next();
472: if (o instanceof Element)
473: _elemMap.put((Element) o, null);
474: }
475: }
476:
477: /** Called after cloning the AbstractGroup instance that owns this object.
478: */
479: private void afterClone() {
480: afterUnmarshal();
481: }
482:
483: //-- CheckableTreeArray --//
484: protected void onAdd(Object newElement, Object followingElement) {
485: checkAdd(newElement, followingElement, false);
486: }
487:
488: protected void onSet(Object newElement, Object replaced) {
489: assert (replaced != null);
490: checkAdd(newElement, replaced, true);
491: }
492:
493: private void checkAdd(Object newVal, Object other,
494: boolean replace) {
495: checkWritable();
496:
497: //allowed type?
498: if (!(newVal instanceof Element)
499: && !(newVal instanceof Text)
500: && !(newVal instanceof CData)
501: && !(newVal instanceof Comment)
502: && !(newVal instanceof EntityReference)
503: && !(newVal instanceof Binary)
504: && !(newVal instanceof ProcessingInstruction))
505: throw new DOMException(
506: DOMException.HIERARCHY_REQUEST_ERR,
507: "Invalid type", getLocator());
508:
509: //to be safe, no auto-detach
510: final Item newItem = (Item) newVal;
511: if (newItem.getParent() != null) {
512: throw new DOMException(
513: DOMException.HIERARCHY_REQUEST_ERR, "Item, "
514: + newItem.toString() + ", owned by "
515: + newItem.getParent() + " "
516: + newItem.getLocator()
517: + "; detach or clone it", getLocator());
518: }
519:
520: //test whether a graph will be created?
521: if (newItem instanceof Group)
522: for (Item p = AbstractGroup.this ; p != null; p = p
523: .getParent())
524: if (p == newItem)
525: throw new DOMException(
526: DOMException.HIERARCHY_REQUEST_ERR,
527: "Add to itself", getLocator());
528:
529: if (newItem instanceof Element) { //Element put into map, this must be done before replaced
530: //try to find the first Element node on the array
531: Element eOther;
532: if ((other != null) && !(other instanceof Element)) {
533: eOther = null;
534: boolean bFirstElemFind = false;
535: for (Iterator it = this .iterator(); it.hasNext();) {
536: Object node = it.next();
537: if (bFirstElemFind) {
538: if (node instanceof Element) {
539: eOther = (Element) node;
540: break;
541: }
542: } else if (node == other) {
543: bFirstElemFind = true;
544: }
545: }
546: } else {
547: eOther = (Element) other;
548: }
549: _elemMap.put((Element) newItem, eOther);
550: }
551:
552: if (replace)
553: onRemove(other);
554: newItem.setParent(AbstractGroup.this ); //it will call this.setModified
555: }
556:
557: protected void onRemove(Object item) {
558: checkWritable();
559: final Item removeItem = (Item) item;
560: removeItem.setParent(null); //it will call this.setModified
561:
562: if (removeItem instanceof Element) //Element remove from map
563: _elemMap.remove((Element) removeItem);
564: }
565: }
566: }
|