OdfStylableElement.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>**********************************************************************
 */
package org.odftoolkit.odfdom.dom.element;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.OdfSchemaConstraint;
import org.odftoolkit.odfdom.dom.OdfSchemaDocument;
import org.odftoolkit.odfdom.dom.OdfStylesDom;
import org.odftoolkit.odfdom.dom.element.style.StyleStyleElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.OdfStylePropertySet;
import org.odftoolkit.odfdom.dom.style.props.OdfStyleProperty;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeAutomaticStyles;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.odftoolkit.odfdom.pkg.OdfValidationException;
import org.odftoolkit.odfdom.type.StyleName;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;

// ToDo: change modifier public to package after refactoring
public abstract class OdfStylableElement extends OdfElement implements OdfStylePropertySet {

  private static final long serialVersionUID = -7828513537641758879L;
  // ToDo: Overall StyleRefactoring: DOM Layer reaches to upper layer here...
  private OdfStyle mAutomaticStyle;
  protected OdfStyleFamily mFamily;
  protected OdfName mStyleNameAttrib;
  private OdfSchemaDocument mOdfSchemaDocument;
  boolean misAutomaticStyleSet = false;
  ErrorHandler mErrorHandler;

  /**
   * Creates a new instance of OdfElementImpl
   *
   * @param ownerDocument
   * @param name
   * @param family
   * @param styleNameAttrib
   * @throws DOMException
   */
  public OdfStylableElement(
      OdfFileDom ownerDocument, OdfName name, OdfStyleFamily family, OdfName styleNameAttrib)
      throws DOMException {
    super(ownerDocument, name.getUri(), name.getQName());
    mFamily = family;
    mStyleNameAttrib = styleNameAttrib;
    mOdfSchemaDocument = (OdfSchemaDocument) ownerDocument.getDocument();
    mErrorHandler = ownerDocument.getDocument().getPackage().getErrorHandler();
  }

  /**
   * Retrieve or create unique ODF AutomaticStyle
   *
   * @return The <code>StyleStyleElement</code> element
   */
  private StyleStyleElement createAutomaticStyle(
      Boolean createStyleName, OdfStyleFamily styleFamily) {

    if (styleFamily == null) {
      styleFamily = getStyleFamily();
    }

    if ((mAutomaticStyle == null) || (mAutomaticStyle.getStyleUserCount() > 1)) {
      // we need a new automatic style
      OdfOfficeAutomaticStyles automatic_styles = getOrCreateAutomaticStyles();
      // ToDo: Move into a createAutomaticStyles function
      if (automatic_styles == null) {
        OdfFileDom fileDom = ((OdfFileDom) this.getOwnerDocument());
        OdfOfficeAutomaticStyles newOfficeAutoStyles =
            fileDom.newOdfElement(OdfOfficeAutomaticStyles.class);
        OdfElement rootElement = fileDom.getRootElement();
        NodeList rootChildren = rootElement.getChildNodes();
        boolean hasInserted = false;
        for (int i = 0; i < rootChildren.getLength(); i++) {
          Node currentNode = rootChildren.item(i);
          if (currentNode instanceof Element) {
            String elementName = ((Element) currentNode).getNodeName();
            if (elementName.equals("office:body") || elementName.equals("office:master-styles")) {
              rootElement.insertBefore(newOfficeAutoStyles, currentNode);
              hasInserted = true;
              break;
            }
          } else {
            continue;
          }
        }
        if (!hasInserted) {
          rootElement.appendChild(newOfficeAutoStyles);
        }
        automatic_styles = newOfficeAutoStyles;
      }
      if (automatic_styles != null) {
        String styleName = getStyleName();
        String parentName = null;
        mAutomaticStyle = automatic_styles.getStyle(styleName, styleFamily);
        if (mAutomaticStyle == null) {
          mAutomaticStyle = automatic_styles.newStyle(styleFamily);
          if (!styleName.isEmpty()) {
            mAutomaticStyle.setStyleParentStyleNameAttribute(styleName);
          }
        } else {
          parentName = mAutomaticStyle.getStyleParentStyleNameAttribute();
          mAutomaticStyle.removeStyleUser(this);
          mAutomaticStyle = automatic_styles.makeStyleUnique(mAutomaticStyle);
          if (parentName != null && !parentName.isEmpty()) {
            mAutomaticStyle.setStyleParentStyleNameAttribute(parentName);
          }
        }
        mAutomaticStyle.addStyleUser(this);
        if (createStyleName) {
          setStyleName(mAutomaticStyle.getStyleNameAttribute());
        }
      }
    }
    return mAutomaticStyle;
  }

  /**
   * Retrieve or create unique ODF AutomaticStyle
   *
   * @return The <code>StyleStyleElement</code> element
   */
  public StyleStyleElement getOrCreateUnqiueAutomaticStyle() {
    return createAutomaticStyle(Boolean.TRUE, null);
  }

  /**
   * Retrieve or create unique ODF AutomaticStyle
   *
   * @return The <code>StyleStyleElement</code> element
   */
  public StyleStyleElement getOrCreateUnqiueAutomaticStyle(
      Boolean createStyleName, OdfStyleFamily styleFamily) {
    return createAutomaticStyle(createStyleName, styleFamily);
  }

  /**
   * Retrieve ODF OfficeAutomaticStyles
   *
   * @return the <code>OdfOfficeAutomaticStyles</code> element that contains the automatic style for
   *     this element. A new node will be created if not existent.
   */
  public OdfOfficeAutomaticStyles getOrCreateAutomaticStyles() {
    OdfFileDom fileDom = (OdfFileDom) this.ownerDocument;
    if (fileDom != null) {
      if (fileDom instanceof OdfContentDom) {
        return ((OdfContentDom) fileDom).getOrCreateAutomaticStyles();
      } else if (fileDom instanceof OdfStylesDom) {
        return ((OdfStylesDom) fileDom).getOrCreateAutomaticStyles();
      } else {
        // if not content.xml nor styles.xml
        return null;
      }
    } else {
      // if the element does not belong to a OdfFileDOM
      return null;
    }
  }

  /**
   * Retrieve ODF OfficeAutomaticStyles
   *
   * @return the <code>OdfOfficeAutomaticStyles</code> element that contains the automatic style for
   *     this element, or null if not available.
   */
  public OdfOfficeAutomaticStyles getAutomaticStyles() {
    OdfFileDom fileDom = (OdfFileDom) this.ownerDocument;
    if (fileDom != null) {
      if (fileDom instanceof OdfContentDom) {
        return ((OdfContentDom) fileDom).getAutomaticStyles();
      } else if (fileDom instanceof OdfStylesDom) {
        return ((OdfStylesDom) fileDom).getAutomaticStyles();
      } else {
        // if not content.xml nor styles.xml
        return null;
      }
    } else {
      // if the element does not belong to a OdfFileDOM
      return null;
    }
  }

  /**
   * Set style attribute value with uri and name
   *
   * @param uri The namespace uri
   * @param qname The qualified name of the attribute
   * @param value The attribute value
   */
  @Override
  public void setAttributeNS(String uri, String qname, String value) {
    super.setAttributeNS(uri, qname, value);

    // check if style has changed
    if (mStyleNameAttrib.equals(uri, qname)) {
      OdfStyle autoStyle = null;

      // optimization: check if we already know this automatic style
      if ((mAutomaticStyle != null) && (mAutomaticStyle.getStyleNameAttribute().equals(value))) {
        // nothing todo
      } else {
        // register new automatic style
        OdfOfficeAutomaticStyles automatic_styles = getAutomaticStyles();
        if (automatic_styles != null) {
          autoStyle = automatic_styles.getStyle(value, getStyleFamily());
        }

        if (mAutomaticStyle != null) {
          mAutomaticStyle.removeStyleUser(this);
        }

        mAutomaticStyle = autoStyle;

        if (mAutomaticStyle != null) {
          mAutomaticStyle.addStyleUser(this);
        }

        if (mErrorHandler != null) {
          // Is String from type NCName? == http://www.w3.org/TR/xmlschema-2/#NCName
          if (!StyleName.isValid(value)) {
            try {
              mErrorHandler.error(
                  new OdfValidationException(
                      OdfSchemaConstraint.DOCUMENT_XML_INVALID_ATTRIBUTE_VALUE, value, "qname"));
            } catch (SAXException ex) {
              Logger.getLogger(StyleStyleElement.class.getName()).log(Level.SEVERE, null, ex);
            }
          }
        }
      }
    }
  }

  /**
   * Retrieve style name
   *
   * @return the style name
   */
  public String getStyleName() {
    return getAttributeNS(mStyleNameAttrib.getUri(), mStyleNameAttrib.getLocalName());
  }

  /**
   * Set style name
   *
   * @param name The style name
   */
  public void setStyleName(String name) {
    setAttributeNS(mStyleNameAttrib.getUri(), mStyleNameAttrib.getQName(), name);
    // if the style name is changed, the reference to the style has to be updated as well
    misAutomaticStyleSet = false;
  }

  /**
   * Retrieve ODF AutomaticStyle
   *
   * @return the <code>OdfStyle</code> element
   */
  public OdfStyle getAutomaticStyle() {
    if (!misAutomaticStyleSet) {
      OdfOfficeAutomaticStyles automatic_styles = getAutomaticStyles();
      if (automatic_styles != null) {
        mAutomaticStyle = automatic_styles.getStyle(getStyleName(), getStyleFamily());
      }
      misAutomaticStyleSet = true;
    }
    return mAutomaticStyle;
  }

  /**
   * Judge if there is an automatic style, not necessary including properties
   *
   * @return true if there is an automatic style
   */
  public boolean hasAutomaticStyle() {
    if (!misAutomaticStyleSet) {
      getAutomaticStyle();
    }
    return mAutomaticStyle != null;
  }

  /*
   * public void setLocalStyleProperties(OdfStyle style) { mAutomaticStyle =
   * style.getAsLocalStyle(); setStyleName(style.getName()); }
   */
  /**
   * Returns a DocumentStyle if there is no local style
   *
   * @return The <code>OdfStyle</code> element
   */
  public OdfStyle reuseDocumentStyle(String styleName) {
    OdfStyle style = null;
    if (styleName != null) {
      style = mOdfSchemaDocument.getDocumentStyles().getStyle(styleName, getStyleFamily());
      if (style != null) {
        setDocumentStyle(style);
      }
    }
    return style;
  }

  /**
   * Set ODF DocumentStyle
   *
   * @param style The document style
   */
  public void setDocumentStyle(OdfStyle style) {
    // when there is a local style, the document style becomes the parent
    // of the local style
    if (!misAutomaticStyleSet) {
      getAutomaticStyle();
    }
    if (mAutomaticStyle != null) {
      mAutomaticStyle.setStyleParentStyleNameAttribute(style.getStyleNameAttribute());
    } else {
      setStyleName(style.getStyleNameAttribute());
    }
  }

  // protected static final String LOCAL_STYLE_PREFIX = "#local-style";

  /*
   * public OdfStyle newDocumentStyle(String name) { OdfStyle newDocStyle =
   * mFamily.newStyle(name, mOdfSchemaDocument.getDocumentStyles());
   * setDocumentStyle(newDocStyle); return newDocStyle; }
   */
  /**
   * Retrieve ODF DocumentStyle
   *
   * @return the document style
   */
  public OdfStyle getDocumentStyle() {
    OdfStyle odfStyle = null;
    String styleName = getDocumentStyleName();
    OdfOfficeStyles documentStyles = mOdfSchemaDocument.getDocumentStyles();
    if (documentStyles != null) {
      odfStyle = documentStyles.getStyle(styleName, getStyleFamily());
    }
    return odfStyle;
  }

  public String getDocumentStyleName() {
    String styleName = null;
    if (!misAutomaticStyleSet) {
      getAutomaticStyle();
    }
    if (mAutomaticStyle != null) {
      styleName = mAutomaticStyle.getStyleParentStyleNameAttribute();
    } else {
      String automaticStyleName = getStyleName();
      OdfOfficeStyles officeStyles = mOdfSchemaDocument.getDocumentStyles();
      if (officeStyles != null
          && officeStyles.getStyle(automaticStyleName, getStyleFamily()) != null) {
        styleName = automaticStyleName;
      }
    }
    return styleName;
  }

  /** @return true if there is a document style. */
  public boolean hasDocumentStyle() {
    String documentStyleName = getDocumentStyleName();
    return documentStyleName != null
        && !documentStyleName.isEmpty()
        && !documentStyleName.equals("null");
  }

  /*
   * public OdfStyle getAutomaticStyle() { if (mAutomaticStyle == null) {
   * mAutomaticStyle = mFamily.newStyle(LOCAL_STYLE_PREFIX, null); // if there
   * is already a document style, but no local style String styleName = null;
   * if ((styleName = getStyleName()) != null) {
   * mAutomaticStyle.setParentName(styleName); } } return mAutomaticStyle; }
   */
  /**
   * Retrieve ODF style family
   *
   * @return the style family.
   */
  public OdfStyleFamily getStyleFamily() {
    return mFamily;
  }

  /*
   * public OdfStyle getMergedStyle() { OdfStyle merged = new
   * OdfStyle("#merged-style", getStyleFamily()); OdfStyle docStyle =
   * getDocumentStyle(); if (mAutomaticStyle != null) { // a document style
   * may be referenced indirectly from the local style... if (docStyle ==
   * null) { docStyle =
   * mOdfSchemaDocument.getDocumentStyles().getStyle(mAutomaticStyle
   * .getParentName()); } // copy local style to merged style
   * mAutomaticStyle.copyTo(merged, true,false); }
   *
   * // copy doc style to merged style // copyTo only copies properties that
   * are not already set at the // target style if (docStyle != null) {
   * docStyle.copyTo(merged, true,false); }
   *
   * return merged; }
   */
  /**
   * Retrieve ODF style property
   *
   * @param property The style property
   * @return string for a property.
   */
  @Override
  public String getProperty(OdfStyleProperty property) {
    // first try automatic style
    if (!misAutomaticStyleSet) {
      getAutomaticStyle();
    }
    StyleStyleElement style = mAutomaticStyle;

    if (style == null) {
      style = getOfficeStyle();
    }

    if (style != null) {
      return style.getProperty(property);
    }

    return null;
  }

  /**
   * Retrieve the set of ODF style proerties
   *
   * @param properties
   * @return a map of all the properties.
   */
  @Override
  public Map<OdfStyleProperty, String> getProperties(Set<OdfStyleProperty> properties) {
    HashMap<OdfStyleProperty, String> map = new HashMap<OdfStyleProperty, String>();
    for (OdfStyleProperty property : properties) {
      map.put(property, getProperty(property));
    }

    return map;
  }

  /**
   * Retrieve the set of strict ODF properties
   *
   * @return a set of all the properties from the style family.
   */
  @Override
  public Set<OdfStyleProperty> getStrictProperties() {
    return getStyleFamily().getProperties();
  }

  /**
   * Judge if there is an automatic style with this property
   *
   * @param property
   * @return true if there is an automatic style with this property.
   */
  @Override
  public boolean hasProperty(OdfStyleProperty property) {
    if (!misAutomaticStyleSet) {
      getAutomaticStyle();
    }
    return (mAutomaticStyle != null) && mAutomaticStyle.hasProperty(property);
  }

  /**
   * Remove the ODF property
   *
   * @param property
   */
  @Override
  public void removeProperty(OdfStyleProperty property) {
    if (mAutomaticStyle != null) {
      mAutomaticStyle.removeProperty(property);
    }
  }

  /**
   * Set ODF properties
   *
   * @param properties
   */
  @Override
  public void setProperties(Map<OdfStyleProperty, String> properties) {
    for (Map.Entry<OdfStyleProperty, String> entry : properties.entrySet()) {
      setProperty(entry.getKey(), entry.getValue());
    }
  }

  /**
   * Set ODF style property with value
   *
   * @param property
   * @param value
   */
  @Override
  public void setProperty(OdfStyleProperty property, String value) {
    getOrCreateUnqiueAutomaticStyle().setProperty(property, value);
  }

  @Override
  protected void onInsertNode() {
    // whenever a OdfStyleableElement is inserted into content.xml or styles.xml,
    super.onInsertNode();

    String stylename = getStyleName();
    // if it has the stylename
    if (stylename.length() != 0) {
      //            if (!misAutomaticStyleSet) {
      //                getAutomaticStyle();
      //            }
      //            if (mAutomaticStyle != null) {
      //                // and the style belongs to automatic style
      //                if (mAutomaticStyle.getStyleNameAttribute().equals(stylename)) {
      //                    // add a user to the style
      //                    mAutomaticStyle.addStyleUser(this);
      //                    return;
      //                }
      //                mAutomaticStyle.removeStyleUser(this);
      //                mAutomaticStyle = null;
      //            }

      OdfOfficeAutomaticStyles automatic_styles = this.getAutomaticStyles();
      // sthe style belongs to automatic style
      if (automatic_styles != null) {
        if (!misAutomaticStyleSet) {
          getAutomaticStyle();
        }
        if (mAutomaticStyle != null) {
          mAutomaticStyle.addStyleUser(this);
        }
      }
    }
  }

  /** */
  @Override
  protected void onRemoveNode() {
    super.onInsertNode();

    if (this.mAutomaticStyle != null) {
      this.mAutomaticStyle.removeStyleUser(this);
      this.mAutomaticStyle = null;
      misAutomaticStyleSet = false;
    }
  }

  // todo: rename after newName rid of deprecated getDocumentStyle()
  private OdfStyle getOfficeStyle() {
    OdfOfficeStyles styles = this.mOdfSchemaDocument.getDocumentStyles();
    if (styles != null) {
      return styles.getStyle(getStyleName(), getStyleFamily());
    } else {
      return null;
    }
  }
}