001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Max Stepanov 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.ecm.platform.domsync.core; 021 022import java.util.ArrayList; 023import java.util.List; 024 025import org.nuxeo.ecm.platform.domsync.core.events.DOMAttrModifiedEvent; 026import org.nuxeo.ecm.platform.domsync.core.events.DOMCharacterDataModifiedEvent; 027import org.nuxeo.ecm.platform.domsync.core.events.DOMMutationEvent; 028import org.nuxeo.ecm.platform.domsync.core.events.DOMNodeInsertedEvent; 029import org.nuxeo.ecm.platform.domsync.core.events.DOMNodeRemovedEvent; 030import org.w3c.dom.Attr; 031import org.w3c.dom.CharacterData; 032import org.w3c.dom.Document; 033import org.w3c.dom.Element; 034import org.w3c.dom.NamedNodeMap; 035import org.w3c.dom.Node; 036import org.w3c.dom.ProcessingInstruction; 037import org.w3c.dom.Text; 038import org.w3c.dom.events.Event; 039import org.w3c.dom.events.EventListener; 040import org.w3c.dom.events.MutationEvent; 041 042/** 043 * @author Max Stepanov 044 */ 045public class DOMSynchronizer implements EventListener, IDOMMutationListener { 046 047 private static final String DOM_SUBTREE_MODIFIED = "DOMSubtreeModified"; 048 049 private static final String DOM_NODE_INSERTED = "DOMNodeInserted"; 050 051 private static final String DOM_NODE_REMOVED = "DOMNodeRemoved"; 052 053 private static final String DOM_NODE_REMOVED_FROM_DOCUMENT = "DOMNodeRemovedFromDocument"; 054 055 private static final String DOM_NODE_INSERTED_INTO_DOCUMENT = "DOMNodeInsertedIntoDocument"; 056 057 private static final String DOM_ATTR_MODIFIED = "DOMAttrModified"; 058 059 private static final String DOM_CHARACTER_DATA_MODIFIED = "DOMCharacterDataModified"; 060 061 private final Document document; 062 063 private final IDOMSupport domSupport; 064 065 private int dispatchLevel; 066 067 private DOMMutationEvent currentEvent; /* for debug/test purposes */ 068 069 private final List<IDOMMutationListener> listeners = new ArrayList<IDOMMutationListener>(); 070 071 public DOMSynchronizer(Document document, IDOMSupport domSupport) { 072 this.document = document; 073 this.domSupport = domSupport; 074 } 075 076 /** 077 * Document event handler 078 */ 079 public void handleEvent(Event evt) { 080 if (!(evt instanceof MutationEvent)) { 081 return; 082 } 083 MutationEvent event = (MutationEvent) evt; 084 String type = event.getType(); 085 086 if (DOM_CHARACTER_DATA_MODIFIED.equals(type)) { 087 Node target = (Node) event.getTarget(); 088 String newValue = event.getNewValue(); 089 dispatchEvent(new DOMCharacterDataModifiedEvent(DOMUtil.computeNodeXPath(document, target), newValue)); 090 091 } else if (DOM_NODE_INSERTED.equals(type)) { 092 Node target = event.getRelatedNode(); 093 Node insertedNode = (Node) event.getTarget(); 094 int position = DOMUtil.getNodePosition(insertedNode); 095 List<DOMNodeInsertedEvent> list = new ArrayList<DOMNodeInsertedEvent>(); 096 buildFragmentInsertedEvents(DOMUtil.computeNodeXPath(document, target), insertedNode, position, list); 097 for (DOMNodeInsertedEvent aList : list) { 098 dispatchEvent(aList); 099 } 100 101 } else if (DOM_NODE_REMOVED.equals(type)) { 102 Node target = (Node) event.getTarget(); 103 dispatchEvent(new DOMNodeRemovedEvent(DOMUtil.computeNodeXPath(document, target))); 104 105 } else if (DOM_ATTR_MODIFIED.equals(type)) { 106 Node target = (Node) event.getTarget(); 107 dispatchEvent(new DOMAttrModifiedEvent(DOMUtil.computeNodeXPath(document, target), event.getAttrName(), 108 event.getAttrChange(), event.getNewValue())); 109 110 } else { 111 System.err.println("!Unsupported event type " + type); 112 } 113 } 114 115 private static void buildFragmentInsertedEvents(String baseXPath, Node node, int position, 116 List<DOMNodeInsertedEvent> list) { 117 if (node instanceof Text) { 118 list.add(new DOMNodeInsertedEvent(baseXPath, "#text" + ((Text) node).getData(), position)); 119 } else if (node instanceof Element) { 120 list.add(new DOMNodeInsertedEvent(baseXPath, DOMUtil.getElementOuterNoChildren((Element) node), position)); 121 if (node.hasChildNodes()) { 122 baseXPath += DOMUtil.computeNodeXPath(node.getParentNode(), node); 123 node = node.getFirstChild(); 124 position = 0; 125 while (node != null) { 126 buildFragmentInsertedEvents(baseXPath, node, position, list); 127 node = node.getNextSibling(); 128 ++position; 129 } 130 } 131 } else { 132 System.err.println("!Unsupported node type"); 133 } 134 } 135 136 private void dispatchEvent(DOMMutationEvent event) { 137 if (dispatchLevel != 0) { 138 if (!event.equals(currentEvent)) { 139 System.err.println("Events don't match"); 140 System.err.println("original " + currentEvent); 141 System.err.println("generated " + event); 142 } 143 return; 144 } 145 IDOMMutationListener[] list = listeners.toArray(new IDOMMutationListener[listeners.size()]); 146 for (IDOMMutationListener listener : list) { 147 listener.handleEvent(event); 148 } 149 } 150 151 public void addMutationListener(IDOMMutationListener listener) { 152 if (!listeners.contains(listener)) { 153 listeners.add(listener); 154 } 155 } 156 157 public void removeMutationListener(IDOMMutationListener listener) { 158 listeners.remove(listener); 159 } 160 161 /** 162 * External mutation event handler 163 */ 164 public void handleEvent(DOMMutationEvent event) { 165 currentEvent = event; 166 ++dispatchLevel; 167 try { 168 Node target = DOMUtil.findNodeByXPath(document, event.getTarget()); 169 if (target == null) { 170 System.err.println("!Null target for " + event.getTarget()); 171 return; 172 } 173 174 if (event instanceof DOMNodeInsertedEvent) { 175 if (!(target instanceof Element) && !(target instanceof Document)) { 176 System.err.println("!Unsupported target node type"); 177 return; 178 } 179 int position = ((DOMNodeInsertedEvent) event).getPosition(); 180 String fragment = ((DOMNodeInsertedEvent) event).getFragment(); 181 Node docFragment; 182 if (fragment.startsWith("#text")) { 183 docFragment = document.createTextNode(fragment.substring(5)); 184 } else { 185 docFragment = domSupport.createContextualFragment(target, fragment); 186 } 187 Node nodeBefore = DOMUtil.getNodeAtPosition(target, position); 188 if (nodeBefore != null) { 189 target.insertBefore(docFragment, nodeBefore); 190 } else { 191 target.appendChild(docFragment); 192 } 193 194 } else if (event instanceof DOMNodeRemovedEvent) { 195 if (!(target instanceof Element) && !(target instanceof CharacterData) 196 && !(target instanceof ProcessingInstruction)) { 197 System.err.println("!Unsupported target node type"); 198 return; 199 } 200 target.getParentNode().removeChild(target); 201 202 } else if (event instanceof DOMAttrModifiedEvent) { 203 if (!(target instanceof Element)) { 204 System.err.println("!Unsupported target node type"); 205 return; 206 } 207 String attrName = ((DOMAttrModifiedEvent) event).getAttrName(); 208 short attrChange = ((DOMAttrModifiedEvent) event).getAttrChange(); 209 String newValue = ((DOMAttrModifiedEvent) event).getNewValue(); 210 NamedNodeMap attrs = target.getAttributes(); 211 if (attrChange == MutationEvent.REMOVAL) { 212 attrs.removeNamedItem(attrName); 213 } else { 214 Attr attr = (Attr) attrs.getNamedItem(attrName); 215 if (attr != null) { 216 attr.setValue(newValue); 217 } else { 218 attr = document.createAttribute(attrName); 219 attr.setValue(newValue); 220 attrs.setNamedItem(attr); 221 } 222 } 223 224 } else if (event instanceof DOMCharacterDataModifiedEvent) { 225 String data = ((DOMCharacterDataModifiedEvent) event).getNewValue(); 226 if (target instanceof CharacterData) { 227 ((CharacterData) target).setData(data); 228 } else if (target instanceof ProcessingInstruction) { 229 ((ProcessingInstruction) target).setData(data); 230 } else { 231 System.err.println("!Unsupported target node type"); 232 } 233 234 } else { 235 System.err.println("!Unsupported event " + event); 236 } 237 } finally { 238 --dispatchLevel; 239 } 240 } 241 242}