KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > editor > structure > api > DocumentElement


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20
21 package org.netbeans.modules.editor.structure.api;
22
23 import java.util.Collections JavaDoc;
24 import java.util.Enumeration JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.HashSet JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.Map JavaDoc;
30 import javax.swing.text.AttributeSet JavaDoc;
31 import javax.swing.text.BadLocationException JavaDoc;
32 import javax.swing.text.Document JavaDoc;
33 import javax.swing.text.Position JavaDoc;
34
35
36 /**
37  * DocumentElement is a building block of the document model tree-based hierarchy.
38  * <br>
39  * DocumentElement represents a piece of a {@link javax.swing.text.Document} with following behaviour
40  * <ul>
41  * <li>DocumentElement can contain other elements (elements can nest),
42  * <li>Boundaries of each two elements cannot cross,
43  * <li>Two elements cannot have the same boundaries.
44  * <li>DocumentElement boundaries cannot be the same (startoffset==endoffset)
45  * </ul>
46  * <br>
47  * The DocumentElement holds a set of attributes which can contain an arbitrary metadata related to the element.
48  * <br>
49  * It is possible to attach a {@link DocumentElementListener} to each DocumentElement.
50  * The listener can notify about children elements added, removed, reordered or when the content or attributes of the element have been changed.
51  * <br>
52  * Each DocumentModel which contains a tree of elements has one root element. This is a special
53  * element which is not created by model providers, but is created by default.
54  *
55  *
56  * @author Marek Fukala
57  * @version 1.0
58  */

59 public final class DocumentElement {
60     
61     private String JavaDoc name;
62     private String JavaDoc type;
63     private Position JavaDoc startPos, endPos;
64     private int startSectionLength, endSectionLength;
65     private DocumentModel model;
66     private Attributes attributes;
67     private int elementEmpty;
68     private boolean isRootElement;
69     
70     private static final int ELEMENT_EMPTY_UNSET = 0;
71     private static final int ELEMENT_EMPTY_TRUE = 1;
72     private static final int ELEMENT_EMPTY_FALSE = 2;
73     
74     //stores DocumentElement listeners
75
private HashSet JavaDoc<DocumentElementListener> deListeners = new HashSet JavaDoc<DocumentElementListener>();
76     
77     DocumentElement(String JavaDoc name, String JavaDoc type, Map JavaDoc attrsMap,
78             int startOffset, int endOffset, DocumentModel model) throws BadLocationException JavaDoc {
79         this.name = name;
80         this.model = model;
81         this.startSectionLength = startSectionLength;
82         this.endSectionLength = endSectionLength;
83         this.type = type;
84         this.attributes = new Attributes(this, attrsMap);
85         this.elementEmpty = ELEMENT_EMPTY_UNSET;
86         this.isRootElement = false;
87         
88         //create positions for start and end offsets
89
setStartPosition(startOffset);
90         setEndPosition(endOffset);
91     }
92     
93     /**
94      * Returns a collection of attributes this element contains.
95      *
96      * @return the attributes for the element
97      */

98     public AttributeSet JavaDoc getAttributes() {
99         return this.attributes;
100     }
101     
102     /**
103      * Returns the document associated with this element.
104      *
105      * @return the document
106      */

107     public Document JavaDoc getDocument() {
108         return model.getDocument();
109     }
110     
111     /**
112      * Returns the child element at the given index.
113      *
114      * @param index the specified index >= 0
115      * @return the child element
116      */

117     public DocumentElement getElement(int index) {
118         return (DocumentElement)getChildren().get(index);
119     }
120     
121     /**
122      * Gets the number of child elements contained by this element.
123      * If this element is a leaf, a count of zero is returned.
124      *
125      * @return the number of child elements >= 0
126      */

127     public int getElementCount() {
128         return getChildren().size();
129     }
130     
131     /**
132      * Returns the offset from the beginning of the document
133      * that this element begins at.
134      *
135      * @return the starting offset >= 0 and < getEndOffset();
136      * @see javax.swing.text.Document
137      */

138     public int getStartOffset() {
139         return startPos.getOffset();
140     }
141     
142     /**
143      * Returns the offset from the beginning of the document
144      * that this element ends at.
145      *
146      * @return the ending offset >= getDocument().getLength() and > getStartOffset();
147      * @see javax.swing.text.Document
148      */

149     public int getEndOffset() {
150         return endPos.getOffset();
151     }
152     
153     /**
154      * Gets the child element index closest to the given offset.
155      * The offset is specified relative to the beginning of the
156      * document. Returns <code>-1</code> if the
157      * <code>Element</code> is a leaf, otherwise returns
158      * the index of the <code>Element</code> that best represents
159      * the given location. Returns <code>0</code> if the location
160      * is less than the start offset. Returns
161      * <code>getElementCount() - 1</code> if the location is
162      * greater than or equal to the end offset.
163      *
164      * @param offset the specified offset >= 0
165      * @return the element index >= 0
166      */

167     public int getElementIndex(int offset) {
168         //find a child closes to the given offset
169
//The javadoc seems to be quite vague in definition of the bahaviour
170
//of this method. What is closest? What if the offset falls right
171
//between two elements?
172
Iterator JavaDoc children = getChildren().iterator();
173         int min_delta = Integer.MAX_VALUE;
174         DocumentElement nearest = null;
175         while(children.hasNext()) {
176             DocumentElement de = (DocumentElement)children.next();
177             
178             //test if the offset falls directly to a child
179
if(de.getStartOffset() <= offset && de.getEndOffset() > offset) {
180                 nearest = de;
181                 break;
182             } else {
183                 //no child element on offset -> try to find nearest child
184
int start_delta = Math.abs(de.getStartOffset() - offset);
185                 int end_delta = Math.abs(de.getEndOffset() - offset);
186                 int delta = Math.min(start_delta, end_delta);
187                 
188                 if(min_delta > delta) {
189                     nearest = de;
190                     min_delta = delta;
191                 }
192             }
193         }
194         
195         if(nearest == null) return -1;
196         else return getChildren().indexOf(nearest);
197         
198     }
199     
200     /** Returns the name of the element.
201      *
202      * @return the element name
203      */

204     public String JavaDoc getName() {
205         return name;
206     }
207     
208     /** Returns parent DocumentElement for this element. Returns null when the DocumentElement is a root element.
209      *
210      * @return the parent element
211      */

212     public DocumentElement getParentElement() {
213         return model.getParent(this);
214     }
215     
216     /** @return true if the element has no children */
217     public boolean isLeaf() {
218         return getChildren().isEmpty();
219     }
220     
221     /* EOF j.s.t.Element methods */
222     
223     //called by the model when an element's attributes has changed
224
void setAttributes(Map JavaDoc attrs) {
225         this.attributes = new Attributes(this, attrs);
226     }
227     
228     
229     /** called by DocumentModel when checking elements after a document content update */
230     void setElementIsEmptyState(boolean state) {
231         elementEmpty = state ? ELEMENT_EMPTY_TRUE : ELEMENT_EMPTY_FALSE;
232     }
233     
234     /** states whether the document is empty - used only by DocumentModel.
235      * First call to isEmpty() method will cache the result
236      */

237     
238     boolean isEmpty() {
239         if(elementEmpty == ELEMENT_EMPTY_UNSET) elementEmpty = model.isEmpty(this) ? ELEMENT_EMPTY_TRUE : ELEMENT_EMPTY_FALSE;
240         return elementEmpty == ELEMENT_EMPTY_TRUE;
241     }
242     
243     /** Returns an instance of DocumentModel within which hierarchy the element lives.
244      * @return the DocumentModel which holds this element
245      */

246     public DocumentModel getDocumentModel() {
247         return model;
248     }
249     
250     /** Returns a type of the element.
251      * Each DocumentModelProvider should create a set of elements types and the pass these
252      * types when elements of corresponding types are created.
253      * Clients of the API then uses the element's type to determine the element type.
254      *
255      * @return the element type
256      */

257     public String JavaDoc getType() {
258         return type;
259     }
260     
261     /** @return a list of the element's children */
262     public List JavaDoc<DocumentElement> getChildren() {
263         return model.getChildren(this);
264     }
265     
266     /** Adds a DocumentElementListener to this DocumentElement instance */
267     public void addDocumentElementListener(DocumentElementListener del) {
268         deListeners.add(del);
269     }
270     
271     /** Removes a DocumentElementListener to this DocumentElement instance */
272     public void removeDocumentElementListener(DocumentElementListener del) {
273         deListeners.remove(del);
274     }
275     
276     /* <<< EOF public methods */
277     
278     //called by the DocumentModel - performance improvement
279
synchronized void setRootElement(boolean value) {
280         this.isRootElement = value;
281     }
282     
283     boolean isRootElement() {
284         return this.isRootElement;
285     }
286     
287     void setStartPosition(int offset) throws BadLocationException JavaDoc {
288         startPos = model.getDocument().createPosition(offset);
289     }
290     
291     void setEndPosition(int offset) throws BadLocationException JavaDoc {
292         endPos = model.getDocument().createPosition(offset);
293     }
294     
295     String JavaDoc getContent() throws BadLocationException JavaDoc {
296         return model.getDocument().getText(getStartOffset(), getEndOffset() - getStartOffset());
297     }
298     
299     private void fireDocumentElementEvent(DocumentElementEvent dee) {
300         for (DocumentElementListener cl: deListeners) {
301             switch(dee.getType()) {
302                 case DocumentElementEvent.CHILD_ADDED: cl.elementAdded(dee);break;
303                 case DocumentElementEvent.CHILD_REMOVED: cl.elementRemoved(dee);break;
304                 case DocumentElementEvent.CONTENT_CHANGED: cl.contentChanged(dee);break;
305                 case DocumentElementEvent.ATTRIBUTES_CHANGED: cl.attributesChanged(dee);break;
306             }
307         }
308     }
309     
310     //called by model when a new DocumentElement was added to this element
311
void childAdded(DocumentElement de) {
312 // System.out.println("[event] " + this + ": child added:" + de);
313
fireDocumentElementEvent(new DocumentElementEvent(DocumentElementEvent.CHILD_ADDED, this, de));
314     }
315     
316     //called by model when one of the children of this element was removed
317
void childRemoved(DocumentElement de) {
318 // System.out.println("[event] " + this + ": child removed:" + de);
319
fireDocumentElementEvent(new DocumentElementEvent(DocumentElementEvent.CHILD_REMOVED, this, de));
320     }
321     
322     //called by model when element content changed
323
void contentChanged() {
324 // System.out.println("[event] " + this + ": content changed");
325
fireDocumentElementEvent(new DocumentElementEvent(DocumentElementEvent.CONTENT_CHANGED, this, null));
326     }
327     
328     //called by model when element attribs changed
329
void attributesChanged() {
330         fireDocumentElementEvent(new DocumentElementEvent(DocumentElementEvent.ATTRIBUTES_CHANGED, this, null));
331     }
332
333     
334     public boolean equals(Object JavaDoc o) {
335         if(!(o instanceof DocumentElement)) return false;
336         
337         DocumentElement de = (DocumentElement)o;
338         
339         return (de.getName().equals(getName()) &&
340                 de.getType().equals(getType()) &&
341                 de.getStartOffset() == getStartOffset() &&
342                 de.getEndOffset() == getEndOffset() /*&&
343                 de.getAttributes().isEqual(getAttributes())*/
); //equality acc. to attribs causes problems with readding of elements in XMLDocumentModelProvider when changing attributes.
344
}
345     
346     public String JavaDoc toString() {
347         String JavaDoc elementContent = "";
348         try {
349             elementContent = getContent().trim().length() > PRINT_MAX_CHARS ?
350                 getContent().trim().substring(0, PRINT_MAX_CHARS) + "..." :
351                 getContent().trim();
352         }catch(BadLocationException JavaDoc e) {
353             elementContent = "error:" + e.getMessage();
354         }
355         return "DE (" + hashCode() + ")[\"" + getName() +
356                 "\" (" + getType() +
357                 ") <" + getStartOffset() +
358                 "-" + getEndOffset() +
359                 "> '" + encodeNewLines(elementContent) +
360                 "']";
361     }
362     
363     private String JavaDoc encodeNewLines(String JavaDoc s) {
364         StringBuffer JavaDoc encoded = new StringBuffer JavaDoc();
365         for(int i = 0; i < s.length(); i++) {
366             if(s.charAt(i) == '\n') encoded.append("\\n"); else encoded.append(s.charAt(i));
367         }
368         return encoded.toString();
369     }
370     
371     /** AttributeSet implementation. */
372     
373     static final class Attributes implements AttributeSet JavaDoc {
374         private Map JavaDoc attrs;
375         private DocumentElement de;
376         
377         Attributes(DocumentElement element, Map JavaDoc/*<String>*/ m) {
378             de = element;
379             attrs = m;
380         }
381         
382         public int getAttributeCount() {
383             return attrs.size();
384         }
385         
386         public boolean isDefined(Object JavaDoc attrName) {
387             return attrs.containsKey(attrName);
388         }
389         
390         public boolean isEqual(AttributeSet JavaDoc attr) {
391             if(getAttributeCount() != attr.getAttributeCount()) return false;
392             return containsAttributes(attr);
393         }
394         
395         public AttributeSet JavaDoc copyAttributes() {
396             HashMap JavaDoc clone = new HashMap JavaDoc(getAttributeCount());
397             clone.putAll(attrs);
398             return new Attributes(de, clone);
399         }
400         
401         public Object JavaDoc getAttribute(Object JavaDoc key) {
402             return attrs.get(key);
403         }
404         
405         public Enumeration JavaDoc<?> getAttributeNames() {
406             return Collections.enumeration(attrs.keySet());
407         }
408         
409         public boolean containsAttribute(Object JavaDoc name, Object JavaDoc value) {
410             return value.equals(getAttribute(name));
411         }
412         
413         public boolean containsAttributes(AttributeSet JavaDoc attributes) {
414             Enumeration JavaDoc e = attributes.getAttributeNames();
415             while(e.hasMoreElements()) {
416                 Object JavaDoc key = e.nextElement();
417                 Object JavaDoc value = attributes.getAttribute(key);
418                 if(!containsAttribute(key, value)) return false;
419             }
420             return true;
421         }
422         
423         public String JavaDoc toString() {
424             Enumeration JavaDoc e = getAttributeNames();
425             StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
426             while(e.hasMoreElements()) {
427                 Object JavaDoc key = e.nextElement();
428                 Object JavaDoc value = getAttribute(key);
429                 sb.append(key);
430                 sb.append('=');
431                 sb.append(value);
432                 sb.append(' ');
433             }
434             return sb.toString();
435         }
436         
437         public AttributeSet JavaDoc getResolveParent() {
438             return de.getParentElement() != null ? de.getParentElement().getAttributes() : null;
439         }
440         
441         public int compareTo(AttributeSet JavaDoc as) {
442             return toString().compareTo(as.toString());
443         }
444         
445     }
446     
447     private final int PRINT_MAX_CHARS = 10;
448 }
449
Popular Tags