001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.xml.xam.dom;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.io.IOException;
047: import java.util.Collection;
048: import java.util.List;
049: import java.util.Map;
050: import javax.swing.text.BadLocationException;
051: import javax.xml.namespace.QName;
052: import org.netbeans.modules.xml.xam.ModelAccess;
053: import org.w3c.dom.CDATASection;
054: import org.w3c.dom.Comment;
055: import org.w3c.dom.Document;
056: import org.w3c.dom.Element;
057: import org.w3c.dom.Node;
058: import org.w3c.dom.ProcessingInstruction;
059:
060: /**
061: * Access to the underlying structure of the model.
062: *
063: * @author Nam Nguyen
064: */
065:
066: public abstract class DocumentModelAccess extends ModelAccess {
067:
068: public interface NodeUpdater {
069: void updateReference(Element node);
070:
071: <T extends Node> void updateReference(List<T> pathToRoot);
072: }
073:
074: public abstract Document getDocumentRoot();
075:
076: /**
077: * Returns the associated document model.
078: * Subclass should override.
079: */
080: public AbstractDocumentModel getModel() {
081: return null;
082: }
083:
084: public abstract boolean areSameNodes(Node n1, Node n2);
085:
086: /**
087: * @return child element index in the children list of given parent.
088: */
089: public abstract int getElementIndexOf(Node parent, Element child);
090:
091: public abstract void setAttribute(Element element, String name,
092: String value, NodeUpdater updater);
093:
094: public abstract void removeAttribute(Element element, String name,
095: NodeUpdater updater);
096:
097: public abstract void appendChild(Node node, Node newChild,
098: NodeUpdater updater);
099:
100: public abstract void insertBefore(Node node, Node newChild,
101: Node refChild, NodeUpdater updater);
102:
103: public abstract void removeChild(Node node, Node child,
104: NodeUpdater updater);
105:
106: public void removeChildren(Node node, Collection<Node> children,
107: NodeUpdater updater) {
108: throw new UnsupportedOperationException();
109: }
110:
111: public abstract void replaceChild(Node node, Node child,
112: Node newChild, NodeUpdater updater);
113:
114: public abstract void setText(Element element, String val,
115: NodeUpdater updater);
116:
117: public abstract void setPrefix(org.w3c.dom.Element node,
118: String prefix);
119:
120: public abstract int findPosition(org.w3c.dom.Node node);
121:
122: public abstract Element getContainingElement(int position);
123:
124: public abstract Element duplicate(Element element);
125:
126: /**
127: * Reorder children list of an element.
128: * @param element the parent element with children to be reordered
129: * @param newIndexes array indexed by existing indexes, with values representing reordered indexes
130: * @param updater the component wrapper of the given element.
131: */
132: public void reorderChildren(Element element, int[] newIndexes,
133: NodeUpdater updater) {
134: throw new UnsupportedOperationException(
135: "Not support yet by this DocumentModelAccess");
136: }
137:
138: public String getXmlFragmentInclusive(Element element) {
139: if (getModel() == null) {
140: throw new UnsupportedOperationException(
141: "Unavailable because access does not support getModel()");
142: }
143: DocumentComponent component = getModel().findComponent(element);
144: if (component == null) {
145: throw new IllegalArgumentException("Know nothing about '"
146: + element.getTagName() + "'");
147: }
148: Node parent = component.getParent() == null ? getModel()
149: .getDocument() : ((DocumentComponent) component
150: .getParent()).getPeer();
151:
152: int end = -1;
153: int start = findPosition(element);
154: assert start > -1 : "Negative start position";
155: Node next = element.getNextSibling();
156: try {
157: javax.swing.text.Document doc = getModel()
158: .getBaseDocument();
159: StringBuilder sb = new StringBuilder(doc.getText(0, doc
160: .getLength() - 1));
161: if (parent instanceof Document) {
162: assert ((Document) parent).getDocumentElement() == element;
163: end = sb.lastIndexOf(">") + 1;
164: } else if (next == null) { // use parent end tag
165: end = sb.indexOf(parent.getNodeName(), start) - 2;
166: } else if (next instanceof Element) {
167: end = findPosition(next);
168: } else {
169: while (next != null && !(next instanceof Element)
170: && !(next instanceof CDATASection)
171: && !(next instanceof ProcessingInstruction)
172: && !(next instanceof Comment)) {
173: next = next.getNextSibling();
174: }
175: if (next instanceof Element) {
176: end = findPosition(next);
177: } else if (next instanceof CDATASection
178: || next instanceof Comment
179: || next instanceof ProcessingInstruction) {
180: end = sb.indexOf(next.getNodeValue(), start);
181: } else {
182: end = sb
183: .indexOf("</" + parent.getNodeName(), start);
184: }
185: }
186:
187: String result = sb.substring(start, end);
188: end = result.lastIndexOf("</" + element.getNodeName());
189: if (end < 0) { // self-closing
190: end = result.indexOf(">") + 1;
191: } else {
192: end = result.indexOf(">", end) + 1;
193: }
194: return result.substring(0, end);
195: } catch (BadLocationException ble) {
196: assert false : "start=" + start + " end=" + end;
197: return "";
198: }
199: }
200:
201: /**
202: * @return XML fragment text of the given element content.
203: */
204: public abstract String getXmlFragment(Element element);
205:
206: public String getCurrentDocumentText() {
207: throw new IllegalArgumentException();
208: }
209:
210: /**
211: * Sets the XML fragment text for given element content.
212: * The XML fragment will be parsed and the resulting nodes will
213: * replace the current children of this documentation element.
214: * @param element element to set content to.
215: * @param text XML fragment text.
216: * @exception IOException if the fragment text is not well-form.
217: */
218: public abstract void setXmlFragment(Element element, String text,
219: NodeUpdater updater) throws IOException;
220:
221: /**
222: * Returns map of attribute names and string values.
223: */
224: public abstract Map<QName, String> getAttributeMap(Element element);
225:
226: /**
227: * Returns path from given element to given root; or null if the node is not in tree.
228: */
229: public abstract List<Element> getPathFromRoot(Document root,
230: Element node);
231:
232: /**
233: * Returns xpath expression of given element.
234: */
235: public abstract String getXPath(Document root, Element node);
236:
237: /**
238: * Provide a uniform return value for undefined attribute values.
239: * XDM supports full fidelty so this deviates slightly from the DOM
240: * specification in that the return value for an undefined attribute
241: * is null instead of "". This method normalizes the return value
242: * for an undefined element to null.
243: */
244: public String normalizeUndefinedAttributeValue(String value) {
245: return value;
246: }
247:
248: /**
249: * Returns node from given xpath expression
250: */
251: public abstract Node findNode(Document root, String xpath);
252:
253: /**
254: * Returns nodes from given xpath expression
255: */
256: public abstract List<Node> findNodes(Document root, String xpath);
257:
258: /**
259: * Returns element identity helper.
260: */
261: public abstract ElementIdentity getElementIdentity();
262:
263: /**
264: * Add/remove merge property change listener.
265: */
266: public abstract void addMergeEventHandler(PropertyChangeListener l);
267:
268: public abstract void removeMergeEventHandler(
269: PropertyChangeListener l);
270:
271: public abstract Node getOldEventNode(PropertyChangeEvent evt);
272:
273: public abstract Node getOldEventParentNode(PropertyChangeEvent evt);
274:
275: public abstract Node getNewEventNode(PropertyChangeEvent evt);
276:
277: public abstract Node getNewEventParentNode(PropertyChangeEvent evt);
278:
279: public String lookupNamespaceURI(Node node, List<Node> pathToRoot) {
280: String prefix = node.getPrefix();
281: if (prefix == null)
282: prefix = ""; //NOI18N
283: String namespace = node.lookupNamespaceURI(prefix);
284: if (namespace == null) {
285: for (Node n : pathToRoot) {
286: namespace = n.lookupNamespaceURI(prefix);
287: if (namespace != null) {
288: break;
289: }
290: }
291: }
292: return namespace;
293: }
294:
295: private long dirtyTimeMillis = 0;
296:
297: public long dirtyIntervalMillis() {
298: if (dirtyTimeMillis == 0)
299: return 0;
300: return System.currentTimeMillis() - dirtyTimeMillis;
301: }
302:
303: public void setDirty() {
304: dirtyTimeMillis = System.currentTimeMillis();
305: }
306:
307: public void unsetDirty() {
308: dirtyTimeMillis = 0;
309: }
310:
311: /**
312: * A chance for extensible model to register attributes from extension that have
313: * QName values. This will help with refactoring of namespace prefixes that happen
314: * during namespace consolidation when a new component is added to model.
315: * Note: should be overridden by implementation as necessary.
316: */
317: public void addQNameValuedAttributes(
318: Map<QName, List<QName>> attributesMap) {
319: }
320: }
|