/*
 * Copyright 2006 Werner Guttmann
 *
 * 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
 *
 * 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. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package org.exolab.castor.builder.factory;

import org.apache.commons.lang3.StringUtils;
import org.exolab.castor.builder.BuilderConfiguration;
import org.exolab.castor.builder.info.CollectionInfo;
import org.exolab.castor.builder.info.FieldInfo;
import org.exolab.castor.builder.info.NodeType;
import org.exolab.castor.builder.info.nature.XMLInfoNature;
import org.exolab.castor.builder.types.XSClass;
import org.exolab.castor.builder.types.XSListType;
import org.exolab.castor.builder.types.XSType;
import org.exolab.javasource.JClass;
import org.exolab.javasource.JSourceCode;

/**
 * A factory for creating XMLFieldHandler instances as embedded in descriptors classes generated
 * throughout code generation.
 *
 * @author <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a>
 * @version $Revision: 6469 $ $Date: 2006-04-13 07:37:49 -0600 (Thu, 13 Apr 2006) $
 */
public final class XMLFieldHandlerFactory {

  /**
   * The XML code generator configuration.
   */
  private BuilderConfiguration _config;

  /**
   * Creates an instance of this factory.
   * 
   * @param config The XML code generator configuration.
   */
  public XMLFieldHandlerFactory(final BuilderConfiguration config) {
    _config = config;
  }

  /**
   * Creates the XMLFieldHandler for the given FieldInfo.
   *
   * @param member the member for which to create an XMLFieldHandler
   * @param xsType the XSType (XML Schema Type) of this field
   * @param localClassName unqualified (no package) name of this class
   * @param jsc the source code to which we'll add this XMLFieldHandler
   * @param forGeneralizedHandler Whether to generate a generalized field handler
   */
  public void createXMLFieldHandler(final FieldInfo member, final XSType xsType,
      final String localClassName, final JSourceCode jsc, final boolean forGeneralizedHandler) {

    XMLInfoNature xmlNature = new XMLInfoNature(member);

    boolean any = false;
    boolean isEnumerated = false;

    // -- a hack, I know, I will change later (kv)
    if (member.getName().equals("_anyObject")) {
      any = true;
    }

    if (xsType.getType() == XSType.CLASS) {
      isEnumerated = ((XSClass) xsType).isEnumerated();
    }

    jsc.add("handler = new org.exolab.castor.xml.XMLFieldHandler() {");
    jsc.indent();

    createGetValueMethod(member, xsType, localClassName, jsc);

    NodeType nodeType = xmlNature.getNodeType();
    boolean isAttribute = (nodeType == NodeType.ATTRIBUTE);
    boolean isContent = (nodeType == NodeType.TEXT);

    createSetValueMethod(member, xsType, localClassName, jsc, any, isAttribute, isContent);
    createResetMethod(member, localClassName, jsc);
    createNewInstanceMethod(member, xsType, jsc, forGeneralizedHandler, any, isEnumerated);

    jsc.unindent();
    jsc.add("};");
  } // --end of XMLFieldHandler

  /**
   * Creates the getValue() method of the corresponsing XMLFieldHandler.
   *
   * @param member The member element.
   * @param xsType The XSType instance
   * @param jsc The source code to which to append the 'getValue' method.
   * @param localClassName Name of the object instance as used locally
   */
  private void createGetValueMethod(final FieldInfo member, final XSType xsType,
      final String localClassName, final JSourceCode jsc) {
    // -- getValue(Object) method

    XMLInfoNature xmlNature = new XMLInfoNature(member);

    if (_config.useJava50()) {
      jsc.add("@Override");
    }
    jsc.add("public java.lang.Object getValue( java.lang.Object object ) ");
    jsc.indent();
    jsc.add("throws IllegalStateException");
    jsc.unindent();
    jsc.add("{");
    jsc.indent();
    jsc.add(localClassName);
    jsc.append(" target = (");
    jsc.append(localClassName);
    jsc.append(") object;");
    // -- handle primitives
    if ((!xsType.isEnumerated()) && (xsType.getJType().isPrimitive())
        && (!xmlNature.isMultivalued())) {
      jsc.add("if (!target." + member.getHasMethodName() + "()) { return null; }");
    }
    // -- Return field value
    jsc.add("return ");
    String value = "target." + member.getReadMethodName() + "()";
    if (xmlNature.isMultivalued()) {
      jsc.append(value); // --Be careful : different for attributes
    } else {
      jsc.append(xsType.createToJavaObjectCode(value));
    }
    jsc.append(";");
    jsc.unindent();
    jsc.add("}");
    // --end of getValue(Object) method
  }

  /**
   * Creates the setValue() method of the corresponsing XMLFieldHandler.
   *
   * @param member The member element.
   * @param xsType The XSType instance
   * @param localClassName Name of the object instance as used locally
   * @param jsc The source code to which to append the 'setValue' method.
   * @param any Whether to create a setValue() method for &lt;xs:any>
   * @param isAttribute Whether to create a setValue() method for an attribute.
   * @param isContent Whether to create a setValue() method for XML content.
   */
  private void createSetValueMethod(final FieldInfo member, final XSType xsType,
      final String localClassName, final JSourceCode jsc, final boolean any,
      final boolean isAttribute, final boolean isContent) {
    if (_config.useJava50()) {
      jsc.add("@Override");
    }
    jsc.add("public void setValue( java.lang.Object object, java.lang.Object value) ");
    jsc.indent();
    jsc.add("throws IllegalStateException, IllegalArgumentException");
    jsc.unindent();
    jsc.add("{");
    jsc.indent();
    jsc.add("try {");
    jsc.indent();
    jsc.add(localClassName);
    jsc.append(" target = (");
    jsc.append(localClassName);
    jsc.append(") object;");
    // -- check for null primitives

    XMLInfoNature xmlNature = new XMLInfoNature(member);

    if (xsType.isPrimitive() && !_config.usePrimitiveWrapper()) {
      if ((!xmlNature.isRequired()) && (!xsType.isEnumerated()) && (!xmlNature.isMultivalued())) {
        jsc.add("// if null, use delete method for optional primitives ");
        jsc.add("if (value == null) {");
        jsc.indent();
        jsc.add("target.");
        jsc.append(member.getDeleteMethodName());
        jsc.append("();");
        jsc.add("return;");
        jsc.unindent();
        jsc.add("}");
      } else {
        jsc.add("// ignore null values for non optional primitives");
        jsc.add("if (value == null) { return; }");
        jsc.add("");
      }
    } // if primitive

    if (xsType.isEnumerated() && StringUtils.isNotEmpty(member.getDefaultValue())) {
      jsc.add("// default value supplied; as such, do not inject null values");
      jsc.add("if (value == null) {");
      jsc.addIndented("return;");
      jsc.add("}");
      jsc.add("");

    }
    jsc.add("target.");
    jsc.append(member.getWriteMethodName());
    jsc.append("( ");
    if (xsType.isPrimitive() && !_config.usePrimitiveWrapper()) {
      jsc.append(xsType.createFromJavaObjectCode("value"));
    } else if (any) {
      jsc.append(" value ");
    } else {
      jsc.append("(");
      jsc.append(xsType.getJType().toString());
      // special handling for the type package
      // when we are dealing with attributes
      // This is a temporary solution since we need to handle
      // the 'types' in specific handlers in the future
      // i.e add specific FieldHandler in org.exolab.castor.xml.handlers
      // dateTime is not concerned by the following since it is directly
      // handle by DateFieldHandler
      if ((isAttribute | isContent) && xsType.isDateTime()
          && xsType.getType() != XSType.DATETIME_TYPE) {
        jsc.append(".parse");
        jsc.append(_config.getJavaNaming().toJavaClassName(xsType.getName()));
        jsc.append("((java.lang.String) value))");
      } else {
        jsc.append(") value");
      }
    }
    jsc.append(");");

    jsc.unindent();
    jsc.add("} catch (java.lang.Exception ex) {");
    jsc.indent();
    jsc.add("throw new IllegalStateException(ex.toString());");
    jsc.unindent();
    jsc.add("}");
    jsc.unindent();
    jsc.add("}");
    // --end of setValue(Object, Object) method
  }

  /**
   * Creates the resetValue() method of the corresponsing XMLFieldHandler.
   *
   * @param member The member element.
   * @param jsc The source code to which to append the 'resetValue' method.
   * @param localClassName Name of the object instance as used locally
   */
  private void createResetMethod(final FieldInfo member, final String localClassName,
      final JSourceCode jsc) {
    // -- reset method (handle collections only)
    if (new XMLInfoNature(member).isMultivalued()) {
      CollectionInfo cInfo = (CollectionInfo) member;
      // FieldInfo content = cInfo.getContent();
      jsc.add("public void resetValue(Object object)"
          + " throws IllegalStateException, IllegalArgumentException {");
      jsc.indent();
      jsc.add("try {");
      jsc.indent();
      jsc.add(localClassName);
      jsc.append(" target = (");
      jsc.append(localClassName);
      jsc.append(") object;");
      String cName = _config.getJavaNaming().toJavaClassName(cInfo.getElementName());
      // if (cInfo instanceof CollectionInfoJ2) {
      // jsc.add("target.clear" + cName + "();");
      // } else {
      jsc.add("target.removeAll" + cName + "();");
      // }
      jsc.unindent();
      jsc.add("} catch (java.lang.Exception ex) {");
      jsc.indent();
      jsc.add("throw new IllegalStateException(ex.toString());");
      jsc.unindent();
      jsc.add("}");
      jsc.unindent();
      jsc.add("}");
    }
    // -- end of reset method
  }

  /**
   * Creates the newInstance() method of the corresponsing XMLFieldHandler.
   *
   * @param member The member element.
   * @param xsType The XSType instance
   * @param jsc The source code to which to append the 'newInstance' method.
   * @param forGeneralizedHandler Whether to generate a generalized field handler
   * @param any Whether to create a newInstance() method for &lt;xs:any>
   * @param isEnumerated Whether to create a newInstance() method for an enumeration.
   */
  private void createNewInstanceMethod(final FieldInfo member, final XSType xsType,
      final JSourceCode jsc, final boolean forGeneralizedHandler, final boolean any,
      final boolean isEnumerated) {
    boolean isAbstract = false;

    // Commented out according to CASTOR-1340
    // if (member.getDeclaringClassInfo() != null) {
    // isAbstract = member.getDeclaringClassInfo().isAbstract();
    // }

    // check whether class of member is declared as abstract
    XMLInfoNature xmlNature = new XMLInfoNature(member);

    if (xmlNature.getSchemaType() != null
        && xmlNature.getSchemaType().getJType() instanceof JClass) {
      JClass jClass = (JClass) xmlNature.getSchemaType().getJType();
      isAbstract = jClass.getModifiers().isAbstract();
    }

    if (!isAbstract && xsType.getJType() instanceof JClass) {
      JClass jClass = (JClass) xsType.getJType();
      isAbstract = jClass.getModifiers().isAbstract();
    }

    if (!isAbstract && xmlNature.getSchemaType() instanceof XSListType) {
      XSListType xsList = (XSListType) xmlNature.getSchemaType();
      if (xsList.getContentType().getJType() instanceof JClass) {
        JClass componentType = (JClass) xsList.getContentType().getJType();
        if (componentType.getModifiers().isAbstract()) {
          isAbstract = componentType.getModifiers().isAbstract();
        }
      }
    }

    if (_config.useJava50()) {
      jsc.add("@Override");
      jsc.add("@SuppressWarnings(\"unused\")");
    }

    jsc.add("public java.lang.Object newInstance(java.lang.Object parent) {");
    jsc.indent();
    jsc.add("return ");

    if (any || forGeneralizedHandler || isEnumerated || xsType.isPrimitive()
        || xsType.getJType().isArray() || (xsType.getType() == XSType.STRING_TYPE) || isAbstract) {
      jsc.append("null;");
    } else {
      jsc.append(xsType.newInstanceCode());
    }

    jsc.unindent();
    jsc.add("}");
  }

}
