All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.ui.XMLMemento Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2000, 2021 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * This class represents the default implementation of the IMemento
 * interface.
 * 

* This class is not intended to be extended by clients. *

* * @see IMemento */ public final class XMLMemento implements IMemento { private Document factory; private Element element; /** * Creates a Document from the Reader and returns a * memento on the first Element for reading the document. *

* Same as calling createReadRoot(reader, null) *

* * @param reader the Reader used to create the memento's document * @return a memento on the first Element for reading the document * @throws WorkbenchException if IO problems, invalid format, or no element. */ public static XMLMemento createReadRoot(Reader reader) throws WorkbenchException { return createReadRoot(reader, null); } /** * Creates a Document from the Reader and returns a * memento on the first Element for reading the document. * * @param reader the Reader used to create the memento's document * @param baseDir the directory used to resolve relative file names in the XML * document. This directory must exist and include the trailing * separator. The directory format, including the separators, * must be valid for the platform. Can be null if * not needed. * @return a memento on the first Element for reading the document * @throws WorkbenchException if IO problems, invalid format, or no element. */ public static XMLMemento createReadRoot(Reader reader, String baseDir) throws WorkbenchException { String errorMessage = null; Exception exception = null; try { @SuppressWarnings("restriction") DocumentBuilder parser = org.eclipse.core.internal.runtime.XmlProcessorFactory .createDocumentBuilderWithErrorOnDOCTYPE(); InputSource source = new InputSource(reader); if (baseDir != null) { source.setSystemId(baseDir); } parser.setErrorHandler(new ErrorHandler() { @Override public void warning(SAXParseException exception) { // ignore } @Override public void error(SAXParseException exception) { // ignore } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } }); Document document = parser.parse(source); NodeList list = document.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { Node node = list.item(i); if (node instanceof Element) { return new XMLMemento(document, (Element) node); } } } catch (ParserConfigurationException e) { exception = e; errorMessage = WorkbenchMessages.XMLMemento_parserConfigError; } catch (IOException e) { exception = e; errorMessage = WorkbenchMessages.XMLMemento_ioError; } catch (SAXException e) { exception = e; errorMessage = WorkbenchMessages.XMLMemento_formatError; } String problemText = null; if (exception != null) { problemText = exception.getMessage(); } if (problemText == null || problemText.isEmpty()) { problemText = errorMessage != null ? errorMessage : WorkbenchMessages.XMLMemento_noElement; } throw new WorkbenchException(problemText, exception); } /** * Returns a root memento for writing a document. * * @param type the element node type to create on the document * @return the root memento for writing a document * @throws DOMException when the element could not be created for the passed * type */ public static XMLMemento createWriteRoot(String type) throws DOMException { try { @SuppressWarnings("restriction") Document document = org.eclipse.core.internal.runtime.XmlProcessorFactory.newDocumentWithErrorOnDOCTYPE(); Element element = document.createElement(type); document.appendChild(element); return new XMLMemento(document, element); } catch (ParserConfigurationException e) { throw new Error(e.getMessage()); } } /** * Creates a memento for the specified document and element. *

* Clients should use createReadRoot and * createWriteRoot to create the initial memento on a document. *

* * @param document the document for the memento * @param element the element node for the memento */ public XMLMemento(Document document, Element element) { super(); this.factory = document; this.element = element; } /** * Creates a new child of this memento with the given type. *

* The getChild and getChildren methods are used to * retrieve children of a given type. *

* * @param type the type * @return a new child memento * @see #getChild * @see #getChildren * @throws DOMException if the child cannot be created */ @Override public IMemento createChild(String type) throws DOMException { Element child = factory.createElement(type); element.appendChild(child); return new XMLMemento(factory, child); } /** * Creates a new child of this memento with the given type and id. The id is * stored in the child memento (using a special reserved key, * TAG_ID) and can be retrieved using getId. *

* The getChild and getChildren methods are used to * retrieve children of a given type. *

* * @param type the type * @param id the child id * @return a new child memento with the given type and id * @see #getID * @throws DOMException if the child cannot be created */ @Override public IMemento createChild(String type, String id) throws DOMException { Element child = factory.createElement(type); child.setAttribute(TAG_ID, id == null ? "" : id); //$NON-NLS-1$ element.appendChild(child); return new XMLMemento(factory, child); } /** * Create a copy of the child node and append it to this node. * * @param child the child to copy * @return An IMenento for the new child node. * @throws DOMException if the child cannot be created */ public IMemento copyChild(IMemento child) throws DOMException { Element childElement = ((XMLMemento) child).element; Element newElement = (Element) factory.importNode(childElement, true); element.appendChild(newElement); return new XMLMemento(factory, newElement); } @Override public IMemento getChild(String type) { // Get the nodes. NodeList nodes = element.getChildNodes(); int size = nodes.getLength(); if (size == 0) { return null; } // Find the first node which is a child of this node. for (int nX = 0; nX < size; nX++) { Node node = nodes.item(nX); if (node instanceof Element) { Element element = (Element) node; if (element.getNodeName().equals(type)) { return new XMLMemento(factory, element); } } } // A child was not found. return null; } @Override public IMemento[] getChildren() { // Get the nodes. final NodeList nodes = element.getChildNodes(); int size = nodes.getLength(); if (size == 0) { return new IMemento[0]; } // Extract each node with given type. ArrayList list = new ArrayList<>(size); for (int nX = 0; nX < size; nX++) { final Node node = nodes.item(nX); if (node instanceof Element) list.add((Element) node); } // Create a memento for each node. size = list.size(); IMemento[] results = new IMemento[size]; for (int x = 0; x < size; x++) { results[x] = new XMLMemento(factory, list.get(x)); } return results; } @Override public IMemento[] getChildren(String type) { // Get the nodes. NodeList nodes = element.getChildNodes(); int size = nodes.getLength(); if (size == 0) { return new IMemento[0]; } // Extract each node with given type. ArrayList list = new ArrayList<>(size); for (int nX = 0; nX < size; nX++) { Node node = nodes.item(nX); if (node instanceof Element) { Element element = (Element) node; if (element.getNodeName().equals(type)) { list.add(element); } } } // Create a memento for each node. size = list.size(); IMemento[] results = new IMemento[size]; for (int x = 0; x < size; x++) { results[x] = new XMLMemento(factory, list.get(x)); } return results; } @Override public Float getFloat(String key) { Attr attr = element.getAttributeNode(key); if (attr == null) { return null; } String strValue = attr.getValue(); try { return Float.valueOf(strValue); } catch (NumberFormatException e) { WorkbenchPlugin.log("Memento problem - Invalid float for key: " //$NON-NLS-1$ + key + " value: " + strValue, e); //$NON-NLS-1$ return null; } } /** * @since 3.4 */ @Override public String getType() { return element.getNodeName(); } @Override public String getID() { return element.getAttribute(TAG_ID); } @Override public Integer getInteger(String key) { Attr attr = element.getAttributeNode(key); if (attr == null) { return null; } String strValue = attr.getValue(); try { return Integer.valueOf(strValue); } catch (NumberFormatException e) { WorkbenchPlugin.log("Memento problem - invalid integer for key: " + key //$NON-NLS-1$ + " value: " + strValue, e); //$NON-NLS-1$ return null; } } @Override public String getString(String key) { Attr attr = element.getAttributeNode(key); if (attr == null) { return null; } return attr.getValue(); } /** * @since 3.4 */ @Override public Boolean getBoolean(String key) { Attr attr = element.getAttributeNode(key); if (attr == null) { return null; } return Boolean.valueOf(attr.getValue()); } /** * Returns the data of the Text node of the memento. Each memento is allowed * only one Text node. * * @return the data of the Text node of the memento, or null if the * memento has no Text node. * @since 2.0 * @throws DOMException if the text node is too big */ @Override public String getTextData() throws DOMException { Text textNode = getTextNode(); if (textNode != null) { return textNode.getData(); } return null; } /** * @since 3.4 */ @Override public String[] getAttributeKeys() { NamedNodeMap map = element.getAttributes(); int size = map.getLength(); String[] attributes = new String[size]; for (int i = 0; i < size; i++) { Node node = map.item(i); attributes[i] = node.getNodeName(); } return attributes; } /** * Returns the Text node of the memento. Each memento is allowed only one Text * node. * * @return the Text node of the memento, or null if the memento has * no Text node. */ private Text getTextNode() { // Get the nodes. NodeList nodes = element.getChildNodes(); int size = nodes.getLength(); if (size == 0) { return null; } for (int nX = 0; nX < size; nX++) { Node node = nodes.item(nX); if (node instanceof Text) { return (Text) node; } } // a Text node was not found return null; } /** * Places the element's attributes into the document. * * @param copyText true if the first text node should be copied * @throws DOMException if the attributes or children cannot be copied to this * node. */ private void putElement(Element element, boolean copyText) throws DOMException { NamedNodeMap nodeMap = element.getAttributes(); int size = nodeMap.getLength(); for (int i = 0; i < size; i++) { Attr attr = (Attr) nodeMap.item(i); putString(attr.getName(), attr.getValue()); } NodeList nodes = element.getChildNodes(); size = nodes.getLength(); // Copy first text node (fixes bug 113659). // Note that text data will be added as the first child (see putTextData) boolean needToCopyText = copyText; for (int i = 0; i < size; i++) { Node node = nodes.item(i); if (node instanceof Element) { XMLMemento child = (XMLMemento) createChild(node.getNodeName()); child.putElement((Element) node, true); } else if (node instanceof Text && needToCopyText) { putTextData(((Text) node).getData()); needToCopyText = false; } } } /** * Sets the value of the given key to the given floating point number. * * @param key the key * @param f the value * @throws DOMException if the attribute cannot be set */ @Override public void putFloat(String key, float f) throws DOMException { element.setAttribute(key, String.valueOf(f)); } /** * Sets the value of the given key to the given integer. * * @param key the key * @param n the value * @throws DOMException if the attribute cannot be set */ @Override public void putInteger(String key, int n) throws DOMException { element.setAttribute(key, String.valueOf(n)); } /** * Copy the attributes and children from memento to the receiver. * * @param memento the IMemento to be copied. * @throws DOMException if the attributes or children cannot be copied to this * node. */ @Override public void putMemento(IMemento memento) throws DOMException { // Do not copy the element's top level text node (this would overwrite the // existing text). // Text nodes of children are copied. putElement(((XMLMemento) memento).element, false); } /** * Sets the value of the given key to the given string. * * @param key the key * @param value the value * @throws DOMException if the attribute cannot be set */ @Override public void putString(String key, String value) throws DOMException { if (value == null) { return; } element.setAttribute(key, value); } /** * Sets the value of the given key to the given boolean value. * * @param key the key * @param value the value * @since 3.4 * @throws DOMException if the attribute cannot be set */ @Override public void putBoolean(String key, boolean value) throws DOMException { element.setAttribute(key, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Sets the memento's Text node to contain the given data. Creates the Text node * if none exists. If a Text node does exist, it's current contents are * replaced. Each memento is allowed only one text node. * * @param data the data to be placed on the Text node * @since 2.0 * @throws DOMException if the text node cannot be created under this node. */ @Override public void putTextData(String data) throws DOMException { Text textNode = getTextNode(); if (textNode == null) { textNode = factory.createTextNode(data); // Always add the text node as the first child (fixes bug 93718) element.insertBefore(textNode, element.getFirstChild()); } else { textNode.setData(data); } } /** * Saves this memento's document current values to the specified writer. * * @param writer the writer used to save the memento's document * @throws IOException if there is a problem serializing the document to the * stream. */ public void save(Writer writer) throws IOException { try (DOMWriter out = new DOMWriter(writer)) { out.print(element); } } @Override public String toString() { try { StringWriter writer = new StringWriter(); save(writer); return writer.toString(); } catch (IOException e) { return super.toString(); } } /** * A simple XML writer. Using this instead of the javax.xml.transform classes * allows compilation against JCL Foundation (bug 80053). */ private static final class DOMWriter extends PrintWriter { /* constants */ private static final String XML_VERSION = ""; //$NON-NLS-1$ /** * Creates a new DOM writer on the given output writer. * * @param output the output writer */ public DOMWriter(Writer output) { super(output); println(XML_VERSION); } /** * Prints the given element. * * @param element the element to print */ public void print(Element element) { // Ensure extra whitespace is not emitted next to a Text node, // as that will result in a situation where the restored text data is not the // same as the saved text data. boolean hasChildren = element.hasChildNodes(); startTag(element, hasChildren); if (hasChildren) { boolean prevWasText = false; NodeList children = element.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); if (node instanceof Element) { if (!prevWasText) { println(); } print((Element) children.item(i)); prevWasText = false; } else if (node instanceof Text) { print(getEscaped(node.getNodeValue())); prevWasText = true; } } if (!prevWasText) { println(); } endTag(element); } } private void startTag(Element element, boolean hasChildren) { StringBuilder sb = new StringBuilder(); sb.append("<"); //$NON-NLS-1$ sb.append(element.getTagName()); NamedNodeMap attributes = element.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Attr attribute = (Attr) attributes.item(i); sb.append(" "); //$NON-NLS-1$ sb.append(attribute.getName()); sb.append("=\""); //$NON-NLS-1$ sb.append(getEscaped(String.valueOf(attribute.getValue()))); sb.append("\""); //$NON-NLS-1$ } sb.append(hasChildren ? ">" : "/>"); //$NON-NLS-1$ //$NON-NLS-2$ print(sb.toString()); } private void endTag(Element element) { StringBuilder sb = new StringBuilder(); sb.append(""); //$NON-NLS-1$ print(sb.toString()); } private static void appendEscapedChar(StringBuilder buffer, char c) { String replacement = getReplacement(c); if (replacement != null) { buffer.append('&'); buffer.append(replacement); buffer.append(';'); } else if (c == 9 || c == 10 || c == 13 || c >= 32) { buffer.append(c); } } private static String getEscaped(String s) { StringBuilder result = new StringBuilder(s.length() + 10); for (int i = 0; i < s.length(); ++i) { appendEscapedChar(result, s.charAt(i)); } return result.toString(); } private static String getReplacement(char c) { // Encode special XML characters into the equivalent character references. // The first five are defined by default for all XML documents. // The next three (#xD, #xA, #x9) are encoded to avoid them // being converted to spaces on deserialization // (fixes bug 93720) switch (c) { case '<': return "lt"; //$NON-NLS-1$ case '>': return "gt"; //$NON-NLS-1$ case '"': return "quot"; //$NON-NLS-1$ case '\'': return "apos"; //$NON-NLS-1$ case '&': return "amp"; //$NON-NLS-1$ case '\r': return "#x0D"; //$NON-NLS-1$ case '\n': return "#x0A"; //$NON-NLS-1$ case '\u0009': return "#x09"; //$NON-NLS-1$ } return null; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy