1 19 20 package org.netbeans.modules.xml.xam.dom; 21 22 import java.beans.PropertyChangeEvent ; 23 import java.lang.ref.WeakReference ; 24 import java.util.ArrayList ; 25 import java.util.Collections ; 26 import java.util.HashMap ; 27 import java.util.HashSet ; 28 import java.util.List ; 29 import java.util.Map ; 30 import java.util.Set ; 31 import javax.swing.event.DocumentEvent ; 32 import javax.swing.event.DocumentListener ; 33 import javax.xml.namespace.QName ; 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 ; 43 import org.w3c.dom.Document ; 44 import org.w3c.dom.Element ; 45 import org.w3c.dom.Node ; 46 47 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 docListener; 58 private javax.swing.text.Document swingDocument; 59 60 public AbstractDocumentModel(ModelSource source) { 61 super(source); 62 docListener = new DocumentChangeListener(); 63 } 64 65 public javax.swing.text.Document getBaseDocument() { 66 return (javax.swing.text.Document ) 67 getModelSource().getLookup().lookup(javax.swing.text.Document .class); 68 } 69 70 public abstract T createRootComponent(Element root); 71 72 public boolean areSameNodes(Node n1, Node n2) { 73 return getAccess().areSameNodes(n1, n2); 74 } 75 76 83 public Set <QName > getQNames() { 84 return Collections.emptySet(); 85 } 86 87 @Override 88 protected boolean needsSync() { 89 javax.swing.text.Document lastDoc = swingDocument; 90 javax.swing.text.Document currentDoc = (javax.swing.text.Document ) 91 getModelSource().getLookup().lookup(javax.swing.text.Document .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 104 protected void syncStarted() { 105 needsSync = false; 106 getAccess().unsetDirty(); 107 } 108 109 @Override 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 { 123 124 public WeakDocumentListener(DocumentListener delegate, 125 javax.swing.text.Document source) { 126 this.source = source; 127 this.delegate = new WeakReference <DocumentListener >(delegate); 128 } 129 130 private DocumentListener getDelegate() { 131 DocumentListener l = delegate.get(); 132 if (l == null) { 133 source.removeDocumentListener(this); 134 } 135 136 return l; 137 } 138 139 public void removeUpdate(DocumentEvent e) { 140 DocumentListener l = getDelegate(); 141 if (l != null) { 142 l.removeUpdate(e); 143 } 144 } 145 146 public void changedUpdate(DocumentEvent e) { 147 DocumentListener l = getDelegate(); 148 if (l != null) { 149 l.changedUpdate(e); 150 } 151 } 152 153 public void insertUpdate(DocumentEvent e) { 154 DocumentListener l = getDelegate(); 155 if (l != null) { 156 l.insertUpdate(e); 157 } 158 } 159 160 private javax.swing.text.Document source; 161 private WeakReference <DocumentListener > delegate; 162 } 163 164 private class DocumentChangeListener implements DocumentListener { 165 public void removeUpdate(DocumentEvent e) { 166 documentChanged(); 167 } 168 169 public void insertUpdate(DocumentEvent e) { 170 documentChanged(); 171 } 172 173 public void changedUpdate(DocumentEvent e) { 174 } 177 } 178 179 protected abstract ComponentUpdater<T> getComponentUpdater(); 180 181 184 private Set <String > elementNames = null; 185 public Set <String > getElementNames() { 186 if (elementNames == null) { 187 elementNames = new HashSet <String >(); 188 Set <QName > qnames = getQNames(); 189 for (QName q : qnames) { 190 elementNames.add(q.getLocalPart()); 191 } 192 } 193 return elementNames; 194 } 195 196 public ChangeInfo prepareChangeInfo(List <Node > pathToRoot) { 197 if (pathToRoot.size() < 1) { 199 throw new IllegalArgumentException ("pathToRoot here should be at least 1"); 200 } 201 if (pathToRoot.get(pathToRoot.size()-1) instanceof Document ) { 202 pathToRoot.remove(pathToRoot.size()-1); 203 } 204 205 if (pathToRoot.size() < 2) { 206 throw new IllegalArgumentException ("pathToRoot here should be at least 2"); 207 } 208 Node current = null; 209 Element parent = null; 210 boolean changedIsDomainElement = true; 211 Set <QName > qnames = getQNames(); 212 Set <String > enames = getElementNames(); 213 if (qnames != null && qnames.size() > 0) { 214 for (int i=0; i<pathToRoot.size(); i++) { 215 Node n = pathToRoot.get(i); 216 if (! (n instanceof Element )) { 217 changedIsDomainElement = false; 218 continue; 219 } 220 221 QName q = new QName (getAccess().lookupNamespaceURI(n, pathToRoot), n.getLocalName()); 222 if (qnames.contains(q)) { 223 current = n; 224 if (i+1 < pathToRoot.size()) { 225 parent = (Element ) pathToRoot.get(i+1); 226 } 227 break; 228 } else if (changedIsDomainElement == true) { 229 changedIsDomainElement = false; 230 } 231 } 232 } else { 233 Node n = pathToRoot.get(0); 234 if (n instanceof Element ) { 235 current = n; 236 parent = (Element ) pathToRoot.get(1); 237 } else { 238 current = pathToRoot.get(1); 239 if (pathToRoot.size() > 2) { 240 parent = (Element ) 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 ("pathToRoot does not contain element"); 250 } 251 parent = (Element ) current; 252 current = pathToRoot.get(i-1); 253 } 254 255 List <Element > rootToParent = new ArrayList <Element >(); 256 if (parent != null) { 257 for (int i = pathToRoot.indexOf(parent); i<pathToRoot.size(); i++) { 258 rootToParent.add(0, (Element )pathToRoot.get(i)); 259 } 260 } 261 262 List <Node > otherNodes = new ArrayList <Node >(); 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 ("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 ("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 oldElement) { 318 Set <String > propertyNames = new HashSet (unit.getRemovedAttributes().keySet()); 319 propertyNames.addAll(unit.getAddedAttributes().keySet()); 320 for (String name : propertyNames) { 321 Attr oldAttr = unit.getRemovedAttributes().get(name); 322 Attr newAttr = unit.getAddedAttributes().get(name); 323 super.firePropertyChangeEvent( 324 new PropertyChangeEvent ( 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 ( 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 ("sync unit should not be null"); 342 } 343 if (! targetComponent.isInDocumentModel()) { 345 return; 346 } 347 348 Element 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 childElement = (Element ) ((AbstractDocumentComponent)c).getPeer(); 364 int index = targetComponent.findDomainIndex(childElement); 365 addChildComponent(targetComponent, c, index); 366 } 367 } 368 369 private DocumentComponent createChildComponent(DocumentComponent parent, Element e) { 370 DocumentModel m = (DocumentModel) parent.getModel(); 371 if (m == null) { 372 throw new IllegalArgumentException ("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 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 if (m == null) return; 391 m.getComponentUpdater().update(child.getParent(), child, ComponentUpdater.Operation.REMOVE); 392 } 393 394 public DocumentComponent findComponent(Element e) { 395 return findComponent((AbstractDocumentComponent) getRootComponent(), e); 396 } 397 398 private DocumentComponent findComponent(DocumentComponent searchRoot, Element e) { 399 if (searchRoot.referencesSameNode(e)) { 400 return searchRoot; 401 } 402 for (Object 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 422 public DocumentComponent findComponent(List <Element > pathFromRoot) { 423 return findComponent((AbstractDocumentComponent)getRootComponent(), pathFromRoot, 0); 424 } 425 426 public AbstractDocumentComponent findComponent(AbstractDocumentComponent base, List <Element > pathFromRoot, int current) { 427 if (pathFromRoot == null || pathFromRoot.size() <= current) { 428 return null; 429 } 430 Element e = pathFromRoot.get(current); 431 if (base.referencesSameNode(e)) { 432 if (pathFromRoot.size() == current + 1) { 433 base.getChildren(); return base; 435 } else { 436 for (Object 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 e = (Element ) getAccess().getContainingElement(position); 454 if (e == null) { 455 return getRootComponent(); 456 } 457 458 List <Element > pathFromRoot = null; 459 try { 460 pathFromRoot = getAccess().getPathFromRoot(this.getDocument(), e); 461 } catch(UnsupportedOperationException ex) { 462 } 464 if (pathFromRoot == null || pathFromRoot.isEmpty()) { 465 return findComponent(e); 466 } else { 467 return findComponent(pathFromRoot); 468 } 469 } 470 471 public String getXPathExpression(DocumentComponent component) { 472 Element e = (Element ) component.getPeer(); 473 return getAccess().getXPath(getDocument(), e); 474 } 475 476 public org.w3c.dom.Document 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 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 e) { 517 if (! (e instanceof Element )) { 518 return false; 519 } 520 521 QName q = new QName (e.getNamespaceURI(), e.getLocalName()); 522 return getQNames().contains(q) || getElementNames().contains(q.getLocalPart()); 523 } 524 525 @Override 526 protected void refresh() { 527 Document lastStable = null; 528 try { 529 lastStable = getDocument(); 530 } catch(Exception ex) { 531 } 533 if (lastStable != null && lastStable.getDocumentElement() != null) { 534 createRootComponent(lastStable.getDocumentElement()); 535 setState(State.VALID); 536 } 537 } 538 539 546 public Map <QName ,List <QName >> getQNameValuedAttributes() { 547 return new HashMap <QName ,List <QName >>(); 548 } 549 } 550 551 552 | Popular Tags |