/*
 * Copyright 2008 Filip Hianik, Vanja Culafic
 *
 * 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.descriptors;

import java.util.List;

import org.castor.core.constants.cpa.JDOConstants;
import org.exolab.castor.builder.BuilderConfiguration;
import org.exolab.castor.builder.info.ClassInfo;
import org.exolab.castor.builder.info.FieldInfo;
import org.exolab.castor.builder.info.nature.JDOClassInfoNature;
import org.exolab.castor.builder.info.nature.JDOFieldInfoNature;
import org.exolab.castor.builder.info.nature.XMLInfoNature;
import org.exolab.castor.builder.info.nature.relation.JDOOneToManyNature;
import org.exolab.castor.builder.info.nature.relation.JDOOneToOneNature;
import org.exolab.castor.builder.types.XSList;
import org.exolab.castor.builder.types.XSType;
import org.exolab.javasource.JClass;
import org.exolab.javasource.JConstructor;
import org.exolab.javasource.JNaming;
import org.exolab.javasource.JPrimitiveType;
import org.exolab.javasource.JSourceCode;
import org.exolab.javasource.JType;

/**
 * A class for creating the source code of JDO-specific descriptor classes.
 *
 * @author Filip Hianik
 * @author Vanja Culafic
 * @since 1.2.1
 *
 * @see DescriptorSourceFactory
 * @see JDODescriptorJClass
 *
 */
public final class JDOClassDescriptorFactory {

  /**
   * The BuilderConfiguration instance.
   */
  private final BuilderConfiguration _config;

  /**
   * Contains all fields exclusive identities.
   */
  private String _fields = null;

  /**
   * Contains all identities.
   */
  private String _identities = null;

  /**
   * Creates a new {@link JDOClassDescriptorFactory} with the given configuration.
   *
   * @param config A {@link BuilderConfiguration} instance
   */
  public JDOClassDescriptorFactory(final BuilderConfiguration config) {
    if (config == null) {
      String err = "The argument 'config' must not be null.";
      throw new IllegalArgumentException(err);
    }
    _config = config;
  }

  /**
   * Creates the Source code of a ClassInfo for a given XML Schema element declaration.
   *
   * @param classInfo the XML Schema element declaration
   * @return the JClass representing the ClassInfo source code
   */
  public JClass createSource(final ClassInfo classInfo) {
    if (!checkClassInfoNature(classInfo)) {
      return null;
    }

    JClass jClass = classInfo.getJClass();
    String descriptorClassName = getQualifiedJDODescriptorClassName(jClass.getName());
    JDODescriptorJClass classDesc = new JDODescriptorJClass(_config, descriptorClassName, jClass);
    JDOClassInfoNature cNature = new JDOClassInfoNature(classInfo);

    // -- get handle to default constructor
    JConstructor ctor = classDesc.getConstructor(0);
    JSourceCode jsc = ctor.getSourceCode();

    jsc = createClassInfoPart(classInfo, jsc);

    // =================
    // FieldDescriptors
    // =================

    for (FieldInfo fInfo : classInfo.getElementFields()) {
      if (checkFieldInfoNatures(fInfo)) {

        if (fInfo.hasNature(JDOOneToOneNature.class.getName())) {
          jsc = createOneToOneFieldInfoPart(fInfo, jsc);

        } else if (fInfo.hasNature(JDOOneToManyNature.class.getName())) {
          jsc = createOneToManyFieldInfoPart(fInfo, jsc);
        } else {
          jsc = createEntityFieldInfoPart(fInfo, jsc);
        }
      }
    }


    _fields = setFields(classInfo.getElementFields());
    _identities = setIdentities(cNature.getPrimaryKeys());

    jsc.add("");

    jsc.add("setFields(new FieldDescriptor[] {" + _fields + "});");
    jsc.add("setIdentities(new FieldDescriptor[] {" + _identities + "});");

    return classDesc;
  }

  /**
   * Returns the fully-qualified class name of the JDODescriptor to create. Given the
   * fully-qualified class name of the class we are creating a JDODescriptor for, return the correct
   * fully-qualified name for the JDODescriptor.
   *
   * @param name fully-qualified class name of the class we are describing
   * @return the fully-qualified class name of the JDODescriptor to create
   */
  private String getQualifiedJDODescriptorClassName(final String name) {
    String descPackage = JNaming.getPackageFromClassName(name);
    String descClassName = JNaming.getLocalNameFromClassName(name);

    if (descPackage != null && descPackage.length() > 0) {
      descPackage = descPackage + "." + JDOConstants.JDO_DESCRIPTOR_PACKAGE + ".";
    } else {
      descPackage = "";
    }
    // TODO integrate XMLConstants.JDO_DESCRIPTOR_SUFFIX;
    return descPackage + descClassName + "JDODescriptor";
  }

  /**
   * Returns the modified string with the first letter upperCase.
   * 
   * @param string String to upper case
   * @return the string with first letter upperCase
   */
  private static String toUpperCaseFirstLetter(final String string) {
    return string.substring(0, 1).toUpperCase() + string.substring(1);
  }

  /**
   * Returns the string which contains names of all FieldDescriptors for Fields that are not
   * identities. Names are separated by comma.
   * 
   * @param fInfos Array of FieldInfos
   * @return names of all FieldDescriptors as one String (separated by comma)
   */
  private String setFields(final FieldInfo[] fInfos) {
    String str = "";

    for (FieldInfo fInfo : fInfos) {
      ClassInfo cInfo = fInfo.getDeclaringClassInfo();
      JDOClassInfoNature cNature = new JDOClassInfoNature(cInfo);
      if (cNature.getPrimaryKeys() != null) {
        if (cNature.getPrimaryKeys().contains(new XMLInfoNature(fInfo).getNodeName())) {
          continue;
        }
      }
      if (str.equals("")) {
        str = str + new XMLInfoNature(fInfo).getNodeName() + "FieldDescr";
      } else {
        str = str + "," + new XMLInfoNature(fInfo).getNodeName() + "FieldDescr";
      }
    }
    return str;
  }

  /**
   * Returns the string which contains names of all FieldDescriptors for Fields that are identities.
   * Names are separated by comma.
   * 
   * @param primaryKeys Array of primary keys.
   * @return names of all FieldDescriptors as one String (separated by comma)
   */
  private String setIdentities(final List<?> primaryKeys) {
    String identities = "";
    if (primaryKeys != null) {
      Object[] pkArray = primaryKeys.toArray();
      for (Object primaryKey : pkArray) {
        if (identities.equals("")) {
          identities = identities + primaryKey + "FieldDescr";
        } else {
          identities = identities + "," + primaryKey + "FieldDescr";
        }
      }
    }
    return identities;
  }

  /**
   * Checks if ClassInfo has the JDOClassNature and if their required attributes are properly set.
   * 
   * @param cInfo the ClassInfo object
   * @return true when ClassInfo has a JDOClassNature with all required attributes properly set,
   *         false otherwise
   */
  private boolean checkClassInfoNature(final ClassInfo cInfo) {
    if (cInfo.hasNature(JDOClassInfoNature.class.getName())) {
      JDOClassInfoNature cNature = new JDOClassInfoNature(cInfo);
      if (cNature.getAccessMode() == null || cNature.getPrimaryKeys() == null
          || cNature.getPrimaryKeys().isEmpty() || cNature.getTableName() == null
          || cNature.getTableName().length() == 0) {
        return false;
      }
      return true;
    }
    return false;
  }

  /**
   * Checks if FieldInfo has the XMLInfoNature and JDOFieldNature and if their required attributes
   * are properly set.
   * 
   * @param fInfo the FieldInfo object
   * @return true when FieldInfo has a XMLInfoNature and JDOFieldNature with all required attributes
   *         properly set, false otherwise
   */
  private boolean checkFieldInfoNatures(final FieldInfo fInfo) {
    if (!fInfo.hasNature(XMLInfoNature.class.getName())) {
      return false;
    }

    if (fInfo.hasNature(JDOFieldInfoNature.class.getName())) {
      JDOFieldInfoNature fNature = new JDOFieldInfoNature(fInfo);
      if (fNature.getColumnName() == null || fNature.getColumnName().length() == 0
          || fNature.getColumnType() == null) {
        return false;
      }
      return true;
    }

    if (fInfo.hasNature(JDOOneToOneNature.class.getName())) {
      JDOOneToOneNature oneNature = new JDOOneToOneNature(fInfo);
      if (oneNature.getForeignKeys().size() != 1) {
        return false;
      }
      return true;
    }

    if (fInfo.hasNature(JDOOneToManyNature.class.getName())) {
      JDOOneToManyNature manyNature = new JDOOneToManyNature(fInfo);
      if (manyNature.getForeignKeys().size() != 1) {
        return false;
      }
      return true;
    }
    return false;
  }

  /**
   * Creates the ClassInfo part of the JDOClassDescriptor.
   * 
   * @param classInfo ClassInfo object
   * @param jsc JSourceCode
   * @return JSourceCode created in this method
   */
  private JSourceCode createClassInfoPart(final ClassInfo classInfo, final JSourceCode jsc) {

    JDOClassInfoNature cNature = new JDOClassInfoNature(classInfo);

    jsc.add("");

    jsc.add("addNature(ClassDescriptorJDONature.class.getName());");
    jsc.add("ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(this);");

    // -- set table name
    String tableName = cNature.getTableName();
    jsc.add("jdoNature.setTableName(\"" + tableName + "\");");

    // -- set corresponding Java class
    // TODO IS THERE A NEED TO CHECK THIS?!
    String className = classInfo.getJClass().getLocalName();
    if ((className != null) && (className.length() > 0)) {
      jsc.add("setJavaClass(");
      jsc.append(className);
      jsc.append(".class);");
    }

    // -- set access mode
    String accessMode = cNature.getAccessMode().getName();
    jsc.add("jdoNature.setAccessMode(AccessMode.valueOf(\"" + accessMode + "\"));");

    // -- set cache key
    // TODO IS THERE A NEED TO CHECK THIS?!
    String fullName = classInfo.getJClass().getName();
    if ((fullName != null) && (fullName.length() > 0)) {
      jsc.add("jdoNature.addCacheParam(\"name\", \"");
      jsc.append(fullName);
      jsc.append("\");");
    }

    jsc.add("");

    // -- Configure class mapping
    jsc.add("mapping.setAccess(ClassMappingAccessType.fromValue(\"");
    jsc.append(accessMode);
    jsc.append("\"));");

    jsc.add("mapping.setAutoComplete(true);");

    // TODO IS THERE A NEED TO CHECK THIS?!
    if ((fullName != null) && (fullName.length() > 0)) {
      jsc.add("mapping.setName(\"");
      jsc.append(fullName);
      jsc.append("\");");
    }

    // -- set class choice
    jsc.add("mapping.setClassChoice(choice);");

    // -- set table
    String table = cNature.getTableName();
    jsc.add("mapTo.setTable(\"" + table + "\");");

    // -- set table mapping
    jsc.add("mapping.setMapTo(mapTo);");

    // -- set mapping
    jsc.add("setMapping(mapping);");

    return jsc;
  }

  /**
   * Creates the source code of the FieldInfo part appeding it to the source code generated till
   * now.
   * 
   * @param fInfo FieldInfo object
   * @param jsc JSourceCode created till now
   * @return JSourceCode created in this method
   */
  private JSourceCode createEntityFieldInfoPart(final FieldInfo fInfo, final JSourceCode jsc) {
    JDOFieldInfoNature fNature = new JDOFieldInfoNature(fInfo);
    JDOClassInfoNature cNature = new JDOClassInfoNature(fInfo.getDeclaringClassInfo());
    XMLInfoNature xmlNature = new XMLInfoNature(fInfo);

    // -- set name
    String name = xmlNature.getNodeName();
    jsc.add("");
    jsc.add("//" + name + " field");
    jsc.add("String " + name + "FieldName = \"" + name + "\";");

    // -- initialize objects
    jsc.add("FieldDescriptorImpl " + name + "FieldDescr;");

    jsc.add("FieldMapping " + name + "FM = new FieldMapping();");

    // -- set typeInfo
    String type = null;
    XSType schemaType;

    schemaType = xmlNature.getSchemaType();
    JType javaType = schemaType.getJType();
    type = javaType.toString();
    String wrapperType = null;
    if (javaType instanceof JPrimitiveType) {
      wrapperType = ((JPrimitiveType) javaType).getWrapperName();
    } else {
      wrapperType = type;
    }

    // TODO IS THERE A NEED TO CHECK THIS?!
    if ((type != null) && (type.length() > 0)) {
      jsc.add("TypeInfo " + name + "Type = new TypeInfo(" + wrapperType + ".class);");
    }

    jsc.add("// Set columns required (= not null)");
    jsc.add(name + "Type.setRequired(" + Boolean.toString(xmlNature.isRequired()) + ");");

    jsc.add("");

    jsc.add("FieldHandler " + name + "Handler;");
    jsc.add("try {");

    // -- get/set methods
    jsc.indent();
    String className = fInfo.getDeclaringClassInfo().getJClass().getLocalName();
    // TODO IS THERE A NEED TO CHECK THIS?!
    if ((className != null) && (className.length() > 0)) {
      jsc.add("Method " + name + "GetMethod = " + className + ".class.getMethod(\"get"
          + toUpperCaseFirstLetter(name) + "\", null);");
      jsc.add("Method " + name + "SetMethod = " + className + ".class.getMethod(\"set"
          + toUpperCaseFirstLetter(name) + "\", new Class[]{");
    }

    // TODO IS THERE A NEED TO CHECK THIS?!
    if ((type != null) && (type.length() > 0)) {
      jsc.addIndented(type + ".class});");
    }

    jsc.add("");
    jsc.add(name + "Handler = new FieldHandlerImpl(" + name + "FieldName, ");
    jsc.append("null, null,");
    jsc.addIndented(name + "GetMethod, " + name + "SetMethod, " + name + "Type);");
    jsc.unindent();
    jsc.add("");

    // -- Catch of exceptions
    jsc.add("} catch (SecurityException e1) {");
    jsc.indent();
    jsc.add("throw new RuntimeException(e1.getMessage());");
    jsc.unindent();
    jsc.add("} catch (MappingException e1) {");
    jsc.indent();
    jsc.add("throw new RuntimeException(e1.getMessage());");
    jsc.unindent();
    jsc.add("} catch (NoSuchMethodException e1) {");
    jsc.indent();
    jsc.add("throw new RuntimeException(e1.getMessage());");
    jsc.unindent();
    jsc.add("}");

    // //-- JDOFieldDescriptorImpl constructor
    // jsc.add("// Instantiate " + name + " field descriptor");
    // jsc.add(name + "FieldDescr = new JDOFieldDescriptorImpl(");
    // jsc.append(name + "FieldName, " + name + "Type,");
    // jsc.indent();
    // jsc.add(name + "Handler, ");
    // jsc.append(Boolean.toString(fInfo.isTransient()) + ", ");
    // String sqlName = new JDOFieldInfoNature(fInfo).getColumnName();
    // jsc.append("new String[] { \"" + sqlName + "\" },");
    // jsc.add("new int[] {SQLTypeInfos");
    // jsc.indent();
    // jsc.add(".javaType2sqlTypeNum(");
    //
    // // TODO IS THERE NEED TO CHECK THIS?!
    // if ((type != null) && (type.length() > 0)) {
    // jsc.append(wrapperType + ".class) },");
    // }
    //
    // jsc.unindent();
    // jsc.add("null, new String[] {}, ");
    // jsc.append(Boolean.toString(fNature.isDirty()) + ", ");
    // jsc.append(Boolean.toString(fNature.isReadOnly()) + ");");
    // jsc.unindent();

    // -- invoke FieldDescriptorImpl constructor
    jsc.add("// Instantiate " + name + " field descriptor");
    jsc.add(name + "FieldDescr = new FieldDescriptorImpl(");
    jsc.append(name + "FieldName, " + name + "Type,");
    jsc.append(name + "Handler, ");
    jsc.append(Boolean.toString(fInfo.isTransient()) + ");");

    jsc.add(name + "FieldDescr.addNature(FieldDescriptorJDONature.class.getName());");

    jsc.add("FieldDescriptorJDONature " + name + "FieldJdoNature = new FieldDescriptorJDONature("
        + name + "FieldDescr);");

    String sqlName = new JDOFieldInfoNature(fInfo).getColumnName();
    jsc.add(name + "FieldJdoNature.setSQLName(new String[] { \"" + sqlName + "\" });");
    jsc.add(name + "FieldJdoNature.setSQLType(new int[] {SQLTypeInfos");
    jsc.append(".javaType2sqlTypeNum(");

    // TODO IS THERE NEED TO CHECK THIS?!
    if ((type != null) && (type.length() > 0)) {
      jsc.append(wrapperType + ".class) });");
    }

    // jsc.add("null, new String[] {}, ");
    jsc.add(name + "FieldJdoNature.setManyTable(null);");
    jsc.add(name + "FieldJdoNature.setManyKey(new String[] {});");
    jsc.add(name + "FieldJdoNature.setDirtyCheck(" + Boolean.toString(fNature.isDirty()) + ");");
    jsc.add(name + "FieldJdoNature.setReadOnly(" + Boolean.toString(fNature.isReadOnly()) + ");");

    jsc.add("");

    jsc.add(name + "FieldDescr.setDirect(false);");
    jsc.add(name + "FieldDescr.setRequired(" + xmlNature.isRequired() + ");");
    jsc.add(name + "FieldDescr.setSetMethod(\"set" + toUpperCaseFirstLetter(name) + "\");");
    jsc.add(name + "FieldDescr.setGetMethod(\"get" + toUpperCaseFirstLetter(name) + "\");");

    jsc.add("");

    // -- parent class descriptor
    jsc.add(name + "FieldDescr.setContainingClassDescriptor(this);");

    boolean isPrimaryKey = false;
    if (cNature.getPrimaryKeys() != null) {
      isPrimaryKey = (cNature.getPrimaryKeys().contains(xmlNature.getNodeName()));
    }

    jsc.add(name + "FieldDescr.setIdentity(" + Boolean.toString(isPrimaryKey) + ");");

    // -- fieldmapping
    jsc.add(name + "FM.setIdentity(" + Boolean.toString(isPrimaryKey) + ");");
    jsc.add(name + "FM.setDirect(false);");
    jsc.add(name + "FM.setName(\"" + name + "\");");
    jsc.add(name + "FM.setRequired(" + xmlNature.isRequired() + ");");
    jsc.add(name + "FM.setSetMethod(\"set" + toUpperCaseFirstLetter(name) + "\");");
    jsc.add(name + "FM.setGetMethod(\"get" + toUpperCaseFirstLetter(name) + "\");");

    // -- sql part
    jsc.add("Sql " + name + "Sql = new Sql();");
    jsc.add(name + "Sql.addName(\"" + name + "\");");

    String sqlType = fNature.getColumnType();
    if ((sqlType != null) && (sqlType.length() > 0)) {
      jsc.add(name + "Sql.setType(\"" + sqlType + "\");");
    }

    jsc.add(name + "FM.setSql(" + name + "Sql);");

    if ((type != null) && (type.length() > 0)) {
      jsc.add(name + "FM.setType(\"" + type + "\");");
    }

    jsc.add("choice.addFieldMapping(" + name + "FM);");

    return jsc;
  }

  /**
   * Creates the source code of the FieldInfo part with one-to-one relation appeding it to the
   * source code generated till now.
   * 
   * @param fInfo FieldInfo object
   * @param jsc JSourceCode created till now
   * @return JSourceCode created in this method
   */
  private JSourceCode createOneToOneFieldInfoPart(final FieldInfo fInfo, final JSourceCode jsc) {
    // JDOFieldInfoNature fNature = new JDOFieldInfoNature(fInfo);
    JDOClassInfoNature cNature = new JDOClassInfoNature(fInfo.getDeclaringClassInfo());
    JDOOneToOneNature oneNature = new JDOOneToOneNature(fInfo);
    XMLInfoNature xmlNature = new XMLInfoNature(fInfo);

    // -- set name
    String name = xmlNature.getNodeName();
    jsc.add("");
    jsc.add("//" + name + " field");
    jsc.add("String " + name + "FieldName = \"" + name + "\";");

    String sqlName = oneNature.getForeignKeys().get(0).toString();
    jsc.add("String " + name + "SqlName = \"" + sqlName + "\";");

    // -- initialize objects
    jsc.add("FieldDescriptorImpl " + name + "FieldDescr;");
    jsc.add("FieldMapping " + name + "FM = new FieldMapping();");

    // -- set typeInfo
    String type = null;
    XSType schemaType;

    schemaType = xmlNature.getSchemaType();
    JType javaType = schemaType.getJType();
    type = javaType.toString();

    String wrapperType = null;
    if (javaType instanceof JPrimitiveType) {
      wrapperType = ((JPrimitiveType) javaType).getWrapperName();
    } else {
      wrapperType = type;
    }


    // TODO IS THERE A NEED TO CHECK THIS?!
    if ((type != null) && (type.length() > 0)) {
      jsc.add("TypeInfo " + name + "Type = new TypeInfo(" + type + ".class);");
    }

    jsc.add("// Set columns required (= not null)");
    jsc.add(name + "Type.setRequired(" + Boolean.toString(xmlNature.isRequired()) + ");");

    jsc.add("");

    jsc.add("FieldHandler " + name + "Handler;");
    jsc.add("try {");

    // -- get/set methods
    jsc.indent();
    // TODO HOW ABOUT GETTING THE NAME FROM NATURE?
    String className = fInfo.getDeclaringClassInfo().getJClass().getLocalName();
    // TODO IS THERE A NEED TO CHECK THIS?!
    if ((className != null) && (className.length() > 0)) {
      jsc.add("Method " + name + "GetMethod = " + className + ".class.getMethod(\"get"
          + toUpperCaseFirstLetter(name) + "\", null);");
      jsc.add("Method " + name + "SetMethod = " + className + ".class.getMethod(\"set"
          + toUpperCaseFirstLetter(name) + "\", new Class[]{");
    }

    // TODO IS THERE A NEED TO CHECK THIS?!
    if ((type != null) && (type.length() > 0)) {
      jsc.addIndented(type + ".class});");
    }

    jsc.add("");
    jsc.add(name + "Handler = new FieldHandlerImpl(" + name + "FieldName, ");
    jsc.append("null, null,");
    jsc.addIndented(name + "GetMethod, " + name + "SetMethod, " + name + "Type);");
    jsc.unindent();
    jsc.add("");

    // -- Catch of exceptions
    jsc.add("} catch (SecurityException e1) {");
    jsc.indent();
    jsc.add("throw new RuntimeException(e1.getMessage());");
    jsc.unindent();
    jsc.add("} catch (MappingException e1) {");
    jsc.indent();
    jsc.add("throw new RuntimeException(e1.getMessage());");
    jsc.unindent();
    jsc.add("} catch (NoSuchMethodException e1) {");
    jsc.indent();
    jsc.add("throw new RuntimeException(e1.getMessage());");
    jsc.unindent();
    jsc.add("}");


    // //-- JDOFieldDescriptorImpl constructor
    // jsc.add("// Instantiate " + name + " field descriptor");
    // jsc.add(name + "FieldDescr = new JDOFieldDescriptorImpl(");
    // jsc.append(name + "FieldName, " + name + "Type,");
    // jsc.indent();
    // jsc.add(name + "Handler, ");
    // jsc.append(Boolean.toString(fInfo.isTransient()) + ", ");
    // jsc.append("new String[] { " + name + "SqlName },");
    // jsc.add("new int[] {SQLTypeInfos");
    // jsc.indent();
    // jsc.add(".javaType2sqlTypeNum(");
    //
    // // TODO IS THERE NEED TO CHECK THIS?!
    // if ((type != null) && (type.length() > 0)) {
    // jsc.append(wrapperType + ".class) },");
    // }
    //
    // jsc.unindent();
    // jsc.add("null, new String[] { " + name + "SqlName }, ");
    // jsc.append(Boolean.toString(oneNature.isDirty()) + ", ");
    // jsc.append(Boolean.toString(oneNature.isReadOnly()) + ");");
    // jsc.unindent();

    // -- invoke FieldDescriptorImpl constructor
    jsc.add("// Instantiate " + name + " field descriptor");
    jsc.add(name + "FieldDescr = new FieldDescriptorImpl(");
    jsc.append(name + "FieldName, " + name + "Type,");
    jsc.append(name + "Handler, ");
    jsc.append(Boolean.toString(fInfo.isTransient()) + ");");

    jsc.add(name + "FieldDescr.addNature(FieldDescriptorJDONature.class.getName());");

    jsc.add("FieldDescriptorJDONature " + name + "FieldJdoNature = new FieldDescriptorJDONature("
        + name + "FieldDescr);");

    jsc.add(name + "FieldJdoNature.setSQLName(new String[] { " + name + "SqlName });");
    jsc.add(name + "FieldJdoNature.setSQLType(new int[] {SQLTypeInfos");
    jsc.append(".javaType2sqlTypeNum(");

    // TODO IS THERE NEED TO CHECK THIS?!
    if ((type != null) && (type.length() > 0)) {
      jsc.append(wrapperType + ".class) });");
    }

    // jsc.add(name + "FieldJdoNature.setManyTable(null);");
    jsc.add(name + "FieldJdoNature.setManyKey(new String[] { " + name + "SqlName });");
    jsc.add(name + "FieldJdoNature.setDirtyCheck(" + Boolean.toString(oneNature.isDirty()) + ");");
    jsc.add(name + "FieldJdoNature.setReadOnly(" + Boolean.toString(oneNature.isReadOnly()) + ");");

    jsc.add("");

    // -- parent class descriptor
    jsc.add(name + "FieldDescr.setContainingClassDescriptor(this);");
    jsc.add(name + "FieldDescr.setClassDescriptor(new " + getLocalName(type) + "JDODescriptor());");

    boolean isPrimaryKey = false;
    if (cNature.getPrimaryKeys() != null) {
      isPrimaryKey = (cNature.getPrimaryKeys().contains(xmlNature.getNodeName()));
    }

    // -- fieldmapping
    jsc.add(name + "FM.setIdentity(" + Boolean.toString(isPrimaryKey) + ");");
    jsc.add(name + "FM.setDirect(false);");
    jsc.add(name + "FM.setName(\"" + name + "\");");
    jsc.add(name + "FM.setRequired(" + xmlNature.isRequired() + ");");
    jsc.add(name + "FM.setSetMethod(\"set" + toUpperCaseFirstLetter(name) + "\");");
    jsc.add(name + "FM.setGetMethod(\"get" + toUpperCaseFirstLetter(name) + "\");");

    // -- sql part
    jsc.add("Sql " + name + "Sql = new Sql();");
    jsc.add(name + "Sql.addName(\"" + sqlName + "\");");
    jsc.add(name + "Sql.setManyKey(new String[] {\"" + sqlName + "\"});");
    jsc.add(name + "FM.setSql(" + name + "Sql);");

    if ((type != null) && (type.length() > 0)) {
      jsc.add(name + "FM.setType(\"" + type + "\");");
    }

    jsc.add("choice.addFieldMapping(" + name + "FM);");
    return jsc;
  }

  /**
   * Creates the source code of the FieldInfo part with one-to-one relation appeding it to the
   * source code generated till now.
   * 
   * @param fInfo FieldInfo object
   * @param jsc JSourceCode created till now
   * @return JSourceCode created in this method
   */
  private JSourceCode createOneToManyFieldInfoPart(final FieldInfo fInfo, final JSourceCode jsc) {

    // JDOFieldInfoNature fNature = new JDOFieldInfoNature(fInfo);
    JDOClassInfoNature cNature = new JDOClassInfoNature(fInfo.getDeclaringClassInfo());
    JDOOneToManyNature manyNature = new JDOOneToManyNature(fInfo);
    XMLInfoNature xmlNature = new XMLInfoNature(fInfo);

    // -- set name
    String name = xmlNature.getNodeName();
    jsc.add("");
    jsc.add("//" + name + " field");
    jsc.add("String " + name + "FieldName = \"" + name + "\";");

    String sqlName = manyNature.getForeignKeys().get(0).toString();
    jsc.add("String " + name + "SqlName = \"" + sqlName + "\";");

    // -- initialize objects
    jsc.add("FieldDescriptorImpl " + name + "FieldDescr;");
    jsc.add("FieldMapping " + name + "FM = new FieldMapping();");

    // -- set typeInfo
    String type = null;
    XSList schemaType;

    schemaType = (XSList) xmlNature.getSchemaType();
    JType javaType = schemaType.getContentType().getJType();
    type = javaType.toString();

    String wrapperType = null;
    if (javaType instanceof JPrimitiveType) {
      wrapperType = ((JPrimitiveType) javaType).getWrapperName();
    } else {
      wrapperType = type;
    }

    // TODO IS THERE A NEED TO CHECK THIS?!
    if ((type != null) && (type.length() > 0)) {
      jsc.add("TypeInfo " + name + "Type = new TypeInfo(" + type + ".class);");
    }

    jsc.add("// Set columns required (= not null)");
    jsc.add(name + "Type.setRequired(" + Boolean.toString(xmlNature.isRequired()) + ");");

    jsc.add("");

    jsc.add("FieldHandler " + name + "Handler;");
    jsc.add("try {");

    // -- get/set methods
    jsc.indent();
    // TODO HOW ABOUT GETTING THE NAME FROM NATURE?
    String className = fInfo.getDeclaringClassInfo().getJClass().getLocalName();
    // TODO IS THERE A NEED TO CHECK THIS?!
    if ((className != null) && (className.length() > 0)) {
      jsc.add("Method " + name + "GetMethod = " + className + ".class.getMethod(\"get"
          + toUpperCaseFirstLetter(name) + "\", null);");
      jsc.add("Method " + name + "SetMethod = " + className + ".class.getMethod(\"set"
          + toUpperCaseFirstLetter(name) + "\", new Class[]{");
    }

    // TODO IS THERE A NEED TO CHECK THIS?!
    if ((type != null) && (type.length() > 0)) {
      jsc.addIndented(type + "[].class});");
    }

    jsc.add("");
    jsc.add(name + "Handler = new FieldHandlerImpl(" + name + "FieldName, ");
    jsc.append("null, null,");
    jsc.addIndented(name + "GetMethod, " + name + "SetMethod, " + name + "Type);");
    jsc.unindent();
    jsc.add("");

    // -- Catch of exceptions
    jsc.add("} catch (SecurityException e1) {");
    jsc.indent();
    jsc.add("throw new RuntimeException(e1.getMessage());");
    jsc.unindent();
    jsc.add("} catch (MappingException e1) {");
    jsc.indent();
    jsc.add("throw new RuntimeException(e1.getMessage());");
    jsc.unindent();
    jsc.add("} catch (NoSuchMethodException e1) {");
    jsc.indent();
    jsc.add("throw new RuntimeException(e1.getMessage());");
    jsc.unindent();
    jsc.add("}");


    // //-- JDOFieldDescriptorImpl constructor
    // jsc.add("// Instantiate " + name + " field descriptor");
    // jsc.add(name + "FieldDescr = new JDOFieldDescriptorImpl(");
    // jsc.append(name + "FieldName, " + name + "Type,");
    // jsc.indent();
    // jsc.add(name + "Handler, ");
    // jsc.append(Boolean.toString(fInfo.isTransient()) + ", ");
    // jsc.append("new String[] { },");
    // jsc.add("new int[] {SQLTypeInfos");
    // jsc.indent();
    // jsc.add(".javaType2sqlTypeNum(");
    //
    // // TODO IS THERE NEED TO CHECK THIS?!
    // if ((type != null) && (type.length() > 0)) {
    // jsc.append(wrapperType + ".class) },");
    // }
    //
    // jsc.unindent();
    // jsc.add("null, new String[] { " + name + "SqlName }, ");
    // jsc.append(Boolean.toString(manyNature.isDirty()) + ", ");
    // jsc.append(Boolean.toString(manyNature.isReadOnly()) + ");");
    // jsc.unindent();

    // -- invoke FieldDescriptorImpl constructor
    jsc.add("// Instantiate " + name + " field descriptor");
    jsc.add(name + "FieldDescr = new FieldDescriptorImpl(");
    jsc.append(name + "FieldName, " + name + "Type,");
    jsc.append(name + "Handler, ");
    jsc.append(Boolean.toString(fInfo.isTransient()) + ");");

    jsc.add(name + "FieldDescr.addNature(FieldDescriptorJDONature.class.getName());");

    jsc.add("FieldDescriptorJDONature " + name + "FieldJdoNature = new FieldDescriptorJDONature("
        + name + "FieldDescr);");

    jsc.add(name + "FieldJdoNature.setSQLName(null);");
    jsc.add(name + "FieldJdoNature.setSQLType(new int[] {SQLTypeInfos");
    jsc.append(".javaType2sqlTypeNum(");

    // TODO IS THERE NEED TO CHECK THIS?!
    if ((type != null) && (type.length() > 0)) {
      jsc.append(wrapperType + ".class) });");
    }

    jsc.add(name + "FieldJdoNature.setManyKey(new String[] { " + name + "SqlName });");
    jsc.add(name + "FieldJdoNature.setDirtyCheck(" + Boolean.toString(manyNature.isDirty()) + ");");
    jsc.add(
        name + "FieldJdoNature.setReadOnly(" + Boolean.toString(manyNature.isReadOnly()) + ");");

    jsc.add("");

    jsc.add(name + "FieldDescr.setDirect(false);");
    jsc.add(name + "FieldDescr.setName(\"" + name + "\");");
    jsc.add(name + "FieldDescr.setRequired(" + xmlNature.isRequired() + ");");
    // TODO support of other collection types
    jsc.add(name + "FieldDescr.setCollection(FieldMappingCollectionType.ARRAY);");

    jsc.add("");

    // -- parent class descriptor
    jsc.add(name + "FieldDescr.setContainingClassDescriptor(this);");
    jsc.add(name + "FieldDescr.setClassDescriptor(new " + getLocalName(type) + "JDODescriptor());");
    jsc.add(name + "FieldDescr.setMultivalued(true);");

    boolean isPrimaryKey = false;
    if (cNature.getPrimaryKeys() != null) {
      isPrimaryKey = (cNature.getPrimaryKeys().contains(xmlNature.getNodeName()));
    }

    // -- fieldmapping
    jsc.add(name + "FM.setIdentity(" + Boolean.toString(isPrimaryKey) + ");");
    jsc.add(name + "FM.setDirect(false);");
    jsc.add(name + "FM.setName(\"" + name + "\");");
    jsc.add(name + "FM.setRequired(" + xmlNature.isRequired() + ");");
    // TODO support of other collection types
    jsc.add(name + "FM.setCollection(FieldMappingCollectionType.ARRAY);");

    // -- sql part
    jsc.add("Sql " + name + "Sql = new Sql();");
    jsc.add(name + "Sql.addName(\"" + sqlName + "\");");
    jsc.add(name + "Sql.setManyKey(new String[] {\"" + sqlName + "\"});");
    jsc.add(name + "FM.setSql(" + name + "Sql);");

    if ((type != null) && (type.length() > 0)) {
      jsc.add(name + "FM.setType(\"" + type + "\");");
    }

    jsc.add("choice.addFieldMapping(" + name + "FM);");

    return jsc;
  }

  /**
   * Returns the unqualified Java type name (i.e. without package).
   *
   * @param name the qualified Java type name.
   * @return The unqualified Java type name.
   */
  private String getLocalName(final String name) {
    // -- use getName method in case it's been overloaded
    return JNaming.getLocalNameFromClassName(name);
  }
}
