001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.tax;
043:
044: import java.util.List;
045: import java.util.LinkedList;
046: import java.util.Iterator;
047: import java.util.Collections;
048:
049: import org.netbeans.tax.spec.Document;
050: import org.netbeans.tax.spec.DocumentFragment;
051: import org.netbeans.tax.spec.Element;
052: import org.netbeans.tax.spec.GeneralEntityReference;
053: import org.netbeans.tax.spec.Attribute;
054:
055: import org.netbeans.tax.event.TreeEventManager;
056:
057: /**
058: * It represents startTag, endTag and emptyTag markup and holds element content.
059: *
060: * @author Libor Kramolis
061: * @version 0.1
062: */
063: public class TreeElement extends TreeParentNode implements
064: Document.Child, DocumentFragment.Child, Element.Child,
065: GeneralEntityReference.Child {
066:
067: /** */
068: public static final String PROP_TAG_NAME = "tagName"; // NOI18N
069: /** */
070: public static final String PROP_ATTRIBUTES = "attributes"; // NOI18N
071:
072: /** */
073: private TreeName tagName; //QName as arrears in output
074:
075: /** */
076: // private String baseURI; //??? => xml:base="baseURI" // NOI18N
077: /** */
078: private TreeNamespaceContext namespaceContext;
079:
080: /** */
081: private TreeNamedObjectMap attributes;
082:
083: /** */
084: private boolean empty; //signals that it represents empty element <empty/>
085: //as opposite to <nonempty></nonempty>
086: //it is a sticky flag
087:
088: /** */
089: private boolean containsCharacterData;
090:
091: //
092: // init
093: //
094:
095: /** Creates new TreeElement.
096: * @throws InvalidArgumentException
097: */
098: public TreeElement(String tagName, boolean empty)
099: throws InvalidArgumentException {
100: super ();
101:
102: TreeName treeName = new TreeName(tagName);
103: checkTagName(treeName);
104: this .tagName = treeName;
105: this .empty = empty;
106: this .containsCharacterData = false;
107:
108: this .namespaceContext = new TreeNamespaceContext(this );
109: this .attributes = new TreeNamedObjectMap(
110: createAttributesContentManager());
111:
112: if (Util.THIS.isLoggable()) /* then */
113: Util.THIS.debug("TreeElement:: : name = " + tagName
114: + " : empty = " + empty); // NOI18N
115: }
116:
117: /** Creates new TreeElement.
118: * @throws InvalidArgumentException
119: */
120: public TreeElement(String qName) throws InvalidArgumentException {
121: this (qName, false);
122: }
123:
124: /**
125: * Creates new TreeElement -- copy constructor.
126: */
127: protected TreeElement(TreeElement element, boolean deep) {
128: super (element, deep);
129:
130: this .tagName = element.tagName;
131: this .empty = element.empty;
132: // this.baseURI = element.baseURI;
133: this .namespaceContext = new TreeNamespaceContext(this );
134: this .attributes = new TreeNamedObjectMap(
135: createAttributesContentManager());
136: this .attributes.addAll((TreeNamedObjectMap) element.attributes
137: .clone());
138: }
139:
140: //
141: // from TreeObject
142: //
143:
144: /**
145: */
146: public Object clone(boolean deep) {
147: return new TreeElement(this , deep);
148: }
149:
150: /**
151: */
152: public boolean equals(Object object, boolean deep) {
153: if (!!!super .equals(object, deep))
154: return false;
155:
156: TreeElement peer = (TreeElement) object;
157: if (this .empty != peer.empty)
158: return false;
159: if (!!!Util.equals(this .getTreeName(), peer.getTreeName()))
160: return false;
161: if (!!!Util.equals(this .getAttributes(), peer.getAttributes()))
162: return false;
163:
164: return true;
165: }
166:
167: /*
168: * Merge name and empty (sticky) properties and delagte merging attributes.
169: * Ignore peer's Namespace context.
170: */
171: public void merge(TreeObject treeObject)
172: throws CannotMergeException {
173: super .merge(treeObject);
174:
175: TreeElement peer = (TreeElement) treeObject;
176: this .empty = empty || peer.empty; //sticky tag
177:
178: try {
179: setTreeName(peer.getTreeName());
180: } catch (Exception exc) {
181: throw new CannotMergeException(treeObject, exc);
182: }
183:
184: attributes.merge(peer.getAttributes());
185: // attributes.setContentManager (this);
186: }
187:
188: //
189: // read only
190: //
191:
192: /**
193: */
194: protected void setReadOnly(boolean newReadOnly) {
195: super .setReadOnly(newReadOnly);
196:
197: attributes.setReadOnly(newReadOnly);
198: }
199:
200: //
201: // itself
202: //
203:
204: /**
205: */
206: public final String getQName() {
207: return tagName.getQualifiedName();
208: }
209:
210: /**
211: * @throws ReadOnlyException
212: * @throws InvalidArgumentException
213: */
214: public final void setQName(String newTagName)
215: throws ReadOnlyException, InvalidArgumentException {
216: setTreeName(new TreeName(newTagName));
217: }
218:
219: /**
220: */
221: public final TreeName getTreeName() {
222: return tagName;
223: }
224:
225: /**
226: */
227: private final void setTreeNameImpl(TreeName newTagName) {
228: TreeName oldTagName = this .tagName;
229:
230: this .tagName = newTagName;
231:
232: firePropertyChange(PROP_TAG_NAME, oldTagName, newTagName);
233: }
234:
235: /**
236: * @throws ReadOnlyException
237: * @throws InvalidArgumentException
238: */
239: public final void setTreeName(TreeName newTagName)
240: throws ReadOnlyException, InvalidArgumentException {
241: //
242: // check new value
243: //
244: if (Util.equals(this .tagName, newTagName))
245: return;
246: checkReadOnly();
247: checkTagName(newTagName);
248:
249: //
250: // set new value
251: //
252: setTreeNameImpl(newTagName);
253: }
254:
255: /**
256: * Treat the empty flag as "sticky" is means that is someone
257: * adds some conetent and then remove it this flag will survive
258: * @return true it this element represents empty one
259: */
260: public boolean isEmpty() {
261: if (empty == false)
262: return false;
263: return getChildNodes().size() == 0;
264: }
265:
266: /**
267: */
268: protected final void checkTagName(TreeName tagName)
269: throws InvalidArgumentException {
270: TreeUtilities.checkElementTagName(tagName);
271: }
272:
273: //
274: // Namespaces
275: //
276:
277: /**
278: */
279: protected final TreeNamespaceContext getNamespaceContext() {
280: return namespaceContext;
281: }
282:
283: /**
284: * @return element namespce or TreeNamespace.NO_NAMESPACE
285: */
286: public final TreeNamespace getNamespace() {
287: String prefix = getNamespacePrefix();
288: String uri = namespaceContext.getURI(prefix);
289:
290: if (uri == null) {
291: return TreeNamespace.NO_NAMESPACE;
292: } else {
293: return new TreeNamespace(prefix, uri);
294: }
295: }
296:
297: /**
298: */
299: public final String getNamespacePrefix() {
300: return tagName.getPrefix();
301: }
302:
303: /**
304: */
305: public final String getNamespaceURI() {
306: return getNamespace().getURI();
307: }
308:
309: /**
310: */
311: public final String getLocalName() {
312: return tagName.getName();
313: }
314:
315: //
316: // Attributes
317: //
318:
319: /**
320: */
321: public final int getAttributesNumber() {
322: return (attributes.size());
323: }
324:
325: /**
326: */
327: public final boolean hasAttributes() {
328: return (attributes.size() != 0);
329: }
330:
331: /**
332: */
333: public final boolean hasAttribute(String name) {
334: return (getAttribute(name) != null);
335: }
336:
337: /**
338: */
339: public final TreeNamedObjectMap getAttributes() {
340: return attributes;
341: }
342:
343: /**
344: */
345: public final TreeAttribute getAttribute(String name) {
346: try {
347: TreeName treeName = new TreeName(name);
348: return (TreeAttribute) attributes.get(treeName);
349: } catch (InvalidArgumentException exc) {
350: return null;
351: }
352: }
353:
354: /**
355: * @throws InvalidArgumentException
356: * @throws ReadOnlyException
357: */
358: public final TreeAttribute addAttribute(String name, String value)
359: throws ReadOnlyException, InvalidArgumentException {
360: //// Will be uncommented after NB 3.3.1 (http://www.netbeans.org/issues/show_bug.cgi?id=17699)
361: // TreeAttribute attr = getAttribute (name);
362: // if ( attr != null ) {
363: // throw new InvalidArgumentException
364: // (attr, Util.THIS.getString ("EXC_attribute_exists", name));
365: // }
366:
367: checkReadOnly();
368: TreeAttribute newAttr = new TreeAttribute(name, value);
369: TreeAttribute oldAttr = removeAttribute(name);
370: attributes.add(newAttr);
371: return oldAttr;
372: }
373:
374: /**
375: * @throws InvalidArgumentException
376: * @throws ReadOnlyException
377: */
378: public final void addAttribute(TreeAttribute newAttr)
379: throws ReadOnlyException, InvalidArgumentException {
380: String qName = newAttr.getQName();
381:
382: //// Will be uncommented after NB 3.3.1 (http://www.netbeans.org/issues/show_bug.cgi?id=17699)
383: // TreeAttribute attr = getAttribute (qName);
384: // if ( attr != null ) {
385: // throw new InvalidArgumentException
386: // (attr, Util.THIS.getString ("EXC_attribute_exists", qName));
387: // }
388:
389: checkReadOnly();
390: TreeAttribute oldAttr = removeAttribute(qName);
391: attributes.add(newAttr);
392: // return oldAttr;
393: }
394:
395: /**
396: * @throws ReadOnlyException
397: */
398: public final TreeAttribute removeAttribute(String name)
399: throws ReadOnlyException {
400: return removeAttribute(getAttribute(name));
401: }
402:
403: /**
404: * @throws ReadOnlyException
405: */
406: public final TreeAttribute removeAttribute(TreeAttribute oldAttr)
407: throws ReadOnlyException {
408: checkReadOnly();
409: attributes.remove(oldAttr);
410: return oldAttr;
411: }
412:
413: /**
414: * @throws ReadOnlyException
415: */
416: public final void removeAttributes() throws ReadOnlyException {
417: checkReadOnly();
418: attributes.clear();
419: }
420:
421: //
422: // Utilities
423: //
424:
425: /**
426: * @throws ReadOnlyException
427: */
428: public final void normalize() throws ReadOnlyException {
429: checkReadOnly();
430: try {
431: getChildNodes().getEventManager().setFirePolicy(
432: TreeEventManager.FIRE_LATER);
433: for (int i = 0; true; i++) {
434: TreeChild child = item(i);
435:
436: if (child instanceof TreeElement) {
437: ((TreeElement) child).normalize();
438: } else if (child instanceof TreeText) {
439: while (true) {
440: TreeChild child2 = item(i + 1);
441: if (child2 instanceof TreeText) {
442: try {
443: ((TreeText) child)
444: .appendData(((TreeText) child2)
445: .getData());
446: removeChild(child2);
447: } catch (InvalidArgumentException exc) { // from TreeText.appendChild : impossible because TreeText.getData
448: break; // get out from 'while (true)'
449: }
450: } else {
451: break; // get out from 'while (true)'
452: }
453: }
454: }
455: }
456: } catch (IndexOutOfBoundsException e) {
457: /* OK */
458: } finally {
459: getChildNodes().getEventManager().setFirePolicy(
460: TreeEventManager.FIRE_NOW);
461: }
462: }
463:
464: /**
465: */
466: public final boolean containsCharacterData() {
467: return containsCharacterData;
468: }
469:
470: /**
471: */
472: private void updateContainsCharacterData() {
473: Iterator it = getChildNodes().iterator();
474: while (it.hasNext()) {
475: Object obj = it.next();
476: if (obj instanceof TreeCharacterData) {
477: TreeCharacterData charData = (TreeCharacterData) obj;
478: if (charData instanceof TreeData) {
479: if (((TreeData) charData).onlyWhiteSpaces() == false) {
480: containsCharacterData = true;
481: }
482: } else {
483: containsCharacterData = true;
484: }
485: if (containsCharacterData) {
486: return;
487: }
488: }
489: }
490: containsCharacterData = false;
491: }
492:
493: //
494: // TreeObjectList.ContentManager
495: //
496:
497: /**
498: */
499: protected TreeObjectList.ContentManager createChildListContentManager() {
500: return new ChildListContentManager();
501: }
502:
503: /**
504: */
505: protected TreeNamedObjectMap.ContentManager createAttributesContentManager() {
506: return new AttributesContentManager();
507: }
508:
509: /**
510: *
511: */
512: protected class ChildListContentManager extends
513: TreeParentNode.ChildListContentManager {
514:
515: /**
516: */
517: public TreeNode getOwnerNode() {
518: return TreeElement.this ;
519: }
520:
521: /**
522: */
523: public void checkAssignableObject(Object obj) {
524: super .checkAssignableObject(obj);
525: checkAssignableClass(Element.Child.class, obj);
526: }
527:
528: } // end: class ChildListContentManager
529:
530: /**
531: *
532: */
533: protected class AttributesContentManager extends
534: TreeNamedObjectMap.ContentManager {
535:
536: /**
537: */
538: public TreeNode getOwnerNode() {
539: return TreeElement.this ;
540: }
541:
542: /**
543: */
544: public void checkAssignableObject(Object obj) {
545: checkAssignableClass(Element.Attribute.class, obj);
546: }
547:
548: /**
549: */
550: public void objectInserted(TreeObject obj) {
551: ((TreeAttribute) obj).setOwnerElement(TreeElement.this );
552: TreeElement.this .firePropertyChange(
553: TreeElement.PROP_ATTRIBUTES,
554: TreeElement.this .attributes, null);
555: }
556:
557: /**
558: */
559: public void objectRemoved(TreeObject obj) {
560: ((TreeAttribute) obj).setOwnerElement(null);
561: TreeElement.this .firePropertyChange(
562: TreeElement.PROP_ATTRIBUTES,
563: TreeElement.this .attributes, null);
564: }
565:
566: /**
567: */
568: public void orderChanged(int[] permutation) {
569: TreeElement.this .firePropertyChange(
570: TreeElement.PROP_ATTRIBUTES,
571: TreeElement.this .attributes, null);
572: }
573:
574: } // end: class AttributesContentManager
575:
576: }
|