XMLMemento.java :  » IDE-Eclipse » ui-workbench » org » eclipse » ui » Java Open Source

Java Open Source » IDE Eclipse » ui workbench 
ui workbench » org » eclipse » ui » XMLMemento.java
/*******************************************************************************
 * Copyright (c) 2000, 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * 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.Writer;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
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.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.InputSource;
import org.xml.sax.SAXException;

/**
 * This class represents the default implementation of the
 * <code>IMemento</code> interface.
 * <p>
 * This class is not intended to be extended by clients.
 * </p>
 *
 * @see IMemento
 */
public final class XMLMemento implements IMemento {
    private Document factory;

    private Element element;

    /**
     * Creates a <code>Document</code> from the <code>Reader</code>
     * and returns a memento on the first <code>Element</code> for reading
     * the document.
     * <p>
     * Same as calling createReadRoot(reader, null)
     * </p>
     * 
     * @param reader the <code>Reader</code> used to create the memento's document
     * @return a memento on the first <code>Element</code> 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 <code>Document</code> from the <code>Reader</code>
     * and returns a memento on the first <code>Element</code> for reading
     * the document.
     * 
     * @param reader the <code>Reader</code> 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 <code>null</code> if not
     *     needed.
     * @return a memento on the first <code>Element</code> 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 {
            DocumentBuilderFactory factory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder parser = factory.newDocumentBuilder();
            InputSource source = new InputSource(reader);
            if (baseDir != null) {
        source.setSystemId(baseDir);
      }
            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.length() == 0) {
      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
     */
    public static XMLMemento createWriteRoot(String type) {
        Document document;
        try {
            document = DocumentBuilderFactory.newInstance()
                    .newDocumentBuilder().newDocument();
            Element element = document.createElement(type);
            document.appendChild(element);
            return new XMLMemento(document, element);
        } catch (ParserConfigurationException e) {
//            throw new Error(e);
            throw new Error(e.getMessage());
        }
    }

    /**
     * Creates a memento for the specified document and element.
     * <p>
     * Clients should use <code>createReadRoot</code> and
     * <code>createWriteRoot</code> to create the initial
     * memento on a document.
     * </p>
     * 
     * @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;
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public IMemento createChild(String type) {
        Element child = factory.createElement(type);
        element.appendChild(child);
        return new XMLMemento(factory, child);
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public IMemento createChild(String type, String id) {
        Element child = factory.createElement(type);
        child.setAttribute(TAG_ID, id == null ? "" : id); //$NON-NLS-1$
        element.appendChild(child);
        return new XMLMemento(factory, child);
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public IMemento copyChild(IMemento child) {
        Element childElement = ((XMLMemento) child).element;
        Element newElement = (Element) factory.importNode(childElement, true);
        element.appendChild(newElement);
        return new XMLMemento(factory, newElement);
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    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;
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    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, (Element) list.get(x));
        }
        return results;
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public Float getFloat(String key) {
        Attr attr = element.getAttributeNode(key);
        if (attr == null) {
      return null;
    }
        String strValue = attr.getValue();
        try {
            return new Float(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
   */
  public String getType() {
    return element.getNodeName();
  }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public String getID() {
        return element.getAttribute(TAG_ID);
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public Integer getInteger(String key) {
        Attr attr = element.getAttributeNode(key);
        if (attr == null) {
      return null;
    }
        String strValue = attr.getValue();
        try {
            return new Integer(strValue);
        } catch (NumberFormatException e) {
            WorkbenchPlugin
                    .log("Memento problem - invalid integer for key: " + key //$NON-NLS-1$
                            + " value: " + strValue, e); //$NON-NLS-1$
            return null;
        }
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public String getString(String key) {
        Attr attr = element.getAttributeNode(key);
        if (attr == null) {
      return null;
    }
        return attr.getValue();
    }

  /**
   * @since 3.4
   */
  public Boolean getBoolean(String key) {
        Attr attr = element.getAttributeNode(key);
        if (attr == null) {
      return null;
    }
        return Boolean.valueOf(attr.getValue());
  }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public String getTextData() {
        Text textNode = getTextNode();
        if (textNode != null) {
            return textNode.getData();
        }
        return null;
    }

  /**
   * @since 3.4
   */
  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 <code>null</code> 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
     */
    private void putElement(Element element, boolean copyText) {
        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;
            }
        }
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public void putFloat(String key, float f) {
        element.setAttribute(key, String.valueOf(f));
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public void putInteger(String key, int n) {
        element.setAttribute(key, String.valueOf(n));
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public void putMemento(IMemento memento) {
      // 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);
    }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public void putString(String key, String value) {
        if (value == null) {
      return;
    }
        element.setAttribute(key, value);
    }

  /**
   * @since 3.4
   */
  public void putBoolean(String key, boolean value) {
    element.setAttribute(key, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
  }

    /* (non-Javadoc)
     * Method declared in IMemento.
     */
    public void putTextData(String data) {
        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 {
      DOMWriter out = new DOMWriter(writer);
        try {
          out.print(element);
      } finally {
        out.close();
      }
  }

  /**
     * 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 {
      
      private int tab;

      /* constants */
      private static final String XML_VERSION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; //$NON-NLS-1$

      /**
       * Creates a new DOM writer on the given output writer.
       * 
       * @param output the output writer
       */
      public DOMWriter(Writer output) {
        super(output);
        tab = 0;
        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) {
            tab++;
            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();
                printTabulation();
              }
              print((Element) children.item(i));
              prevWasText = false;
            }
            else if (node instanceof Text) {
              print(getEscaped(node.getNodeValue()));
              prevWasText = true;
            }
          }
          tab--;
          if (!prevWasText) {
            println();
            printTabulation();
          }
          endTag(element);
          }
      }

      private void printTabulation() {
          // Indenting is disabled, as it can affect the result of getTextData().
          // In 3.0, elements were separated by a newline but not indented.
        // This causes getTextData() to return "\n" even if no text data had explicitly been set.
          // The code here emulates that behaviour.
        
//        for (int i = 0; i < tab; i++)
//          super.print("\t"); //$NON-NLS-1$
      }

      private void startTag(Element element, boolean hasChildren) {
        StringBuffer sb = new StringBuffer();
        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) {
        StringBuffer sb = new StringBuffer();
        sb.append("</"); //$NON-NLS-1$
        sb.append(element.getNodeName());
        sb.append(">"); //$NON-NLS-1$
         print(sb.toString());
      }
      
      private static void appendEscapedChar(StringBuffer 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) {
        StringBuffer result = new StringBuffer(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;
      }
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.