OdfXMLFactory.java

/**
 * **********************************************************************
 *
 * <p>DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * <p>Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * <p>Use is subject to license terms.
 *
 * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0. You can also obtain a copy of the License at
 * http://odftoolkit.org/docs/license.txt
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 *
 * <p>See the License for the specific language governing permissions and limitations under the
 * License.
 *
 * <p>**********************************************************************
 */

/*
 * This file is automatically generated.
 * Don't edit manually.
 */
package org.odftoolkit.odfdom.pkg;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.w3c.dom.DOMException;

/**
 * This factory determines what elements are being used in the DOC layer (i.e. the convenient
 * layer).
 *
 * <p>The mapping of ODF element to convenient class can be changed from the user during run time.
 *
 * <p>For example, a user might want to create a table always with a certain style or default data
 * and might want to overwrite the mapping for <code>{odf.element table:table}</code>, that a
 * different class instead of <code>OdfTable</code> is being used.
 */
public class OdfXMLFactory {

  private static Map<OdfName, Class> mElementTypes = new HashMap<OdfName, Class>();
  private static Map<OdfName, Class> mAttributeTypes = new HashMap<OdfName, Class>();
  private static Map<String, String> mElementRenames = new HashMap<String, String>();
  // a set for the element which need to load a none generated manual written class
  private static Set<String> mHandwrittenElementClasses = new HashSet<String>();
  private static final String LOCAL_NAME_DELIMITER = "-";
  private static final String ELEMENT_NAME_DELIMITER = ":";
  private static final String ELEMENT_PACKAGE_NAME = "element";
  private static final String ATTRIBUTE_PACKAGE_NAME = "attribute";

  static {
    mElementRenames.put("text:h", "text:heading");
    mElementRenames.put("text:p", "text:paragraph");

    mHandwrittenElementClasses.add("draw:frame");
    mHandwrittenElementClasses.add("draw:image");
    mHandwrittenElementClasses.add("number:currency-style");
    mHandwrittenElementClasses.add("number:date-style");
    mHandwrittenElementClasses.add("number:percentage-style");
    mHandwrittenElementClasses.add("number:number-style");
    mHandwrittenElementClasses.add("number:time-style");
    // Starting Refactoring. Goal: using XML DOM classes with sets for styles instead of own layer
    // mHandwrittenElements.add("office:automatic-styles");
    // mHandwrittenElements.add("office:master-styles");
    // mHandwrittenElements.add("office:styles");
    mHandwrittenElementClasses.add("style:default-style");
    mHandwrittenElementClasses.add("style:style");
    mHandwrittenElementClasses.add("style:page-layout");
    mHandwrittenElementClasses.add("text:h");
    mHandwrittenElementClasses.add("text:list");
    mHandwrittenElementClasses.add("text:list-level-style-bullet");
    mHandwrittenElementClasses.add("text:list-level-style-image");
    mHandwrittenElementClasses.add("text:list-level-style-number");
    mHandwrittenElementClasses.add("text:list-style");
    mHandwrittenElementClasses.add("text:outline-level-style");
    mHandwrittenElementClasses.add("text:outline-style");
    mHandwrittenElementClasses.add("text:p");
    mHandwrittenElementClasses.add("text:span");
  }

  /**
   * @param odfName the name of the ODF attribute the desired DOM class should represent.
   * @return the Java DOM attribute class to be mapped to a certain ODF attribute.
   */
  private static Class getOdfAttributeClass(OdfName odfName) {
    return getOdfNodeClass(odfName, ATTRIBUTE_PACKAGE_NAME, mAttributeTypes);
  }

  /**
   * @param odfName the name of the ODF element the desired DOM class should represent.
   * @return the Java DOM element class to be mapped to a certain ODF element.
   */
  private static Class getOdfElementClass(OdfName odfName) {
    return getOdfNodeClass(odfName, ELEMENT_PACKAGE_NAME, mElementTypes);
  }

  private static Class getOdfNodeClass(
      OdfName odfName, String nodeType, Map<OdfName, Class> classCache) {
    Class c = null;
    String className = "";
    c = classCache.get(odfName);
    if (c == null) {
      String prefix = odfName.getPrefix();
      if (prefix != null && !(nodeType.equals(ATTRIBUTE_PACKAGE_NAME) && prefix.equals("xmlns"))) {
        String qName = odfName.getQName();
        String localName = odfName.getLocalName();
        // judge whether the element need to load class from incubator package.
        if (mHandwrittenElementClasses.contains(qName)) {
          // judge whether the element need to rename before find class name.
          if (mElementRenames.containsKey(qName)) {
            String renameName = mElementRenames.get(qName);
            StringTokenizer stok = new StringTokenizer(renameName, ELEMENT_NAME_DELIMITER);
            prefix = stok.nextToken();
            localName = stok.nextToken();
          }
          className = getOdfIncubatorNodeClassName(prefix, localName);
        } else if ("manifest".equals(prefix)) {
          className = getOdfPKGNodeClassName(prefix, localName, nodeType);
        } else {
          className = getOdfDOMNodeClassName(prefix, localName, nodeType);
        }
        try {
          c = Class.forName(className);
          classCache.put(odfName, c);
        } catch (ClassNotFoundException ex) {
          // all classes are first tring to load and warning is given later
        } catch (NoClassDefFoundError dex) {
          Logger.getLogger(OdfXMLFactory.class.getName())
              .log(Level.FINER, "NoClassDefFoundError: " + className, dex.getMessage());
        }
      }
    }
    return c;
  }

  private static String getOdfIncubatorNodeClassName(String prefix, String localName) {
    boolean contains = false;
    StringBuilder className = new StringBuilder();

    if (localName.indexOf(LOCAL_NAME_DELIMITER) != -1) {
      StringTokenizer stok = new StringTokenizer(localName, LOCAL_NAME_DELIMITER);
      while (stok.hasMoreElements()) {
        String substr = stok.nextToken();
        if (substr.equals(prefix)) {
          contains = true;
        }
        className = className.append(toUpperCaseFirstCharacter(substr));
      }
    } else {
      className = className.append(toUpperCaseFirstCharacter(localName));
    }
    if (!((contains && !localName.endsWith("table"))
        || (localName.equals(prefix))
        || (localName.startsWith(prefix) && prefix.equals("anim")))) {
      className = className.insert(0, toUpperCaseFirstCharacter(prefix));
    }
    className = className.insert(0, "org.odftoolkit.odfdom.incubator.doc." + prefix + "." + "Odf");

    return className.toString();
  }

  private static String getOdfPKGNodeClassName(String prefix, String localName, String nodeType) {
    StringBuilder className = new StringBuilder("org.odftoolkit.odfdom.pkg." + prefix + ".");
    if (localName.indexOf(LOCAL_NAME_DELIMITER) != -1) {
      StringTokenizer stok = new StringTokenizer(localName, LOCAL_NAME_DELIMITER);
      while (stok.hasMoreElements()) {
        className = className.append(toUpperCaseFirstCharacter(stok.nextToken()));
      }
    } else {
      className = className.append(toUpperCaseFirstCharacter(localName));
    }
    className.append(toUpperCaseFirstCharacter(nodeType));
    return className.toString();
  }

  private static String getOdfDOMNodeClassName(String prefix, String localName, String nodeType) {
    StringBuilder className =
        new StringBuilder("org.odftoolkit.odfdom.dom." + nodeType + "." + prefix + ".");
    className = className.append(toUpperCaseFirstCharacter(prefix));
    if (localName.indexOf(LOCAL_NAME_DELIMITER) != -1) {
      StringTokenizer stok = new StringTokenizer(localName, LOCAL_NAME_DELIMITER);
      while (stok.hasMoreElements()) {
        className = className.append(toUpperCaseFirstCharacter(stok.nextToken()));
      }
    } else {
      className = className.append(toUpperCaseFirstCharacter(localName));
    }
    className.append(toUpperCaseFirstCharacter(nodeType));
    return className.toString();
  }

  private static String toUpperCaseFirstCharacter(String token) {
    return token.substring(0, 1).toUpperCase() + token.substring(1);
  }

  public static OdfElement newOdfElement(OdfFileDom dom, OdfName name) throws DOMException {
    OdfElement element = null;

    // lookup registered element class for qname
    Class elementClass = getOdfElementClass(name);

    // if a class was registered create an instance of that class
    if (elementClass != null) {
      element = (OdfElement) getNodeFromClass(dom, elementClass);
    } else {
      String oldPrefix = name.getPrefix();
      if (oldPrefix != null) {
        // check if the namespace prefix is correct or add potential namespace to DOM
        OdfName adaptedName = addNamespaceToDom(name, dom);
        String newPrefix = adaptedName.getPrefix();
        // in case the prefix was changed as it existed before
        if (oldPrefix != null
            && !oldPrefix.equals(newPrefix)
            // "_1" is the suffix added by OdfFileDom to an existing Namespace
            && newPrefix.indexOf("__") == -1) {
          // look up again if there is a class registered for this prefix
          element = newOdfElement(dom, adaptedName);
        } else {
          element = (OdfElement) new OdfAlienElement(dom, adaptedName);
          Logger.getLogger(OdfXMLFactory.class.getName())
              .log(Level.FINER, "None-ODF element created for {0}", adaptedName.getQName());
        }
      } else {
        element = (OdfElement) new OdfAlienElement(dom, name);
        Logger.getLogger(OdfXMLFactory.class.getName())
            .log(Level.FINER, "None-ODF element created for {0}", name.getQName());
      }
    }
    return element;
  }

  public static OdfAttribute newOdfAttribute(OdfFileDom dom, OdfName name) throws DOMException {
    OdfAttribute attr = null;

    // lookup registered attribute class for qname

    // SJ: but there exists elements & attributes having the same qName ("style:style")
    // so it is not ensured to get a OdfAttribute ... in case of "style:style" you get a
    // OdfStyle which is not a OdfAttribute :-(
    Class attributeClass = getOdfAttributeClass(name);

    // if a class was registered create an instance of that class
    if (attributeClass != null) {
      Object node = getNodeFromClass(dom, attributeClass);
      if (node instanceof OdfAttribute) {
        return (OdfAttribute) node;
      }
    }
    // add a namespace unless it is a xmlns attribute (no attr value to set the uri)
    String prefix = name.getPrefix();
    if (prefix != null && !prefix.equals("xmlns")) {
      // check if the namespace prefix is correct or add potential namespace to DOM
      OdfName adaptedName = addNamespaceToDom(name, dom);
      String newPrefix = adaptedName.getPrefix();
      // in case the prefix was changed as it existed before
      if (!prefix.equals(newPrefix) && newPrefix.indexOf("__") == -1) {
        // look up again if there is a class registered for this prefix
        attr = newOdfAttribute(dom, adaptedName);
      } else {
        attr = (OdfAttribute) new OdfAlienAttribute(dom, name);
        Logger.getLogger(OdfXMLFactory.class.getName())
            .log(Level.FINER, "None-ODF attribute created for {0}", adaptedName.getQName());
      }
    } else {
      // create an alien attribute for namespace attribute "xmlns:*"
      attr = (OdfAttribute) new OdfAlienAttribute(dom, name);
    }
    return attr;
  }

  private static OdfName addNamespaceToDom(OdfName name, OdfFileDom dom) {
    OdfNamespace newNS = dom.setNamespace(name.getPrefix(), name.getUri());
    return OdfName.newName(newNS, name.getLocalName());
  }

  /**
   * @param dom the XML DOM file where the node should be created on.
   * @param nodeClass being an XMLNode the Java class of the instance to be created.
   * @return an object instance of the XML node class being provided (usually an attribute or
   *     element).
   */
  static Object getNodeFromClass(OdfFileDom dom, Class nodeClass) {
    Object o = null;
    try {
      Constructor ctor = nodeClass.getConstructor(new Class[] {OdfFileDom.class});
      o = ctor.newInstance(new Object[] {dom});
    } catch (Exception cause) {
      // an exception at this point is a bug. Throw an Error
      throw new Error("ODF DOM error in attribute factory", cause);
    }
    return o;
  }
}