KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > xml > xam > dom > AbstractDocumentModel


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

19
20 package org.netbeans.modules.xml.xam.dom;
21
22 import java.beans.PropertyChangeEvent JavaDoc;
23 import java.lang.ref.WeakReference JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Collections JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.HashSet JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Set JavaDoc;
31 import javax.swing.event.DocumentEvent JavaDoc;
32 import javax.swing.event.DocumentListener JavaDoc;
33 import javax.xml.namespace.QName JavaDoc;
34 import org.netbeans.modules.xml.xam.AbstractModel;
35 import org.netbeans.modules.xml.xam.Component;
36 import org.netbeans.modules.xml.xam.ComponentEvent;
37 import org.netbeans.modules.xml.xam.ComponentUpdater;
38 import org.netbeans.modules.xml.xam.Model.State;
39 import org.netbeans.modules.xml.xam.ModelSource;
40 import org.netbeans.modules.xml.xam.spi.DocumentModelAccessProvider;
41 import org.openide.util.Lookup;
42 import org.w3c.dom.Attr JavaDoc;
43 import org.w3c.dom.Document JavaDoc;
44 import org.w3c.dom.Element JavaDoc;
45 import org.w3c.dom.Node JavaDoc;
46
47 /**
48  * @author Chris Webster
49  * @author Rico
50  * @author Nam Nguyen
51  */

52 public abstract class AbstractDocumentModel<T extends DocumentComponent<T>>
53         extends AbstractModel<T> implements DocumentModel<T> {
54
55     protected DocumentModelAccess access;
56     private boolean needsSync;
57     private DocumentListener JavaDoc docListener;
58     private javax.swing.text.Document JavaDoc swingDocument;
59     
60     public AbstractDocumentModel(ModelSource source) {
61         super(source);
62     docListener = new DocumentChangeListener();
63     }
64
65     public javax.swing.text.Document JavaDoc getBaseDocument() {
66         return (javax.swing.text.Document JavaDoc)
67             getModelSource().getLookup().lookup(javax.swing.text.Document JavaDoc.class);
68     }
69     
70     public abstract T createRootComponent(Element JavaDoc root);
71     
72     public boolean areSameNodes(Node JavaDoc n1, Node JavaDoc n2) {
73         return getAccess().areSameNodes(n1, n2);
74     }
75     
76     /**
77      * Returns QName of elements used in model. Domain model implementation needs
78      * to override this to be able to embed elements outside of the domain such as
79      * child elements of documentation in schema model.
80      * @return full set of element QName's or null if there is no needs for distinction
81      * between domain and non-domain elements.
82      */

83     public Set JavaDoc<QName JavaDoc> getQNames() {
84         return Collections.emptySet();
85     }
86     
87     @Override JavaDoc
88     protected boolean needsSync() {
89     javax.swing.text.Document JavaDoc lastDoc = swingDocument;
90     javax.swing.text.Document JavaDoc currentDoc = (javax.swing.text.Document JavaDoc)
91         getModelSource().getLookup().lookup(javax.swing.text.Document JavaDoc.class);
92     if (currentDoc == null) {
93         swingDocument = null;
94         return false;
95     }
96     if (lastDoc == null || currentDoc != lastDoc) {
97         swingDocument = currentDoc;
98         currentDoc.addDocumentListener(new WeakDocumentListener(docListener, currentDoc));
99     }
100     return needsSync || !currentDoc.equals(lastDoc);
101     }
102     
103     @Override JavaDoc
104     protected void syncStarted() {
105         needsSync = false;
106         getAccess().unsetDirty();
107     }
108
109     @Override JavaDoc
110     protected synchronized void syncCompleted() {
111         super.syncCompleted();
112     }
113
114     
115     private synchronized void documentChanged() {
116     if (!isIntransaction()) {
117         getAccess().setDirty();
118         needsSync = true;
119     }
120     }
121
122     private static class WeakDocumentListener implements DocumentListener JavaDoc {
123     
124     public WeakDocumentListener(DocumentListener JavaDoc delegate,
125                     javax.swing.text.Document JavaDoc source) {
126         this.source = source;
127         this.delegate = new WeakReference JavaDoc<DocumentListener JavaDoc>(delegate);
128     }
129     
130     private DocumentListener JavaDoc getDelegate() {
131         DocumentListener JavaDoc l = delegate.get();
132         if (l == null) {
133         source.removeDocumentListener(this);
134         }
135         
136         return l;
137     }
138     
139     public void removeUpdate(DocumentEvent JavaDoc e) {
140         DocumentListener JavaDoc l = getDelegate();
141         if (l != null) {
142         l.removeUpdate(e);
143         }
144     }
145     
146     public void changedUpdate(DocumentEvent JavaDoc e) {
147         DocumentListener JavaDoc l = getDelegate();
148         if (l != null) {
149         l.changedUpdate(e);
150         }
151     }
152     
153     public void insertUpdate(DocumentEvent JavaDoc e) {
154         DocumentListener JavaDoc l = getDelegate();
155         if (l != null) {
156         l.insertUpdate(e);
157         }
158     }
159     
160     private javax.swing.text.Document JavaDoc source;
161     private WeakReference JavaDoc<DocumentListener JavaDoc> delegate;
162     }
163     
164     private class DocumentChangeListener implements DocumentListener JavaDoc {
165     public void removeUpdate(DocumentEvent JavaDoc e) {
166         documentChanged();
167     }
168     
169     public void insertUpdate(DocumentEvent JavaDoc e) {
170         documentChanged();
171     }
172     
173     public void changedUpdate(DocumentEvent JavaDoc e) {
174         // ignore these events as these are not changes
175
// to the document text but the document itself
176
}
177     }
178
179     protected abstract ComponentUpdater<T> getComponentUpdater();
180     
181     /**
182      * Allows match just by tag name, in case full QName is not available.
183      */

184     private Set JavaDoc<String JavaDoc> elementNames = null;
185     public Set JavaDoc<String JavaDoc> getElementNames() {
186         if (elementNames == null) {
187             elementNames = new HashSet JavaDoc<String JavaDoc>();
188             Set JavaDoc<QName JavaDoc> qnames = getQNames();
189             for (QName JavaDoc q : qnames) {
190                 elementNames.add(q.getLocalPart());
191             }
192         }
193         return elementNames;
194     }
195     
196     public ChangeInfo prepareChangeInfo(List JavaDoc<Node JavaDoc> pathToRoot) {
197         // we already handle change on root before enter here
198
if (pathToRoot.size() < 1) {
199             throw new IllegalArgumentException JavaDoc("pathToRoot here should be at least 1");
200         }
201         if (pathToRoot.get(pathToRoot.size()-1) instanceof Document JavaDoc) {
202             pathToRoot.remove(pathToRoot.size()-1);
203         }
204         
205         if (pathToRoot.size() < 2) {
206             throw new IllegalArgumentException JavaDoc("pathToRoot here should be at least 2");
207         }
208         Node JavaDoc current = null;
209         Element JavaDoc parent = null;
210         boolean changedIsDomainElement = true;
211         Set JavaDoc<QName JavaDoc> qnames = getQNames();
212         Set JavaDoc<String JavaDoc> enames = getElementNames();
213         if (qnames != null && qnames.size() > 0) {
214             for (int i=0; i<pathToRoot.size(); i++) {
215                 Node JavaDoc n = pathToRoot.get(i);
216                 if (! (n instanceof Element JavaDoc)) {
217                     changedIsDomainElement = false;
218                     continue;
219                 }
220                 
221                 QName JavaDoc q = new QName JavaDoc(getAccess().lookupNamespaceURI(n, pathToRoot), n.getLocalName());
222                 if (qnames.contains(q)) {
223                     current = n;
224                     if (i+1 < pathToRoot.size()) {
225                         parent = (Element JavaDoc) pathToRoot.get(i+1);
226                     }
227                     break;
228                 } else if (changedIsDomainElement == true) {
229                     changedIsDomainElement = false;
230                 }
231             }
232         } else {
233             Node JavaDoc n = pathToRoot.get(0);
234             if (n instanceof Element JavaDoc) {
235                 current = n;
236                 parent = (Element JavaDoc) pathToRoot.get(1);
237             } else {
238                 current = pathToRoot.get(1);
239                 if (pathToRoot.size() > 2) {
240                     parent = (Element JavaDoc) pathToRoot.get(2);
241                 }
242                 changedIsDomainElement = false;
243             }
244         }
245         
246         if (! changedIsDomainElement) {
247             int i = pathToRoot.indexOf(current);
248             if (i < 1) {
249                 throw new IllegalArgumentException JavaDoc("pathToRoot does not contain element");
250             }
251             parent = (Element JavaDoc) current;
252             current = pathToRoot.get(i-1);
253         }
254         
255         List JavaDoc<Element JavaDoc> rootToParent = new ArrayList JavaDoc<Element JavaDoc>();
256         if (parent != null) {
257             for (int i = pathToRoot.indexOf(parent); i<pathToRoot.size(); i++) {
258                 rootToParent.add(0, (Element JavaDoc)pathToRoot.get(i));
259             }
260         }
261         
262         List JavaDoc<Node JavaDoc> otherNodes = new ArrayList JavaDoc<Node JavaDoc>();
263         if (parent != null) {
264             int iCurrent = pathToRoot.indexOf(current);
265             for (int i=0; i < iCurrent; i++) {
266                 otherNodes.add(0, pathToRoot.get(i));
267             }
268         }
269         
270         return new ChangeInfo(parent, current, changedIsDomainElement, rootToParent, otherNodes);
271     }
272     
273     public SyncUnit prepareSyncUnit(ChangeInfo change, SyncUnit order) {
274         if (change.getChangedNode() == null) {
275             throw new IllegalStateException JavaDoc("Bad change info");
276         }
277         AbstractDocumentComponent parentComponent = (AbstractDocumentComponent) change.getParentComponent();
278         if (parentComponent == null) {
279             parentComponent = (AbstractDocumentComponent) findComponent(change.getRootToParentPath());
280         }
281         if (parentComponent == null) {
282             throw new IllegalArgumentException JavaDoc("Could not find parent component");
283         }
284         
285         DocumentComponent toRemove = null;
286         DocumentComponent toAdd = null;
287         boolean changed = false;
288         
289         if (change.isDomainElement()) {
290             if (change.isDomainElementAdded()) {
291                 toAdd = createChildComponent(parentComponent, change.getChangedElement());
292             } else {
293                 toRemove = parentComponent.findChildComponent(change.getChangedElement());
294                 if (toRemove == null) {
295                     parentComponent.findChildComponentByIdentity(change.getChangedElement());
296                 }
297             }
298         } else {
299             changed = true;
300         }
301         
302         if (order == null) {
303             order = new SyncUnit(parentComponent);
304         }
305         
306         order.addChange(change);
307         if (toRemove != null) order.addToRemoveList(toRemove);
308         if (toAdd != null) order.addToAddList(toAdd);
309         if (changed) order.setComponentChanged(true);
310         return order;
311     }
312     
313     protected void firePropertyChangedEvents(SyncUnit unit) {
314         firePropertyChangedEvents(unit, null);
315     }
316     
317     protected void firePropertyChangedEvents(SyncUnit unit, Element JavaDoc oldElement) {
318         Set JavaDoc<String JavaDoc> propertyNames = new HashSet JavaDoc(unit.getRemovedAttributes().keySet());
319         propertyNames.addAll(unit.getAddedAttributes().keySet());
320         for (String JavaDoc name : propertyNames) {
321             Attr JavaDoc oldAttr = unit.getRemovedAttributes().get(name);
322             Attr JavaDoc newAttr = unit.getAddedAttributes().get(name);
323             super.firePropertyChangeEvent(
324                     new PropertyChangeEvent JavaDoc(
325                     unit.getTarget(), name,
326                     oldAttr == null ? null : oldAttr.getValue(),
327                     newAttr == null ? null : newAttr.getValue()));
328         }
329         if (unit.hasTextContentChanges()) {
330             super.firePropertyChangeEvent(
331                     new PropertyChangeEvent JavaDoc(
332                     unit.getTarget(), DocumentComponent.TEXT_CONTENT_PROPERTY,
333                     oldElement == null ? "" : getAccess().getXmlFragment(oldElement),
334                     getAccess().getXmlFragment(unit.getTarget().getPeer())));
335         }
336     }
337     
338     public void processSyncUnit(SyncUnit syncOrder) {
339         AbstractDocumentComponent targetComponent = (AbstractDocumentComponent) syncOrder.getTarget();
340         if (targetComponent == null) {
341             throw new IllegalArgumentException JavaDoc("sync unit should not be null");
342         }
343         // skip target component whose some ancestor removed in previous processed syncUnit
344
if (! targetComponent.isInDocumentModel()) {
345             return;
346         }
347         
348         Element JavaDoc oldElement = syncOrder.getTarget().getPeer();
349         syncOrder.updateTargetReference();
350         if (syncOrder.isComponentChanged()) {
351             ComponentEvent.EventType changeType = ComponentEvent.EventType.VALUE_CHANGED;
352             if (! syncOrder.hasWhitespaceChangeOnly()) {
353                 fireComponentChangedEvent(new ComponentEvent(targetComponent, changeType));
354             }
355             firePropertyChangedEvents(syncOrder, oldElement);
356         }
357         
358         for (DocumentComponent c : syncOrder.getToRemoveList()) {
359             removeChildComponent(c);
360         }
361         
362         for (DocumentComponent c : syncOrder.getToAddList()) {
363             Element JavaDoc childElement = (Element JavaDoc) ((AbstractDocumentComponent)c).getPeer();
364             int index = targetComponent.findDomainIndex(childElement);
365             addChildComponent(targetComponent, c, index);
366         }
367     }
368     
369     private DocumentComponent createChildComponent(DocumentComponent parent, Element JavaDoc e) {
370         DocumentModel m = (DocumentModel) parent.getModel();
371         if (m == null) {
372             throw new IllegalArgumentException JavaDoc("Cannot create child component from a deleted component.");
373         }
374         return m.createComponent(parent, e);
375     }
376     
377     public void addChildComponent(Component target, Component child, int index) {
378         AbstractDocumentModel m = (AbstractDocumentModel)target.getModel();
379         //assert m != null : "Cannot add child to a deleted component.";
380
//Work-around xdm overlapping in firing
381
if (m == null) return;
382         m.getComponentUpdater().update(target, child, index, ComponentUpdater.Operation.ADD);
383     }
384     
385     public void removeChildComponent(Component child) {
386         if (child.getParent() == null) return;
387         AbstractDocumentModel m = (AbstractDocumentModel) child.getParent().getModel();
388         //Work-around xdm overlapping in firing
389
//assert m != null : "Cannot remove child from a deleted component.";
390
if (m == null) return;
391         m.getComponentUpdater().update(child.getParent(), child, ComponentUpdater.Operation.REMOVE);
392     }
393     
394     public DocumentComponent findComponent(Element JavaDoc e) {
395         return findComponent((AbstractDocumentComponent) getRootComponent(), e);
396     }
397     
398     private DocumentComponent findComponent(DocumentComponent searchRoot, Element JavaDoc e) {
399         if (searchRoot.referencesSameNode(e)) {
400             return searchRoot;
401         }
402         for (Object JavaDoc o : searchRoot.getChildren()) {
403             DocumentComponent found = findComponent((DocumentComponent) o, e);
404             if (found != null) {
405                 return found;
406             }
407         }
408         return null;
409     }
410     
411     /**
412      * Find the component given a path to its element node from root. All elements, except for
413      * the target element should be in the latest version of the xdm tree. All components on the
414      * path will be updated with latest version elements.
415      *
416      * Note that returned component could be part of an embedded model, which could be of
417      * a different type of model.
418      *
419      * @param pathFromRoot list of elements from model root to backing element of target component.
420      * @return component backed by the last element on pathFromRoot or null if not found.
421      */

422     public DocumentComponent findComponent(List JavaDoc<Element JavaDoc> pathFromRoot) {
423         return findComponent((AbstractDocumentComponent)getRootComponent(), pathFromRoot, 0);
424     }
425     
426     public AbstractDocumentComponent findComponent(AbstractDocumentComponent base, List JavaDoc<Element JavaDoc> pathFromRoot, int current) {
427         if (pathFromRoot == null || pathFromRoot.size() <= current) {
428             return null;
429         }
430         Element JavaDoc e = pathFromRoot.get(current);
431         if (base.referencesSameNode(e)) {
432             if (pathFromRoot.size() == current + 1) {
433                 base.getChildren(); // make sure children inited
434
return base;
435             } else {
436                 for (Object JavaDoc child : base.getChildren()) {
437                     AbstractDocumentComponent ac = (AbstractDocumentComponent) child;
438                     AbstractDocumentComponent found = findComponent(ac, pathFromRoot, current+1);
439                     if (found != null) {
440                         return found;
441                     }
442                 }
443             }
444         }
445         return null;
446     }
447     
448     public DocumentComponent findComponent(int position) {
449         if (getState() != State.VALID) {
450             return getRootComponent();
451         }
452             
453         Element JavaDoc e = (Element JavaDoc) getAccess().getContainingElement(position);
454         if (e == null) {
455             return getRootComponent();
456         }
457         
458         List JavaDoc<Element JavaDoc> pathFromRoot = null;
459         try {
460             pathFromRoot = getAccess().getPathFromRoot(this.getDocument(), e);
461         } catch(UnsupportedOperationException JavaDoc ex) {
462             // OK
463
}
464         if (pathFromRoot == null || pathFromRoot.isEmpty()) {
465             return findComponent(e);
466         } else {
467             return findComponent(pathFromRoot);
468         }
469     }
470     
471     public String JavaDoc getXPathExpression(DocumentComponent component) {
472         Element JavaDoc e = (Element JavaDoc) component.getPeer();
473         return getAccess().getXPath(getDocument(), e);
474     }
475
476     public org.w3c.dom.Document JavaDoc getDocument() {
477         return getAccess().getDocumentRoot();
478     }
479
480     public DocumentModelAccess getAccess() {
481         if (access == null) {
482             access = getEffectiveAccessProvider().createModelAccess(this);
483             if (! (access instanceof ReadOnlyAccess)) {
484                 access.addUndoableEditListener(this);
485                 setIdentifyingAttributes();
486             }
487         }
488         return access;
489     }
490
491     private DocumentModelAccessProvider getEffectiveAccessProvider() {
492     DocumentModelAccessProvider p = (DocumentModelAccessProvider)
493         getModelSource().getLookup().lookup(DocumentModelAccessProvider.class);
494     return p == null ? getAccessProvider() : p;
495     }
496     
497     public static DocumentModelAccessProvider getAccessProvider() {
498         DocumentModelAccessProvider provider = (DocumentModelAccessProvider)
499             Lookup.getDefault().lookup(DocumentModelAccessProvider.class);
500         if (provider == null) {
501             return ReadOnlyAccess.Provider.getInstance();
502         }
503         return provider;
504     }
505     
506     /**
507      * Set the identifying attributes for underlying access to merge.
508      */

509     protected void setIdentifyingAttributes() {
510         ElementIdentity eid = getAccess().getElementIdentity();
511         eid.addIdentifier("id");
512         eid.addIdentifier("name");
513         eid.addIdentifier("ref");
514     }
515
516     protected boolean isDomainElement(Node JavaDoc e) {
517         if (! (e instanceof Element JavaDoc)) {
518             return false;
519         }
520         
521         QName JavaDoc q = new QName JavaDoc(e.getNamespaceURI(), e.getLocalName());
522         return getQNames().contains(q) || getElementNames().contains(q.getLocalPart());
523     }
524     
525     @Override JavaDoc
526     protected void refresh() {
527         Document JavaDoc lastStable = null;
528         try {
529             lastStable = getDocument();
530         } catch(Exception JavaDoc ex) {
531             // document is not available when underlying model is broken
532
}
533         if (lastStable != null && lastStable.getDocumentElement() != null) {
534             createRootComponent(lastStable.getDocumentElement());
535             setState(State.VALID);
536         }
537     }
538     
539     /**
540      * Returns QName of all attributes with QName value, sorted by containing
541      * element QName.
542      * Note: if domain model implementation return null, namespace
543      * consolidation will not attempt namespace prefix refactoring on each
544      * mutation of the underlying XDM DOM tree.
545      */

546     public Map JavaDoc<QName JavaDoc,List JavaDoc<QName JavaDoc>> getQNameValuedAttributes() {
547         return new HashMap JavaDoc<QName JavaDoc,List JavaDoc<QName JavaDoc>>();
548     }
549 }
550
551
552
Popular Tags