Source Code Cross Referenced for XDMModel.java in  » IDE-Netbeans » xml » org » netbeans » modules » xml » xdm » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » IDE Netbeans » xml » org.netbeans.modules.xml.xdm 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003:         *
0004:         * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005:         *
0006:         * The contents of this file are subject to the terms of either the GNU
0007:         * General Public License Version 2 only ("GPL") or the Common
0008:         * Development and Distribution License("CDDL") (collectively, the
0009:         * "License"). You may not use this file except in compliance with the
0010:         * License. You can obtain a copy of the License at
0011:         * http://www.netbeans.org/cddl-gplv2.html
0012:         * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013:         * specific language governing permissions and limitations under the
0014:         * License.  When distributing the software, include this License Header
0015:         * Notice in each file and include the License file at
0016:         * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
0017:         * particular file as subject to the "Classpath" exception as provided
0018:         * by Sun in the GPL Version 2 section of the License file that
0019:         * accompanied this code. If applicable, add the following below the
0020:         * License Header, with the fields enclosed by brackets [] replaced by
0021:         * your own identifying information:
0022:         * "Portions Copyrighted [year] [name of copyright owner]"
0023:         *
0024:         * Contributor(s):
0025:         *
0026:         * The Original Software is NetBeans. The Initial Developer of the Original
0027:         * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028:         * Microsystems, Inc. All Rights Reserved.
0029:         *
0030:         * If you wish your version of this file to be governed by only the CDDL
0031:         * or only the GPL Version 2, indicate your decision by adding
0032:         * "[Contributor] elects to include this software in this distribution
0033:         * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034:         * single choice of license, a recipient has the option to distribute
0035:         * your version of this file under either the CDDL, the GPL Version 2 or
0036:         * to extend the choice of license to its licensees as provided above.
0037:         * However, if you add GPL Version 2 code and therefore, elected the GPL
0038:         * Version 2 license, then the option applies only if the new code is
0039:         * made subject to such option by the copyright holder.
0040:         */
0041:        package org.netbeans.modules.xml.xdm;
0042:
0043:        import java.beans.PropertyChangeListener;
0044:        import java.beans.PropertyChangeSupport;
0045:        import java.io.IOException;
0046:        import java.util.ArrayList;
0047:        import java.util.Collection;
0048:        import java.util.List;
0049:        import java.util.Map;
0050:        import javax.swing.event.UndoableEditEvent;
0051:        import javax.swing.event.UndoableEditListener;
0052:        import javax.swing.text.BadLocationException;
0053:        import javax.swing.undo.CompoundEdit;
0054:        import javax.swing.undo.UndoableEdit;
0055:        import javax.swing.undo.UndoableEditSupport;
0056:        import javax.xml.XMLConstants;
0057:        import javax.xml.namespace.QName;
0058:        import org.netbeans.api.lexer.Language;
0059:        import org.netbeans.api.xml.lexer.XMLTokenId;
0060:        import org.netbeans.editor.BaseDocument;
0061:        import org.netbeans.modules.xml.xam.ModelSource;
0062:        import org.netbeans.modules.xml.xam.dom.ElementIdentity;
0063:        import org.netbeans.modules.xml.xdm.diff.DiffFinder;
0064:        import org.netbeans.modules.xml.xdm.diff.XDMTreeDiff;
0065:        import org.netbeans.modules.xml.xdm.diff.Difference;
0066:        import org.netbeans.modules.xml.xdm.diff.Add;
0067:        import org.netbeans.modules.xml.xdm.diff.Change;
0068:        import org.netbeans.modules.xml.xdm.diff.Delete;
0069:        import org.netbeans.modules.xml.xdm.diff.DefaultElementIdentity;
0070:        import org.netbeans.modules.xml.xdm.diff.MergeDiff;
0071:        import org.netbeans.modules.xml.xdm.diff.NodeIdDiffFinder;
0072:        import org.netbeans.modules.xml.xdm.diff.NodeInfo;
0073:        import org.netbeans.modules.xml.xdm.diff.SyncPreparation;
0074:        import org.netbeans.modules.xml.xdm.nodes.Attribute;
0075:        import org.netbeans.modules.xml.xdm.nodes.Document;
0076:        import org.netbeans.modules.xml.xdm.nodes.Element;
0077:        import org.netbeans.modules.xml.xdm.nodes.Node;
0078:        import org.netbeans.modules.xml.xdm.nodes.NodeImpl;
0079:        import org.netbeans.modules.xml.xdm.nodes.Text;
0080:        import org.netbeans.modules.xml.xdm.nodes.Token;
0081:        import org.netbeans.modules.xml.xdm.nodes.XMLSyntaxParser;
0082:        import org.netbeans.modules.xml.xdm.visitor.FindVisitor;
0083:        import org.netbeans.modules.xml.xdm.visitor.FlushVisitor;
0084:        import org.netbeans.modules.xml.xdm.visitor.NamespaceRefactorVisitor;
0085:        import org.netbeans.modules.xml.xdm.visitor.PathFromRootVisitor;
0086:        import org.netbeans.modules.xml.xdm.visitor.Utils;
0087:        import org.w3c.dom.NamedNodeMap;
0088:        import org.w3c.dom.NodeList;
0089:
0090:        /**
0091:         */
0092:        public class XDMModel {
0093:
0094:            /**
0095:             * @param ms requires an instance of org.netbeans.editor.BaseDocument to be
0096:             * available in the lookup;
0097:             */
0098:            public XDMModel(ModelSource ms) {
0099:                source = ms;
0100:                assert getSwingDocument() != null;
0101:                ues = new UndoableEditSupport(this );
0102:                pcs = new PropertyChangeSupport(this );
0103:                parser = new XMLSyntaxParser();
0104:                setStatus(Status.UNPARSED);
0105:
0106:                //establish a default element identification mechanism
0107:                //domain models should override this by invoking "setElementIdentity"
0108:                ElementIdentity eID = createElementIdentity();
0109:                setElementIdentity(eID);
0110:            }
0111:
0112:            public String getIndentation() {
0113:                return currentIndent;
0114:            }
0115:
0116:            public void setIndentation(String indent) {
0117:                currentIndent = indent;
0118:                indentInitialized = true;
0119:            }
0120:
0121:            private void setDefaultIndentation() {
0122:                currentIndent = DEFAULT_INDENT;
0123:            }
0124:
0125:            /*
0126:             * override this method if domain model wants to identify elements
0127:             * using a different mechanism than this default one.
0128:             *
0129:             */
0130:            private ElementIdentity createElementIdentity() {
0131:                //Establish DOM element identities
0132:                ElementIdentity eID = new DefaultElementIdentity();
0133:                //Following values are suitable for Schema and WSDL documents
0134:                //these default values can be reset by eID.reset() call
0135:                eID.addIdentifier("id");
0136:                eID.addIdentifier("name");
0137:                eID.addIdentifier("ref");
0138:                return eID;
0139:            }
0140:
0141:            /**
0142:             * This api flushes the changes made to the model to the underlying document.
0143:             */
0144:            public synchronized void flush() {
0145:                flushDocument(getDocument());
0146:            }
0147:
0148:            /**
0149:             * This api syncs the model with the underlying swing document.
0150:             * If its the first time sync is called, swing document is parsed and model
0151:             * is initialized. Otherwise the changes made to swing document are applied
0152:             * to the model using DiffMerger.
0153:             */
0154:            public synchronized void sync() throws IOException {
0155:                if (preparation == null) {
0156:                    prepareSync();
0157:                }
0158:                finishSync();
0159:            }
0160:
0161:            public synchronized void prepareSync() {
0162:                Status oldStat = getStatus();
0163:                try {
0164:                    setStatus(Status.PARSING); // to access in case old broken tree            
0165:                    //must set the language for XML lexer to work.
0166:                    getSwingDocument().putProperty(Language.class,
0167:                            XMLTokenId.language());
0168:                    Document newDoc = parser.parse(getSwingDocument());
0169:                    Document oldDoc = getCurrentDocument();
0170:                    if (oldDoc == null) {
0171:                        preparation = new SyncPreparation(newDoc);
0172:                    } else {
0173:                        newDoc.assignNodeIdRecursively();
0174:                        XDMTreeDiff treeDiff = new XDMTreeDiff(eID);
0175:                        List<Difference> preparedDiffs = treeDiff.performDiff(
0176:                                this , newDoc);
0177:                        preparation = new SyncPreparation(oldDoc, preparedDiffs);
0178:                    }
0179:                } catch (BadLocationException ble) {
0180:                    preparation = new SyncPreparation(ble);
0181:                } catch (IllegalArgumentException iae) {
0182:                    preparation = new SyncPreparation(iae);
0183:                } catch (IOException ioe) {
0184:                    preparation = new SyncPreparation(ioe);
0185:                } finally {
0186:                    setStatus(oldStat); // we are not mutating yet, so alway restore
0187:                }
0188:            }
0189:
0190:            private SyncPreparation preparation = null;
0191:
0192:            private void finishSync() throws IOException {
0193:                if (preparation == null) {
0194:                    return; // unprepared or other thread has stealth the sync
0195:                }
0196:
0197:                if (preparation.hasErrors()) {
0198:                    IOException error = preparation.getError();
0199:                    preparation = null;
0200:                    setStatus(Status.BROKEN);
0201:                    throw error;
0202:                }
0203:
0204:                Status savedStatus = getStatus();
0205:                setStatus(Status.PARSING);
0206:                Document oldDoc = getCurrentDocument();
0207:                try {
0208:                    if (preparation.getNewDocument() != null) {
0209:                        Document newDoc = preparation.getNewDocument();
0210:                        newDoc.addedToTree(this );
0211:                        setDocument(newDoc);
0212:                    } else {
0213:                        assert preparation.getOldDocument() != null : "Invalid preparation oldDoc is null";
0214:                        if (oldDoc != preparation.getOldDocument()) {
0215:                            // other thread has completed the sync before me
0216:                            setStatus(savedStatus);
0217:                            return;
0218:                        }
0219:                        List<Difference> diffs = preparation.getDifferences();
0220:                        mergeDiff(diffs);
0221:                        //diffs = DiffFinder.filterWhitespace(diffs);
0222:                        fireDiffEvents(diffs);
0223:                        if (getCurrentDocument() != oldDoc) {
0224:                            fireUndoableEditEvent(getCurrentDocument(), oldDoc);
0225:                        }
0226:                    }
0227:                    setStatus(Status.STABLE);
0228:                } catch (IllegalArgumentException iae) {
0229:                    if (getStatus() != Status.STABLE) {
0230:                        IOException ioe = new IOException();
0231:                        ioe.initCause(iae);
0232:                        throw ioe;
0233:                    } else {
0234:                        // XAM will review the mutation and veto by it wrapped IOException
0235:                        if (iae.getCause() instanceof  IOException) {
0236:                            setStatus(Status.BROKEN);
0237:                            throw (IOException) iae.getCause();
0238:                        } else {
0239:                            throw iae;
0240:                        }
0241:                    }
0242:                } finally {
0243:                    if (getStatus() != Status.STABLE) {
0244:                        setStatus(Status.BROKEN);
0245:                        setDocument(oldDoc);
0246:                    }
0247:                    preparation = null;
0248:                }
0249:            }
0250:
0251:            public void mergeDiff(List<Difference> diffs) {
0252:                setStatus(Status.PARSING);
0253:                new MergeDiff().merge(this , diffs);
0254:                // exception in event firing should not put tree out-of-sync with buffer
0255:                setStatus(Status.STABLE);
0256:            }
0257:
0258:            private void fireDiffEvents(final List<Difference> deList) {
0259:                for (Difference de : deList) {
0260:                    NodeInfo.NodeType nodeType = de.getNodeType();
0261:                    //	    if ( nodeType == NodeInfo.NodeType.WHITE_SPACE ) continue;//skip if WS
0262:                    if (de instanceof  Add) {
0263:                        NodeInfo newNodeInfo = ((Add) de).getNewNodeInfo();
0264:                        assert newNodeInfo != null;
0265:                        pcs.firePropertyChange(PROP_ADDED, null, newNodeInfo);
0266:                    } else if (de instanceof  Delete) {
0267:                        NodeInfo OldNodeInfo = ((Delete) de).getOldNodeInfo();
0268:                        assert OldNodeInfo != null;
0269:                        pcs.firePropertyChange(PROP_DELETED, OldNodeInfo, null);
0270:                    } else if (de instanceof  Change) {
0271:                        NodeInfo oldNodeInfo = ((Change) de).getOldNodeInfo();
0272:                        assert oldNodeInfo != null;
0273:
0274:                        NodeInfo newNodeInfo = ((Change) de).getNewNodeInfo();
0275:                        assert newNodeInfo != null;
0276:
0277:                        //fire delete and add events for position change of element/text
0278:                        if (((Change) de).isPositionChanged()) {
0279:                            pcs.firePropertyChange(PROP_DELETED, oldNodeInfo,
0280:                                    null);
0281:                            pcs.firePropertyChange(PROP_ADDED, null,
0282:                                    newNodeInfo);
0283:                        } else if (de.getNodeType() == NodeInfo.NodeType.TEXT) { //text change only
0284:                            pcs.firePropertyChange(PROP_MODIFIED, oldNodeInfo,
0285:                                    newNodeInfo);
0286:                        } else if (de.getNodeType() == NodeInfo.NodeType.ELEMENT) {
0287:                            List<Node> path1 = new ArrayList<Node>(oldNodeInfo
0288:                                    .getOriginalAncestors());
0289:                            path1.add(0, oldNodeInfo.getNode());
0290:                            List<Node> path2 = new ArrayList<Node>(newNodeInfo
0291:                                    .getNewAncestors());
0292:                            path2.add(0, newNodeInfo.getNode());
0293:                            //fire attribute change events
0294:                            List<Change.AttributeDiff> attrChanges = ((Change) de)
0295:                                    .getAttrChanges();
0296:                            for (Change.AttributeDiff attrDiff : attrChanges) {
0297:                                Node oldAttr = attrDiff.getOldAttribute();
0298:                                Node newAttr = attrDiff.getNewAttribute();
0299:                                if (attrDiff instanceof  Change.AttributeAdd) {
0300:                                    assert newAttr != null;
0301:                                    pcs.firePropertyChange(PROP_ADDED, null,
0302:                                            new NodeInfo(newAttr, -1, path1,
0303:                                                    path2));
0304:                                } else if (attrDiff instanceof  Change.AttributeDelete) {
0305:                                    assert oldAttr != null;
0306:                                    pcs.firePropertyChange(PROP_DELETED,
0307:                                            new NodeInfo(oldAttr, -1, path1,
0308:                                                    path2), null);
0309:                                } else if (attrDiff instanceof  Change.AttributeChange) {
0310:                                    assert oldAttr != null;
0311:                                    assert newAttr != null;
0312:                                    NodeInfo old = new NodeInfo(oldAttr, -1,
0313:                                            path1, path2);
0314:                                    NodeInfo now = new NodeInfo(newAttr, -1,
0315:                                            path1, path2);
0316:                                    pcs.firePropertyChange(PROP_MODIFIED, old,
0317:                                            now);
0318:                                }
0319:                            }
0320:                        }
0321:                    }
0322:                }
0323:            }
0324:
0325:            private interface Updater {
0326:                void update(Node parent, Node oldNode, Node newNode);
0327:            }
0328:
0329:            private List<Node> getPathToRoot(Node node, Document root) {
0330:                PathFromRootVisitor pathVisitor = new PathFromRootVisitor();
0331:                List<Node> path = pathVisitor.findPath(root, node);
0332:                if (path == null || path.isEmpty()) {
0333:                    throw new IllegalArgumentException(
0334:                            "old node must be in the tree");
0335:                }
0336:                return path;
0337:            }
0338:
0339:            private static String classifyMutationType(Node oldNode,
0340:                    Node newNode) {
0341:                if (newNode == null && oldNode == null || newNode != null
0342:                        && oldNode != null) {
0343:                    return PROP_MODIFIED;
0344:                } else if (newNode != null) {
0345:                    return PROP_ADDED;
0346:                } else {
0347:                    return PROP_DELETED;
0348:                }
0349:            }
0350:
0351:            private enum MutationType {
0352:                CHILDREN, ATTRIBUTE, BOTH
0353:            }
0354:
0355:            private List<Node> mutate(Node parent, Node oldNode, Node newNode,
0356:                    Updater updater) {
0357:                return mutate(parent, oldNode, newNode, updater, null);
0358:            }
0359:
0360:            private List<Node> mutate(Node parent, Node oldNode, Node newNode,
0361:                    Updater updater, MutationType type) {
0362:                checkStableOrParsingState();
0363:                if (newNode != null)
0364:                    checkNodeInTree(newNode);
0365:
0366:                Document currentDocument = getDocument();
0367:                List<Node> ancestors;
0368:                if (parent == null) {
0369:                    assert (oldNode != null);
0370:                    ancestors = getPathToRoot(oldNode, currentDocument);
0371:                    oldNode = ancestors.remove(0);
0372:                } else {
0373:                    if (oldNode != null) {
0374:                        assert parent.getIndexOfChild(oldNode) > -1;
0375:                        ancestors = getPathToRoot(oldNode, currentDocument);
0376:                        assert oldNode.getId() == ancestors.get(0).getId();
0377:                        oldNode = ancestors.remove(0);
0378:                        assert parent.getId() == ancestors.get(0).getId();
0379:                    } else {
0380:                        ancestors = getPathToRoot(parent, currentDocument);
0381:                    }
0382:                }
0383:
0384:                final Node oldParent = ancestors.remove(0);
0385:                Node newParent;
0386:                if (type == null) {
0387:                    if (oldNode instanceof  Attribute
0388:                            || newNode instanceof  Attribute
0389:                            || (oldNode == null && newNode == null)) {
0390:                        type = MutationType.ATTRIBUTE;
0391:                    } else if (ancestors.size() == 1) {
0392:                        assert (oldParent instanceof  Element);
0393:                        //might have to add namespace declaration to root
0394:                        type = MutationType.BOTH;
0395:                    } else {
0396:                        type = MutationType.CHILDREN;
0397:                    }
0398:                }
0399:                switch (type) {
0400:                case ATTRIBUTE:
0401:                    newParent = (Node) oldParent.clone(true, true, false);
0402:                    break;
0403:                case CHILDREN:
0404:                    newParent = (Node) oldParent.clone(true, false, true);
0405:                    break;
0406:                default:
0407:                    newParent = (Node) oldParent.clone(true, true, true);
0408:                }
0409:
0410:                if (oldNode != null && oldNode.getNodeType() != Node.TEXT_NODE
0411:                        && newNode == null) { // pure remove
0412:                    undoPrettyPrint(newParent, oldNode, oldParent);
0413:                }
0414:                updater.update(newParent, oldNode, newNode);
0415:                if (oldNode == null && newNode != null
0416:                        && newNode.getNodeType() != Node.TEXT_NODE) { // pure add
0417:                    doPrettyPrint(newParent, newNode, oldParent);
0418:                }
0419:
0420:                List<Node> newAncestors = updateAncestors(ancestors, newParent,
0421:                        oldParent);
0422:                if (getStatus() != Status.PARSING && newNode instanceof  Element) {
0423:                    consolidateNamespaces(newAncestors, newParent,
0424:                            (Element) newNode);
0425:                }
0426:                Document d = (Document) (!newAncestors.isEmpty() ? newAncestors
0427:                        .get(newAncestors.size() - 1) : newParent);
0428:                d.addedToTree(this );
0429:                setDocument(d);
0430:                ancestors.add(0, oldParent);
0431:                newAncestors.add(0, newParent);
0432:                if (getStatus() != Status.PARSING) { // not merging
0433:                    fireUndoableEditEvent(d, currentDocument);
0434:                    String mutationType = classifyMutationType(oldNode, newNode);
0435:                    //TODO seems missing delete/change; also, who are listening to these xdm mutation events
0436:                    NodeInfo newNodeInfo = new NodeInfo(newNode, -1, ancestors,
0437:                            newAncestors);
0438:                    pcs.firePropertyChange(mutationType, null, newNodeInfo);
0439:                }
0440:                return newAncestors;
0441:            }
0442:
0443:            private void consolidateNamespaces(List<Node> ancestors,
0444:                    Node parent, Element newNode) {
0445:                if (parent instanceof  Document)
0446:                    return; // no actions if newNode is root itself
0447:                assert ancestors.size() > 0;
0448:                Element root = (Element) (ancestors.size() == 1 ? parent
0449:                        : ancestors.get(ancestors.size() - 2));
0450:                List<Node> parentAndAncestors = new ArrayList(ancestors);
0451:                parentAndAncestors.add(0, parent);
0452:                consolidateAttributePrefix(parentAndAncestors, newNode);
0453:                NamedNodeMap nnm = newNode.getAttributes();
0454:                for (int i = 0; i < nnm.getLength(); i++) {
0455:                    if (nnm.item(i) instanceof  Attribute) {
0456:                        Attribute attr = (Attribute) nnm.item(i);
0457:                        consolidateNamespace(root, parentAndAncestors, newNode,
0458:                                attr);
0459:                    }
0460:                }
0461:
0462:                // use parent node prefix
0463:                String parentPrefix = parent.getPrefix();
0464:                String parentNS = NodeImpl.lookupNamespace(parent, ancestors);
0465:                if (parentNS != null
0466:                        && !parentNS.equals(XMLConstants.NULL_NS_URI)) {
0467:                    new NamespaceRefactorVisitor(this ).refactor(newNode,
0468:                            parentNS, parentPrefix, parentAndAncestors);
0469:                }
0470:            }
0471:
0472:            private void consolidateAttributePrefix(
0473:                    List<Node> parentAndAncestors, Element newNode) {
0474:                NamedNodeMap nnm = newNode.getAttributes();
0475:                for (int i = 0; i < nnm.getLength(); i++) {
0476:                    if (!(nnm.item(i) instanceof  Attribute))
0477:                        continue;
0478:                    Attribute attr = (Attribute) nnm.item(i);
0479:                    String prefix = attr.getPrefix();
0480:                    if (prefix != null && !attr.isXmlnsAttribute()) {
0481:                        String namespace = newNode.lookupNamespaceURI(prefix);
0482:                        if (namespace == null)
0483:                            continue;
0484:                        prefix = NodeImpl.lookupPrefix(namespace,
0485:                                parentAndAncestors);
0486:                        if (prefix != null) {
0487:                            attr.setPrefix(prefix);
0488:                        }
0489:                    }
0490:                }
0491:            }
0492:
0493:            /**
0494:             * Consolidate new node top-leveled namespaces with parent's.
0495:             * Note: this assume #consolidateAttributePrefix has been called
0496:             */
0497:            private void consolidateNamespace(Element root,
0498:                    List<Node> parentAndAncestors, Element newNode,
0499:                    Attribute attr) {
0500:                if (attr.isXmlnsAttribute()) {
0501:                    String prefix = attr.getLocalName();
0502:                    if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) {
0503:                        prefix = XMLConstants.DEFAULT_NS_PREFIX;
0504:                    }
0505:                    String namespace = attr.getValue();
0506:                    assert (namespace != null);
0507:
0508:                    Node parent = parentAndAncestors.get(0);
0509:                    String parentNS = NodeImpl.lookupNamespace(parent,
0510:                            parentAndAncestors);
0511:
0512:                    String existingNS = NodeImpl.lookupNamespace(prefix,
0513:                            parentAndAncestors);
0514:                    String existingPrefix = NodeImpl.lookupPrefix(namespace,
0515:                            parentAndAncestors);
0516:
0517:                    // 1. prefix is free (existingNS == null) and namespace is never declared (existingPrefix == null)
0518:                    // 2. prefix is used and for the same namespace
0519:                    // 3. namespace is declared by different prefix
0520:                    // 4. prefix is used and for different namespace
0521:
0522:                    if (existingNS == null && existingPrefix == null) { // case 1.
0523:                        newNode.removeAttributeNode(attr);
0524:                        root.appendAttribute(attr);
0525:                    } else if (namespace.equals(existingNS)
0526:                            && prefix.equals(existingPrefix)) { // case 2
0527:                        assert prefix.equals(existingPrefix) : "prefix='"
0528:                                + prefix + "' existingPrefix='"
0529:                                + existingPrefix + "'";
0530:                        newNode.removeAttributeNode(attr);
0531:                    } else if (existingPrefix != null) { // case 3.
0532:                        // skip attribute redeclaring namespace of parent element
0533:                        // we will refactor to parent prefix after processing all xmlns-attributes
0534:                        if (!namespace.equals(parentNS)) {
0535:                            new NamespaceRefactorVisitor(this ).refactor(
0536:                                    newNode, namespace, existingPrefix,
0537:                                    parentAndAncestors);
0538:                        }
0539:                    } else { // existingNS != null && existingPrefix == null
0540:                        // case 4 just leave prefix as overriding with different namespace
0541:                    }
0542:                }
0543:            }
0544:
0545:            /**
0546:             * This api replaces given old node with given new node.
0547:             * The old node passed must be in tree, the new node must not be in tree,
0548:             * and new node must be clone of old node.
0549:             * @param oldValue The old node to be replaced.
0550:             * @param newValue The new node.
0551:             * @return The new parent
0552:             */
0553:            public synchronized List<Node> modify(Node oldValue, Node newValue) {
0554:                if (oldValue.getId() != newValue.getId()) {
0555:                    throw new IllegalArgumentException(
0556:                            "newValue must be a clone of oldValue");
0557:                }
0558:
0559:                if (oldValue instanceof  Document) {
0560:                    assert newValue instanceof  Document;
0561:                    Document oldDoc = (Document) oldValue;
0562:                    Document newDoc = (Document) newValue;
0563:                    newDoc.addedToTree(this );
0564:                    setDocument(newDoc);
0565:                    if (getStatus() != Status.PARSING) { // not merging
0566:                        fireUndoableEditEvent(oldDoc, currentDocument);
0567:                        String mutationType = classifyMutationType(oldDoc,
0568:                                newDoc);
0569:                        ArrayList<Node> ancestors = new ArrayList<Node>();
0570:                        NodeInfo oldNodeInfo = new NodeInfo(oldDoc, -1,
0571:                                ancestors, ancestors);
0572:                        NodeInfo newNodeInfo = new NodeInfo(newDoc, -1,
0573:                                ancestors, ancestors);
0574:                        pcs.firePropertyChange(mutationType, oldNodeInfo,
0575:                                newNodeInfo);
0576:                    }
0577:                    return new ArrayList<Node>();
0578:                }
0579:
0580:                Updater modifier = new Updater() {
0581:                    public void update(Node newParent, Node oldNode,
0582:                            Node newNode) {
0583:                        if (oldNode instanceof  Attribute) {
0584:                            ((Element) newParent).replaceAttribute(
0585:                                    (Attribute) newNode, (Attribute) oldNode);
0586:                        } else {
0587:                            newParent.replaceChild(newNode, oldNode);
0588:                        }
0589:                    }
0590:                };
0591:                return mutate(null, oldValue, newValue, modifier);
0592:            }
0593:
0594:            /**
0595:             * This api adds given node to given parent at given index.
0596:             * The added node will be part of childnodes of the parent,
0597:             * and its index will be the given index. If the given index
0598:             * is out of the parents childnodes range, the node will be
0599:             * appended.
0600:             * @param parent The parent node to which the node is to be added.
0601:             * @param node The node which is to be added.
0602:             * @param offset The index at which the node is to be added.
0603:             * @return The parent node resulted by addition of this node
0604:             */
0605:            public synchronized List<Node> add(Node parent, Node node,
0606:                    final int offset) {
0607:                if (offset < 0)
0608:                    throw new IndexOutOfBoundsException();
0609:                Updater adder = new Updater() {
0610:                    public void update(Node newParent, Node oldNode,
0611:                            Node newNode) {
0612:                        if (newParent instanceof  Element
0613:                                && newNode instanceof  Attribute) {
0614:                            Element newElement = (Element) newParent;
0615:                            if (offset > newElement.getAttributes().getLength())
0616:                                throw new IndexOutOfBoundsException();
0617:                            newElement
0618:                                    .addAttribute((Attribute) newNode, offset);
0619:                        } else {
0620:                            //reset id, since adding root element
0621:                            if (newParent instanceof  Document
0622:                                    && newNode instanceof  Element)
0623:                                resetIdMeter();
0624:
0625:                            if (offset > newParent.getChildNodes().getLength())
0626:                                throw new IndexOutOfBoundsException();
0627:                            if (offset < newParent.getChildNodes().getLength()) {
0628:                                Node refChild = (Node) newParent
0629:                                        .getChildNodes().item(offset);
0630:                                newParent.insertBefore(newNode, refChild);
0631:                            } else {
0632:                                newParent.appendChild(newNode);
0633:                            }
0634:                        }
0635:                    }
0636:                };
0637:                return mutate(parent, null, node, adder);
0638:            }
0639:
0640:            /**
0641:             * This api adds given node to given parent before given ref node.
0642:             * The inserted node will be part of childnodes of the parent,
0643:             * and will appear before ref node.
0644:             * @param parent The parent node to which the node is to be added.
0645:             * @param node The node which is to be added.
0646:             * @param refChild The ref node (child) of parent node,
0647:             *                  before which the node is to be added.
0648:             * @return The parent node resulted by inserion of this node.
0649:             */
0650:            public synchronized List<Node> insertBefore(Node parent, Node node,
0651:                    Node refChild) {
0652:                final Node ref = refChild;
0653:                Updater updater = new Updater() {
0654:                    public void update(Node newParent, Node oldNode,
0655:                            Node newNode) {
0656:                        newParent.insertBefore(newNode, ref);
0657:                    }
0658:                };
0659:
0660:                return mutate(parent, null, node, updater);
0661:            }
0662:
0663:            /**
0664:             * This api adds given node to given parent at the end.
0665:             * The added node will be part of childnodes of the parent,
0666:             * and it will be the last node.
0667:             * @param parent The parent node to which the node is to be appended.
0668:             * @param node The node which is to be appended.
0669:             * @return The parent node resulted by addition of this node
0670:             */
0671:            public synchronized List<Node> append(Node parent, Node node) {
0672:                Updater appender = new Updater() {
0673:                    public void update(Node parent, Node oldNode, Node newNode) {
0674:                        //reset id, since adding root element
0675:                        if (parent instanceof  Document
0676:                                && newNode instanceof  Element)
0677:                            resetIdMeter();
0678:                        parent.appendChild(newNode);
0679:                    }
0680:                };
0681:                return mutate(parent, null, node, appender);
0682:            }
0683:
0684:            /**
0685:             * This api deletes given node from a tree.
0686:             * @param node The node  to be deleted.
0687:             * @return The parent node resulted by deletion of this node.
0688:             */
0689:            public synchronized List<Node> delete(Node n) {
0690:                Updater remover = new Updater() {
0691:                    public void update(Node newParent, Node oldNode,
0692:                            Node newNode) {
0693:                        newParent.removeChild(oldNode);
0694:                    }
0695:                };
0696:                return mutate(null, n, null, remover);
0697:            }
0698:
0699:            /**
0700:             * This api changes index of the given node.
0701:             * @param nodes The nodes to be moved.
0702:             * @param indexes the new indexes of the nodes.
0703:             * @return The parent node resulted by deletion of this node.
0704:             */
0705:            public synchronized List<Node> reorder(Node parent, Node n,
0706:                    final int index) {
0707:                if (index < 0)
0708:                    throw new IndexOutOfBoundsException("index=" + index);
0709:                Updater u = new Updater() {
0710:                    public void update(Node newParent, Node oldNode,
0711:                            Node newNode) {
0712:                        if (newParent instanceof  Element
0713:                                && newNode instanceof  Attribute) {
0714:                            Element parent = (Element) newParent;
0715:                            int i = index;
0716:                            if (index > parent.getAttributes().getLength()) {
0717:                                i = parent.getAttributes().getLength();
0718:                            }
0719:                            parent.reorderAttribute((Attribute) oldNode, i);
0720:                        } else {
0721:                            int i = index;
0722:                            if (index > newParent.getChildNodes().getLength()) {
0723:                                i = newParent.getChildNodes().getLength();
0724:                            }
0725:                            ((NodeImpl) newParent).reorderChild(oldNode, i);
0726:                        }
0727:                    }
0728:                };
0729:                return mutate(parent, n, null, u);
0730:            }
0731:
0732:            /**
0733:             * This api changes indexes of the given node children.
0734:             * @param nodes The nodes to be moved.
0735:             * @param indexes the new indexes of the nodes.
0736:             * @return The parent node resulted by deletion of this node.
0737:             */
0738:            public synchronized List<Node> reorderChildren(Node parent,
0739:                    final int[] permutation) {
0740:                Updater u = new Updater() {
0741:                    public void update(Node newParent, Node oldNode,
0742:                            Node newNode) {
0743:                        ((NodeImpl) newParent).reorderChildren(permutation);
0744:                    }
0745:                };
0746:                return mutate(parent, null, null, u, MutationType.CHILDREN);
0747:            }
0748:
0749:            /**
0750:             * This api deletes given node from a given parent node.
0751:             * @param parent The parent node from which the node is to be deleted.
0752:             * @param child The node  to be deleted.
0753:             * @return The parent node resulted by deletion of this node.
0754:             */
0755:            public synchronized List<Node> remove(final Node parent, Node child) {
0756:                Updater remover = new Updater() {
0757:                    public void update(Node newParent, Node oldNode,
0758:                            Node newNode) {
0759:                        assert parent.isEquivalentNode(newParent);
0760:                        newParent.removeChild(oldNode);
0761:                    }
0762:                };
0763:                return mutate(parent, child, null, remover);
0764:            }
0765:
0766:            /**
0767:             * This api deletes given node from a given parent node.
0768:             * @param parent The parent node from which the node is to be deleted.
0769:             * @param toRemove collection of node to be deleted.
0770:             * @return The parent node resulted by deletion of this node.
0771:             */
0772:            public synchronized List<Node> removeChildNodes(final Node parent,
0773:                    final Collection<Node> toRemove) {
0774:                Updater remover = new Updater() {
0775:                    public void update(Node newParent, Node oldNode,
0776:                            Node newNode) {
0777:                        assert parent.isEquivalentNode(newParent);
0778:                        for (Node n : toRemove) {
0779:                            newParent.removeChild(n);
0780:                        }
0781:                    }
0782:                };
0783:                return mutate(parent, null, null, remover,
0784:                        MutationType.CHILDREN);
0785:            }
0786:
0787:            public synchronized List<Node> replaceChild(final Node parent,
0788:                    Node child, Node newChild) {
0789:                Updater updater = new Updater() {
0790:                    public void update(Node newParent, Node oldNode,
0791:                            Node newNode) {
0792:                        assert newParent.isEquivalentNode(parent);
0793:                        newParent.replaceChild(newNode, oldNode);
0794:                    }
0795:                };
0796:                return mutate(null, child, newChild, updater);
0797:            }
0798:
0799:            /**
0800:             * This api sets an attribute given name and value of a given element node.
0801:             * If an attribute with given name already present in element, it will only
0802:             * set the value. Otherwise a new attribute node, with given name and value,
0803:             * will be appended to the attibute list of the element node.
0804:             * @param element The element of which the attribute to be set.
0805:             * @param name The name of the attribute to be set.
0806:             * @param value The value of the attribute to be set.
0807:             * @return The element resulted by setting of attribute.
0808:             */
0809:            public synchronized List<Node> setAttribute(Element element,
0810:                    final String name, final String value) {
0811:                Updater updater = new Updater() {
0812:                    public void update(Node newParent, Node oldNode,
0813:                            Node newNode) {
0814:                        ((Element) newParent).setAttribute(name, value);
0815:                    }
0816:                };
0817:                return mutate(element, null, null, updater);
0818:            }
0819:
0820:            /**
0821:             * This api removes an attribute given name and value of a given element node.
0822:             * @param element The element of which the attribute to be removed.
0823:             * @param name The name of the attribute to be removed.
0824:             * @return The element resulted by removed of attribute.
0825:             */
0826:            public synchronized List<Node> removeAttribute(Element element,
0827:                    final String name) {
0828:                Updater updater = new Updater() {
0829:                    public void update(Node newParent, Node oldNode,
0830:                            Node newNode) {
0831:                        ((Element) newParent).removeAttribute(name);
0832:                    }
0833:                };
0834:                return mutate(element, null, null, updater);
0835:            }
0836:
0837:            private interface CheckIOExceptionUpdater extends Updater {
0838:                public IOException getError();
0839:            }
0840:
0841:            public synchronized List<Node> setXmlFragmentText(Element node,
0842:                    final String value) throws IOException {
0843:                CheckIOExceptionUpdater updater = new CheckIOExceptionUpdater() {
0844:                    public void update(Node newParent, Node oldNode,
0845:                            Node newNode) {
0846:                        try {
0847:                            ((Element) newParent).setXmlFragmentText(value);
0848:                        } catch (IOException ioe) {
0849:                            error = ioe;
0850:                        }
0851:                    }
0852:
0853:                    public IOException getError() {
0854:                        return error;
0855:                    }
0856:
0857:                    private IOException error;
0858:                };
0859:                List<Node> retPath = mutate(node, null, null, updater,
0860:                        MutationType.CHILDREN);
0861:                if (updater.getError() != null) {
0862:                    throw updater.getError();
0863:                } else {
0864:                    return retPath;
0865:                }
0866:            }
0867:
0868:            public synchronized List<Node> setTextValue(Node node, String value) {
0869:                Node text = (Node) currentDocument.createTextNode(value);
0870:                Updater updater = new Updater() {
0871:                    public void update(Node newParent, Node oldNode,
0872:                            Node newNode) {
0873:                        while (newParent.hasChildNodes()) {
0874:                            newParent.removeChild(newParent.getLastChild());
0875:                        }
0876:                        newParent.appendChild(newNode);
0877:                    }
0878:                };
0879:                return mutate(node, null, text, updater);
0880:            }
0881:
0882:            /**
0883:             * This is utility method which updates all the ancestors in the given
0884:             * ancestor list of given originalNode. The list returned represents
0885:             * the ancestors of given modified node.
0886:             * @param ancestors the list of ancestors starting from parent
0887:             * @param modifiedNode The modified node for which the new list is to be created
0888:             * @param originalNode The original node which ancestors are given
0889:             * @return The list of new ancestors starting parent for the modified node
0890:             */
0891:            private List<Node> updateAncestors(List<Node> ancestors,
0892:                    Node modifiedNode, Node originalNode) {
0893:                assert ancestors != null && modifiedNode != null
0894:                        && originalNode != null;
0895:                List<Node> newAncestors = new ArrayList<Node>(ancestors.size());
0896:                Node currentModifiedNode = modifiedNode;
0897:                Node currentOrigNode = originalNode;
0898:                for (Node parentNode : ancestors) {
0899:                    Node newParentNode = (Node) parentNode.clone(false, true,
0900:                            true);
0901:                    newParentNode.replaceChild(currentModifiedNode,
0902:                            currentOrigNode);
0903:                    newAncestors.add(newParentNode);
0904:                    currentOrigNode = parentNode;
0905:                    currentModifiedNode = newParentNode;
0906:                }
0907:                return newAncestors;
0908:            }
0909:
0910:            /**
0911:             * This api returns the latest stable document in the model.
0912:             * @return The latest stable document in the model.
0913:             */
0914:            public synchronized Document getDocument() {
0915:                checkStableOrParsingState();
0916:                return currentDocument;
0917:            }
0918:
0919:            /**
0920:             * This api returns the current document in the model, regardless of the state.
0921:             * @return The latest stable document in the model.
0922:             */
0923:            public synchronized Document getCurrentDocument() {
0924:                return currentDocument;
0925:            }
0926:
0927:            /**
0928:             * Reset document to provided known version and cause events to be fired.
0929:             * Note caller are responsible to handle exception and decide which version
0930:             * to keep after exception and do proper cleanup.
0931:             */
0932:            synchronized void resetDocument(Document newDoc) {
0933:                try {
0934:                    fireUndoEvents = false;
0935:                    List<Difference> diffs = new NodeIdDiffFinder().findDiff(
0936:                            getCurrentDocument(), newDoc);
0937:                    List<Difference> filtered = DiffFinder
0938:                            .filterWhitespace(diffs);
0939:                    //flushDocument(newDoc);
0940:                    setDocument(newDoc);
0941:                    if (filtered != null && !filtered.isEmpty()) {
0942:                        fireDiffEvents(filtered);
0943:                    }
0944:                } finally {
0945:                    fireUndoEvents = true;
0946:                }
0947:            }
0948:
0949:            private void flushDocument(Document newDoc) {
0950:                checkStableState();
0951:                UndoableEditListener uel = null;
0952:                BaseDocument d = getSwingDocument();
0953:                final CompoundEdit ce = new CompoundEdit();
0954:                try {
0955:                    FlushVisitor flushvisitor = new FlushVisitor();
0956:                    String newXMLText = flushvisitor.flushModel(newDoc);
0957:                    uel = new UndoableEditListener() {
0958:                        public void undoableEditHappened(UndoableEditEvent e) {
0959:                            ce.addEdit(e.getEdit());
0960:                        }
0961:                    };
0962:                    d.addUndoableEditListener(uel);
0963:                    Utils.replaceDocument(d, newXMLText);
0964:                } catch (BadLocationException ble) {
0965:                    throw new IllegalStateException(
0966:                            "It is possible that model source file is locked",
0967:                            ble);
0968:                } finally {
0969:                    if (uel != null) {
0970:                        d.removeUndoableEditListener(uel);
0971:                    }
0972:                    ce.end();
0973:                    for (UndoableEditListener l : ues
0974:                            .getUndoableEditListeners()) {
0975:                        l.undoableEditHappened(new UndoableEditEvent(this , ce));
0976:                    }
0977:                }
0978:            }
0979:
0980:            public synchronized String getCurrentDocumentText() {
0981:                return new FlushVisitor().flushModel(getCurrentDocument());
0982:            }
0983:
0984:            private BaseDocument getSwingDocument() {
0985:                BaseDocument bd = (BaseDocument) source.getLookup().lookup(
0986:                        BaseDocument.class);
0987:                return bd;
0988:            }
0989:
0990:            public synchronized void setDocument(Document newDoc) {
0991:                currentDocument = newDoc;
0992:            }
0993:
0994:            /**
0995:             * This returns the statuc of the model.
0996:             * @return the status.
0997:             * @see #Status
0998:             */
0999:            public synchronized Status getStatus() {
1000:                return status;
1001:            }
1002:
1003:            /**
1004:             * This api adds an undoable edit listener.
1005:             * @param l The undoable edit listener to be added.
1006:             */
1007:            public synchronized void addUndoableEditListener(
1008:                    UndoableEditListener l) {
1009:                ues.addUndoableEditListener(l);
1010:            }
1011:
1012:            /**
1013:             * This api removes an undoable edit listener.
1014:             * @param l The undoable edit listener to be removed.
1015:             */
1016:            public synchronized void removeUndoableEditListener(
1017:                    UndoableEditListener l) {
1018:                ues.addUndoableEditListener(l);
1019:            }
1020:
1021:            /**
1022:             * This api adds a property change listener.
1023:             * @param pcl The property change listener to be added.
1024:             */
1025:            public synchronized void addPropertyChangeListener(
1026:                    PropertyChangeListener pcl) {
1027:                pcs.addPropertyChangeListener(pcl);
1028:            }
1029:
1030:            /**
1031:             * This api removes a property change listener.
1032:             * @param pcl The property change listener to be removed.
1033:             */
1034:            public synchronized void removePropertyChangeListener(
1035:                    PropertyChangeListener pcl) {
1036:                pcs.removePropertyChangeListener(pcl);
1037:            }
1038:
1039:            /**
1040:             * Find the node with same id in the current tree.
1041:             */
1042:            private synchronized Node findNode(int id) {
1043:                FindVisitor fv = new FindVisitor();
1044:                return fv.find(getDocument(), id);
1045:            }
1046:
1047:            /**
1048:             * This represents the status of the XDM Model.
1049:             * Status STABLE means the latest attempt to parse was successful
1050:             * Status BROKEN means that the latest attempt to parse was unsuccessful.
1051:             * Status UNPARSED means the document has not been parsed yet.
1052:             * Status PARSING means the document is being parsed.
1053:             */
1054:            //TODO Last Parsed status
1055:            public enum Status {
1056:                BROKEN, STABLE, UNPARSED, PARSING;
1057:            }
1058:
1059:            private void fireUndoableEditEvent(Document newDoc, Document oldDoc) {
1060:                if (fireUndoEvents) {
1061:                    assert newDoc != oldDoc;
1062:                    UndoableEdit ee = new XDMModelUndoableEdit(oldDoc, newDoc,
1063:                            this );
1064:                    UndoableEditEvent ue = new UndoableEditEvent(this , ee);
1065:                    for (UndoableEditListener l : ues
1066:                            .getUndoableEditListeners()) {
1067:                        l.undoableEditHappened(ue);
1068:                    }
1069:                }
1070:            }
1071:
1072:            private void checkNodeInTree(Node n) {
1073:                if (n.isInTree()) {
1074:                    throw new IllegalArgumentException(
1075:                            "newValue must not have been added to model"); // NOI18N
1076:                }
1077:            }
1078:
1079:            private void checkStableState() {
1080:                if (getStatus() != Status.STABLE) {
1081:                    throw new IllegalStateException(
1082:                            "flush can only be called from STABLE STATE"); //NOI18N
1083:                }
1084:            }
1085:
1086:            private void checkStableOrParsingState() {
1087:                if (getStatus() != Status.STABLE
1088:                        && getStatus() != Status.PARSING) {
1089:                    throw new IllegalStateException(
1090:                            "The model is not initialized or is broken."); //NOI18N
1091:                }
1092:            }
1093:
1094:            private void setStatus(Status s) {
1095:                status = s;
1096:            }
1097:
1098:            /**
1099:             * This api keeps track of the nodes created in this model.
1100:             * @return the id of the next node to be created.
1101:             */
1102:            public int getNextNodeId() {
1103:                int nodeId = nodeCount;
1104:                nodeCount++;
1105:                return nodeId;
1106:            }
1107:
1108:            /**
1109:             * resets id meter
1110:             */
1111:            private void resetIdMeter() {
1112:                nodeCount = 1;
1113:            }
1114:
1115:            private boolean isPretty() {
1116:                return pretty;
1117:            }
1118:
1119:            public void setPretty(boolean print) {
1120:                pretty = print;
1121:            }
1122:
1123:            private void doPrettyPrint(Node newParent, Node newNode,
1124:                    Node oldParent) {
1125:                if ((getStatus() != Status.PARSING) && isPretty()) {
1126:                    if (isSimpleContent(newParent)) {//skip if simpleContent
1127:                        /*
1128:                         * <test name="test1">A new text node</test>
1129:                         */
1130:                        return;
1131:                    }
1132:                    if (!indentInitialized)
1133:                        initializeIndent(oldParent);
1134:                    String parentIndent = calculateNodeIndent(oldParent);
1135:                    if (!isPretty(newParent, newNode)) {//skip if already pretty
1136:                        int offset = 1;
1137:                        if (oldParent.getChildNodes().getLength() == 0) {//old parent did not have prettyprint before
1138:                            /*
1139:                             * before
1140:                             *
1141:                             * <test name="test1"></test>
1142:                             *
1143:                             * after
1144:                             *
1145:                             * <test name="test1">
1146:                             *     <c name="c1">
1147:                             * </test>
1148:                             */
1149:                            newParent.insertBefore(
1150:                                    createPrettyText(parentIndent
1151:                                            + getIndentation()), newNode);
1152:                            offset++;
1153:                        }
1154:                        int index = ((NodeImpl) newParent)
1155:                                .getIndexOfChild(newNode);
1156:                        if (index > 0) {
1157:                            Node oldText = (Node) newParent.getChildNodes()
1158:                                    .item(index - 1);
1159:                            if (checkPrettyText(oldText)) {
1160:                                /*
1161:                                 * before
1162:                                 *
1163:                                 * <test name="test1">
1164:                                 *     <a name="a1">
1165:                                 *     <b name="b1">
1166:                                 * <c name="c1">
1167:                                 * </test>
1168:                                 *
1169:                                 * after
1170:                                 *
1171:                                 * <test name="test1">
1172:                                 *     <a name="a1">
1173:                                 *     <b name="b1">
1174:                                 *     <c name="c1">
1175:                                 * </test>
1176:                                 */
1177:                                Text newText = createPrettyText(parentIndent
1178:                                        + getIndentation());
1179:                                newParent.replaceChild(newText, oldText);
1180:                            } else {
1181:                                /*
1182:                                 * before
1183:                                 *
1184:                                 * <test name="test1">
1185:                                 *     <a name="a1">
1186:                                 *     <b name="b1"><c name="c1">
1187:                                 * </test>
1188:                                 *
1189:                                 * after
1190:                                 *
1191:                                 * <test name="test1">
1192:                                 *     <a name="a1">
1193:                                 *     <b name="b1">
1194:                                 *     <c name="c1">
1195:                                 * </test>
1196:                                 */
1197:                                newParent.insertBefore(
1198:                                        createPrettyText(parentIndent
1199:                                                + getIndentation()), newNode);
1200:                                offset++;
1201:                            }
1202:                        }
1203:                        Node ref = null;
1204:                        if ((index + offset) < newParent.getChildNodes()
1205:                                .getLength())
1206:                            ref = (Node) newParent.getChildNodes().item(
1207:                                    (index + offset));
1208:                        if (ref != null) {
1209:                            if (!checkPrettyText(ref)) {
1210:                                /*
1211:                                 * before
1212:                                 *
1213:                                 * <test name="test1">
1214:                                 *     <a name="a1">
1215:                                 *     <b name="b1">
1216:                                 *     <c name="c1"><d name="d1">
1217:                                 * </test>
1218:                                 *
1219:                                 * after
1220:                                 *
1221:                                 * <test name="test1">
1222:                                 *     <a name="a1">
1223:                                 *     <b name="b1">
1224:                                 *     <c name="c1">
1225:                                 *     <d name="d1">
1226:                                 * </test>
1227:                                 */
1228:                                newParent.insertBefore(
1229:                                        createPrettyText(parentIndent
1230:                                                + getIndentation()), ref);
1231:                            }
1232:                        } else {
1233:                            /*
1234:                             * before
1235:                             *
1236:                             * <test name="test1">
1237:                             *     <a name="a1">
1238:                             *     <b name="b1">
1239:                             *     <c name="c1"></test>
1240:                             *
1241:                             * after
1242:                             *
1243:                             * <test name="test1">
1244:                             *     <a name="a1">
1245:                             *     <b name="b1">
1246:                             *     <c name="c1">
1247:                             * </test>
1248:                             */
1249:                            newParent
1250:                                    .appendChild(createPrettyText(parentIndent));
1251:                        }
1252:                    }
1253:
1254:                    //recurse pretty print
1255:                    doPrettyPrintRecursive(newNode, parentIndent, newParent);//for children of node
1256:                }
1257:            }
1258:
1259:            /*
1260:             * initialized only once
1261:             *
1262:             */
1263:            private void initializeIndent(final Node n) {
1264:                String parentIndent = calculateNodeIndent(n);
1265:                List<Node> pathToRoot = new PathFromRootVisitor().findPath(
1266:                        getDocument(), n);
1267:                if (parentIndent.length() > 0 && pathToRoot.size() - 2 > 0) {
1268:                    //exclude Document and the root from path for indent step calculation
1269:                    double step = Math.floor(parentIndent.length()
1270:                            / (double) (pathToRoot.size() - 2));
1271:                    StringBuffer sb = new StringBuffer();
1272:                    for (int i = 0; i < step; i++)
1273:                        sb.append(" ");
1274:                    String indentString = sb.toString();
1275:                    if (indentString.length() > 0)
1276:                        setIndentation(indentString);
1277:                    else
1278:                        setDefaultIndentation();
1279:                } else
1280:                    setDefaultIndentation();
1281:            }
1282:
1283:            private String calculateNodeIndent(final Node n) {
1284:                String indent = "";
1285:                Node parent = (Node) n.getParentNode();
1286:                if (parent != null) {
1287:                    int index = parent.getIndexOfChild(n);
1288:                    if (index > 0) {
1289:                        Node txt = (Node) parent.getChildNodes()
1290:                                .item(index - 1);
1291:                        if (checkPrettyText(txt)) {
1292:                            String wsValue = ((NodeImpl) txt).getTokens()
1293:                                    .get(0).getValue();
1294:                            int ndx = wsValue.lastIndexOf("\n");
1295:                            if (ndx != -1 && (ndx + 1) < wsValue.length())
1296:                                indent = wsValue.substring(ndx + 1);
1297:                        }
1298:                    }
1299:                }
1300:                return indent;
1301:            }
1302:
1303:            private void doPrettyPrintRecursive(Node n, String indent,
1304:                    Node parent) {
1305:                if ((getStatus() != Status.PARSING) && isPretty()) {
1306:                    if (isSimpleContent(n))
1307:                        return; //skip if simpleContent
1308:                    else if (n instanceof  Element && isPretty(n)) {//adjust for pretty length difference
1309:                        fixPrettyForCopiedNode(n, indent, parent);
1310:                    } else {
1311:                        List<Node> childList = new ArrayList<Node>();
1312:                        List<Node> visitList = new ArrayList<Node>();
1313:                        NodeList childs = n.getChildNodes();
1314:                        for (int i = 0; i < childs.getLength(); i++) {
1315:                            childList.add((Node) childs.item(i));
1316:                            if (childs.item(i) instanceof  Element)
1317:                                visitList.add((Node) childs.item(i));
1318:                        }
1319:                        String parentIndent = indent + getIndentation();
1320:                        if (childList.size() > 0)
1321:                            n.appendChild(createPrettyText(parentIndent));
1322:                        String childIndent = parentIndent + getIndentation();
1323:                        for (int i = childList.size() - 1; i >= 0; i--) {
1324:                            Node ref = (Node) childList.get(i);
1325:                            Text postText = createPrettyText(childIndent);
1326:                            n.insertBefore(postText, ref);
1327:                        }
1328:                        childList.clear(); //no need to keep it beyond here
1329:                        for (int i = 0; i < visitList.size(); i++) {
1330:                            doPrettyPrintRecursive((Node) visitList.get((i)),
1331:                                    parentIndent, n);
1332:                        }
1333:                        visitList.clear(); //no need to keep it beyond here
1334:                    }
1335:                }
1336:            }
1337:
1338:            /*
1339:             * This function will fix the pretty text of nodes that are cut or copied and
1340:             * pasted to xdm tree
1341:             */
1342:            private void fixPrettyForCopiedNode(Node n, String indent,
1343:                    Node parent) {
1344:                NodeList childs = n.getChildNodes();
1345:                if (childs.getLength() == 0)
1346:                    return;
1347:                Text nlastChild = (Text) childs.item(childs.getLength() - 1);
1348:                String lc = ((NodeImpl) nlastChild).getTokens().get(0)
1349:                        .getValue();
1350:                NodeImpl pfirstChild = (NodeImpl) parent.getChildNodes()
1351:                        .item(0);
1352:                String fc = pfirstChild.getTokens().get(0).getValue();
1353:
1354:                if (fc.length() == lc.length()) {//return if already pretty
1355:                    return;
1356:                } else {
1357:                    String parentIndent = indent + getIndentation();
1358:                    String childIndent = parentIndent + getIndentation();
1359:                    List<Node> childList = new ArrayList<Node>();
1360:                    for (int i = 0; i < childs.getLength(); i++) {
1361:                        childList.add((Node) childs.item(i));
1362:                    }
1363:                    for (int i = 0; i < childList.size(); i++) {
1364:                        Node txt = (Node) n.getChildNodes().item(i);
1365:                        if (checkPrettyText(txt)) {
1366:                            String newIndent = childIndent + getIndentation();
1367:                            if (i == 0) {
1368:                                newIndent = childIndent;
1369:                            } else if (i == childList.size() - 1) {
1370:                                newIndent = parentIndent;
1371:                            }
1372:                            n.replaceChild(createPrettyText(newIndent), txt);
1373:                        }
1374:                    }
1375:                    for (int i = 0; i < childList.size(); i++) {
1376:                        fixPrettyForCopiedNode(
1377:                                (Node) n.getChildNodes().item(i), childIndent,
1378:                                n);
1379:                    }
1380:                    childList.clear(); //no need to keep it beyond here
1381:                }
1382:            }
1383:
1384:            private Text createPrettyText(String indent) {
1385:                String textChars = "\n" + indent;
1386:                Text txt = (Text) this .getDocument().createTextNode(textChars);
1387:                return txt;
1388:            }
1389:
1390:            private void undoPrettyPrint(Node newParent, Node oldNode,
1391:                    Node oldParent) {
1392:                if ((getStatus() != Status.PARSING) && isPretty()) {
1393:                    String parentIndent = calculateNodeIndent(oldParent);
1394:                    int piLength = parentIndent != null ? parentIndent.length()
1395:                            : 0;
1396:                    int index = ((NodeImpl) oldParent).getIndexOfChild(oldNode);
1397:                    Node txtBefore = null;
1398:                    if (index > 0) {//remove pre pretty print node
1399:                        txtBefore = (Node) oldParent.getChildNodes().item(
1400:                                index - 1);
1401:                        if (checkPrettyText(txtBefore)
1402:                                && piLength <= getLength((Text) txtBefore)) {
1403:                            /*
1404:                             * before
1405:                             *
1406:                             * <test name="test1">
1407:                             *     <a name="a1">
1408:                             *     <b name="b1">
1409:                             *     <c name="c1">
1410:                             * </test>
1411:                             *
1412:                             * after
1413:                             *
1414:                             * <test name="test1">
1415:                             *     <a name="a1">
1416:                             *     <b name="b1"><c name="c1">
1417:                             * </test>
1418:                             */
1419:                            newParent.removeChild(txtBefore);
1420:                        }
1421:                    }
1422:                    if (newParent.getChildNodes().getLength() == 2
1423:                            && index + 1 < oldParent.getChildNodes()
1424:                                    .getLength()) {//remove post pretty print node
1425:                        Node txtAfter = (Node) oldParent.getChildNodes().item(
1426:                                index + 1);
1427:                        if (checkPrettyText(txtAfter)
1428:                                && piLength <= getLength((Text) txtAfter)) {
1429:                            /*
1430:                             * before
1431:                             *
1432:                             * <test name="test1">
1433:                             *     <a name="a1">
1434:                             *     <b name="b1"><c name="c1">
1435:                             * </test>
1436:                             *
1437:                             * after
1438:                             *
1439:                             * <test name="test1">
1440:                             *     <a name="a1">
1441:                             *     <b name="b1"><c name="c1"></test>
1442:                             */
1443:                            newParent.removeChild(txtAfter);
1444:                        }
1445:                    }
1446:                }
1447:            }
1448:
1449:            private int getLength(Text n) {
1450:                int len = 0;
1451:                for (Token token : ((NodeImpl) n).getTokens())
1452:                    len += token.getValue().length();
1453:                return len;
1454:            }
1455:
1456:            private boolean checkPrettyText(Node txt) {
1457:                if (txt instanceof  Text) {
1458:                    if ((((NodeImpl) txt).getTokens().size() == 1)
1459:                            && isWhitespaceOnly(((NodeImpl) txt).getTokens()
1460:                                    .get(0).getValue())) {
1461:                        return true;
1462:                    }
1463:                }
1464:                return false;
1465:            }
1466:
1467:            private boolean isPossibleWhiteSpace(String text) {
1468:                return text.length() > 0
1469:                        && Character.isWhitespace(text.charAt(0))
1470:                        && Character.isWhitespace(text
1471:                                .charAt(text.length() - 1));
1472:            }
1473:
1474:            private boolean isWhitespaceOnly(String tn) {
1475:                return isPossibleWhiteSpace(tn) && tn.trim().length() == 0;
1476:            }
1477:
1478:            public ElementIdentity getElementIdentity() {
1479:                return eID;
1480:
1481:            }
1482:
1483:            public void setElementIdentity(ElementIdentity eID) {
1484:                this .eID = eID;
1485:            }
1486:
1487:            private boolean isSimpleContent(Node newParent) {
1488:                NodeList childs = newParent.getChildNodes();
1489:                for (int i = 0; i < childs.getLength(); i++)
1490:                    if (!(childs.item(i) instanceof  Text))
1491:                        return false;
1492:                return true;
1493:            }
1494:
1495:            private boolean isPretty(Node newParent) {
1496:                return isPretty(newParent, null);
1497:            }
1498:
1499:            private boolean isPretty(Node newParent, Node newNode) {
1500:                boolean parentPretty = false;
1501:                NodeList childs = newParent.getChildNodes();
1502:                int len = childs.getLength();
1503:
1504:                /*
1505:                 * <test name="test1"></test>  parentPretty = true
1506:                 *
1507:                 * <test name="test1"> parentPretty = true
1508:                 * </test>
1509:                 *
1510:                 * <test name="test1">  parentPretty = true
1511:                 *     <c name="c1">
1512:                 * </test>
1513:                 *
1514:                 * <test name="test1"><c name="c1">  parentPretty = false
1515:                 * </test>
1516:                 */
1517:                if (len == 0)
1518:                    parentPretty = true;
1519:                else if (len == 1 && childs.item(0) instanceof  Text)
1520:                    parentPretty = true;
1521:                else if (len > 2 && checkPrettyText((Node) childs.item(0))
1522:                        && checkPrettyText((Node) childs.item(len - 1)))
1523:                    parentPretty = true;
1524:
1525:                if (!parentPretty)
1526:                    return false;
1527:
1528:                if (newNode != null) {
1529:                    //now check newNode pretty
1530:                    Node preText = null;
1531:                    Node postText = null;
1532:                    int index = ((NodeImpl) newParent).getIndexOfChild(newNode);
1533:                    if (index > 0)
1534:                        preText = (Node) newParent.getChildNodes().item(
1535:                                index - 1);
1536:                    if ((index + 1) < newParent.getChildNodes().getLength())
1537:                        postText = (Node) newParent.getChildNodes().item(
1538:                                (index + 1));
1539:
1540:                    /*
1541:                     * <test name="test1">
1542:                     *     <a name="a1"/>
1543:                     *     <b name="b1"/>
1544:                     *     <c name="c1"/>  'c' pretty = true
1545:                     * </test>
1546:                     *
1547:                     * <test name="test1">
1548:                     *     <a name="a1"/>
1549:                     *     <b name="b1"/><c name="c1"/>  'c' pretty = false
1550:                     * </test>
1551:                     *
1552:                     */
1553:                    if (checkPrettyText(preText) && checkPrettyText(postText))
1554:                        return true;
1555:                } else
1556:                    return parentPretty;
1557:
1558:                return false;
1559:            }
1560:
1561:            /**
1562:             * Set/get mapping of QName-valued attributes by element.
1563:             * Key of the mapping is QName of the element.
1564:             * Value of the mapping is list QName's of the attributes.
1565:             * If the mapping is set, it will be used to identify the 
1566:             * attribute values affected by namespace prefix refactoring 
1567:             * during namespace consolidation.  If not set, namespace consolidation
1568:             * would skip prefix rename refactoring case.
1569:             */
1570:            public void setQNameValuedAttributes(
1571:                    Map<QName, List<QName>> attrsByElement) {
1572:                qnameValuedAttributesByElementMap = attrsByElement;
1573:            }
1574:
1575:            public Map<QName, List<QName>> getQNameValuedAttributes() {
1576:                return qnameValuedAttributesByElementMap;
1577:            }
1578:
1579:            /**
1580:             * The xml syntax parser
1581:             */
1582:            private XMLSyntaxParser parser;
1583:
1584:            /**
1585:             * The current stable document represented by the model
1586:             */
1587:            private Document currentDocument;
1588:
1589:            /**
1590:             * Property change support
1591:             */
1592:            private PropertyChangeSupport pcs;
1593:
1594:            /**
1595:             * The underlying model source
1596:             */
1597:            private ModelSource source;
1598:
1599:            /**
1600:             * Current status of the model
1601:             */
1602:            private Status status;
1603:
1604:            private boolean pretty = false;
1605:
1606:            /**
1607:             * Undoable edit support
1608:             */
1609:            private UndoableEditSupport ues;
1610:
1611:            /**
1612:             * whether to fire undo events
1613:             */
1614:            private boolean fireUndoEvents = true;
1615:
1616:            /**
1617:             * The names of property change events fired
1618:             */
1619:            /**
1620:             * Indicates node modified
1621:             */
1622:            public static final String PROP_MODIFIED = "modified";
1623:            /**
1624:             * Indicates node deleted
1625:             */
1626:            public static final String PROP_DELETED = "deleted";
1627:            /**
1628:             * Indicates node added
1629:             */
1630:            public static final String PROP_ADDED = "added";
1631:
1632:            public static final String DEFAULT_INDENT = "    ";
1633:
1634:            /**
1635:             * current node count
1636:             */
1637:            private int nodeCount = 0;
1638:
1639:            private ElementIdentity eID;
1640:
1641:            private String currentIndent = "";
1642:
1643:            private boolean indentInitialized = false;
1644:
1645:            private Map<QName, List<QName>> qnameValuedAttributesByElementMap;
1646:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.