~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to xml/xdm/src/org/netbeans/modules/xml/xdm/XDMModel.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
6
 * The contents of this file are subject to the terms of either the GNU
 
7
 * General Public License Version 2 only ("GPL") or the Common
 
8
 * Development and Distribution License("CDDL") (collectively, the
 
9
 * "License"). You may not use this file except in compliance with the
 
10
 * License. You can obtain a copy of the License at
 
11
 * http://www.netbeans.org/cddl-gplv2.html
 
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 
13
 * specific language governing permissions and limitations under the
 
14
 * License.  When distributing the software, include this License Header
 
15
 * Notice in each file and include the License file at
 
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 
17
 * particular file as subject to the "Classpath" exception as provided
 
18
 * by Sun in the GPL Version 2 section of the License file that
 
19
 * accompanied this code. If applicable, add the following below the
 
20
 * License Header, with the fields enclosed by brackets [] replaced by
 
21
 * your own identifying information:
 
22
 * "Portions Copyrighted [year] [name of copyright owner]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
26
 * The Original Software is NetBeans. The Initial Developer of the Original
 
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 
28
 * Microsystems, Inc. All Rights Reserved.
 
29
 *
 
30
 * If you wish your version of this file to be governed by only the CDDL
 
31
 * or only the GPL Version 2, indicate your decision by adding
 
32
 * "[Contributor] elects to include this software in this distribution
 
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 
34
 * single choice of license, a recipient has the option to distribute
 
35
 * your version of this file under either the CDDL, the GPL Version 2 or
 
36
 * to extend the choice of license to its licensees as provided above.
 
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 
38
 * Version 2 license, then the option applies only if the new code is
 
39
 * made subject to such option by the copyright holder.
 
40
 */
 
41
package org.netbeans.modules.xml.xdm;
 
42
 
 
43
import java.beans.PropertyChangeListener;
 
44
import java.beans.PropertyChangeSupport;
 
45
import java.io.IOException;
 
46
import java.util.ArrayList;
 
47
import java.util.Collection;
 
48
import java.util.List;
 
49
import java.util.Map;
 
50
import java.util.logging.Level;
 
51
import java.util.logging.Logger;
 
52
import javax.swing.event.UndoableEditEvent;
 
53
import javax.swing.event.UndoableEditListener;
 
54
import javax.swing.text.BadLocationException;
 
55
import javax.swing.undo.CompoundEdit;
 
56
import javax.swing.undo.UndoableEdit;
 
57
import javax.swing.undo.UndoableEditSupport;
 
58
import javax.xml.XMLConstants;
 
59
import javax.xml.namespace.QName;
 
60
import org.netbeans.editor.BaseDocument;
 
61
import org.netbeans.modules.xml.xam.ModelSource;
 
62
import org.netbeans.modules.xml.xam.dom.ElementIdentity;
 
63
import org.netbeans.modules.xml.xdm.diff.DiffFinder;
 
64
import org.netbeans.modules.xml.xdm.diff.XDMTreeDiff;
 
65
import org.netbeans.modules.xml.xdm.diff.Difference;
 
66
import org.netbeans.modules.xml.xdm.diff.Add;
 
67
import org.netbeans.modules.xml.xdm.diff.Change;
 
68
import org.netbeans.modules.xml.xdm.diff.Delete;
 
69
import org.netbeans.modules.xml.xdm.diff.DefaultElementIdentity;
 
70
import org.netbeans.modules.xml.xdm.diff.MergeDiff;
 
71
import org.netbeans.modules.xml.xdm.diff.NodeIdDiffFinder;
 
72
import org.netbeans.modules.xml.xdm.diff.NodeInfo;
 
73
import org.netbeans.modules.xml.xdm.diff.SyncPreparation;
 
74
import org.netbeans.modules.xml.xdm.nodes.Attribute;
 
75
import org.netbeans.modules.xml.xdm.nodes.Document;
 
76
import org.netbeans.modules.xml.xdm.nodes.Element;
 
77
import org.netbeans.modules.xml.xdm.nodes.Node;
 
78
import org.netbeans.modules.xml.xdm.nodes.NodeImpl;
 
79
import org.netbeans.modules.xml.xdm.nodes.Text;
 
80
import org.netbeans.modules.xml.xdm.nodes.Token;
 
81
import org.netbeans.modules.xml.xdm.nodes.XMLSyntaxParser;
 
82
import org.netbeans.modules.xml.xdm.visitor.FindVisitor;
 
83
import org.netbeans.modules.xml.xdm.visitor.FlushVisitor;
 
84
import org.netbeans.modules.xml.xdm.visitor.NamespaceRefactorVisitor;
 
85
import org.netbeans.modules.xml.xdm.visitor.PathFromRootVisitor;
 
86
import org.netbeans.modules.xml.xdm.visitor.Utils;
 
87
import org.w3c.dom.NamedNodeMap;
 
88
import org.w3c.dom.NodeList;
 
89
 
 
90
/**
 
91
 */
 
92
public class XDMModel {
 
93
    
 
94
    /**
 
95
     * @param ms requires an instance of org.netbeans.editor.BaseDocument to be
 
96
     * available in the lookup;
 
97
     */
 
98
    public XDMModel(ModelSource ms) {
 
99
        source = ms;
 
100
        assert getSwingDocument() != null;
 
101
        ues = new UndoableEditSupport(this);
 
102
        pcs = new PropertyChangeSupport(this);
 
103
        parser = new XMLSyntaxParser();
 
104
        setStatus(Status.UNPARSED);
 
105
        
 
106
        //establish a default element identification mechanism
 
107
        //domain models should override this by invoking "setElementIdentity"
 
108
        ElementIdentity eID = createElementIdentity();
 
109
        setElementIdentity(eID);
 
110
    }
 
111
    
 
112
    public String getIndentation() {
 
113
        return currentIndent;
 
114
    }
 
115
    
 
116
    public void setIndentation(String indent) {
 
117
        currentIndent = indent;
 
118
        indentInitialized = true;
 
119
    }
 
120
    
 
121
    private void setDefaultIndentation() {
 
122
        currentIndent = DEFAULT_INDENT;
 
123
    }
 
124
    
 
125
        /*
 
126
         * override this method if domain model wants to identify elements
 
127
         * using a different mechanism than this default one.
 
128
         *
 
129
         */
 
130
    private ElementIdentity createElementIdentity() {
 
131
        //Establish DOM element identities
 
132
        ElementIdentity eID = new DefaultElementIdentity();
 
133
        //Following values are suitable for Schema and WSDL documents
 
134
        //these default values can be reset by eID.reset() call
 
135
        eID.addIdentifier( "id" );
 
136
        eID.addIdentifier( "name" );
 
137
        eID.addIdentifier( "ref" );
 
138
        return eID;
 
139
    }
 
140
    
 
141
    /**
 
142
     * This api flushes the changes made to the model to the underlying document.
 
143
     */
 
144
    public synchronized void flush() {
 
145
        flushDocument(getDocument());
 
146
    }
 
147
    
 
148
    /**
 
149
     * This api syncs the model with the underlying swing document.
 
150
     * If its the first time sync is called, swing document is parsed and model
 
151
     * is initialized. Otherwise the changes made to swing document are applied
 
152
     * to the model using DiffMerger.
 
153
     */
 
154
    public synchronized void sync() throws IOException {
 
155
        if (preparation == null) {
 
156
            prepareSync();
 
157
        }
 
158
        finishSync();
 
159
    }
 
160
    
 
161
    public synchronized void prepareSync() {
 
162
        Status oldStat = getStatus();
 
163
        try {
 
164
            setStatus(Status.PARSING);  // to access in case old broken tree
 
165
            Document newDoc = parser.parse(getSwingDocument());
 
166
            Document oldDoc = getCurrentDocument();
 
167
            if (oldDoc == null) {
 
168
                preparation = new SyncPreparation(newDoc);
 
169
            } else {
 
170
                newDoc.assignNodeIdRecursively();
 
171
                XDMTreeDiff treeDiff = new XDMTreeDiff(eID);
 
172
                List<Difference> preparedDiffs = treeDiff.performDiff( this, newDoc );
 
173
                preparation = new SyncPreparation(oldDoc, preparedDiffs);
 
174
            }
 
175
        } catch (BadLocationException ble) {
 
176
            preparation = new SyncPreparation(ble);
 
177
        } catch (IllegalArgumentException iae) {
 
178
            preparation = new SyncPreparation(iae);
 
179
        } catch (IOException ioe) {
 
180
            preparation = new SyncPreparation(ioe);
 
181
        } finally {
 
182
            setStatus(oldStat);  // we are not mutating yet, so alway restore
 
183
        }
 
184
    }
 
185
    
 
186
    private SyncPreparation preparation = null;
 
187
    
 
188
    private void finishSync() throws IOException {
 
189
        if (preparation == null) {
 
190
            return; // unprepared or other thread has stealth the sync
 
191
        }
 
192
        
 
193
        if (preparation.hasErrors()) {
 
194
            IOException error = preparation.getError();
 
195
            preparation = null;
 
196
            setStatus(Status.BROKEN);
 
197
            throw error;
 
198
        }
 
199
        
 
200
        Status savedStatus = getStatus();
 
201
        setStatus(Status.PARSING);
 
202
        Document oldDoc = getCurrentDocument();
 
203
        try {
 
204
            if (preparation.getNewDocument() != null) {
 
205
                Document newDoc = preparation.getNewDocument();
 
206
                newDoc.addedToTree(this);
 
207
                setDocument(newDoc);
 
208
            } else {
 
209
                assert preparation.getOldDocument() != null : "Invalid preparation oldDoc is null";
 
210
                if (oldDoc != preparation.getOldDocument()) {
 
211
                    // other thread has completed the sync before me
 
212
                    setStatus(savedStatus);
 
213
                    return;
 
214
                }
 
215
                List<Difference> diffs = preparation.getDifferences();
 
216
                mergeDiff(diffs);
 
217
                //diffs = DiffFinder.filterWhitespace(diffs);
 
218
                fireDiffEvents(diffs);
 
219
                if (getCurrentDocument() != oldDoc) {
 
220
                    fireUndoableEditEvent(getCurrentDocument(), oldDoc);
 
221
                }
 
222
            }
 
223
            setStatus(Status.STABLE);
 
224
        } catch (IllegalArgumentException iae) {
 
225
            if (getStatus() != Status.STABLE) {
 
226
                IOException ioe = new IOException();
 
227
                ioe.initCause(iae);
 
228
                throw ioe;
 
229
            } else {
 
230
                // XAM will review the mutation and veto by it wrapped IOException
 
231
                if (iae.getCause() instanceof IOException) {
 
232
                    setStatus(Status.BROKEN);
 
233
                    throw (IOException) iae.getCause();
 
234
                } else {
 
235
                    throw iae;
 
236
                }
 
237
            }
 
238
        } finally {
 
239
            if(getStatus() != Status.STABLE) {
 
240
                setStatus(Status.BROKEN);
 
241
                setDocument(oldDoc);
 
242
            }
 
243
            preparation = null;
 
244
        }
 
245
    }
 
246
    
 
247
    public void mergeDiff(List<Difference> diffs) {
 
248
        setStatus(Status.PARSING);
 
249
        new MergeDiff().merge(this, diffs);
 
250
        // exception in event firing should not put tree out-of-sync with buffer
 
251
        setStatus(Status.STABLE);
 
252
    }
 
253
    
 
254
    private void fireDiffEvents(final List<Difference> deList) {
 
255
        for ( Difference de:deList ) {
 
256
            NodeInfo.NodeType nodeType = de.getNodeType();
 
257
//          if ( nodeType == NodeInfo.NodeType.WHITE_SPACE ) continue;//skip if WS
 
258
            if ( de instanceof Add ) {
 
259
                NodeInfo newNodeInfo = ((Add)de).getNewNodeInfo();
 
260
                assert newNodeInfo != null;
 
261
                pcs.firePropertyChange( PROP_ADDED, null, newNodeInfo );
 
262
            } else if ( de instanceof Delete ) {
 
263
                NodeInfo OldNodeInfo = ((Delete)de).getOldNodeInfo();
 
264
                assert OldNodeInfo != null;
 
265
                pcs.firePropertyChange( PROP_DELETED, OldNodeInfo, null );
 
266
            } else if ( de instanceof Change ) {
 
267
                NodeInfo oldNodeInfo = ((Change)de).getOldNodeInfo();
 
268
                assert oldNodeInfo != null;
 
269
                
 
270
                NodeInfo newNodeInfo = ((Change)de).getNewNodeInfo();
 
271
                assert newNodeInfo != null;
 
272
                
 
273
                //fire delete and add events for position change of element/text
 
274
                if ( ((Change)de).isPositionChanged() ) {
 
275
                    pcs.firePropertyChange( PROP_DELETED, oldNodeInfo, null );
 
276
                    pcs.firePropertyChange( PROP_ADDED, null, newNodeInfo );
 
277
                } else if ( de.getNodeType() == NodeInfo.NodeType.TEXT ) { //text change only
 
278
                    pcs.firePropertyChange( PROP_MODIFIED, oldNodeInfo, newNodeInfo );
 
279
                } else if ( de.getNodeType() == NodeInfo.NodeType.ELEMENT ) {
 
280
                    List<Node> path1 = new ArrayList<Node>(oldNodeInfo.getOriginalAncestors());
 
281
                    path1.add(0, oldNodeInfo.getNode());
 
282
                    List<Node> path2 = new ArrayList<Node>(newNodeInfo.getNewAncestors());
 
283
                    path2.add(0, newNodeInfo.getNode());
 
284
                    //fire attribute change events
 
285
                    List<Change.AttributeDiff> attrChanges = ((Change)de).getAttrChanges();
 
286
                    for (Change.AttributeDiff attrDiff:attrChanges) {
 
287
                        Node oldAttr = attrDiff.getOldAttribute();
 
288
                        Node newAttr = attrDiff.getNewAttribute();
 
289
                        if(attrDiff instanceof Change.AttributeAdd) {
 
290
                            assert newAttr != null;
 
291
                            pcs.firePropertyChange( PROP_ADDED, null,
 
292
                                    new NodeInfo(newAttr, -1, path1, path2));
 
293
                        } else if(attrDiff instanceof Change.AttributeDelete) {
 
294
                            assert oldAttr != null;
 
295
                            pcs.firePropertyChange(
 
296
                                    PROP_DELETED, new NodeInfo(oldAttr, -1, path1, path2), null );
 
297
                        } else if(attrDiff instanceof Change.AttributeChange) {
 
298
                            assert oldAttr != null;
 
299
                            assert newAttr != null;                            
 
300
                            NodeInfo old = new NodeInfo(oldAttr, -1, path1, path2);
 
301
                            NodeInfo now = new NodeInfo(newAttr, -1, path1, path2);
 
302
                            pcs.firePropertyChange( PROP_MODIFIED, old, now);
 
303
                        }
 
304
                    }
 
305
                }
 
306
            }
 
307
        }
 
308
    }
 
309
    
 
310
    private interface Updater {
 
311
        void update(Node parent, Node oldNode, Node newNode);
 
312
    }
 
313
    
 
314
    private List<Node> getPathToRoot(Node node, Document root) {
 
315
        PathFromRootVisitor pathVisitor = new PathFromRootVisitor();
 
316
        List<Node> path = pathVisitor.findPath(root, node);
 
317
        if (path==null || path.isEmpty()) {
 
318
            throw new IllegalArgumentException("old node must be in the tree");
 
319
        }
 
320
        return path;
 
321
    }
 
322
    
 
323
    private static String classifyMutationType(Node oldNode, Node newNode) {
 
324
        if (newNode == null && oldNode == null ||
 
325
                newNode != null && oldNode != null) {
 
326
            return PROP_MODIFIED;
 
327
        } else if (newNode != null) {
 
328
            return PROP_ADDED;
 
329
        } else {
 
330
            return PROP_DELETED;
 
331
        }
 
332
    }
 
333
    
 
334
    private enum MutationType { CHILDREN, ATTRIBUTE, BOTH }
 
335
    
 
336
    private List<Node> mutate(Node parent, Node oldNode, Node newNode, Updater updater) {
 
337
        return mutate(parent, oldNode, newNode, updater, null);
 
338
    }
 
339
    
 
340
    private List<Node> mutate(Node parent, Node oldNode, Node newNode, Updater updater, MutationType type) {
 
341
        checkStableOrParsingState();
 
342
        if (newNode != null) checkNodeInTree(newNode);
 
343
        
 
344
        Document currentDocument = getDocument();
 
345
        List<Node> ancestors;
 
346
        if (parent == null) {
 
347
            assert(oldNode != null);
 
348
            ancestors = getPathToRoot(oldNode, currentDocument);
 
349
            oldNode = ancestors.remove(0);
 
350
        } else {
 
351
            if (oldNode != null) {
 
352
                assert parent.getIndexOfChild(oldNode) > -1;
 
353
                ancestors = getPathToRoot(oldNode, currentDocument);
 
354
                assert oldNode.getId() == ancestors.get(0).getId();
 
355
                oldNode = ancestors.remove(0);
 
356
                assert parent.getId() == ancestors.get(0).getId();
 
357
            } else {
 
358
                ancestors = getPathToRoot(parent, currentDocument);
 
359
            }
 
360
        }
 
361
        
 
362
        final Node oldParent = ancestors.remove(0);
 
363
        Node newParent;
 
364
        if (type == null) {
 
365
            if (oldNode instanceof Attribute || newNode instanceof Attribute ||
 
366
                    (oldNode == null && newNode == null)) {
 
367
                type = MutationType.ATTRIBUTE;
 
368
            } else if (ancestors.size() == 1) {
 
369
                assert (oldParent instanceof Element);
 
370
                //might have to add namespace declaration to root
 
371
                type = MutationType.BOTH;
 
372
            } else {
 
373
                type = MutationType.CHILDREN;
 
374
            }
 
375
        }
 
376
        switch(type) {
 
377
            case ATTRIBUTE:
 
378
                newParent = (Node)oldParent.clone(true,true,false);
 
379
                break;
 
380
            case CHILDREN:
 
381
                newParent = (Node)oldParent.clone(true,false,true);
 
382
                break;
 
383
            default:
 
384
                newParent = (Node)oldParent.clone(true,true,true);
 
385
        }
 
386
        
 
387
        if (oldNode != null && oldNode.getNodeType() != Node.TEXT_NODE && newNode == null) { // pure remove
 
388
            undoPrettyPrint(newParent, oldNode, oldParent);
 
389
        }
 
390
        updater.update(newParent, oldNode, newNode);
 
391
        if (oldNode == null && newNode != null && newNode.getNodeType() != Node.TEXT_NODE ) { // pure add
 
392
            doPrettyPrint(newParent, newNode, oldParent);
 
393
        }
 
394
        
 
395
        List<Node> newAncestors = updateAncestors(ancestors, newParent, oldParent);
 
396
        if(getStatus() != Status.PARSING && newNode instanceof Element) {
 
397
            consolidateNamespaces(newAncestors, newParent, (Element)newNode);
 
398
        }
 
399
        Document d = (Document) (!newAncestors.isEmpty() ?
 
400
            newAncestors.get(newAncestors.size()-1) : newParent);
 
401
        d.addedToTree(this);
 
402
        setDocument(d);
 
403
        ancestors.add(0, oldParent);
 
404
        newAncestors.add(0, newParent);
 
405
        if(getStatus() != Status.PARSING) { // not merging
 
406
            fireUndoableEditEvent(d, currentDocument);
 
407
            String mutationType = classifyMutationType(oldNode, newNode);
 
408
            //TODO seems missing delete/change; also, who are listening to these xdm mutation events
 
409
            NodeInfo newNodeInfo = new NodeInfo( newNode, -1, ancestors, newAncestors );
 
410
            pcs.firePropertyChange(mutationType, null, newNodeInfo);
 
411
        }
 
412
        return newAncestors;
 
413
    }
 
414
    
 
415
    private void consolidateNamespaces(List<Node> ancestors, Node parent, Element newNode) {
 
416
        if (parent instanceof Document) return; // no actions if newNode is root itself
 
417
        assert ancestors.size() > 0;
 
418
        Element root = (Element) (ancestors.size() == 1 ?
 
419
            parent : ancestors.get(ancestors.size()-2));
 
420
        List<Node> parentAndAncestors = new ArrayList(ancestors);
 
421
        parentAndAncestors.add(0, parent);
 
422
        consolidateAttributePrefix(parentAndAncestors, newNode);
 
423
        NamedNodeMap nnm = newNode.getAttributes();
 
424
        for (int i=0; i<nnm.getLength(); i++) {
 
425
            if (nnm.item(i) instanceof Attribute) {
 
426
                Attribute attr = (Attribute) nnm.item(i);
 
427
                consolidateNamespace(root, parentAndAncestors, newNode, attr);
 
428
            }
 
429
        }
 
430
        
 
431
        // use parent node prefix
 
432
        String parentPrefix = parent.getPrefix();
 
433
        String parentNS = NodeImpl.lookupNamespace(parent, ancestors);
 
434
        if (parentNS != null && ! parentNS.equals(XMLConstants.NULL_NS_URI)) {
 
435
            new NamespaceRefactorVisitor(this).refactor(
 
436
                        newNode, parentNS, parentPrefix, parentAndAncestors);
 
437
        }
 
438
    }
 
439
    
 
440
    private void consolidateAttributePrefix(List<Node> parentAndAncestors, Element newNode) {
 
441
        NamedNodeMap nnm = newNode.getAttributes();
 
442
        for (int i=0; i<nnm.getLength(); i++) {
 
443
            if (! (nnm.item(i) instanceof Attribute))  continue;
 
444
            Attribute attr = (Attribute) nnm.item(i);
 
445
            String prefix = attr.getPrefix();
 
446
            if (prefix != null && ! attr.isXmlnsAttribute()) {
 
447
                String namespace = newNode.lookupNamespaceURI(prefix);
 
448
                if (namespace == null) continue;
 
449
                prefix = NodeImpl.lookupPrefix(namespace, parentAndAncestors);
 
450
                  if (prefix != null) {
 
451
                    attr.setPrefix(prefix);
 
452
                }
 
453
            }
 
454
        }
 
455
    }
 
456
    
 
457
    /**
 
458
     * Consolidate new node top-leveled namespaces with parent's.
 
459
     * Note: this assume #consolidateAttributePrefix has been called
 
460
     */
 
461
    private void consolidateNamespace(Element root, List<Node> parentAndAncestors,
 
462
            Element newNode, Attribute attr) {
 
463
        if (attr.isXmlnsAttribute()) {
 
464
            String prefix = attr.getLocalName();
 
465
            if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) {
 
466
                prefix = XMLConstants.DEFAULT_NS_PREFIX;
 
467
            }
 
468
            String namespace = attr.getValue();
 
469
            assert (namespace != null);
 
470
            
 
471
            Node parent = parentAndAncestors.get(0);
 
472
            String parentNS = NodeImpl.lookupNamespace(parent, parentAndAncestors);
 
473
            
 
474
            String existingNS = NodeImpl.lookupNamespace(prefix, parentAndAncestors);
 
475
            String existingPrefix = NodeImpl.lookupPrefix(namespace, parentAndAncestors);
 
476
            
 
477
            // 1. prefix is free (existingNS == null) and namespace is never declared (existingPrefix == null)
 
478
            // 2. prefix is used and for the same namespace
 
479
            // 3. namespace is declared by different prefix
 
480
            // 4. prefix is used and for different namespace
 
481
            
 
482
            if (existingNS == null && existingPrefix == null) { // case 1.
 
483
                newNode.removeAttributeNode(attr);
 
484
                root.appendAttribute(attr);
 
485
            } else if (namespace.equals(existingNS) && prefix.equals(existingPrefix)) { // case 2
 
486
                assert prefix.equals(existingPrefix) : "prefix='"+prefix+"' existingPrefix='"+existingPrefix+"'";
 
487
                newNode.removeAttributeNode(attr);
 
488
            } else if (existingPrefix != null) { // case 3.
 
489
                // skip attribute redeclaring namespace of parent element
 
490
                // we will refactor to parent prefix after processing all xmlns-attributes
 
491
                if (! namespace.equals(parentNS)) {
 
492
                    new NamespaceRefactorVisitor(this).refactor(
 
493
                        newNode, namespace, existingPrefix, parentAndAncestors);
 
494
                }
 
495
            } else { // existingNS != null && existingPrefix == null
 
496
                // case 4 just leave prefix as overriding with different namespace
 
497
            }
 
498
        }
 
499
    }
 
500
    
 
501
    /**
 
502
     * This api replaces given old node with given new node.
 
503
     * The old node passed must be in tree, the new node must not be in tree,
 
504
     * and new node must be clone of old node.
 
505
     * @param oldValue The old node to be replaced.
 
506
     * @param newValue The new node.
 
507
     * @return The new parent
 
508
     */
 
509
    public synchronized List<Node> modify(Node oldValue, Node newValue) {
 
510
        if (oldValue.getId() != newValue.getId()) {
 
511
            throw new IllegalArgumentException("newValue must be a clone of oldValue");
 
512
        }
 
513
        
 
514
        if (oldValue instanceof Document) {
 
515
            assert newValue instanceof Document;
 
516
            Document oldDoc = (Document) oldValue;
 
517
            Document newDoc = (Document) newValue;
 
518
            newDoc.addedToTree(this);
 
519
            setDocument(newDoc);
 
520
            if (getStatus() != Status.PARSING) { // not merging
 
521
                fireUndoableEditEvent(oldDoc, currentDocument);
 
522
                String mutationType = classifyMutationType(oldDoc, newDoc);
 
523
                ArrayList<Node> ancestors = new ArrayList<Node>();
 
524
                NodeInfo oldNodeInfo = new NodeInfo( oldDoc, -1, ancestors, ancestors );
 
525
                NodeInfo newNodeInfo = new NodeInfo( newDoc, -1, ancestors, ancestors );
 
526
                pcs.firePropertyChange(mutationType, oldNodeInfo, newNodeInfo);
 
527
            }
 
528
            return new ArrayList<Node>();
 
529
        }
 
530
        
 
531
        Updater modifier = new Updater() {
 
532
            public void update(Node newParent, Node oldNode, Node newNode) {
 
533
                if (oldNode instanceof Attribute) {
 
534
                    ((Element)newParent).replaceAttribute((Attribute)newNode,(Attribute)oldNode);
 
535
                } else {
 
536
                    newParent.replaceChild(newNode, oldNode);
 
537
                }
 
538
            }
 
539
        };
 
540
        return mutate(null, oldValue, newValue, modifier);
 
541
    }
 
542
    
 
543
    /**
 
544
     * This api adds given node to given parent at given index.
 
545
     * The added node will be part of childnodes of the parent,
 
546
     * and its index will be the given index. If the given index
 
547
     * is out of the parents childnodes range, the node will be
 
548
     * appended.
 
549
     * @param parent The parent node to which the node is to be added.
 
550
     * @param node The node which is to be added.
 
551
     * @param offset The index at which the node is to be added.
 
552
     * @return The parent node resulted by addition of this node
 
553
     */
 
554
    public synchronized List<Node> add(Node parent, Node node, final int offset) {
 
555
        if (offset<0) throw new IndexOutOfBoundsException();
 
556
        Updater adder = new Updater() {
 
557
            public void update(Node newParent, Node oldNode, Node newNode) {
 
558
                if (newParent instanceof Element && newNode instanceof Attribute) {
 
559
                    Element newElement = (Element)newParent;
 
560
                    if (offset>newElement.getAttributes().getLength())
 
561
                        throw new IndexOutOfBoundsException();
 
562
                    newElement.addAttribute((Attribute)newNode,offset);
 
563
                } else {
 
564
                    //reset id, since adding root element
 
565
                    if(newParent instanceof Document && newNode instanceof Element)
 
566
                        resetIdMeter();
 
567
                    
 
568
                    if (offset>newParent.getChildNodes().getLength())
 
569
                        throw new IndexOutOfBoundsException();
 
570
                    if(offset<newParent.getChildNodes().getLength()) {
 
571
                        Node refChild = (Node)newParent.getChildNodes().item(offset);
 
572
                        newParent.insertBefore(newNode,refChild);
 
573
                    } else {
 
574
                        newParent.appendChild(newNode);
 
575
                    }
 
576
                }
 
577
            }
 
578
        };
 
579
        return mutate(parent, null, node, adder);
 
580
    }
 
581
    
 
582
    /**
 
583
     * This api adds given node to given parent before given ref node.
 
584
     * The inserted node will be part of childnodes of the parent,
 
585
     * and will appear before ref node.
 
586
     * @param parent The parent node to which the node is to be added.
 
587
     * @param node The node which is to be added.
 
588
     * @param refChild The ref node (child) of parent node,
 
589
     *                  before which the node is to be added.
 
590
     * @return The parent node resulted by inserion of this node.
 
591
     */
 
592
    public synchronized List<Node> insertBefore(Node parent, Node node, Node refChild) {
 
593
        final Node ref = refChild;
 
594
        Updater updater = new Updater() {
 
595
            public void update(Node newParent, Node oldNode, Node newNode) {
 
596
                newParent.insertBefore(newNode, ref);
 
597
            }
 
598
        };
 
599
        
 
600
        return mutate(parent, null, node, updater);
 
601
    }
 
602
    
 
603
    /**
 
604
     * This api adds given node to given parent at the end.
 
605
     * The added node will be part of childnodes of the parent,
 
606
     * and it will be the last node.
 
607
     * @param parent The parent node to which the node is to be appended.
 
608
     * @param node The node which is to be appended.
 
609
     * @return The parent node resulted by addition of this node
 
610
     */
 
611
    public synchronized List<Node> append(Node parent, Node node) {
 
612
        Updater appender = new Updater() {
 
613
            public void update(Node parent, Node oldNode, Node newNode) {
 
614
                //reset id, since adding root element
 
615
                if(parent instanceof Document && newNode instanceof Element)
 
616
                    resetIdMeter();
 
617
                parent.appendChild(newNode);
 
618
            }
 
619
        };
 
620
        return mutate(parent, null, node, appender);
 
621
    }
 
622
    
 
623
    /**
 
624
     * This api deletes given node from a tree.
 
625
     * @param node The node  to be deleted.
 
626
     * @return The parent node resulted by deletion of this node.
 
627
     */
 
628
    public synchronized List<Node> delete(Node n) {
 
629
        Updater remover = new Updater() {
 
630
            public void update(Node newParent, Node oldNode, Node newNode) {
 
631
                newParent.removeChild(oldNode);
 
632
            }
 
633
        };
 
634
        return mutate(null, n, null, remover);
 
635
    }
 
636
    
 
637
    /**
 
638
     * This api changes index of the given node.
 
639
     * @param nodes The nodes to be moved.
 
640
     * @param indexes the new indexes of the nodes.
 
641
     * @return The parent node resulted by deletion of this node.
 
642
     */
 
643
    public synchronized List<Node> reorder(Node parent, Node n, final int index) {
 
644
        if (index < 0) throw new IndexOutOfBoundsException("index="+index);
 
645
        Updater u = new Updater() {
 
646
            public void update(Node newParent, Node oldNode, Node newNode) {
 
647
                if (newParent instanceof Element && newNode instanceof Attribute) {
 
648
                    Element parent = (Element) newParent;
 
649
                    int i = index;
 
650
                    if (index > parent.getAttributes().getLength()) {
 
651
                        i = parent.getAttributes().getLength();
 
652
                    }
 
653
                    parent.reorderAttribute((Attribute) oldNode, i);
 
654
                } else {
 
655
                    int i = index;
 
656
                    if (index > newParent.getChildNodes().getLength()) {
 
657
                        i = newParent.getChildNodes().getLength();
 
658
                    }
 
659
                    ((NodeImpl)newParent).reorderChild(oldNode, i);
 
660
                }
 
661
            }
 
662
        };
 
663
        return mutate(parent, n, null, u);
 
664
    }
 
665
    
 
666
    /**
 
667
     * This api changes indexes of the given node children.
 
668
     * @param nodes The nodes to be moved.
 
669
     * @param indexes the new indexes of the nodes.
 
670
     * @return The parent node resulted by deletion of this node.
 
671
     */
 
672
    public synchronized List<Node> reorderChildren(Node parent, final int[] permutation) {
 
673
        Updater u = new Updater() {
 
674
            public void update(Node newParent, Node oldNode, Node newNode) {
 
675
                ((NodeImpl)newParent).reorderChildren(permutation);
 
676
            }
 
677
        };
 
678
        return mutate(parent, null, null, u, MutationType.CHILDREN);
 
679
    }
 
680
    
 
681
    /**
 
682
     * This api deletes given node from a given parent node.
 
683
     * @param parent The parent node from which the node is to be deleted.
 
684
     * @param child The node  to be deleted.
 
685
     * @return The parent node resulted by deletion of this node.
 
686
     */
 
687
    public synchronized List<Node> remove(final Node parent, Node child) {
 
688
        Updater remover = new Updater() {
 
689
            public void update(Node newParent, Node oldNode, Node newNode) {
 
690
                assert parent.isEquivalentNode(newParent);
 
691
                newParent.removeChild(oldNode);
 
692
            }
 
693
        };
 
694
        return mutate(parent, child, null, remover);
 
695
    }
 
696
    
 
697
    /**
 
698
     * This api deletes given node from a given parent node.
 
699
     * @param parent The parent node from which the node is to be deleted.
 
700
     * @param toRemove collection of node to be deleted.
 
701
     * @return The parent node resulted by deletion of this node.
 
702
     */
 
703
    public synchronized List<Node> removeChildNodes(final Node parent, final Collection<Node> toRemove) {
 
704
        Updater remover = new Updater() {
 
705
            public void update(Node newParent, Node oldNode, Node newNode) {
 
706
                assert parent.isEquivalentNode(newParent);
 
707
                for (Node n : toRemove) {
 
708
                    newParent.removeChild(n);
 
709
                }
 
710
            }
 
711
        };
 
712
        return mutate(parent, null, null, remover, MutationType.CHILDREN);
 
713
    }
 
714
    
 
715
    public synchronized List<Node> replaceChild(final Node parent, Node child, Node newChild) {
 
716
        Updater updater = new Updater() {
 
717
            public void update(Node newParent, Node oldNode, Node newNode) {
 
718
                assert newParent.isEquivalentNode(parent);
 
719
                newParent.replaceChild(newNode, oldNode);
 
720
            }
 
721
        };
 
722
        return mutate(null, child, newChild, updater);
 
723
    }
 
724
    
 
725
    /**
 
726
     * This api sets an attribute given name and value of a given element node.
 
727
     * If an attribute with given name already present in element, it will only
 
728
     * set the value. Otherwise a new attribute node, with given name and value,
 
729
     * will be appended to the attibute list of the element node.
 
730
     * @param element The element of which the attribute to be set.
 
731
     * @param name The name of the attribute to be set.
 
732
     * @param value The value of the attribute to be set.
 
733
     * @return The element resulted by setting of attribute.
 
734
     */
 
735
    public synchronized List<Node> setAttribute(Element element, final String name, final String value) {
 
736
        Updater updater = new Updater() {
 
737
            public void update(Node newParent, Node oldNode, Node newNode) {
 
738
                ((Element)newParent).setAttribute(name,value);
 
739
            }
 
740
        };
 
741
        return mutate(element, null, null, updater);
 
742
    }
 
743
    
 
744
    /**
 
745
     * This api removes an attribute given name and value of a given element node.
 
746
     * @param element The element of which the attribute to be removed.
 
747
     * @param name The name of the attribute to be removed.
 
748
     * @return The element resulted by removed of attribute.
 
749
     */
 
750
    public synchronized List<Node> removeAttribute(Element element, final String name) {
 
751
        Updater updater = new Updater() {
 
752
            public void update(Node newParent, Node oldNode, Node newNode) {
 
753
                ((Element)newParent).removeAttribute(name);
 
754
            }
 
755
        };
 
756
        return mutate(element, null, null, updater);
 
757
    }
 
758
    
 
759
    private interface CheckIOExceptionUpdater extends Updater {
 
760
        public IOException getError();
 
761
    }
 
762
    
 
763
    public synchronized List<Node> setXmlFragmentText(Element node, final String value) throws IOException {
 
764
        CheckIOExceptionUpdater updater = new CheckIOExceptionUpdater() {
 
765
            public void update(Node newParent, Node oldNode, Node newNode) {
 
766
                try {
 
767
                    ((Element)newParent).setXmlFragmentText(value);
 
768
                } catch(IOException ioe) {
 
769
                    error = ioe;
 
770
                }
 
771
            }
 
772
            public IOException getError() {
 
773
                return error;
 
774
            }
 
775
            private IOException error;
 
776
        };
 
777
        List<Node> retPath = mutate(node, null, null, updater, MutationType.CHILDREN);
 
778
        if (updater.getError() != null) {
 
779
            throw updater.getError();
 
780
        } else {
 
781
            return retPath;
 
782
        }
 
783
    }
 
784
    
 
785
    public synchronized List<Node> setTextValue(Node node, String value) {
 
786
        Node text = (Node) currentDocument.createTextNode(value);
 
787
        Updater updater = new Updater() {
 
788
            public void update(Node newParent, Node oldNode, Node newNode) {
 
789
                while(newParent.hasChildNodes()) {
 
790
                    newParent.removeChild(newParent.getLastChild());
 
791
                }
 
792
                newParent.appendChild(newNode);
 
793
            }
 
794
        };
 
795
        return mutate(node, null, text, updater);
 
796
    }
 
797
    
 
798
    /**
 
799
     * This is utility method which updates all the ancestors in the given
 
800
     * ancestor list of given originalNode. The list returned represents
 
801
     * the ancestors of given modified node.
 
802
     * @param ancestors the list of ancestors starting from parent
 
803
     * @param modifiedNode The modified node for which the new list is to be created
 
804
     * @param originalNode The original node which ancestors are given
 
805
     * @return The list of new ancestors starting parent for the modified node
 
806
     */
 
807
    private List<Node> updateAncestors(List<Node> ancestors, Node modifiedNode, Node originalNode) {
 
808
        assert ancestors != null && modifiedNode != null && originalNode != null;
 
809
        List<Node> newAncestors = new ArrayList<Node>(ancestors.size());
 
810
        Node currentModifiedNode = modifiedNode;
 
811
        Node currentOrigNode = originalNode;
 
812
        for(Node parentNode: ancestors) {
 
813
            Node newParentNode = (Node)parentNode.clone(false,true,true);
 
814
            newParentNode.replaceChild(currentModifiedNode, currentOrigNode);
 
815
            newAncestors.add(newParentNode);
 
816
            currentOrigNode = parentNode;
 
817
            currentModifiedNode = newParentNode;
 
818
        }
 
819
        return newAncestors;
 
820
    }
 
821
    
 
822
    /**
 
823
     * This api returns the latest stable document in the model.
 
824
     * @return The latest stable document in the model.
 
825
     */
 
826
    public synchronized Document getDocument() {
 
827
        checkStableOrParsingState();
 
828
        return currentDocument;
 
829
    }
 
830
    
 
831
    /**
 
832
     * This api returns the current document in the model, regardless of the state.
 
833
     * @return The latest stable document in the model.
 
834
     */
 
835
    public synchronized Document getCurrentDocument() {
 
836
        return currentDocument;
 
837
    }
 
838
    
 
839
    /**
 
840
     * Reset document to provided known version and cause events to be fired.
 
841
     * Note caller are responsible to handle exception and decide which version
 
842
     * to keep after exception and do proper cleanup.
 
843
     */
 
844
    synchronized void resetDocument(Document newDoc) {
 
845
        try {
 
846
            fireUndoEvents = false;
 
847
            List<Difference> diffs = new NodeIdDiffFinder().findDiff(getCurrentDocument(), newDoc);
 
848
            List<Difference> filtered = DiffFinder.filterWhitespace(diffs);
 
849
            //flushDocument(newDoc);
 
850
            setDocument(newDoc);
 
851
            if ( filtered != null && !filtered.isEmpty() ) {
 
852
                fireDiffEvents(filtered);
 
853
            }
 
854
        } finally {
 
855
            fireUndoEvents = true;
 
856
        }
 
857
    }
 
858
    
 
859
    private void flushDocument(Document newDoc) {
 
860
        checkStableState();
 
861
        UndoableEditListener uel = null;
 
862
        BaseDocument d = getSwingDocument();
 
863
        final CompoundEdit ce = new CompoundEdit();
 
864
        try {
 
865
            FlushVisitor flushvisitor = new FlushVisitor();
 
866
            String newXMLText = flushvisitor.flushModel(newDoc);
 
867
            uel = new UndoableEditListener() {
 
868
                public void undoableEditHappened(UndoableEditEvent e) {
 
869
                    ce.addEdit(e.getEdit());
 
870
                }
 
871
            };
 
872
            d.addUndoableEditListener(uel);
 
873
            Utils.replaceDocument(d, newXMLText);
 
874
        } catch (BadLocationException ble) {
 
875
            throw new IllegalStateException("It is possible that model source file is locked", ble);
 
876
        } finally {
 
877
            if (uel != null) {
 
878
                d.removeUndoableEditListener(uel);
 
879
            }
 
880
            ce.end();
 
881
            for (UndoableEditListener l : ues.getUndoableEditListeners()) {
 
882
                l.undoableEditHappened(new UndoableEditEvent(this,ce));
 
883
            }
 
884
        }
 
885
    }
 
886
    
 
887
    public synchronized String getCurrentDocumentText() {
 
888
        return new FlushVisitor().flushModel(getCurrentDocument());
 
889
    }
 
890
    
 
891
    private BaseDocument getSwingDocument() {
 
892
        BaseDocument bd = (BaseDocument)
 
893
        source.getLookup().lookup(BaseDocument.class);
 
894
        return bd;
 
895
    }
 
896
    
 
897
    public synchronized void setDocument(Document newDoc) {
 
898
        currentDocument = newDoc;
 
899
    }
 
900
    
 
901
    /**
 
902
     * This returns the statuc of the model.
 
903
     * @return the status.
 
904
     * @see #Status
 
905
     */
 
906
    public synchronized Status getStatus() {
 
907
        return status;
 
908
    }
 
909
    
 
910
    /**
 
911
     * This api adds an undoable edit listener.
 
912
     * @param l The undoable edit listener to be added.
 
913
     */
 
914
    public synchronized void addUndoableEditListener(UndoableEditListener l) {
 
915
        ues.addUndoableEditListener(l);
 
916
    }
 
917
    
 
918
    /**
 
919
     * This api removes an undoable edit listener.
 
920
     * @param l The undoable edit listener to be removed.
 
921
     */
 
922
    public synchronized void removeUndoableEditListener(UndoableEditListener l) {
 
923
        ues.addUndoableEditListener(l);
 
924
    }
 
925
    
 
926
    /**
 
927
     * This api adds a property change listener.
 
928
     * @param pcl The property change listener to be added.
 
929
     */
 
930
    public synchronized void addPropertyChangeListener(PropertyChangeListener pcl) {
 
931
        pcs.addPropertyChangeListener(pcl);
 
932
    }
 
933
    
 
934
    /**
 
935
     * This api removes a property change listener.
 
936
     * @param pcl The property change listener to be removed.
 
937
     */
 
938
    public synchronized void removePropertyChangeListener(PropertyChangeListener pcl) {
 
939
        pcs.removePropertyChangeListener(pcl);
 
940
    }
 
941
    
 
942
    /**
 
943
     * Find the node with same id in the current tree.
 
944
     */
 
945
    private synchronized Node findNode(int id) {
 
946
        FindVisitor fv = new FindVisitor();
 
947
        return fv.find(getDocument(), id);
 
948
    }
 
949
    
 
950
    /**
 
951
     * This represents the status of the XDM Model.
 
952
     * Status STABLE means the latest attempt to parse was successful
 
953
     * Status BROKEN means that the latest attempt to parse was unsuccessful.
 
954
     * Status UNPARSED means the document has not been parsed yet.
 
955
     * Status PARSING means the document is being parsed.
 
956
     */
 
957
    //TODO Last Parsed status
 
958
    public enum Status {BROKEN, STABLE, UNPARSED, PARSING;}
 
959
    
 
960
    private void fireUndoableEditEvent(Document newDoc, Document oldDoc) {
 
961
        if (fireUndoEvents) {
 
962
            assert newDoc != oldDoc;
 
963
            UndoableEdit ee = new XDMModelUndoableEdit(oldDoc, newDoc, this);
 
964
            UndoableEditEvent ue = new UndoableEditEvent(this, ee);
 
965
            for (UndoableEditListener l:ues.getUndoableEditListeners()) {
 
966
                l.undoableEditHappened(ue);
 
967
            }
 
968
        }
 
969
    }
 
970
    
 
971
    private void checkNodeInTree(Node n) {
 
972
        if (n.isInTree()) {
 
973
            throw new IllegalArgumentException("newValue must not have been added to model"); // NOI18N
 
974
        }
 
975
    }
 
976
    
 
977
    private void checkStableState() {
 
978
        if (getStatus() != Status.STABLE ) {
 
979
            throw new IllegalStateException("flush can only be called from STABLE STATE"); //NOI18N
 
980
        }
 
981
    }
 
982
    
 
983
    private void checkStableOrParsingState() {
 
984
        if (getStatus() != Status.STABLE && getStatus() != Status.PARSING) {
 
985
            throw new IllegalStateException("The model is not initialized or is broken."); //NOI18N
 
986
        }
 
987
    }
 
988
    
 
989
    private void setStatus(Status s) {
 
990
        status = s;
 
991
    }
 
992
    
 
993
    /**
 
994
     * This api keeps track of the nodes created in this model.
 
995
     * @return the id of the next node to be created.
 
996
     */
 
997
    public int getNextNodeId() {
 
998
        int nodeId = nodeCount;
 
999
        nodeCount++;
 
1000
        return nodeId;
 
1001
    }
 
1002
    
 
1003
    /**
 
1004
     * resets id meter
 
1005
     */
 
1006
    private void resetIdMeter() {
 
1007
        nodeCount = 1;
 
1008
    }
 
1009
    
 
1010
    private boolean isPretty() {
 
1011
        return pretty;
 
1012
    }
 
1013
    
 
1014
    public void setPretty(boolean print) {
 
1015
        pretty = print;
 
1016
    }
 
1017
    
 
1018
    private void doPrettyPrint(Node newParent, Node newNode, Node oldParent) {
 
1019
        if ((getStatus() != Status.PARSING) && isPretty()) {
 
1020
            if(isSimpleContent(newParent)) {//skip if simpleContent
 
1021
                /*
 
1022
                 * <test name="test1">A new text node</test>
 
1023
                 */
 
1024
                return;
 
1025
            }
 
1026
            if(!indentInitialized)
 
1027
                initializeIndent(oldParent);
 
1028
            String parentIndent = calculateNodeIndent(oldParent);
 
1029
            if(!isPretty(newParent, newNode)) {//skip if already pretty
 
1030
                int offset = 1;
 
1031
                if(oldParent.getChildNodes().getLength() == 0) {//old parent did not have prettyprint before
 
1032
                    /*
 
1033
                     * before
 
1034
                     *
 
1035
                     * <test name="test1"></test>
 
1036
                     *
 
1037
                     * after
 
1038
                     *
 
1039
                     * <test name="test1">
 
1040
                     *     <c name="c1">
 
1041
                     * </test>
 
1042
                     */
 
1043
                    newParent.insertBefore(createPrettyText(
 
1044
                            parentIndent+getIndentation()), newNode);
 
1045
                    offset++;
 
1046
                }
 
1047
                int index = ((NodeImpl)newParent).getIndexOfChild(newNode);
 
1048
                if(index > 0) {
 
1049
                    Node oldText = (Node)newParent.getChildNodes().item(index-1);
 
1050
                    if(checkPrettyText(oldText)) {
 
1051
                        /*
 
1052
                         * before
 
1053
                         *
 
1054
                         * <test name="test1">
 
1055
                         *     <a name="a1">
 
1056
                         *     <b name="b1">
 
1057
                         * <c name="c1">
 
1058
                         * </test>
 
1059
                         *
 
1060
                         * after
 
1061
                         *
 
1062
                         * <test name="test1">
 
1063
                         *     <a name="a1">
 
1064
                         *     <b name="b1">
 
1065
                         *     <c name="c1">
 
1066
                         * </test>
 
1067
                         */
 
1068
                        Text newText = createPrettyText(
 
1069
                                parentIndent+getIndentation());
 
1070
                        newParent.replaceChild(newText, oldText);
 
1071
                    } else {
 
1072
                        /*
 
1073
                         * before
 
1074
                         *
 
1075
                         * <test name="test1">
 
1076
                         *     <a name="a1">
 
1077
                         *     <b name="b1"><c name="c1">
 
1078
                         * </test>
 
1079
                         *
 
1080
                         * after
 
1081
                         *
 
1082
                         * <test name="test1">
 
1083
                         *     <a name="a1">
 
1084
                         *     <b name="b1">
 
1085
                         *     <c name="c1">
 
1086
                         * </test>
 
1087
                         */
 
1088
                        newParent.insertBefore(createPrettyText(
 
1089
                                parentIndent+getIndentation()), newNode);
 
1090
                        offset++;
 
1091
                    }
 
1092
                }
 
1093
                Node ref = null;
 
1094
                if((index+offset) < newParent.getChildNodes().getLength())
 
1095
                    ref = (Node)newParent.getChildNodes().item((index+offset));
 
1096
                if(ref != null) {
 
1097
                    if(!checkPrettyText(ref)) {
 
1098
                        /*
 
1099
                         * before
 
1100
                         *
 
1101
                         * <test name="test1">
 
1102
                         *     <a name="a1">
 
1103
                         *     <b name="b1">
 
1104
                         *     <c name="c1"><d name="d1">
 
1105
                         * </test>
 
1106
                         *
 
1107
                         * after
 
1108
                         *
 
1109
                         * <test name="test1">
 
1110
                         *     <a name="a1">
 
1111
                         *     <b name="b1">
 
1112
                         *     <c name="c1">
 
1113
                         *     <d name="d1">
 
1114
                         * </test>
 
1115
                         */
 
1116
                        newParent.insertBefore(createPrettyText(
 
1117
                                parentIndent+getIndentation()), ref);
 
1118
                    }
 
1119
                } else {
 
1120
                    /*
 
1121
                     * before
 
1122
                     *
 
1123
                     * <test name="test1">
 
1124
                     *     <a name="a1">
 
1125
                     *     <b name="b1">
 
1126
                     *     <c name="c1"></test>
 
1127
                     *
 
1128
                     * after
 
1129
                     *
 
1130
                     * <test name="test1">
 
1131
                     *     <a name="a1">
 
1132
                     *     <b name="b1">
 
1133
                     *     <c name="c1">
 
1134
                     * </test>
 
1135
                     */
 
1136
                    newParent.appendChild(createPrettyText(parentIndent));
 
1137
                }
 
1138
            }
 
1139
            
 
1140
            //recurse pretty print
 
1141
            doPrettyPrintRecursive(newNode, parentIndent, newParent);//for children of node
 
1142
        }
 
1143
    }
 
1144
    
 
1145
    /*
 
1146
     * initialized only once
 
1147
     *
 
1148
     */
 
1149
    private void initializeIndent(final Node n) {
 
1150
        String parentIndent = calculateNodeIndent(n);
 
1151
        List<Node> pathToRoot = new PathFromRootVisitor().findPath(getDocument(), n);
 
1152
        if(parentIndent.length() > 0 && pathToRoot.size()-2 > 0) {
 
1153
            //exclude Document and the root from path for indent step calculation
 
1154
            double step = Math.floor(parentIndent.length() / (double) (pathToRoot.size()-2));
 
1155
            StringBuffer sb = new StringBuffer();
 
1156
            for(int i=0;i<step;i++)
 
1157
                sb.append(" ");
 
1158
            String indentString = sb.toString();
 
1159
            if(indentString.length() > 0)
 
1160
                setIndentation(indentString);
 
1161
            else
 
1162
                setDefaultIndentation();
 
1163
        } else
 
1164
            setDefaultIndentation();
 
1165
    }
 
1166
    
 
1167
    private String calculateNodeIndent(final Node n) {
 
1168
        String indent = "";
 
1169
        Node parent = (Node) n.getParentNode();
 
1170
        if(parent != null) {
 
1171
            int index = parent.getIndexOfChild(n);
 
1172
            if(index > 0) {
 
1173
                Node txt = (Node) parent.getChildNodes().item(index-1);
 
1174
                if(checkPrettyText(txt)) {
 
1175
                    String wsValue = ((NodeImpl)txt).getTokens().get(0).getValue();
 
1176
                    int ndx = wsValue.lastIndexOf("\n");
 
1177
                    if(ndx != -1 && (ndx+1) < wsValue.length())
 
1178
                        indent = wsValue.substring(ndx+1);
 
1179
                }
 
1180
            }
 
1181
        }
 
1182
        return indent;
 
1183
    }
 
1184
    
 
1185
    private void doPrettyPrintRecursive(Node n, String indent, Node parent) {
 
1186
        if ((getStatus() != Status.PARSING) && isPretty()) {
 
1187
            if(isSimpleContent(n))
 
1188
                return; //skip if simpleContent
 
1189
            else if(n instanceof Element && isPretty(n)) {//adjust for pretty length difference
 
1190
                fixPrettyForCopiedNode(n, indent, parent);
 
1191
            } else {
 
1192
                List<Node> childList = new ArrayList<Node>();
 
1193
                List<Node> visitList = new ArrayList<Node>();
 
1194
                NodeList childs = n.getChildNodes();
 
1195
                for(int i=0;i<childs.getLength();i++) {
 
1196
                    childList.add((Node)childs.item(i));
 
1197
                    if(childs.item(i) instanceof Element)
 
1198
                        visitList.add((Node)childs.item(i));
 
1199
                }
 
1200
                String parentIndent = indent+getIndentation();
 
1201
                if(childList.size() > 0)
 
1202
                    n.appendChild(createPrettyText(parentIndent));
 
1203
                String childIndent = parentIndent+getIndentation();
 
1204
                for(int i=childList.size()-1;i>=0;i--) {
 
1205
                    Node ref = (Node)childList.get(i);
 
1206
                    Text postText = createPrettyText(childIndent);
 
1207
                    n.insertBefore(postText, ref);
 
1208
                }
 
1209
                childList.clear(); //no need to keep it beyond here
 
1210
                for(int i=0;i<visitList.size();i++) {
 
1211
                    doPrettyPrintRecursive((Node)visitList.get((i)), parentIndent, n);
 
1212
                }
 
1213
                visitList.clear(); //no need to keep it beyond here
 
1214
            }
 
1215
        }
 
1216
    }
 
1217
 
 
1218
    /*
 
1219
     * This function will fix the pretty text of nodes that are cut or copied and
 
1220
     * pasted to xdm tree
 
1221
     */
 
1222
    private void fixPrettyForCopiedNode(Node n, String indent, Node parent) {
 
1223
        NodeList childs = n.getChildNodes();
 
1224
        if(childs.getLength() == 0)
 
1225
            return;
 
1226
        Text nlastChild = (Text)childs.item(childs.getLength()-1);
 
1227
        String lc = ((NodeImpl)nlastChild).getTokens().get(0).getValue();
 
1228
        NodeImpl pfirstChild = (NodeImpl)parent.getChildNodes().item(0);
 
1229
        String fc = pfirstChild.getTokens().get(0).getValue();
 
1230
        
 
1231
        if(fc.length() == lc.length()) {//return if already pretty
 
1232
            return;
 
1233
        } else {
 
1234
            String parentIndent = indent+getIndentation();
 
1235
            String childIndent = parentIndent+getIndentation();
 
1236
            List<Node> childList = new ArrayList<Node>();
 
1237
            for(int i=0;i<childs.getLength();i++) {
 
1238
                childList.add((Node)childs.item(i));
 
1239
            }                    
 
1240
            for(int i=0;i<childList.size();i++) {
 
1241
                Node txt = (Node)n.getChildNodes().item(i);
 
1242
                if(checkPrettyText(txt)) {
 
1243
                    String newIndent = childIndent+getIndentation();
 
1244
                    if(i==0) {
 
1245
                        newIndent = childIndent; 
 
1246
                    } else if(i == childList.size()-1) {
 
1247
                        newIndent = parentIndent;
 
1248
                    }
 
1249
                    n.replaceChild(createPrettyText(newIndent), txt);
 
1250
                }
 
1251
            }
 
1252
            for(int i=0;i<childList.size();i++) {
 
1253
                fixPrettyForCopiedNode((Node)n.getChildNodes().item(i), 
 
1254
                        childIndent, n);
 
1255
            }
 
1256
            childList.clear(); //no need to keep it beyond here
 
1257
        }        
 
1258
    }
 
1259
    
 
1260
    private Text createPrettyText(String indent) {
 
1261
        String textChars = "\n"+indent;
 
1262
        Text txt = (Text)this.getDocument().createTextNode(textChars);
 
1263
        return txt;
 
1264
    }
 
1265
    
 
1266
    private void undoPrettyPrint(Node newParent, Node oldNode, Node oldParent) {
 
1267
        if ((getStatus() != Status.PARSING) && isPretty()) {
 
1268
            String parentIndent = calculateNodeIndent(oldParent);
 
1269
            int piLength = parentIndent != null ? parentIndent.length() : 0;
 
1270
            int index = ((NodeImpl)oldParent).getIndexOfChild(oldNode);
 
1271
            Node txtBefore = null;
 
1272
            if(index > 0) {//remove pre pretty print node
 
1273
                txtBefore = (Node)oldParent.getChildNodes().item(index-1);
 
1274
                if(checkPrettyText(txtBefore) &&
 
1275
                        piLength <= getLength((Text) txtBefore)) {
 
1276
                                        /*
 
1277
                                         * before
 
1278
                                         *
 
1279
                                         * <test name="test1">
 
1280
                                         *     <a name="a1">
 
1281
                                         *     <b name="b1">
 
1282
                                         *     <c name="c1">
 
1283
                                         * </test>
 
1284
                                         *
 
1285
                                         * after
 
1286
                                         *
 
1287
                                         * <test name="test1">
 
1288
                                         *     <a name="a1">
 
1289
                                         *     <b name="b1"><c name="c1">
 
1290
                                         * </test>
 
1291
                                         */
 
1292
                    newParent.removeChild(txtBefore);
 
1293
                }
 
1294
            }
 
1295
            if(newParent.getChildNodes().getLength() == 2 &&
 
1296
                    index+1 < oldParent.getChildNodes().getLength()) {//remove post pretty print node
 
1297
                Node txtAfter = (Node)oldParent.getChildNodes().item(index+1);
 
1298
                if(checkPrettyText(txtAfter) &&
 
1299
                        piLength <= getLength((Text) txtAfter)) {
 
1300
                                        /*
 
1301
                                         * before
 
1302
                                         *
 
1303
                                         * <test name="test1">
 
1304
                                         *     <a name="a1">
 
1305
                                         *     <b name="b1"><c name="c1">
 
1306
                                         * </test>
 
1307
                                         *
 
1308
                                         * after
 
1309
                                         *
 
1310
                                         * <test name="test1">
 
1311
                                         *     <a name="a1">
 
1312
                                         *     <b name="b1"><c name="c1"></test>
 
1313
                                         */
 
1314
                    newParent.removeChild(txtAfter);
 
1315
                }
 
1316
            }
 
1317
        }
 
1318
    }
 
1319
    
 
1320
    private int getLength(Text n) {
 
1321
        int len = 0;
 
1322
        for(Token token:((NodeImpl)n).getTokens())
 
1323
            len += token.getValue().length();
 
1324
        return len;
 
1325
    }
 
1326
    
 
1327
    private boolean checkPrettyText(Node txt) {
 
1328
        if (txt instanceof Text) {
 
1329
            if ((((NodeImpl)txt).getTokens().size() == 1) &&
 
1330
                    isWhitespaceOnly(((NodeImpl)txt).getTokens().get(0).getValue())) {
 
1331
                return true;
 
1332
            }
 
1333
        }
 
1334
        return false;
 
1335
    }
 
1336
    
 
1337
    private boolean isPossibleWhiteSpace(String text) {
 
1338
        return text.length() > 0 &&
 
1339
                Character.isWhitespace(text.charAt(0)) &&
 
1340
                Character.isWhitespace(text.charAt(text.length()-1));
 
1341
    }
 
1342
    
 
1343
    private boolean isWhitespaceOnly(String tn) {
 
1344
        return isPossibleWhiteSpace(tn) &&
 
1345
                tn.trim().length() == 0;
 
1346
    }
 
1347
    
 
1348
    public ElementIdentity getElementIdentity() {
 
1349
        return eID;
 
1350
        
 
1351
        
 
1352
    }
 
1353
    
 
1354
    public void setElementIdentity(ElementIdentity eID) {
 
1355
        this.eID = eID;
 
1356
    }
 
1357
    
 
1358
    private boolean isSimpleContent(Node newParent) {
 
1359
        NodeList childs = newParent.getChildNodes();
 
1360
        for(int i=0;i<childs.getLength();i++)
 
1361
            if(!(childs.item(i) instanceof Text))
 
1362
                return false;
 
1363
        return true;
 
1364
    }
 
1365
    
 
1366
    private boolean isPretty(Node newParent) {
 
1367
        return isPretty(newParent, null);
 
1368
    }
 
1369
    
 
1370
    private boolean isPretty(Node newParent, Node newNode) {
 
1371
        boolean parentPretty = false;
 
1372
        NodeList childs = newParent.getChildNodes();
 
1373
        int len = childs.getLength();
 
1374
        
 
1375
                /*
 
1376
                 * <test name="test1"></test>  parentPretty = true
 
1377
                 *
 
1378
                 * <test name="test1"> parentPretty = true
 
1379
                 * </test>
 
1380
                 *
 
1381
                 * <test name="test1">  parentPretty = true
 
1382
                 *     <c name="c1">
 
1383
                 * </test>
 
1384
                 *
 
1385
                 * <test name="test1"><c name="c1">  parentPretty = false
 
1386
                 * </test>
 
1387
                 */
 
1388
        if(len == 0)
 
1389
            parentPretty = true;
 
1390
        else if(len == 1 && childs.item(0) instanceof Text)
 
1391
            parentPretty = true;
 
1392
        else if(len > 2 &&
 
1393
                checkPrettyText((Node) childs.item(0)) && checkPrettyText((Node) childs.item(len-1)))
 
1394
            parentPretty = true;
 
1395
        
 
1396
        if(!parentPretty)
 
1397
            return false;
 
1398
        
 
1399
        if(newNode != null) {
 
1400
            //now check newNode pretty
 
1401
            Node preText = null;
 
1402
            Node postText = null;
 
1403
            int index = ((NodeImpl)newParent).getIndexOfChild(newNode);
 
1404
            if(index > 0)
 
1405
                preText = (Node)newParent.getChildNodes().item(index-1);
 
1406
            if((index+1) < newParent.getChildNodes().getLength())
 
1407
                postText = (Node)newParent.getChildNodes().item((index+1));
 
1408
            
 
1409
                        /*
 
1410
                         * <test name="test1">
 
1411
                         *     <a name="a1"/>
 
1412
                         *     <b name="b1"/>
 
1413
                         *     <c name="c1"/>  'c' pretty = true
 
1414
                         * </test>
 
1415
                         *
 
1416
                         * <test name="test1">
 
1417
                         *     <a name="a1"/>
 
1418
                         *     <b name="b1"/><c name="c1"/>  'c' pretty = false
 
1419
                         * </test>
 
1420
                         *
 
1421
                         */
 
1422
            if(checkPrettyText(preText) && checkPrettyText(postText))
 
1423
                return true;
 
1424
        } else
 
1425
            return parentPretty;
 
1426
        
 
1427
        return false;
 
1428
    }
 
1429
 
 
1430
    /**
 
1431
     * Set/get mapping of QName-valued attributes by element.
 
1432
     * Key of the mapping is QName of the element.
 
1433
     * Value of the mapping is list QName's of the attributes.
 
1434
     * If the mapping is set, it will be used to identify the 
 
1435
     * attribute values affected by namespace prefix refactoring 
 
1436
     * during namespace consolidation.  If not set, namespace consolidation
 
1437
     * would skip prefix rename refactoring case.
 
1438
     */
 
1439
    public void setQNameValuedAttributes(Map<QName,List<QName>> attrsByElement) {
 
1440
        qnameValuedAttributesByElementMap = attrsByElement;
 
1441
    }
 
1442
    public Map<QName,List<QName>> getQNameValuedAttributes() {
 
1443
        return qnameValuedAttributesByElementMap;
 
1444
    }
 
1445
    
 
1446
    /**
 
1447
     * The xml syntax parser
 
1448
     */
 
1449
    private XMLSyntaxParser parser;
 
1450
    
 
1451
    /**
 
1452
     * The current stable document represented by the model
 
1453
     */
 
1454
    private Document currentDocument;
 
1455
    
 
1456
    /**
 
1457
     * Property change support
 
1458
     */
 
1459
    private PropertyChangeSupport pcs;
 
1460
    
 
1461
    /**
 
1462
     * The underlying model source
 
1463
     */
 
1464
    private ModelSource source;
 
1465
    
 
1466
    /**
 
1467
     * Current status of the model
 
1468
     */
 
1469
    private Status status;
 
1470
    
 
1471
    private boolean pretty = false;
 
1472
    
 
1473
    /**
 
1474
     * Undoable edit support
 
1475
     */
 
1476
    private UndoableEditSupport ues;
 
1477
    
 
1478
    /**
 
1479
     * whether to fire undo events
 
1480
     */
 
1481
    private boolean fireUndoEvents = true;
 
1482
    
 
1483
    /**
 
1484
     * The names of property change events fired
 
1485
     */
 
1486
    /**
 
1487
     * Indicates node modified
 
1488
     */
 
1489
    public static final String PROP_MODIFIED = "modified";
 
1490
    /**
 
1491
     * Indicates node deleted
 
1492
     */
 
1493
    public static final String PROP_DELETED = "deleted";
 
1494
    /**
 
1495
     * Indicates node added
 
1496
     */
 
1497
    public static final String PROP_ADDED = "added";
 
1498
    
 
1499
    public static final String DEFAULT_INDENT = "    ";
 
1500
    
 
1501
    /**
 
1502
     * current node count
 
1503
     */
 
1504
    private int nodeCount = 0;
 
1505
    
 
1506
    private ElementIdentity eID;
 
1507
    
 
1508
    private String currentIndent = "";
 
1509
    
 
1510
    private boolean indentInitialized = false;
 
1511
    
 
1512
    private Map<QName,List<QName>> qnameValuedAttributesByElementMap;
 
1513
}