KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > xml > xdm > XDMModel


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 package org.netbeans.modules.xml.xdm;
20
21 import java.beans.PropertyChangeListener JavaDoc;
22 import java.beans.PropertyChangeSupport JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Collection JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Map JavaDoc;
28 import javax.swing.event.UndoableEditEvent JavaDoc;
29 import javax.swing.event.UndoableEditListener JavaDoc;
30 import javax.swing.text.BadLocationException JavaDoc;
31 import javax.swing.undo.CompoundEdit JavaDoc;
32 import javax.swing.undo.UndoableEdit JavaDoc;
33 import javax.swing.undo.UndoableEditSupport JavaDoc;
34 import javax.xml.XMLConstants JavaDoc;
35 import javax.xml.namespace.QName JavaDoc;
36 import org.netbeans.editor.BaseDocument;
37 import org.netbeans.modules.xml.xam.ModelSource;
38 import org.netbeans.modules.xml.xam.dom.ElementIdentity;
39 import org.netbeans.modules.xml.xdm.diff.DiffFinder;
40 import org.netbeans.modules.xml.xdm.diff.XDMTreeDiff;
41 import org.netbeans.modules.xml.xdm.diff.Difference;
42 import org.netbeans.modules.xml.xdm.diff.Add;
43 import org.netbeans.modules.xml.xdm.diff.Change;
44 import org.netbeans.modules.xml.xdm.diff.Delete;
45 import org.netbeans.modules.xml.xdm.diff.DefaultElementIdentity;
46 import org.netbeans.modules.xml.xdm.diff.MergeDiff;
47 import org.netbeans.modules.xml.xdm.diff.NodeIdDiffFinder;
48 import org.netbeans.modules.xml.xdm.diff.NodeInfo;
49 import org.netbeans.modules.xml.xdm.diff.SyncPreparation;
50 import org.netbeans.modules.xml.xdm.nodes.Attribute;
51 import org.netbeans.modules.xml.xdm.nodes.Document;
52 import org.netbeans.modules.xml.xdm.nodes.Element;
53 import org.netbeans.modules.xml.xdm.nodes.Node;
54 import org.netbeans.modules.xml.xdm.nodes.NodeImpl;
55 import org.netbeans.modules.xml.xdm.nodes.Text;
56 import org.netbeans.modules.xml.xdm.nodes.Token;
57 import org.netbeans.modules.xml.xdm.nodes.XMLSyntaxParser;
58 import org.netbeans.modules.xml.xdm.visitor.FindVisitor;
59 import org.netbeans.modules.xml.xdm.visitor.FlushVisitor;
60 import org.netbeans.modules.xml.xdm.visitor.NamespaceRefactorVisitor;
61 import org.netbeans.modules.xml.xdm.visitor.PathFromRootVisitor;
62 import org.netbeans.modules.xml.xdm.visitor.Utils;
63 import org.w3c.dom.NamedNodeMap JavaDoc;
64 import org.w3c.dom.NodeList JavaDoc;
65
66 /**
67  */

68 public class XDMModel {
69     
70     /**
71      * @param ms requires an instance of org.netbeans.editor.BaseDocument to be
72      * available in the lookup;
73      */

74     public XDMModel(ModelSource ms) {
75         source = ms;
76         assert getSwingDocument() != null;
77         ues = new UndoableEditSupport JavaDoc(this);
78         pcs = new PropertyChangeSupport JavaDoc(this);
79         parser = new XMLSyntaxParser();
80         setStatus(Status.UNPARSED);
81         
82         //establish a default element identification mechanism
83
//domain models should override this by invoking "setElementIdentity"
84
ElementIdentity eID = createElementIdentity();
85         setElementIdentity(eID);
86     }
87     
88     public String JavaDoc getIndentation() {
89         return currentIndent;
90     }
91     
92     public void setIndentation(String JavaDoc indent) {
93         currentIndent = indent;
94         indentInitialized = true;
95     }
96     
97     private void setDefaultIndentation() {
98         currentIndent = DEFAULT_INDENT;
99     }
100     
101         /*
102          * override this method if domain model wants to identify elements
103          * using a different mechanism than this default one.
104          *
105          */

106     private ElementIdentity createElementIdentity() {
107         //Establish DOM element identities
108
ElementIdentity eID = new DefaultElementIdentity();
109         //Following values are suitable for Schema and WSDL documents
110
//these default values can be reset by eID.reset() call
111
eID.addIdentifier( "id" );
112         eID.addIdentifier( "name" );
113         eID.addIdentifier( "ref" );
114         return eID;
115     }
116     
117     /**
118      * This api flushes the changes made to the model to the underlying document.
119      */

120     public synchronized void flush() {
121         flushDocument(getDocument());
122     }
123     
124     /**
125      * This api syncs the model with the underlying swing document.
126      * If its the first time sync is called, swing document is parsed and model
127      * is initialized. Otherwise the changes made to swing document are applied
128      * to the model using DiffMerger.
129      */

130     public synchronized void sync() throws IOException JavaDoc {
131         if (preparation == null) {
132             prepareSync();
133         }
134         finishSync();
135     }
136     
137     public synchronized void prepareSync() {
138         Status oldStat = getStatus();
139         try {
140             setStatus(Status.PARSING); // to access in case old broken tree
141
Document newDoc = parser.parse(getSwingDocument());
142             Document oldDoc = getCurrentDocument();
143             if (oldDoc == null) {
144                 preparation = new SyncPreparation(newDoc);
145             } else {
146                 newDoc.assignNodeIdRecursively();
147                 XDMTreeDiff treeDiff = new XDMTreeDiff(eID);
148                 List JavaDoc<Difference> preparedDiffs = treeDiff.performDiff( this, newDoc );
149                 preparation = new SyncPreparation(oldDoc, preparedDiffs);
150             }
151         } catch (BadLocationException JavaDoc ble) {
152             preparation = new SyncPreparation(ble);
153         } catch (IllegalArgumentException JavaDoc iae) {
154             preparation = new SyncPreparation(iae);
155         } catch (IOException JavaDoc ioe) {
156             preparation = new SyncPreparation(ioe);
157         } finally {
158             setStatus(oldStat); // we are not mutating yet, so alway restore
159
}
160     }
161     
162     private SyncPreparation preparation = null;
163     
164     private void finishSync() throws IOException JavaDoc {
165         if (preparation == null) {
166             return; // unprepared or other thread has stealth the sync
167
}
168         
169         if (preparation.hasErrors()) {
170             IOException JavaDoc error = preparation.getError();
171             preparation = null;
172             setStatus(Status.BROKEN);
173             throw error;
174         }
175         
176         Status savedStatus = getStatus();
177         setStatus(Status.PARSING);
178         Document oldDoc = getCurrentDocument();
179         try {
180             if (preparation.getNewDocument() != null) {
181                 Document newDoc = preparation.getNewDocument();
182                 newDoc.addedToTree(this);
183                 setDocument(newDoc);
184             } else {
185                 assert preparation.getOldDocument() != null : "Invalid preparation oldDoc is null";
186                 if (oldDoc != preparation.getOldDocument()) {
187                     // other thread has completed the sync before me
188
setStatus(savedStatus);
189                     return;
190                 }
191                 List JavaDoc<Difference> diffs = preparation.getDifferences();
192                 mergeDiff(diffs);
193                 //diffs = DiffFinder.filterWhitespace(diffs);
194
fireDiffEvents(diffs);
195                 if (getCurrentDocument() != oldDoc) {
196                     fireUndoableEditEvent(getCurrentDocument(), oldDoc);
197                 }
198             }
199             setStatus(Status.STABLE);
200         } catch (IllegalArgumentException JavaDoc iae) {
201             if (getStatus() != Status.STABLE) {
202                 IOException JavaDoc ioe = new IOException JavaDoc();
203                 ioe.initCause(iae);
204                 throw ioe;
205             } else {
206                 // XAM will review the mutation and veto by it wrapped IOException
207
if (iae.getCause() instanceof IOException JavaDoc) {
208                     setStatus(Status.BROKEN);
209                     throw (IOException JavaDoc) iae.getCause();
210                 } else {
211                     throw iae;
212                 }
213             }
214         } finally {
215             if(getStatus() != Status.STABLE) {
216                 setStatus(Status.BROKEN);
217                 setDocument(oldDoc);
218             }
219             preparation = null;
220         }
221     }
222     
223     public void mergeDiff(List JavaDoc<Difference> diffs) {
224         setStatus(Status.PARSING);
225         new MergeDiff().merge(this, diffs);
226         // exception in event firing should not put tree out-of-sync with buffer
227
setStatus(Status.STABLE);
228     }
229     
230     private void fireDiffEvents(final List JavaDoc<Difference> deList) {
231         for ( Difference de:deList ) {
232             NodeInfo.NodeType nodeType = de.getNodeType();
233 // if ( nodeType == NodeInfo.NodeType.WHITE_SPACE ) continue;//skip if WS
234
if ( de instanceof Add ) {
235                 NodeInfo newNodeInfo = ((Add)de).getNewNodeInfo();
236                 assert newNodeInfo != null;
237                 pcs.firePropertyChange( PROP_ADDED, null, newNodeInfo );
238             } else if ( de instanceof Delete ) {
239                 NodeInfo OldNodeInfo = ((Delete)de).getOldNodeInfo();
240                 assert OldNodeInfo != null;
241                 pcs.firePropertyChange( PROP_DELETED, OldNodeInfo, null );
242             } else if ( de instanceof Change ) {
243                 NodeInfo oldNodeInfo = ((Change)de).getOldNodeInfo();
244                 assert oldNodeInfo != null;
245                 
246                 NodeInfo newNodeInfo = ((Change)de).getNewNodeInfo();
247                 assert newNodeInfo != null;
248                 
249                 //fire delete and add events for position change of element/text
250
if ( ((Change)de).isPositionChanged() ) {
251                     pcs.firePropertyChange( PROP_DELETED, oldNodeInfo, null );
252                     pcs.firePropertyChange( PROP_ADDED, null, newNodeInfo );
253                 } else if ( de.getNodeType() == NodeInfo.NodeType.TEXT ) { //text change only
254
pcs.firePropertyChange( PROP_MODIFIED, oldNodeInfo, newNodeInfo );
255                 } else if ( de.getNodeType() == NodeInfo.NodeType.ELEMENT ) {
256                     List JavaDoc<Node> path1 = new ArrayList JavaDoc<Node>(oldNodeInfo.getOriginalAncestors());
257                     path1.add(0, oldNodeInfo.getNode());
258                     List JavaDoc<Node> path2 = new ArrayList JavaDoc<Node>(newNodeInfo.getNewAncestors());
259                     path2.add(0, newNodeInfo.getNode());
260                     //fire attribute change events
261
List JavaDoc<Change.AttributeDiff> attrChanges = ((Change)de).getAttrChanges();
262                     for (Change.AttributeDiff attrDiff:attrChanges) {
263                         Node oldAttr = attrDiff.getOldAttribute();
264                         Node newAttr = attrDiff.getNewAttribute();
265                         if(attrDiff instanceof Change.AttributeAdd) {
266                             assert newAttr != null;
267                             pcs.firePropertyChange( PROP_ADDED, null,
268                                     new NodeInfo(newAttr, -1, path1, path2));
269                         } else if(attrDiff instanceof Change.AttributeDelete) {
270                             assert oldAttr != null;
271                             pcs.firePropertyChange(
272                                     PROP_DELETED, new NodeInfo(oldAttr, -1, path1, path2), null );
273                         } else if(attrDiff instanceof Change.AttributeChange) {
274                             assert oldAttr != null;
275                             assert newAttr != null;
276                             NodeInfo old = new NodeInfo(oldAttr, -1, path1, path2);
277                             NodeInfo now = new NodeInfo(newAttr, -1, path1, path2);
278                             pcs.firePropertyChange( PROP_MODIFIED, old, now);
279                         }
280                     }
281                 }
282             }
283         }
284     }
285     
286     private interface Updater {
287         void update(Node parent, Node oldNode, Node newNode);
288     }
289     
290     private List JavaDoc<Node> getPathToRoot(Node node, Document root) {
291         PathFromRootVisitor pathVisitor = new PathFromRootVisitor();
292         List JavaDoc<Node> path = pathVisitor.findPath(root, node);
293         if (path==null || path.isEmpty()) {
294             throw new IllegalArgumentException JavaDoc("old node must be in the tree");
295         }
296         return path;
297     }
298     
299     private static String JavaDoc classifyMutationType(Node oldNode, Node newNode) {
300         if (newNode == null && oldNode == null ||
301                 newNode != null && oldNode != null) {
302             return PROP_MODIFIED;
303         } else if (newNode != null) {
304             return PROP_ADDED;
305         } else {
306             return PROP_DELETED;
307         }
308     }
309     
310     private enum MutationType { CHILDREN, ATTRIBUTE, BOTH }
311     
312     private List JavaDoc<Node> mutate(Node parent, Node oldNode, Node newNode, Updater updater) {
313         return mutate(parent, oldNode, newNode, updater, null);
314     }
315     
316     private List JavaDoc<Node> mutate(Node parent, Node oldNode, Node newNode, Updater updater, MutationType type) {
317         checkStableOrParsingState();
318         if (newNode != null) checkNodeInTree(newNode);
319         
320         Document currentDocument = getDocument();
321         List JavaDoc<Node> ancestors;
322         if (parent == null) {
323             assert(oldNode != null);
324             ancestors = getPathToRoot(oldNode, currentDocument);
325             oldNode = ancestors.remove(0);
326         } else {
327             if (oldNode != null) {
328                 assert parent.getIndexOfChild(oldNode) > -1;
329                 ancestors = getPathToRoot(oldNode, currentDocument);
330                 assert oldNode.getId() == ancestors.get(0).getId();
331                 oldNode = ancestors.remove(0);
332                 assert parent.getId() == ancestors.get(0).getId();
333             } else {
334                 ancestors = getPathToRoot(parent, currentDocument);
335             }
336         }
337         
338         final Node oldParent = ancestors.remove(0);
339         Node newParent;
340         if (type == null) {
341             if (oldNode instanceof Attribute || newNode instanceof Attribute ||
342                     (oldNode == null && newNode == null)) {
343                 type = MutationType.ATTRIBUTE;
344             } else if (ancestors.size() == 1) {
345                 assert (oldParent instanceof Element);
346                 //might have to add namespace declaration to root
347
type = MutationType.BOTH;
348             } else {
349                 type = MutationType.CHILDREN;
350             }
351         }
352         switch(type) {
353             case ATTRIBUTE:
354                 newParent = (Node)oldParent.clone(true,true,false);
355                 break;
356             case CHILDREN:
357                 newParent = (Node)oldParent.clone(true,false,true);
358                 break;
359             default:
360                 newParent = (Node)oldParent.clone(true,true,true);
361         }
362         
363         if (oldNode != null && oldNode.getNodeType() != Node.TEXT_NODE && newNode == null) { // pure remove
364
undoPrettyPrint(newParent, oldNode, oldParent);
365         }
366         updater.update(newParent, oldNode, newNode);
367         if (oldNode == null && newNode != null && newNode.getNodeType() != Node.TEXT_NODE ) { // pure add
368
doPrettyPrint(newParent, newNode, oldParent);
369         }
370         
371         List JavaDoc<Node> newAncestors = updateAncestors(ancestors, newParent, oldParent);
372         if(getStatus() != Status.PARSING && newNode instanceof Element) {
373             consolidateNamespaces(newAncestors, newParent, (Element)newNode);
374         }
375         Document d = (Document) (!newAncestors.isEmpty() ?
376             newAncestors.get(newAncestors.size()-1) : newParent);
377         d.addedToTree(this);
378         setDocument(d);
379         ancestors.add(0, oldParent);
380         newAncestors.add(0, newParent);
381         if(getStatus() != Status.PARSING) { // not merging
382
fireUndoableEditEvent(d, currentDocument);
383             String JavaDoc mutationType = classifyMutationType(oldNode, newNode);
384             //TODO seems missing delete/change; also, who are listening to these xdm mutation events
385
NodeInfo newNodeInfo = new NodeInfo( newNode, -1, ancestors, newAncestors );
386             pcs.firePropertyChange(mutationType, null, newNodeInfo);
387         }
388         return newAncestors;
389     }
390     
391     private void consolidateNamespaces(List JavaDoc<Node> ancestors, Node parent, Element newNode) {
392         if (parent instanceof Document) return; // no actions if newNode is root itself
393
assert ancestors.size() > 0;
394         Element root = (Element) (ancestors.size() == 1 ?
395             parent : ancestors.get(ancestors.size()-2));
396         List JavaDoc<Node> parentAndAncestors = new ArrayList JavaDoc(ancestors);
397         parentAndAncestors.add(0, parent);
398         consolidateAttributePrefix(parentAndAncestors, newNode);
399         NamedNodeMap JavaDoc nnm = newNode.getAttributes();
400         for (int i=0; i<nnm.getLength(); i++) {
401             if (nnm.item(i) instanceof Attribute) {
402                 Attribute attr = (Attribute) nnm.item(i);
403                 consolidateNamespace(root, parentAndAncestors, newNode, attr);
404             }
405         }
406         
407         // use parent node prefix
408
String JavaDoc parentNS = parent.getNamespaceURI();
409         String JavaDoc parentPrefix = parent.getPrefix();
410         if (parentNS != null && ! parentNS.equals(XMLConstants.NULL_NS_URI)) {
411             new NamespaceRefactorVisitor(this).refactor(
412                         newNode, parentNS, parentPrefix, parentAndAncestors);
413         }
414     }
415     
416     private void consolidateAttributePrefix(List JavaDoc<Node> parentAndAncestors, Element newNode) {
417         NamedNodeMap JavaDoc nnm = newNode.getAttributes();
418         for (int i=0; i<nnm.getLength(); i++) {
419             if (! (nnm.item(i) instanceof Attribute)) continue;
420             Attribute attr = (Attribute) nnm.item(i);
421             String JavaDoc prefix = attr.getPrefix();
422             if (prefix != null && ! attr.isXmlnsAttribute()) {
423                 String JavaDoc namespace = newNode.lookupNamespaceURI(prefix);
424                 if (namespace == null) continue;
425                 prefix = NodeImpl.lookupPrefix(namespace, parentAndAncestors);
426                   if (prefix != null) {
427                     attr.setPrefix(prefix);
428                 }
429             }
430         }
431     }
432     
433     /**
434      * Consolidate new node top-leveled namespaces with parent's.
435      * Note: this assume #consolidateAttributePrefix has been called
436      */

437     private void consolidateNamespace(Element root, List JavaDoc<Node> parentAndAncestors,
438             Element newNode, Attribute attr) {
439         if (attr.isXmlnsAttribute()) {
440             String JavaDoc prefix = attr.getLocalName();
441             if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) {
442                 prefix = XMLConstants.DEFAULT_NS_PREFIX;
443             }
444             String JavaDoc namespace = attr.getValue();
445             assert (namespace != null);
446             
447             Node parent = parentAndAncestors.get(0);
448             String JavaDoc parentNS = parent.getNamespaceURI();
449             
450             String JavaDoc existingNS = NodeImpl.lookupNamespace(prefix, parentAndAncestors);
451             String JavaDoc existingPrefix = NodeImpl.lookupPrefix(namespace, parentAndAncestors);
452             
453             // 1. prefix is free (existingNS == null) and namespace is never declared (existingPrefix == null)
454
// 2. prefix is used and for the same namespace
455
// 3. namespace is declared by different prefix
456
// 4. prefix is used and for different namespace
457

458             if (existingNS == null && existingPrefix == null) { // case 1.
459
newNode.removeAttributeNode(attr);
460                 root.appendAttribute(attr);
461             } else if (namespace.equals(existingNS) && prefix.equals(existingPrefix)) { // case 2
462
assert prefix.equals(existingPrefix) : "prefix='"+prefix+"' existingPrefix='"+existingPrefix+"'";
463                 newNode.removeAttributeNode(attr);
464             } else if (existingPrefix != null) { // case 3.
465
// skip attribute redeclaring namespace of parent element
466
// we will refactor to parent prefix after processing all xmlns-attributes
467
if (! namespace.equals(parentNS)) {
468                     new NamespaceRefactorVisitor(this).refactor(
469                         newNode, namespace, existingPrefix, parentAndAncestors);
470                 }
471             } else { // existingNS != null && existingPrefix == null
472
// case 4 just leave prefix as overriding with different namespace
473
}
474         }
475     }
476     
477     /**
478      * This api replaces given old node with given new node.
479      * The old node passed must be in tree, the new node must not be in tree,
480      * and new node must be clone of old node.
481      * @param oldValue The old node to be replaced.
482      * @param newValue The new node.
483      * @return The new parent
484      */

485     public synchronized List JavaDoc<Node> modify(Node oldValue, Node newValue) {
486         if (oldValue.getId() != newValue.getId()) {
487             throw new IllegalArgumentException JavaDoc("newValue must be a clone of oldValue");
488         }
489         
490         if (oldValue instanceof Document) {
491             assert newValue instanceof Document;
492             Document oldDoc = (Document) oldValue;
493             Document newDoc = (Document) newValue;
494             newDoc.addedToTree(this);
495             setDocument(newDoc);
496             if (getStatus() != Status.PARSING) { // not merging
497
fireUndoableEditEvent(oldDoc, currentDocument);
498                 String JavaDoc mutationType = classifyMutationType(oldDoc, newDoc);
499                 ArrayList JavaDoc<Node> ancestors = new ArrayList JavaDoc<Node>();
500                 NodeInfo oldNodeInfo = new NodeInfo( oldDoc, -1, ancestors, ancestors );
501                 NodeInfo newNodeInfo = new NodeInfo( newDoc, -1, ancestors, ancestors );
502                 pcs.firePropertyChange(mutationType, oldNodeInfo, newNodeInfo);
503             }
504             return new ArrayList JavaDoc<Node>();
505         }
506         
507         Updater modifier = new Updater() {
508             public void update(Node newParent, Node oldNode, Node newNode) {
509                 if (oldNode instanceof Attribute) {
510                     ((Element)newParent).replaceAttribute((Attribute)newNode,(Attribute)oldNode);
511                 } else {
512                     newParent.replaceChild(newNode, oldNode);
513                 }
514             }
515         };
516         return mutate(null, oldValue, newValue, modifier);
517     }
518     
519     /**
520      * This api adds given node to given parent at given index.
521      * The added node will be part of childnodes of the parent,
522      * and its index will be the given index. If the given index
523      * is out of the parents childnodes range, the node will be
524      * appended.
525      * @param parent The parent node to which the node is to be added.
526      * @param node The node which is to be added.
527      * @param offset The index at which the node is to be added.
528      * @return The parent node resulted by addition of this node
529      */

530     public synchronized List JavaDoc<Node> add(Node parent, Node node, final int offset) {
531         if (offset<0) throw new IndexOutOfBoundsException JavaDoc();
532         Updater adder = new Updater() {
533             public void update(Node newParent, Node oldNode, Node newNode) {
534                 if (newParent instanceof Element && newNode instanceof Attribute) {
535                     Element newElement = (Element)newParent;
536                     if (offset>newElement.getAttributes().getLength())
537                         throw new IndexOutOfBoundsException JavaDoc();
538                     newElement.addAttribute((Attribute)newNode,offset);
539                 } else {
540                     //reset id, since adding root element
541
if(newParent instanceof Document && newNode instanceof Element)
542                         resetIdMeter();
543                     
544                     if (offset>newParent.getChildNodes().getLength())
545                         throw new IndexOutOfBoundsException JavaDoc();
546                     if(offset<newParent.getChildNodes().getLength()) {
547                         Node refChild = (Node)newParent.getChildNodes().item(offset);
548                         newParent.insertBefore(newNode,refChild);
549                     } else {
550                         newParent.appendChild(newNode);
551                     }
552                 }
553             }
554         };
555         return mutate(parent, null, node, adder);
556     }
557     
558     /**
559      * This api adds given node to given parent before given ref node.
560      * The inserted node will be part of childnodes of the parent,
561      * and will appear before ref node.
562      * @param parent The parent node to which the node is to be added.
563      * @param node The node which is to be added.
564      * @param refChild The ref node (child) of parent node,
565      * before which the node is to be added.
566      * @return The parent node resulted by inserion of this node.
567      */

568     public synchronized List JavaDoc<Node> insertBefore(Node parent, Node node, Node refChild) {
569         final Node ref = refChild;
570         Updater updater = new Updater() {
571             public void update(Node newParent, Node oldNode, Node newNode) {
572                 newParent.insertBefore(newNode, ref);
573             }
574         };
575         
576         return mutate(parent, null, node, updater);
577     }
578     
579     /**
580      * This api adds given node to given parent at the end.
581      * The added node will be part of childnodes of the parent,
582      * and it will be the last node.
583      * @param parent The parent node to which the node is to be appended.
584      * @param node The node which is to be appended.
585      * @return The parent node resulted by addition of this node
586      */

587     public synchronized List JavaDoc<Node> append(Node parent, Node node) {
588         Updater appender = new Updater() {
589             public void update(Node parent, Node oldNode, Node newNode) {
590                 //reset id, since adding root element
591
if(parent instanceof Document && newNode instanceof Element)
592                     resetIdMeter();
593                 parent.appendChild(newNode);
594             }
595         };
596         return mutate(parent, null, node, appender);
597     }
598     
599     /**
600      * This api deletes given node from a tree.
601      * @param node The node to be deleted.
602      * @return The parent node resulted by deletion of this node.
603      */

604     public synchronized List JavaDoc<Node> delete(Node n) {
605         Updater remover = new Updater() {
606             public void update(Node newParent, Node oldNode, Node newNode) {
607                 newParent.removeChild(oldNode);
608             }
609         };
610         return mutate(null, n, null, remover);
611     }
612     
613     /**
614      * This api changes index of the given node.
615      * @param nodes The nodes to be moved.
616      * @param indexes the new indexes of the nodes.
617      * @return The parent node resulted by deletion of this node.
618      */

619     public synchronized List JavaDoc<Node> reorder(Node parent, Node n, final int index) {
620         if (index < 0) throw new IndexOutOfBoundsException JavaDoc("index="+index);
621         Updater u = new Updater() {
622             public void update(Node newParent, Node oldNode, Node newNode) {
623                 if (newParent instanceof Element && newNode instanceof Attribute) {
624                     Element parent = (Element) newParent;
625                     int i = index;
626                     if (index > parent.getAttributes().getLength()) {
627                         i = parent.getAttributes().getLength();
628                     }
629                     parent.reorderAttribute((Attribute) oldNode, i);
630                 } else {
631                     int i = index;
632                     if (index > newParent.getChildNodes().getLength()) {
633                         i = newParent.getChildNodes().getLength();
634                     }
635                     ((NodeImpl)newParent).reorderChild(oldNode, i);
636                 }
637             }
638         };
639         return mutate(parent, n, null, u);
640     }
641     
642     /**
643      * This api changes indexes of the given node children.
644      * @param nodes The nodes to be moved.
645      * @param indexes the new indexes of the nodes.
646      * @return The parent node resulted by deletion of this node.
647      */

648     public synchronized List JavaDoc<Node> reorderChildren(Node parent, final int[] permutation) {
649         Updater u = new Updater() {
650             public void update(Node newParent, Node oldNode, Node newNode) {
651                 ((NodeImpl)newParent).reorderChildren(permutation);
652             }
653         };
654         return mutate(parent, null, null, u, MutationType.CHILDREN);
655     }
656     
657     /**
658      * This api deletes given node from a given parent node.
659      * @param parent The parent node from which the node is to be deleted.
660      * @param child The node to be deleted.
661      * @return The parent node resulted by deletion of this node.
662      */

663     public synchronized List JavaDoc<Node> remove(final Node parent, Node child) {
664         Updater remover = new Updater() {
665             public void update(Node newParent, Node oldNode, Node newNode) {
666                 assert parent.isEquivalentNode(newParent);
667                 newParent.removeChild(oldNode);
668             }
669         };
670         return mutate(parent, child, null, remover);
671     }
672     
673     /**
674      * This api deletes given node from a given parent node.
675      * @param parent The parent node from which the node is to be deleted.
676      * @param toRemove collection of node to be deleted.
677      * @return The parent node resulted by deletion of this node.
678      */

679     public synchronized List JavaDoc<Node> removeChildNodes(final Node parent, final Collection JavaDoc<Node> toRemove) {
680         Updater remover = new Updater() {
681             public void update(Node newParent, Node oldNode, Node newNode) {
682                 assert parent.isEquivalentNode(newParent);
683                 for (Node n : toRemove) {
684                     newParent.removeChild(n);
685                 }
686             }
687         };
688         return mutate(parent, null, null, remover, MutationType.CHILDREN);
689     }
690     
691     public synchronized List JavaDoc<Node> replaceChild(final Node parent, Node child, Node newChild) {
692         Updater updater = new Updater() {
693             public void update(Node newParent, Node oldNode, Node newNode) {
694                 assert newParent.isEquivalentNode(parent);
695                 newParent.replaceChild(newNode, oldNode);
696             }
697         };
698         return mutate(null, child, newChild, updater);
699     }
700     
701     /**
702      * This api sets an attribute given name and value of a given element node.
703      * If an attribute with given name already present in element, it will only
704      * set the value. Otherwise a new attribute node, with given name and value,
705      * will be appended to the attibute list of the element node.
706      * @param element The element of which the attribute to be set.
707      * @param name The name of the attribute to be set.
708      * @param value The value of the attribute to be set.
709      * @return The element resulted by setting of attribute.
710      */

711     public synchronized List JavaDoc<Node> setAttribute(Element element, final String JavaDoc name, final String JavaDoc value) {
712         Updater updater = new Updater() {
713             public void update(Node newParent, Node oldNode, Node newNode) {
714                 ((Element)newParent).setAttribute(name,value);
715             }
716         };
717         return mutate(element, null, null, updater);
718     }
719     
720     /**
721      * This api removes an attribute given name and value of a given element node.
722      * @param element The element of which the attribute to be removed.
723      * @param name The name of the attribute to be removed.
724      * @return The element resulted by removed of attribute.
725      */

726     public synchronized List JavaDoc<Node> removeAttribute(Element element, final String JavaDoc name) {
727         Updater updater = new Updater() {
728             public void update(Node newParent, Node oldNode, Node newNode) {
729                 ((Element)newParent).removeAttribute(name);
730             }
731         };
732         return mutate(element, null, null, updater);
733     }
734     
735     private interface CheckIOExceptionUpdater extends Updater {
736         public IOException JavaDoc getError();
737     }
738     
739     public synchronized List JavaDoc<Node> setXmlFragmentText(Element node, final String JavaDoc value) throws IOException JavaDoc {
740         CheckIOExceptionUpdater updater = new CheckIOExceptionUpdater() {
741             public void update(Node newParent, Node oldNode, Node newNode) {
742                 try {
743                     ((Element)newParent).setXmlFragmentText(value);
744                 } catch(IOException JavaDoc ioe) {
745                     error = ioe;
746                 }
747             }
748             public IOException JavaDoc getError() {
749                 return error;
750             }
751             private IOException JavaDoc error;
752         };
753         List JavaDoc<Node> retPath = mutate(node, null, null, updater, MutationType.CHILDREN);
754         if (updater.getError() != null) {
755             throw updater.getError();
756         } else {
757             return retPath;
758         }
759     }
760     
761     public synchronized List JavaDoc<Node> setTextValue(Node node, String JavaDoc value) {
762         Node text = (Node) currentDocument.createTextNode(value);
763         Updater updater = new Updater() {
764             public void update(Node newParent, Node oldNode, Node newNode) {
765                 while(newParent.hasChildNodes()) {
766                     newParent.removeChild(newParent.getLastChild());
767                 }
768                 newParent.appendChild(newNode);
769             }
770         };
771         return mutate(node, null, text, updater);
772     }
773     
774     /**
775      * This is utility method which updates all the ancestors in the given
776      * ancestor list of given originalNode. The list returned represents
777      * the ancestors of given modified node.
778      * @param ancestors the list of ancestors starting from parent
779      * @param modifiedNode The modified node for which the new list is to be created
780      * @param originalNode The original node which ancestors are given
781      * @return The list of new ancestors starting parent for the modified node
782      */

783     private List JavaDoc<Node> updateAncestors(List JavaDoc<Node> ancestors, Node modifiedNode, Node originalNode) {
784         assert ancestors != null && modifiedNode != null && originalNode != null;
785         List JavaDoc<Node> newAncestors = new ArrayList JavaDoc<Node>(ancestors.size());
786         Node currentModifiedNode = modifiedNode;
787         Node currentOrigNode = originalNode;
788         for(Node parentNode: ancestors) {
789             Node newParentNode = (Node)parentNode.clone(false,true,true);
790             newParentNode.replaceChild(currentModifiedNode, currentOrigNode);
791             newAncestors.add(newParentNode);
792             currentOrigNode = parentNode;
793             currentModifiedNode = newParentNode;
794         }
795         return newAncestors;
796     }
797     
798     /**
799      * This api returns the latest stable document in the model.
800      * @return The latest stable document in the model.
801      */

802     public synchronized Document getDocument() {
803         checkStableOrParsingState();
804         return currentDocument;
805     }
806     
807     /**
808      * This api returns the current document in the model, regardless of the state.
809      * @return The latest stable document in the model.
810      */

811     public synchronized Document getCurrentDocument() {
812         return currentDocument;
813     }
814     
815     /**
816      * Reset document to provided known version and cause events to be fired.
817      * Note caller are responsible to handle exception and decide which version
818      * to keep after exception and do proper cleanup.
819      */

820     synchronized void resetDocument(Document newDoc) {
821         try {
822             fireUndoEvents = false;
823             List JavaDoc<Difference> diffs = new NodeIdDiffFinder().findDiff(getCurrentDocument(), newDoc);
824             List JavaDoc<Difference> filtered = DiffFinder.filterWhitespace(diffs);
825             //flushDocument(newDoc);
826
setDocument(newDoc);
827             if ( filtered != null && !filtered.isEmpty() ) {
828                 fireDiffEvents(filtered);
829             }
830         } finally {
831             fireUndoEvents = true;
832         }
833     }
834     
835     private void flushDocument(Document newDoc) {
836         checkStableState();
837     UndoableEditListener JavaDoc uel = null;
838     BaseDocument d = getSwingDocument();
839     final CompoundEdit JavaDoc ce = new CompoundEdit JavaDoc();
840         try {
841             FlushVisitor flushvisitor = new FlushVisitor();
842             String JavaDoc newXMLText = flushvisitor.flushModel(newDoc);
843         uel = new UndoableEditListener JavaDoc() {
844         public void undoableEditHappened(UndoableEditEvent JavaDoc e) {
845             ce.addEdit(e.getEdit());
846         }
847         };
848         d.addUndoableEditListener(uel);
849             Utils.replaceDocument(d, newXMLText);
850         } catch (BadLocationException JavaDoc ble) {
851             assert false;
852         } finally {
853         if (uel != null) {
854         d.removeUndoableEditListener(uel);
855         }
856         ce.end();
857         for (UndoableEditListener JavaDoc l : ues.getUndoableEditListeners()) {
858         l.undoableEditHappened(new UndoableEditEvent JavaDoc(this,ce));
859         }
860     }
861     }
862     
863     private BaseDocument getSwingDocument() {
864         BaseDocument bd = (BaseDocument)
865         source.getLookup().lookup(BaseDocument.class);
866         return bd;
867     }
868     
869     public synchronized void setDocument(Document newDoc) {
870         currentDocument = newDoc;
871     }
872     
873     /**
874      * This returns the statuc of the model.
875      * @return the status.
876      * @see #Status
877      */

878     public synchronized Status getStatus() {
879         return status;
880     }
881     
882     /**
883      * This api adds an undoable edit listener.
884      * @param l The undoable edit listener to be added.
885      */

886     public synchronized void addUndoableEditListener(UndoableEditListener JavaDoc l) {
887         ues.addUndoableEditListener(l);
888     }
889     
890     /**
891      * This api removes an undoable edit listener.
892      * @param l The undoable edit listener to be removed.
893      */

894     public synchronized void removeUndoableEditListener(UndoableEditListener JavaDoc l) {
895         ues.addUndoableEditListener(l);
896     }
897     
898     /**
899      * This api adds a property change listener.
900      * @param pcl The property change listener to be added.
901      */

902     public synchronized void addPropertyChangeListener(PropertyChangeListener JavaDoc pcl) {
903         pcs.addPropertyChangeListener(pcl);
904     }
905     
906     /**
907      * This api removes a property change listener.
908      * @param pcl The property change listener to be removed.
909      */

910     public synchronized void removePropertyChangeListener(PropertyChangeListener JavaDoc pcl) {
911         pcs.removePropertyChangeListener(pcl);
912     }
913     
914     /**
915      * Find the node with same id in the current tree.
916      */

917     private synchronized Node findNode(int id) {
918         FindVisitor fv = new FindVisitor();
919         return fv.find(getDocument(), id);
920     }
921     
922     /**
923      * This represents the status of the XDM Model.
924      * Status STABLE means the latest attempt to parse was successful
925      * Status BROKEN means that the latest attempt to parse was unsuccessful.
926      * Status UNPARSED means the document has not been parsed yet.
927      * Status PARSING means the document is being parsed.
928      */

929     //TODO Last Parsed status
930
public enum Status {BROKEN, STABLE, UNPARSED, PARSING;}
931     
932     private void fireUndoableEditEvent(Document newDoc, Document oldDoc) {
933         if (fireUndoEvents) {
934             assert newDoc != oldDoc;
935             UndoableEdit JavaDoc ee = new XDMModelUndoableEdit(oldDoc, newDoc, this);
936             UndoableEditEvent JavaDoc ue = new UndoableEditEvent JavaDoc(this, ee);
937             for (UndoableEditListener JavaDoc l:ues.getUndoableEditListeners()) {
938                 l.undoableEditHappened(ue);
939             }
940         }
941     }
942     
943     private void checkNodeInTree(Node n) {
944         if (n.isInTree()) {
945             throw new IllegalArgumentException JavaDoc("newValue must not have been added to model"); // NOI18N
946
}
947     }
948     
949     private void checkStableState() {
950         if (getStatus() != Status.STABLE ) {
951             throw new IllegalStateException JavaDoc("flush can only be called from STABLE STATE"); //NOI18N
952
}
953     }
954     
955     private void checkStableOrParsingState() {
956         if (getStatus() != Status.STABLE && getStatus() != Status.PARSING) {
957             throw new IllegalStateException JavaDoc("The model is not initialized or is broken."); //NOI18N
958
}
959     }
960     
961     private void setStatus(Status s) {
962         status = s;
963     }
964     
965     /**
966      * This api keeps track of the nodes created in this model.
967      * @return the id of the next node to be created.
968      */

969     public int getNextNodeId() {
970         int nodeId = nodeCount;
971         nodeCount++;
972         return nodeId;
973     }
974     
975     /**
976      * resets id meter
977      */

978     private void resetIdMeter() {
979         nodeCount = 1;
980     }
981     
982     private boolean isPretty() {
983         return pretty;
984     }
985     
986     public void setPretty(boolean print) {
987         pretty = print;
988     }
989     
990     private void doPrettyPrint(Node newParent, Node newNode, Node oldParent) {
991         if ((getStatus() != Status.PARSING) && isPretty()) {
992             if(isSimpleContent(newParent)) {//skip if simpleContent
993
/*
994                  * <test name="test1">A new text node</test>
995                  */

996                 return;
997             }
998             if(!indentInitialized)
999                 initializeIndent(oldParent);
1000            String JavaDoc parentIndent = calculateNodeIndent(oldParent);
1001            if(!isPretty(newParent, newNode)) {//skip if already pretty
1002
int offset = 1;
1003                if(oldParent.getChildNodes().getLength() == 0) {//old parent did not have prettyprint before
1004
/*
1005                     * before
1006                     *
1007                     * <test name="test1"></test>
1008                     *
1009                     * after
1010                     *
1011                     * <test name="test1">
1012                     * <c name="c1">
1013                     * </test>
1014                     */

1015                    newParent.insertBefore(createPrettyText(
1016                            parentIndent+getIndentation()), newNode);
1017                    offset++;
1018                }
1019                int index = ((NodeImpl)newParent).getIndexOfChild(newNode);
1020                if(index > 0) {
1021                    Node oldText = (Node)newParent.getChildNodes().item(index-1);
1022                    if(checkPrettyText(oldText)) {
1023                        /*
1024                         * before
1025                         *
1026                         * <test name="test1">
1027                         * <a name="a1">
1028                         * <b name="b1">
1029                         * <c name="c1">
1030                         * </test>
1031                         *
1032                         * after
1033                         *
1034                         * <test name="test1">
1035                         * <a name="a1">
1036                         * <b name="b1">
1037                         * <c name="c1">
1038                         * </test>
1039                         */

1040                        Text newText = createPrettyText(
1041                                parentIndent+getIndentation());
1042                        newParent.replaceChild(newText, oldText);
1043                    } else {
1044                        /*
1045                         * before
1046                         *
1047                         * <test name="test1">
1048                         * <a name="a1">
1049                         * <b name="b1"><c name="c1">
1050                         * </test>
1051                         *
1052                         * after
1053                         *
1054                         * <test name="test1">
1055                         * <a name="a1">
1056                         * <b name="b1">
1057                         * <c name="c1">
1058                         * </test>
1059                         */

1060                        newParent.insertBefore(createPrettyText(
1061                                parentIndent+getIndentation()), newNode);
1062                        offset++;
1063                    }
1064                }
1065                Node ref = null;
1066                if((index+offset) < newParent.getChildNodes().getLength())
1067                    ref = (Node)newParent.getChildNodes().item((index+offset));
1068                if(ref != null) {
1069                    if(!checkPrettyText(ref)) {
1070                        /*
1071                         * before
1072                         *
1073                         * <test name="test1">
1074                         * <a name="a1">
1075                         * <b name="b1">
1076                         * <c name="c1"><d name="d1">
1077                         * </test>
1078                         *
1079                         * after
1080                         *
1081                         * <test name="test1">
1082                         * <a name="a1">
1083                         * <b name="b1">
1084                         * <c name="c1">
1085                         * <d name="d1">
1086                         * </test>
1087                         */

1088                        newParent.insertBefore(createPrettyText(
1089                                parentIndent+getIndentation()), ref);
1090                    }
1091                } else {
1092                    /*
1093                     * before
1094                     *
1095                     * <test name="test1">
1096                     * <a name="a1">
1097                     * <b name="b1">
1098                     * <c name="c1"></test>
1099                     *
1100                     * after
1101                     *
1102                     * <test name="test1">
1103                     * <a name="a1">
1104                     * <b name="b1">
1105                     * <c name="c1">
1106                     * </test>
1107                     */

1108                    newParent.appendChild(createPrettyText(parentIndent));
1109                }
1110            }
1111            
1112            //recurse pretty print
1113
doPrettyPrintRecursive(newNode, parentIndent, newParent);//for children of node
1114
}
1115    }
1116    
1117    /*
1118     * initialized only once
1119     *
1120     */

1121    private void initializeIndent(final Node n) {
1122        String JavaDoc parentIndent = calculateNodeIndent(n);
1123        List JavaDoc<Node> pathToRoot = new PathFromRootVisitor().findPath(getDocument(), n);
1124        if(parentIndent.length() > 0 && pathToRoot.size()-2 > 0) {
1125            //exclude Document and the root from path for indent step calculation
1126
double step = Math.floor(parentIndent.length() / (double) (pathToRoot.size()-2));
1127            StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1128            for(int i=0;i<step;i++)
1129                sb.append(" ");
1130            String JavaDoc indentString = sb.toString();
1131            if(indentString.length() > 0)
1132                setIndentation(indentString);
1133            else
1134                setDefaultIndentation();
1135        } else
1136            setDefaultIndentation();
1137    }
1138    
1139    private String JavaDoc calculateNodeIndent(final Node n) {
1140        String JavaDoc indent = "";
1141        Node parent = (Node) n.getParentNode();
1142        if(parent != null) {
1143            int index = parent.getIndexOfChild(n);
1144            if(index > 0) {
1145                Node txt = (Node) parent.getChildNodes().item(index-1);
1146                if(checkPrettyText(txt)) {
1147                    String JavaDoc wsValue = ((NodeImpl)txt).getTokens().get(0).getValue();
1148                    int ndx = wsValue.lastIndexOf("\n");
1149                    if(ndx != -1 && (ndx+1) < wsValue.length())
1150                        indent = wsValue.substring(ndx+1);
1151                }
1152            }
1153        }
1154        return indent;
1155    }
1156    
1157    private void doPrettyPrintRecursive(Node n, String JavaDoc indent, Node parent) {
1158        if ((getStatus() != Status.PARSING) && isPretty()) {
1159            if(isSimpleContent(n))
1160                return; //skip if simpleContent
1161
else if(n instanceof Element && isPretty(n)) {//adjust for pretty length difference
1162
fixPrettyForCopiedNode(n, indent, parent);
1163            } else {
1164                List JavaDoc<Node> childList = new ArrayList JavaDoc<Node>();
1165                List JavaDoc<Node> visitList = new ArrayList JavaDoc<Node>();
1166                NodeList JavaDoc childs = n.getChildNodes();
1167                for(int i=0;i<childs.getLength();i++) {
1168                    childList.add((Node)childs.item(i));
1169                    if(childs.item(i) instanceof Element)
1170                        visitList.add((Node)childs.item(i));
1171                }
1172                String JavaDoc parentIndent = indent+getIndentation();
1173                if(childList.size() > 0)
1174                    n.appendChild(createPrettyText(parentIndent));
1175                String JavaDoc childIndent = parentIndent+getIndentation();
1176                for(int i=childList.size()-1;i>=0;i--) {
1177                    Node ref = (Node)childList.get(i);
1178                    Text postText = createPrettyText(childIndent);
1179                    n.insertBefore(postText, ref);
1180                }
1181                childList.clear(); //no need to keep it beyond here
1182
for(int i=0;i<visitList.size();i++) {
1183                    doPrettyPrintRecursive((Node)visitList.get((i)), parentIndent, n);
1184                }
1185                visitList.clear(); //no need to keep it beyond here
1186
}
1187        }
1188    }
1189
1190    /*
1191     * This function will fix the pretty text of nodes that are cut or copied and
1192     * pasted to xdm tree
1193     */

1194    private void fixPrettyForCopiedNode(Node n, String JavaDoc indent, Node parent) {
1195        NodeList JavaDoc childs = n.getChildNodes();
1196        if(childs.getLength() == 0)
1197            return;
1198        Text nlastChild = (Text)childs.item(childs.getLength()-1);
1199        String JavaDoc lc = ((NodeImpl)nlastChild).getTokens().get(0).getValue();
1200        NodeImpl pfirstChild = (NodeImpl)parent.getChildNodes().item(0);
1201        String JavaDoc fc = pfirstChild.getTokens().get(0).getValue();
1202        
1203        if(fc.length() == lc.length()) {//return if already pretty
1204
return;
1205        } else {
1206            String JavaDoc parentIndent = indent+getIndentation();
1207            String JavaDoc childIndent = parentIndent+getIndentation();
1208            List JavaDoc<Node> childList = new ArrayList JavaDoc<Node>();
1209            for(int i=0;i<childs.getLength();i++) {
1210                childList.add((Node)childs.item(i));
1211            }
1212            for(int i=0;i<childList.size();i++) {
1213                Node txt = (Node)n.getChildNodes().item(i);
1214                if(checkPrettyText(txt)) {
1215                    String JavaDoc newIndent = childIndent+getIndentation();
1216                    if(i==0) {
1217                        newIndent = childIndent;
1218                    } else if(i == childList.size()-1) {
1219                        newIndent = parentIndent;
1220                    }
1221                    n.replaceChild(createPrettyText(newIndent), txt);
1222                }
1223            }
1224            for(int i=0;i<childList.size();i++) {
1225                fixPrettyForCopiedNode((Node)n.getChildNodes().item(i),
1226                        childIndent, n);
1227            }
1228            childList.clear(); //no need to keep it beyond here
1229
}
1230    }
1231    
1232    private Text createPrettyText(String JavaDoc indent) {
1233        String JavaDoc textChars = "\n"+indent;
1234        Text txt = (Text)this.getDocument().createTextNode(textChars);
1235        return txt;
1236    }
1237    
1238    private void undoPrettyPrint(Node newParent, Node oldNode, Node oldParent) {
1239        if ((getStatus() != Status.PARSING) && isPretty()) {
1240            String JavaDoc parentIndent = calculateNodeIndent(oldParent);
1241            int piLength = parentIndent != null ? parentIndent.length() : 0;
1242            int index = ((NodeImpl)oldParent).getIndexOfChild(oldNode);
1243            Node txtBefore = null;
1244            if(index > 0) {//remove pre pretty print node
1245
txtBefore = (Node)oldParent.getChildNodes().item(index-1);
1246                if(checkPrettyText(txtBefore) &&
1247                        piLength <= getLength((Text) txtBefore)) {
1248                                        /*
1249                                         * before
1250                                         *
1251                                         * <test name="test1">
1252                                         * <a name="a1">
1253                                         * <b name="b1">
1254                                         * <c name="c1">
1255                                         * </test>
1256                                         *
1257                                         * after
1258                                         *
1259                                         * <test name="test1">
1260                                         * <a name="a1">
1261                                         * <b name="b1"><c name="c1">
1262                                         * </test>
1263                                         */

1264                    newParent.removeChild(txtBefore);
1265                }
1266            }
1267            if(newParent.getChildNodes().getLength() == 2 &&
1268                    index+1 < oldParent.getChildNodes().getLength()) {//remove post pretty print node
1269
Node txtAfter = (Node)oldParent.getChildNodes().item(index+1);
1270                if(checkPrettyText(txtAfter) &&
1271                        piLength <= getLength((Text) txtAfter)) {
1272                                        /*
1273                                         * before
1274                                         *
1275                                         * <test name="test1">
1276                                         * <a name="a1">
1277                                         * <b name="b1"><c name="c1">
1278                                         * </test>
1279                                         *
1280                                         * after
1281                                         *
1282                                         * <test name="test1">
1283                                         * <a name="a1">
1284                                         * <b name="b1"><c name="c1"></test>
1285                                         */

1286                    newParent.removeChild(txtAfter);
1287                }
1288            }
1289        }
1290    }
1291    
1292    private int getLength(Text n) {
1293        int len = 0;
1294        for(Token token:((NodeImpl)n).getTokens())
1295            len += token.getValue().length();
1296        return len;
1297    }
1298    
1299    private boolean checkPrettyText(Node txt) {
1300        if (txt instanceof Text) {
1301            if ((((NodeImpl)txt).getTokens().size() == 1) &&
1302                    isWhitespaceOnly(((NodeImpl)txt).getTokens().get(0).getValue())) {
1303                return true;
1304            }
1305        }
1306        return false;
1307    }
1308    
1309    private boolean isPossibleWhiteSpace(String JavaDoc text) {
1310        return text.length() > 0 &&
1311                Character.isWhitespace(text.charAt(0)) &&
1312                Character.isWhitespace(text.charAt(text.length()-1));
1313    }
1314    
1315    private boolean isWhitespaceOnly(String JavaDoc tn) {
1316        return isPossibleWhiteSpace(tn) &&
1317                tn.trim().length() == 0;
1318    }
1319    
1320    public ElementIdentity getElementIdentity() {
1321        return eID;
1322        
1323        
1324    }
1325    
1326    public void setElementIdentity(ElementIdentity eID) {
1327        this.eID = eID;
1328    }
1329    
1330    private boolean isSimpleContent(Node newParent) {
1331        NodeList JavaDoc childs = newParent.getChildNodes();
1332        for(int i=0;i<childs.getLength();i++)
1333            if(!(childs.item(i) instanceof Text))
1334                return false;
1335        return true;
1336    }
1337    
1338    private boolean isPretty(Node newParent) {
1339        return isPretty(newParent, null);
1340    }
1341    
1342    private boolean isPretty(Node newParent, Node newNode) {
1343        boolean parentPretty = false;
1344        NodeList JavaDoc childs = newParent.getChildNodes();
1345        int len = childs.getLength();
1346        
1347                /*
1348                 * <test name="test1"></test> parentPretty = true
1349                 *
1350                 * <test name="test1"> parentPretty = true
1351                 * </test>
1352                 *
1353                 * <test name="test1"> parentPretty = true
1354                 * <c name="c1">
1355                 * </test>
1356                 *
1357                 * <test name="test1"><c name="c1"> parentPretty = false
1358                 * </test>
1359                 */

1360        if(len == 0)
1361            parentPretty = true;
1362        else if(len == 1 && childs.item(0) instanceof Text)
1363            parentPretty = true;
1364        else if(len > 2 &&
1365                checkPrettyText((Node) childs.item(0)) && checkPrettyText((Node) childs.item(len-1)))
1366            parentPretty = true;
1367        
1368        if(!parentPretty)
1369            return false;
1370        
1371        if(newNode != null) {
1372            //now check newNode pretty
1373
Node preText = null;
1374            Node postText = null;
1375            int index = ((NodeImpl)newParent).getIndexOfChild(newNode);
1376            if(index > 0)
1377                preText = (Node)newParent.getChildNodes().item(index-1);
1378            if((index+1) < newParent.getChildNodes().getLength())
1379                postText = (Node)newParent.getChildNodes().item((index+1));
1380            
1381                        /*
1382                         * <test name="test1">
1383                         * <a name="a1"/>
1384                         * <b name="b1"/>
1385                         * <c name="c1"/> 'c' pretty = true
1386                         * </test>
1387                         *
1388                         * <test name="test1">
1389                         * <a name="a1"/>
1390                         * <b name="b1"/><c name="c1"/> 'c' pretty = false
1391                         * </test>
1392                         *
1393                         */

1394            if(checkPrettyText(preText) && checkPrettyText(postText))
1395                return true;
1396        } else
1397            return parentPretty;
1398        
1399        return false;
1400    }
1401
1402    /**
1403     * Set/get mapping of QName-valued attributes by element.
1404     * Key of the mapping is QName of the element.
1405     * Value of the mapping is list QName's of the attributes.
1406     * If the mapping is set, it will be used to identify the
1407     * attribute values affected by namespace prefix refactoring
1408     * during namespace consolidation. If not set, namespace consolidation
1409     * would skip prefix rename refactoring case.
1410     */

1411    public void setQNameValuedAttributes(Map JavaDoc<QName JavaDoc,List JavaDoc<QName JavaDoc>> attrsByElement) {
1412        qnameValuedAttributesByElementMap = attrsByElement;
1413    }
1414    public Map JavaDoc<QName JavaDoc,List JavaDoc<QName JavaDoc>> getQNameValuedAttributes() {
1415        return qnameValuedAttributesByElementMap;
1416    }
1417    
1418    /**
1419     * The xml syntax parser
1420     */

1421    private XMLSyntaxParser parser;
1422    
1423    /**
1424     * The current stable document represented by the model
1425     */

1426    private Document currentDocument;
1427    
1428    /**
1429     * Property change support
1430     */

1431    private PropertyChangeSupport JavaDoc pcs;
1432    
1433    /**
1434     * The underlying model source
1435     */

1436    private ModelSource source;
1437    
1438    /**
1439     * Current status of the model
1440     */

1441    private Status status;
1442    
1443    private boolean pretty = false;
1444    
1445    /**
1446     * Undoable edit support
1447     */

1448    private UndoableEditSupport JavaDoc ues;
1449    
1450    /**
1451     * whether to fire undo events
1452     */

1453    private boolean fireUndoEvents = true;
1454    
1455    /**
1456     * The names of property change events fired
1457     */

1458    /**
1459     * Indicates node modified
1460     */

1461    public static final String JavaDoc PROP_MODIFIED = "modified";
1462    /**
1463     * Indicates node deleted
1464     */

1465    public static final String JavaDoc PROP_DELETED = "deleted";
1466    /**
1467     * Indicates node added
1468     */

1469    public static final String JavaDoc PROP_ADDED = "added";
1470    
1471    public static final String JavaDoc DEFAULT_INDENT = " ";
1472    
1473    /**
1474     * current node count
1475     */

1476    private int nodeCount = 0;
1477    
1478    private ElementIdentity eID;
1479    
1480    private String JavaDoc currentIndent = "";
1481    
1482    private boolean indentInitialized = false;
1483    
1484    private Map JavaDoc<QName JavaDoc,List JavaDoc<QName JavaDoc>> qnameValuedAttributesByElementMap;
1485}
1486
Popular Tags