Package org.objectweb.speedo.generation.parser.ejb

Source Code of org.objectweb.speedo.generation.parser.ejb.EJBAnnotationParser

/**
* Copyright (C) 2001-2005 France Telecom R&D
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package org.objectweb.speedo.generation.parser.ejb;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.objectweb.asm.Type;
import org.objectweb.speedo.api.SpeedoException;
import org.objectweb.speedo.api.SpeedoProperties;
import org.objectweb.speedo.generation.api.SpeedoXMLError;
import org.objectweb.speedo.generation.lib.AbstractGeneratorComponent;
import org.objectweb.speedo.lib.Personality;
import org.objectweb.speedo.locale.LocaleHelper;
import org.objectweb.speedo.metadata.SpeedoCallback;
import org.objectweb.speedo.metadata.SpeedoClass;
import org.objectweb.speedo.metadata.SpeedoCollection;
import org.objectweb.speedo.metadata.SpeedoColumn;
import org.objectweb.speedo.metadata.SpeedoDiscriminator;
import org.objectweb.speedo.metadata.SpeedoField;
import org.objectweb.speedo.metadata.SpeedoIdentity;
import org.objectweb.speedo.metadata.SpeedoInheritance;
import org.objectweb.speedo.metadata.SpeedoInheritedField;
import org.objectweb.speedo.metadata.SpeedoJoin;
import org.objectweb.speedo.metadata.SpeedoJoinColumn;
import org.objectweb.speedo.metadata.SpeedoMap;
import org.objectweb.speedo.metadata.SpeedoNoFieldColumn;
import org.objectweb.speedo.metadata.SpeedoNullValue;
import org.objectweb.speedo.metadata.SpeedoPackage;
import org.objectweb.speedo.metadata.SpeedoTable;
import org.objectweb.speedo.metadata.SpeedoXMLDescriptor;
import org.objectweb.speedo.mim.api.HomeItf;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Inheritance;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PrePersist;
import javax.persistence.PostPersist;
import javax.persistence.PreRemove;
import javax.persistence.PostRemove;
import javax.persistence.PreUpdate;
import javax.persistence.PostUpdate;
import javax.persistence.PostLoad;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.PrimaryKeyJoinColumns;
import javax.persistence.SecondaryTable;
import javax.persistence.SecondaryTables;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;

public class EJBAnnotationParser extends AbstractGeneratorComponent {
  public final static String LOGGER_NAME = SpeedoProperties.LOGGER_NAME
      + ".generation.parser.ejb";
  private static byte[] ba = new byte[0];
  private static Byte[] oba = new Byte[0];
  private static char[] bc = new char[0];
  private static Character[] obc = new Character[0];
  /**
   * This is the set of primitive types.
   */
  private static final String[] PRIMITIVETYPES = {
    Type.getDescriptor(Boolean.TYPE),
    Type.getDescriptor(Byte.TYPE),
    Type.getDescriptor(Character.TYPE),
    Type.getDescriptor(Short.TYPE),
    Type.getDescriptor(Integer.TYPE),
    Type.getDescriptor(Long.TYPE)
  };
  /**
   * This is the set of supported types for persistent fields.
   */
  private static final String[] FIELDTYPES = {
    Type.getDescriptor(Boolean.TYPE),
    Type.getDescriptor(Boolean.class),
    Type.getDescriptor(Byte.TYPE),
    Type.getDescriptor(Byte.class),
    Type.getDescriptor(Character.TYPE),
    Type.getDescriptor(Character.class),
    Type.getDescriptor(Short.TYPE),
    Type.getDescriptor(Short.class),
    Type.getDescriptor(Integer.TYPE),
    Type.getDescriptor(Integer.class),
    Type.getDescriptor(Long.TYPE),
    Type.getDescriptor(Long.class),
    Type.getDescriptor(Float.TYPE),
    Type.getDescriptor(Float.class),
    Type.getDescriptor(Double.TYPE),
    Type.getDescriptor(Double.class),
    Type.getDescriptor(String.class),
    Type.getDescriptor(java.util.Date.class),
    Type.getDescriptor(java.util.Calendar.class),
    Type.getDescriptor(java.sql.Date.class),
    Type.getDescriptor(java.sql.Time.class),
    Type.getDescriptor(java.sql.Timestamp.class),
    Type.getDescriptor(java.math.BigInteger.class),
    Type.getDescriptor(java.math.BigDecimal.class),
    Type.getDescriptor(ba.getClass()),
    Type.getDescriptor(oba.getClass()),
    Type.getDescriptor(bc.getClass()),
    Type.getDescriptor(obc.getClass()),
  };
  /**
   * This is the set of supported types for fields that compose a composite
   * primary key.
   */
  private static final String[] COMPPKFIELDTYPES = {
    Type.getDescriptor(Boolean.TYPE),
    Type.getDescriptor(Boolean.class),
    Type.getDescriptor(Byte.TYPE),
    Type.getDescriptor(Byte.class),
    Type.getDescriptor(Character.TYPE),
    Type.getDescriptor(Character.class),
    Type.getDescriptor(Short.TYPE),
    Type.getDescriptor(Short.class),
    Type.getDescriptor(Integer.TYPE),
    Type.getDescriptor(Integer.class),
    Type.getDescriptor(Long.TYPE),
    Type.getDescriptor(Long.class),
    Type.getDescriptor(String.class),
    Type.getDescriptor(java.util.Date.class),
    Type.getDescriptor(java.sql.Date.class)
  };
  /**
   * This is the set of supported types for Version field.
   */
  private static final String[] VERSIONTYPES = {
    Type.getDescriptor(Short.TYPE),
    Type.getDescriptor(Short.class),
    Type.getDescriptor(Integer.TYPE),
    Type.getDescriptor(Integer.class),
    Type.getDescriptor(Long.TYPE),
    Type.getDescriptor(Long.class),
    Type.getDescriptor(Timestamp.class)
  };
  int parsedFiles;
  int nbErrors;
  LoaderForAnnotAccess annotCL;

  public EJBAnnotationParser() {
    super(Personality.EJB);
  }
 
  static String getter2attribute(String getter) {
    return Character.toLowerCase(getter.charAt(3))
        + getter.substring(4, getter.length());
  }

  public String getTitle() {
    return LocaleHelper.getEJBParsingRB().getString("ejbannotpar");
  }
 
  /**
   * The parser instance is reused at each process method call
   */

  // IMPLEMENTATION OF THE GeneratorComponent INTERFACE //
  // ---------------------------------------------------//
  @Override
  public boolean init() throws SpeedoException {
    logger = scp.loggerFactory.getLogger(LOGGER_NAME);
    logger.log(BasicLevel.DEBUG, "- ejb Annotation Parsing -");
    if (scp.xml.isEmpty())
      return false;
    parsedFiles = 0;
    annotCL = new LoaderForAnnotAccess(logger, this.getClass()
        .getClassLoader());
    annotCL.addDirURL(scp.output);
    return true;
  }

  /**
   * Look for annotated EJB entity classes in the class paths and parse them.
   */
  @Override
  public void process() throws SpeedoException {
    logger.log(BasicLevel.DEBUG, scp.xml.size() + " files to parse");
    for (Object xmldesc : scp.getXmldescriptor().values()) {
      logger.log(BasicLevel.INFO, LocaleHelper.getEJBParsingRB().getString("ejbpfile")
          + ((SpeedoXMLDescriptor) xmldesc).xmlFile);
      List<SpeedoClass> parselist = new ArrayList<SpeedoClass>();
      for (Object pac : ((SpeedoXMLDescriptor) xmldesc).packages.values()) {
        for (Object cl : ((SpeedoPackage) pac).classes.values()) {
          Class clazz;
          try {
            clazz = (Class) Class.forName(
                ((SpeedoClass) cl).getFQName(), false, annotCL);
          } catch (ClassNotFoundException e) {
            logger.log(BasicLevel.WARN,
                LocaleHelper.getEJBParsingRB().getString("ejbclnotfnd")
                + ((SpeedoClass) cl).getFQName());
            continue;
          }
          if (!isAnnotatedEntityClass(clazz)) {
            logger.log(BasicLevel.WARN,
                LocaleHelper.getEJBParsingRB().getString("ejbclnotannot")
                + ((SpeedoClass) cl).getFQName());
            continue;
          }
          ((SpeedoClass) cl).mainTable = new SpeedoTable();
          ((SpeedoClass) cl).mainTable.name = "main_table_to_be_defined_later";
          ((SpeedoClass) cl).inheritance = new SpeedoInheritance();
          ((SpeedoClass) cl).inheritance.clazz = null;
          ((SpeedoClass) cl).inheritance.superClassName = null;
          Class superc = clazz.getSuperclass();
          if (superc == null) { // No superclass
            parselist.add((SpeedoClass) cl);
            continue;
          } else {
            ((SpeedoClass) cl).inheritance.superClassName = superc.getName();
          }
          // Look for the superclass in the parse list and insert
          // new class after (should parse superclass before
          // subclass).
          int i = -1;
          boolean found = false;
          for (SpeedoClass sc : parselist) {
            if (! found) {
              i++;
            }
            // Is superc an existing class?
            if (superc.getName().equals(sc.getFQName())) {
              ((SpeedoClass) cl).inheritance.clazz = sc;
              found = true;
              // i is now fixed for the insertion position
              continue;
            }
            // Is cl a superclass of an existing class?
            if (((SpeedoClass) cl).getFQName().equals(sc.inheritance.superClassName)) {
              sc.inheritance.clazz = sc;
            }
          }
          if (found) {
            parselist.add(i + 1, (SpeedoClass) cl);
          } else {
            parselist.add(0, (SpeedoClass) cl);
          }
        }
      }
      // Remove inheritance info that cannot have been built entirely
      for (SpeedoClass sc : parselist) {
        if (sc.inheritance.clazz == null) {
          sc.inheritance = null;
        }
      }
      // The parsing order as been defined such that super-classes are
      // parsed before sub-classes.
      nbErrors = 0;
      for (SpeedoClass sc : parselist) {
        try {
          logger.log(BasicLevel.DEBUG, "");
          parseEntityClass(Class.forName(sc.getFQName(), false, annotCL), sc);
          parsedFiles++;
        } catch (ClassNotFoundException e) {
          nbErrors++;
          logger.log(BasicLevel.ERROR,
              "Class not found - should never happen here: "
              + sc.getFQName());
        }
      }
      if (nbErrors > 0) {
        if (nbErrors == 1) {
          logger.log(BasicLevel.ERROR,
              "Abort EJB enhancement: 1 error found while parsing Entity classes defined in '"
              + ((SpeedoXMLDescriptor) xmldesc).xmlFile + "'.");
        } else {
          logger.log(BasicLevel.ERROR, "Abort EJB enhancement: " + nbErrors
              + " errors found while parsing Entity classes defined in '"
              + ((SpeedoXMLDescriptor) xmldesc).xmlFile + "'.");
        }
        throw new SpeedoException("EJB parsing error.");
      }     
    }
  }

  /**
   * Determine if this is an EJB3 annotated entity class.
   */
  private boolean isAnnotatedEntityClass(Class clazz)
      throws SpeedoException {
    Entity e = (Entity) clazz.getAnnotation(Entity.class);
    return (e != null);
  }

  /**
   * Parse an EJB3 Entity class.
   *
   * @param c    The Class to be parsed.
   * @param sc  The SpeedoClass to be constructed.
   * @throws SpeedoException
   */
  private void parseEntityClass(Class c, SpeedoClass sc)
      throws SpeedoException {
    Entity e = (Entity) c.getAnnotation(Entity.class);
    if (e == null) { // This is not an Entity class
      throw new SpeedoException(c.getName()
          + ": should be an annotated EJB3 entity class!");
    }
    logger.log(BasicLevel.DEBUG, "Parse annotation of class: "
        + c.getName());
    // name of the Entity when used in a query
    sc.nameForQuery = e.name();
    // kind of access to persistent information (variables ou
    // accessors)
    for (Class itf : c.getInterfaces()) {
      if (itf == Serializable.class) {
        sc.isSerializable = true;
        break;
      }
    }

    // ---------------------------------------------------------------------
    // Define name for Primary Table (parse definition later...)
    Table t = (Table) c.getAnnotation(Table.class);
    if (t == null) { // There is no Table annotation: use defaults
      sc.mainTable.name = sc.name;
    } else {
      sc.mainTable.name = t.name();
    }

    // ---------------------------------------------------------------------
    // Parse the attribute information for retrieving mapping information
    if (c.getAnnotation(IdClass.class) != null) {
      sc.identity = new SpeedoIdentity();
      sc.identity.objectidJClass = ((IdClass) c.getAnnotation(IdClass.class)).value();
      sc.identity.objectidClass = sc.identity.objectidJClass.getName();
      sc.identity.strategy = SpeedoIdentity.USER_ID;
      parsePkClass(sc);
    }
    // look for persistent attributes represented by methods
    // (getters/setters)
    for (Method m : c.getMethods()) {
      if (!isRelevantAttribute(m, c)) {
        continue;
      }
      SpeedoField sf = new SpeedoField();
      sf.name = getter2attribute(m.getName());
      // sf.visibility;
      sf.moClass = sc;
      sc.fields.put(sf.name, sf);
      logger.log(BasicLevel.DEBUG, "New SpeedoField for EJB property: "
          + sf.name + ", TYPE: " + sf.type);
      parseManyToOne(m.getAnnotation(ManyToOne.class), sc, sf);
      parseOneToOne(m.getAnnotation(OneToOne.class), sc, sf);
      parseOneToMany(m.getAnnotation(OneToMany.class), sc, sf);
      parseManyToMany(m.getAnnotation(ManyToMany.class), sc, sf);
      parseType((Class) m.getReturnType(), m.getGenericReturnType(), sc,
          sf);
      if (sf.relationType == SpeedoField.NO_BI_RELATION) {
        excludeAnnotation(new Object[] {
            m.getAnnotation(JoinColumns.class),
            m.getAnnotation(JoinColumn.class),
            m.getAnnotation(JoinTable.class) }, sc, sf.name,
            "property");
        parseVersion(m.getAnnotation(Version.class), sc, sf);
        parseColumn(m.getAnnotation(Column.class), sc, sf);
        parseBasic(m.getAnnotation(Basic.class), sc, sf, true);
        parseLob(m.getAnnotation(Lob.class), sc, sf, true);
        if (m.getAnnotation(Embedded.class) != null) {
          parseEmbeddedOverriding(
              (AttributeOverride) m.getAnnotation(AttributeOverride.class),
              (AttributeOverrides) m.getAnnotation(AttributeOverrides.class),
              sc, sf);
        }
      } else {
        excludeAnnotation(new Object[] {
            m.getAnnotation(Version.class),
            m.getAnnotation(Column.class),
            m.getAnnotation(Basic.class),
            m.getAnnotation(Lob.class) }, sc, sf.name,
            "association property");
        if (m.getAnnotation(JoinColumns.class) != null) {
          if (m.getAnnotation(JoinColumn.class) != null) {
            nbErrors++;
            logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
                + ": cannot define both 'JoinColumns' and'JoinColumn' annotation for property ("
                + sf.name + ").");
          } else {
            parseJoinColumns(
                m.getAnnotation(JoinColumns.class).value(),
                sc, sf);
          }
        } else {
          if (m.getAnnotation(JoinColumn.class) != null) {
            parseJoinColumns(
                new JoinColumn[] { m.getAnnotation(JoinColumn.class) },
                sc, sf);
          }
        }
      }
      parseEmbeddedId(m.getAnnotation(EmbeddedId.class), sc, sf);
      if (parseId(m.getAnnotation(Id.class), sc, sf)) {
        // ---------------------------------------------------------------------
        // In case of Long identifier, try parsing Id generator
        parseGeneratedValue((GeneratedValue) m.getAnnotation(GeneratedValue.class), sc);
      } else {
        if (m.getAnnotation(GeneratedValue.class) != null) {
          logger.log(BasicLevel.WARN, sc.getSourceDescShort()
              + ": can only associate an identifier generator to a Long ID - ignored!");
        }
      }
    }
    // look for persistent attributes represented by variables
    for (Field f : c.getDeclaredFields()) {
      if (!isRelevantAttribute(f, c)) {
        continue;
      }
      SpeedoField sf = new SpeedoField();
      sf.name = f.getName();
      // sf.visibility;
      sf.moClass = sc;
      sc.fields.put(sf.name, sf);
      logger.log(BasicLevel.DEBUG, "New SpeedoField for EJB field: "
          + sf.name + ", TYPE: " + sf.type);
      parseManyToOne(f.getAnnotation(ManyToOne.class), sc, sf);
      parseOneToOne(f.getAnnotation(OneToOne.class), sc, sf);
      parseOneToMany(f.getAnnotation(OneToMany.class), sc, sf);
      parseManyToMany(f.getAnnotation(ManyToMany.class), sc, sf);
      parseType((Class) f.getType(), f.getGenericType(), sc, sf);
      if (sf.relationType == SpeedoField.NO_BI_RELATION) {
        excludeAnnotation(new Object[] {
            f.getAnnotation(JoinColumns.class),
            f.getAnnotation(JoinColumn.class),
            f.getAnnotation(JoinTable.class) }, sc, sf.name,
            "field");
        parseVersion(f.getAnnotation(Version.class), sc, sf);
        parseColumn(f.getAnnotation(Column.class), sc, sf);
        parseBasic(f.getAnnotation(Basic.class), sc, sf, false);
        parseLob(f.getAnnotation(Lob.class), sc, sf, false);
        if (f.getAnnotation(Embedded.class) != null) {
          parseEmbeddedOverriding(
              (AttributeOverride) f.getAnnotation(AttributeOverride.class),
              (AttributeOverrides) f.getAnnotation(AttributeOverrides.class),
              sc, sf);
        }
      } else {
        excludeAnnotation(new Object[] {
            f.getAnnotation(Version.class),
            f.getAnnotation(Column.class),
            f.getAnnotation(Basic.class),
            f.getAnnotation(Lob.class) }, sc, sf.name,
            "association field");
        if (f.getAnnotation(JoinColumns.class) != null) {
          if (f.getAnnotation(JoinColumn.class) != null) {
            nbErrors++;
            logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
                + ": cannot define both 'JoinColumns' and'JoinColumn' annotation at the same time for field ("
                + sf.name + ").");
          } else {
            parseJoinColumns(f.getAnnotation(JoinColumns.class)
                .value(), sc, sf);
          }
        } else {
          if (f.getAnnotation(JoinColumn.class) != null) {
            parseJoinColumns(new JoinColumn[] { f
                .getAnnotation(JoinColumn.class) }, sc, sf);
          }
        }
      }
      parseEmbeddedId(f.getAnnotation(EmbeddedId.class), sc, sf);
      if (parseId(f.getAnnotation(Id.class), sc, sf)) {
        // ---------------------------------------------------------------------
        // In case of Long identifier, try parsing Id generator
        parseGeneratedValue((GeneratedValue) f.getAnnotation(GeneratedValue.class), sc);
      } else {
        if (f.getAnnotation(GeneratedValue.class) != null) {
          logger.log(BasicLevel.WARN, sc.getSourceDescShort()
              + ": can only associate an identifier generator to a Long ID - ignored!");
        }
      }
    }
    // Verify that a mapping has been defined for each field when needed
    for (SpeedoField sf : (Collection<SpeedoField>) sc.fields.values()) {
      if (sf.mappedByReversefield) {
        sf.relationType = SpeedoField.NO_BI_RELATION;
        continue;
      }
      if (sf.relationType == SpeedoField.NO_BI_RELATION) {
        continue;
      }
      if (sf.relationType == SpeedoField.MANY_REFERENCE) {
        if (sf.join == null) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": a mapping must be associated with the n-ary association field ("
              + sf.name + ").");
        }
      } else { // sf.relationType == SpeedoField.ONE_REFERENCE
        if (sf.columns.length == 0) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": a mapping must be associated with the unary association field ("
              + sf.name + ").");
        }
      }
      // The real type of relation will be calculated later (@see ReverseFieldAdder).
      sf.relationType = SpeedoField.NO_BI_RELATION;
    }
    // Verify that there is an identifier definition
    if (sc.getPKFields().size() == 0) {
      SpeedoClass sch = sc.getSuper();
      while (sch != null) {
        if (sch.getPKFields().size() != 0) {
          break;
        }
        sch = sch.getSuper();
      }
      if (sch == null) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": no identity defined for the class.");
      }
    }
   
    // ---------------------------------------------------------------------
    // Parse table definitions
    // - Primary table
    if (t != null) {
      parseTable(t, sc, sc.mainTable);
    }
    // - Secondary tables
    SecondaryTable st = (SecondaryTable) c.getAnnotation(SecondaryTable.class);
    SecondaryTables sts = (SecondaryTables) c.getAnnotation(SecondaryTables.class);
    if (sts != null) {
      if (st != null) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": cannot define both 'SecondaryTable' and 'SecondaryTables' annotations at the same time.");
      } else {
        for (SecondaryTable st2 : sts.value()) {
          parseSecondaryTable(st2, sc);
        }
      }
    } else {
      if (st != null) {
        parseSecondaryTable(st, sc);
      }
    }
   
    // Parse inheritance strategy information
    parseDiscriminatorColumn(c, sc);
    parseDiscriminatorValue(c, sc);
    parseInheritanceJoin(c, sc);
    parseInheritance(c, sc);
    if (sc.identity == null) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": an identifier must be defined for this Entity class.");
    }

    // Parse attributes overriding
    AttributeOverride ao = (AttributeOverride) c.getAnnotation(AttributeOverride.class);
    AttributeOverrides aos = (AttributeOverrides) c.getAnnotation(AttributeOverrides.class);
    parseAttributeOverriding(ao, aos, sc);
    for (Method m : c.getMethods()) {
      ao = (AttributeOverride) m.getAnnotation(AttributeOverride.class);
      aos = (AttributeOverrides) m  .getAnnotation(AttributeOverrides.class);
      if (m.getAnnotation(Embedded.class) == null) {
        parseAttributeOverriding(ao, aos, sc);
      }
    }
    for (Field f : c.getDeclaredFields()) {
      ao = (AttributeOverride) f.getAnnotation(AttributeOverride.class);
      aos = (AttributeOverrides) f.getAnnotation(AttributeOverrides.class);
      if (f.getAnnotation(Embedded.class) == null) {
        parseAttributeOverriding(ao, aos, sc);
      }
    }

    // ---------------------------------------------------------------------
    // Parse the callback associated to Entity lifecycle:
    // could be present inside the Entity class itself or
    // inside associated EntityListener classes.
    EntityListeners els = (EntityListeners) c.getAnnotation(EntityListeners.class);
    if (els != null) {
      // Parse the listener classes for callbacks
      for (Class elc : els.value()) {
        parseCallBacks(elc.getMethods(), sc, elc);
      }
    }
    // Parse the class for callbacks
    parseCallBacks(c.getMethods(), sc, null);
  }

  /**
   * Verify if it is a relevant attribute definition to be parsed.
   *
   * @param methorfield  The attribute definition (Method or Field).
   * @param c        The Java class under parsing.
   * @return        true if it must be parsed.
   */
  private boolean isRelevantAttribute(Object methorfield, Class c) {
    if (methorfield instanceof Field) {
      Field f = (Field) methorfield;
      if ((f.getAnnotation(Column.class) == null)
          && (f.getAnnotation(Basic.class) == null)
          && (f.getAnnotation(OneToOne.class) == null)
          && (f.getAnnotation(OneToMany.class) == null)
          && (f.getAnnotation(ManyToOne.class) == null)
          && (f.getAnnotation(ManyToMany.class) == null)
          && (f.getAnnotation(Id.class) == null)
          && (f.getAnnotation(JoinColumn.class) == null)
          && (f.getAnnotation(Version.class) == null)) {
        return false;
      }
      if (f.getDeclaringClass() != c) {
        return false;
      }
      if ((f.getModifiers() & Modifier.TRANSIENT) == Modifier.TRANSIENT) {
        return false;
      }
      if (f.getAnnotation(Transient.class) != null) {
        return false;
      }
    } else {
      Method m = (Method) methorfield;
      if ((m.getAnnotation(Column.class) == null)
          && (m.getAnnotation(Basic.class) == null)
          && (m.getAnnotation(OneToOne.class) == null)
          && (m.getAnnotation(OneToMany.class) == null)
          && (m.getAnnotation(ManyToOne.class) == null)
          && (m.getAnnotation(ManyToMany.class) == null)
          && (m.getAnnotation(Id.class) == null)
          && (m.getAnnotation(JoinColumn.class) == null)
          && (m.getAnnotation(Version.class) == null)) {
        return false;
      }
      if (m.getDeclaringClass() != c) {
        return false;
      }
      if (!m.getName().startsWith("get")) { // Could it be a getter
        return false;
      }
      try {
        c.getDeclaredMethod(m.getName().replaceFirst("g", "s"),
            new Class[] {m.getReturnType()});
      } catch (NoSuchMethodException e1) {
        return false;
      }
      if (m.getParameterTypes().length != 0) { // Getter => no arg
        return false;
      }
      if (m.getReturnType() == null) { // Getter => return type must exist
        return false;
      }
      if (m.getAnnotation(Transient.class) != null) {
        return false;
      }
    }
    return true;
  }
 
  /**
   * Parse the DiscriminatorColumn annotation associated with an Entity class.
   *
   * @param c    The Entity class to be parsed.
   * @param sc  The SpeedoClass under construction.
   */
  private void parseDiscriminatorColumn(Class c, SpeedoClass sc) {
    DiscriminatorColumn a = (DiscriminatorColumn) c.getAnnotation(DiscriminatorColumn.class);
    if (a == null) {
      return;
    }
    if (sc.inheritance == null) {
      // This is a root class in an inheritance hierarchy
      sc.inheritance = new SpeedoInheritance();
      sc.inheritance.clazz = sc;
      sc.inheritance.superClassName = null;
      sc.inheritance.discriminator = new SpeedoDiscriminator();
      String cn = a.name();
      if (cn.equals("")) {
        cn = "TYPE"; // Default name of the discriminator column
      }
      SpeedoNoFieldColumn snofc = new SpeedoNoFieldColumn();
      SpeedoColumn scol = sc.getColumn(cn, true);
      if (scol == null) {
        // There is no existing column: create one
        scol = new SpeedoColumn();
        scol.name = cn;
        scol.allowNull = false;
        if (! a.columnDefinition().equals("")) {
          scol.sqlType = a.columnDefinition();
        }
        scol.length = a.length();
        scol.table = sc.mainTable;
      }
      snofc.column = scol;
      switch (a.discriminatorType()) {
      case STRING:
        snofc.type = Type.getDescriptor(String.class);
        break;
      case CHAR:
        snofc.type = Type.getDescriptor(Character.TYPE);
        break;
      case INTEGER:
        snofc.type = Type.getDescriptor(Integer.TYPE);
        break;
      }
      sc.inheritance.discriminator.elements.add(snofc);
    } else {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": discriminator column can only be defined in the root class of an inheritance hierarchy!");
      return;
    }
  }

  /**
   * Parse the DiscriminatorValue annotation associated with an Entity class.
   *
   * @param c    The Entity class to be parsed.
   * @param sc  The SpeedoClass under construction.
   */
  private void parseDiscriminatorValue(Class c, SpeedoClass sc) {
    DiscriminatorValue a = (DiscriminatorValue) c.getAnnotation(DiscriminatorValue.class);
    if (a == null) {
      return;
    }
    SpeedoNoFieldColumn snofc = (SpeedoNoFieldColumn) sc.getAncestor().inheritance.discriminator.elements.get(0);
    if (a.value().equals("")) {
      sc.inheritance.discriminatorValues.put(snofc, SpeedoInheritance.SPEEDO_DEFAULT_DISCRIMINENT_VALUE);
    } else {
      sc.inheritance.discriminatorValues.put(snofc, a.value());
    }
  }
 
  /**
   * Parse the inheritance join in case of JOINED inheritance mapping
   * strategy.
   *
   * @param c    The Entity class to be parsed.
   * @param sc  The SpeedoClass under construction.
   */
  private void parseInheritanceJoin(Class c, SpeedoClass sc) {
    if (sc.inheritance == null) {
      return;
    }
    PrimaryKeyJoinColumn a1 = (PrimaryKeyJoinColumn) c.getAnnotation(PrimaryKeyJoinColumn.class);
    PrimaryKeyJoinColumns a2 = (PrimaryKeyJoinColumns) c.getAnnotation(PrimaryKeyJoinColumns.class);
    if ((a1 == null) && (a2 == null)) {
      return;
    }
    if ((a1 != null) && (a2 != null)) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": can only support a unique join description - two found (a join column and a set of join columns)!");
      return;
    }
    if (a2.value().length == 1) {
      a1 = a2.value()[0];
    }
    sc.inheritance.join = new SpeedoJoin();
    sc.inheritance.join.mainTable = sc.mainTable;
    sc.inheritance.join.extTable = sc.getSuper().mainTable;
    // First parse PrimaryKeyJoinColumn
    if (a1 != null) {
      SpeedoJoinColumn jcol = new SpeedoJoinColumn();
      jcol.column = sc.getColumn(a1.name(), true);
      if (jcol.column == null) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": inheritance join define with a column that does not exist - ("
            + a1.name() + ") not found!");
        return;
      }
      if (a1.referencedColumnName().equals("")) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": inheritance join define with no column associated in superclass!");
        return;
      }
      jcol.targetColumn = a1.referencedColumnName();
      sc.inheritance.join.columns.add(jcol);
      return;
    }
    // Now parse PrimaryKeyJoinColumns: the PrimaryKeyJoinColmun from the "pkjcs" array
    for (PrimaryKeyJoinColumn pkjc : a2.value()) {
      SpeedoJoinColumn jcol = new SpeedoJoinColumn();
      jcol.column = sc.getColumn(pkjc.name(), true);
      if (jcol.column == null) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": inheritance join define with a column that does not exist - ("
            + pkjc.name() + ") not found!");
        continue;
      }
      // Define the referenced column in the super class of this jcol
      if (pkjc.referencedColumnName().equals("")) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": inheritance join define with no column associated in superclass!");
        continue;
      }
      jcol.targetColumn = pkjc.referencedColumnName();
      sc.inheritance.join.columns.add(jcol);
    }
  }

  /**
   * Parse the Inheritance annotation associated with an Entity class.
   *
   * @param c    The Entity class to be parsed.
   * @param sc  The SpeedoClass under construction.
   */
  private void parseInheritance(Class c, SpeedoClass sc) {
    Inheritance a = (Inheritance) c.getAnnotation(Inheritance.class);
    if (a == null) {
      return;
    }
    if (sc.inheritance == null) {
      // This is a root class in an inheritance hierarchy
      sc.inheritance = new SpeedoInheritance();
      sc.inheritance.clazz = sc;
      sc.inheritance.superClassName = null;
    }
    switch (a.strategy()) {
    case SINGLE_TABLE: // filtered inheritance mapping
      SpeedoNoFieldColumn snofc;
      if (sc.inheritance.join != null) {
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": no join column definition required for SINGLE_TABLE strategy inheritance - ignored!");
        sc.inheritance.join = null;
      }
      if (sc.getAncestor() == null) {
        // This is the root class of the inheritance hierarchy
        sc.inheritance.strategy = SpeedoInheritance.STRATEGY_NEW_TABLE;
        if (sc.inheritance.discriminator == null) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": SINGLE_TABLE strategy inheritance requires discriminator description - none found!");
          return;
        }
        snofc = (SpeedoNoFieldColumn) sc.inheritance.discriminator.elements.get(0);
      } else {
        sc.inheritance.strategy = SpeedoInheritance.STRATEGY_SUPERCLASS_TABLE;
        if (sc.inheritance.discriminator != null) {
          logger.log(BasicLevel.WARN, sc.getSourceDescShort()
              + ": discriminator column should be defined only in root class of inheritance hiereachy - ignored!");
          sc.inheritance.discriminator = null;
        }
        snofc = (SpeedoNoFieldColumn) sc.getAncestor().inheritance.discriminator.elements.get(0);
        if (sc.identity != null) {
          logger.log(BasicLevel.WARN, sc.getSourceDescShort()
              + ": identity cannot be redefined into subclasses when SINGLE_TABLE strategy is used - ignored!");
        }
        sc.identity = sc.getAncestor().identity;
      }
      sc.inheritance.discriminatorValues = new HashMap();
      break;
    case TABLE_PER_CLASS: // horizontal inheritance mapping
      if (sc.inheritance.join != null) {
        logger.log(BasicLevel.WARN, sc.getSourceDescShort()
            + ": no join column definition required for TABLE_PER_CLASS inheritance strategy - ignored!");
        sc.inheritance.join = null;
      }
      sc.inheritance.strategy = SpeedoInheritance.STRATEGY_NEW_TABLE;
      if (sc.inheritance.discriminator != null) {
        logger.log(BasicLevel.WARN, sc.getSourceDescShort()
            + ": no discriminator column definition required for TABLE_PER_CLASS inheritance strategy - ignored!");
        sc.inheritance.discriminator = null;
      }
      // Define attribute mapping for attributes of the superclasses
      SpeedoClass inhsc = sc.getSuper();
      while (inhsc != null) {
        for (Object inhsf : inhsc.fields.values()) {
          if (inhsf instanceof SpeedoInheritedField) {
            continue;
          }
                    SpeedoInheritedField sif =
                        sc.inheritance.newSpeedoInheritedField((SpeedoField)inhsf);
                    for (SpeedoColumn scol : sif.inheritedField.columns) {
            SpeedoColumn nscol = (SpeedoColumn) scol.clone();
            nscol.table = sc.mainTable;
            sif.addColumn(nscol);
          }
        }
        inhsc = inhsc.getSuper();
      }
      break;
    case JOINED: // vertical inheritance mapping
      sc.inheritance.strategy = SpeedoInheritance.STRATEGY_NEW_TABLE;
      if (sc.getAncestor() == null) {
        // This is the root class of the inheritance hierarchy
        if (sc.inheritance.join != null) {
          logger.log(BasicLevel.WARN, sc.getSourceDescShort()
              + ": no join column definition required for root class for JOINED inheritance strategy - ignored!");
          sc.inheritance.join = null;
        }
        if (sc.inheritance.discriminator == null) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": JOINED strategy inheritance requires discriminator description - none found!");
          return;
        }
        snofc = (SpeedoNoFieldColumn) sc.inheritance.discriminator.elements.get(0);
      } else {
        if (sc.inheritance.join == null) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": JOINED strategy inheritance requires a join description - none found!");
          return;
        }
        if (sc.inheritance.discriminator != null) {
          logger.log(BasicLevel.WARN, sc.getSourceDescShort()
              + ": discriminator column should be defined only in root class of inheritance hiereachy - ignored!");
          sc.inheritance.discriminator = null;
        }
        snofc = (SpeedoNoFieldColumn) sc.getAncestor().inheritance.discriminator.elements.get(0);
        sc.identity = sc.getAncestor().identity;
      }
      sc.inheritance.discriminatorValues = new HashMap();
      break;
    }
  }
 
  static final Class[] OBJSIGN = {Object.class};
  static final Class[] EMPTYSIGN = {};
  /**
   * Verify if the given Class may be used as an EJB3 PK class.
   *
   * @param sc  The SpeedoClass under construction. The identity field
   *         defines a PK class that has be verified as compliant wrt
   *         EJB3.
   * @return    true if the PK class is conform.
   */
  private boolean parsePkClass(SpeedoClass sc) {
    if ((sc.identity.objectidJClass.getModifiers() & Member.PUBLIC) != Member.PUBLIC) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
          + sc.identity.objectidJClass.getName() + ") must be public.");
      return false;
    }
    try {
      sc.identity.objectidJClass.getConstructor(EMPTYSIGN);
    } catch (NoSuchMethodException e) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
          + sc.identity.objectidJClass.getName() + ") must have a public constructor with no arg.");
      return false;
    }
    if (! Serializable.class.isAssignableFrom(sc.identity.objectidJClass)) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
          + sc.identity.objectidJClass.getName() + ") must be Serializable.");
      return false;
    }
    Method meq, mha;
    try {
      meq = sc.identity.objectidJClass.getDeclaredMethod("equals", OBJSIGN);
    } catch (NoSuchMethodException e) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
          + sc.identity.objectidJClass.getName() + ") must implement 'equals'.");
      return false;
    }
    try {
      mha = sc.identity.objectidJClass.getDeclaredMethod("hashCode", EMPTYSIGN);
    } catch (NoSuchMethodException e) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
          + sc.identity.objectidJClass.getName() + ") must implement 'hashCode'.");
      return false;
    }
    for (Method m : sc.identity.objectidJClass.getDeclaredMethods()) {
      if ((m == meq) || (m == mha)) {
        continue;
      }
      if (m.getName().startsWith("get")) {
        if (m.getParameterTypes().length != 0) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
              + sc.identity.objectidJClass.getName() + ") define primary key getter with arguments ("
              + m.getName() + ").");
          continue;
        }
        if (! isValidType(Type.getDescriptor(m.getReturnType()), COMPPKFIELDTYPES)) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
              + sc.identity.objectidJClass.getName() + ") define primary key getter with invalid type ("
              + m.getName() + ").");
          continue;
        }
      } else if (m.getName().startsWith("set")) {
        if (m.getParameterTypes().length != 1) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
              + sc.identity.objectidJClass.getName() + ") not define primary key setter one argument ("
              + m.getName() + ").");
          continue;
        }
        if (m.getReturnType() != Void.TYPE) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
              + sc.identity.objectidJClass.getName() + ") define primary key setter with non void return type ("
              + m.getName() + ").");
          continue;
        }
        if (! isValidType(Type.getDescriptor(m.getParameterTypes()[0]), COMPPKFIELDTYPES)) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
              + sc.identity.objectidJClass.getName() + ") define primary key setter with invalid argument type ("
              + m.getName() + ").");
          continue;
        }
      }
    }
    for (Field f : sc.identity.objectidJClass.getDeclaredFields()) {
      if (! isValidType(Type.getDescriptor(f.getType()), COMPPKFIELDTYPES)) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": PK class ("
            + sc.identity.objectidJClass.getName() + ") define primary key field with invalid type ("
            + f.getName() + ").");
      }
    }
    return true;
  }
 
  /**
   * Parse the type associated with a field.
   * "type" field may temporarily stores type name of target association entity
   * for verification purpose at type parsing.
   *
   * @param t    The type that has to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   */
  private void parseType(Class t, java.lang.reflect.Type gt, SpeedoClass sc, SpeedoField sf) {
    if (t == null) {
      logger.log(BasicLevel.WARN, sc.getSourceDescShort()
          + ": unknown type for element of collection for field ("
          + sf.name + ").");
      return;
    }
    SpeedoClass scf = scp.smi.getSpeedoClass(t.getName());
    if (scf != null) {
      // This is en Entity class.
      sf.relationType = SpeedoField.ONE_REFERENCE;
      if (sf.type != null) { // defined at association definition time
        if (! sf.type.equals(t.getName())) {
          logger.log(BasicLevel.WARN, sc.getSourceDescShort()
              + ": type for field ("
              + sf.name + ") is different from the one defined in association - association type ignored.");
        }
      }
      sf.type = Type.getDescriptor(t);
      return;
    }
    if (isValidType(Type.getDescriptor(t), FIELDTYPES) || t.isEnum()) {
      // This a basic valid type or an enum type. Nothing to do.
      sf.type = Type.getDescriptor(t);
      return;
    }
    // Verifies if it is a Collection or a Serializable type.
    if (Set.class.isAssignableFrom(t)
        || List.class.isAssignableFrom(t)
        || Collection.class.isAssignableFrom(t)) {
      // This is a valid type: a Set, List or Collection of Entity class.
      sf.jdoTuple = new SpeedoCollection();
      sf.jdoTuple.moField = sf;
      if (t == gt) {
        // No generic type defined.
        if (sf.type == null) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": unknown type for element of Set/List/Collection for field ("
              + sf.name + ").");
          return;
        }
        ((SpeedoCollection) sf.jdoTuple).elementType = sf.type;
        sf.type = Type.getDescriptor(t);
        return;
      }
      // The Java generic type is completely defined.
      sf.relationType = SpeedoField.MANY_REFERENCE;
      SpeedoClass escf = scp.smi.getSpeedoClass(((Class)((ParameterizedType) gt).getActualTypeArguments()[0]).getName());
      if (escf == null) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": element of collection should belong to an Entity class (found "
            + Type.getDescriptor((Class)((ParameterizedType) gt).getActualTypeArguments()[0])
            + ") for field (" + sf.name + ").");
        return;
      }
      if (! sf.type.equals(escf.name)) {
        logger.log(BasicLevel.WARN, sc.getSourceDescShort()
            + ": type for element of collection field ("
            + sf.name + ") is different from the one defined in association - association type ignored.");
      }
      ((SpeedoCollection) sf.jdoTuple).elementType = ((Class)((ParameterizedType) gt).getActualTypeArguments()[0]).getName();
      sf.type = Type.getDescriptor(t);
      return;
    }
    if (Map.class.isAssignableFrom(t)) {
      // This is a Map valid type.
      sf.jdoTuple = new SpeedoMap();
      sf.jdoTuple.moField = sf;
      if (t == gt) {
        logger.log(BasicLevel.WARN, sc.getSourceDescShort()
            + ": unknown type for element of Map for field ("
            + sf.name + ").");
        return;
      }
      // The Java generic type is completely defined.
      sf.relationType = SpeedoField.MANY_REFERENCE;
      SpeedoClass escf = scp.smi.getSpeedoClass(((Class)((ParameterizedType) gt).getActualTypeArguments()[1]).getName());
      if (escf == null) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": element of Map should belong to an Entity class (found "
            + Type.getDescriptor((Class)((ParameterizedType) gt).getActualTypeArguments()[1])
            + ") for field (" + sf.name + ").");
        return;
      }
      ((SpeedoMap) sf.jdoTuple).keyType = ((Class)((ParameterizedType) gt).getActualTypeArguments()[0]).getName();
      ((SpeedoMap) sf.jdoTuple).valueType = ((Class)((ParameterizedType) gt).getActualTypeArguments()[1]).getName();
      sf.type = Type.getDescriptor(t);
      return;
    }
    if (Serializable.class.isAssignableFrom(t)) {
        // This is a Serializable valid type. Nothing to do.
        sf.type = Type.getDescriptor(t);
        return;
    }
    // All other types are not supported.
    nbErrors++;
    logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
        + ": unsupported type (" + Type.getDescriptor(t)
        + ") for field (" + sf.name + ").");
  }
 
  /**
   * Parse the version field associated to a class for supporting database
   * optimistic locking policy.
   *
   * @param a    The Version annotation to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField to be associated as the version field.
   */
  private void parseVersion(Version a, SpeedoClass sc, SpeedoField sf) {
    if (a == null) {
      return;
    }
    if (sc.versionField == null) {
      if (! isValidType(sf.type, VERSIONTYPES)) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": try to define a field version ("
            + sf.name + ") - unsupported type ("
            + sf.type + ").");
      }
      sc.versionField = sf;
    } else {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": a field version has already been defined ("
          + sc.versionField.name + ") - cannot associate a second one ("
          + sf.name + ").");
    }
  }
 
  /**
   * Parse field meta-information from the Column annotation.
   *
   * @param col  The annotation to extract information from.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   */
  private void parseColumn(Column col, SpeedoClass sc, SpeedoField sf) {
    SpeedoColumn scol = new SpeedoColumn();
    if (col == null) {
      if (sf.relationType != SpeedoField.NO_BI_RELATION) {
        return;
      }
      scol.name = sf.name;
      scol.table = sc.mainTable;
      sf.addColumn(scol);
      logger.log(BasicLevel.DEBUG, sc.getSourceDescShort()
              + ": add a default column definition for field '"
              + sf.name + "'.");
      return;
    } else {
      if (sf.relationType != SpeedoField.NO_BI_RELATION) {
        logger.log(BasicLevel.WARN, sc.getSourceDescShort()
            + ": try to define a column associated with an association field - ignored.");
        return;
      }
    }
    if (!col.table().equals("")) {
      if (col.table().equals(sc.mainTable.name)) {
        scol.table = sc.mainTable;
      } else {
        scol.table = sc.getExtTable(col.table(), true);
      }
    } else {
      scol.table = sc.mainTable;
    }
    if (col.unique()) {
      ArrayList<SpeedoColumn> cols = new ArrayList<SpeedoColumn>(1);
      cols.add(scol);
      scol.table.addUniqueConstraint(cols);
      logger.log(BasicLevel.DEBUG, sc.getSourceDescShort()
          + ": add a unique constraint ["
          + cols
          + "] to table (" + scol.table.name + ").");
    }
    if (col.name().equals("")) {
      scol.name = sf.name;
    } else {
      scol.name = col.name();
    }
    scol.allowNull = col.nullable();
    // TODO: have a clean assignment of Speedo length/scale wrt
    // EJB3 length/precision/scale
    scol.length = col.length();
    scol.scale = col.scale();
    // ? = col.precision();
    scol.insertable = col.insertable();
    scol.updatable = col.updatable();
    if (!col.columnDefinition().equals("")) {
      scol.sqlType = col.columnDefinition();
    }
    sf.addColumn(scol);
    logger.log(BasicLevel.DEBUG, sc.getSourceDescShort()
        + ": add a column definition for field '"
        + sf.name + "' (tabName=" + scol.table.name
        + ", colName=" + scol.name + ").");
  }
 
  /**
   * Parse field meta-information from the Column annotation.
   *
   * @param col  The annotation to extract information from.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoInheritedField under construction.
   */
  private void parseOverrideColumn(Column col, SpeedoClass sc, SpeedoInheritedField sf) {
    // TODO: code copy from parseColumn->implement!!
    if (col == null) {
      return;
    }
    SpeedoColumn scol = new SpeedoColumn();
    if (!col.table().equals("")) {
      scol.table = sc.getExtTable(col.table(), true);
    } else {
      scol.table = sc.mainTable;
    }
    if (col.unique()) {
      ArrayList<SpeedoColumn> cols = new ArrayList<SpeedoColumn>(1);
      cols.add(scol);
      scol.table.addUniqueConstraint(cols);
      logger.log(BasicLevel.DEBUG, sc.getSourceDescShort()
          + ": add a unique constraint ["
          + cols
          + "] to table (" + scol.table.name + ").");
    }
    if (col.name().equals("")) {
      scol.name = sf.name;
    } else {
      scol.name = col.name();
    }
    scol.allowNull = col.nullable();
    // TODO: have a clean assignment of Speedo length/scale wrt
    // EJB3 length/precision/scale
    scol.length = col.length();
    scol.scale = col.scale();
    // ? = col.precision();
    scol.insertable = col.insertable();
    scol.updatable = col.updatable();
    if (!col.columnDefinition().equals("")) {
      scol.sqlType = col.columnDefinition();
    }
    sf.addColumn(scol);
    logger.log(BasicLevel.DEBUG, sc.getSourceDescShort()
        + ": add a column definition overriding for field '"
        + sf.name + "' (tabName=" + scol.table.name
        + ", colName=" + scol.name + ").");
  }

  /**
   * Parse an Id annotation to define the SpeedoIdentifier associated with a
   * SpeedoClass.
   *
   * @param ida  The Id annotation.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   * @return    true if the parsed Id is a correctly defined unique field
   *         identifier with a Long type.
   */
  private boolean parseId(Id ida, SpeedoClass sc, SpeedoField sf) {
    if (ida == null) {
      return false;
    }
    if ((sc.identity != null) && (sc.identity.objectidJClass != null)) {
      // There is an IdClass: verify field conformance
      try {
        Field f = sc.identity.objectidJClass.getDeclaredField(sf.name);
        if (sf.type.equals(Type.getDescriptor(f.getType()))) {
          sf.primaryKey = true;
          return false;
        }
        logger.log(BasicLevel.WARN, sc.getSourceDescShort()
            + ": try to define field (" + sf.name
            + ") as an Id field - has not the same type in IdClass ("
            + sc.identity.objectidJClass.getName() + ").");
      } catch (NoSuchFieldException e) {
        // Field does not exist:
      }
      // There is no public field in PK class. Look for public accessors
      // (getter & setter) for accessing this field.
      try {
        Method m = sc.identity.objectidJClass.getDeclaredMethod(toJavaBean("get", sf.name),
            EMPTYSIGN);
        if (! sf.type.equals(Type.getDescriptor(m.getReturnType()))) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": try to define field getter (" + m.getName()
              + ") for an Id field - has not the same type in IdClass ("
              + sc.identity.objectidJClass.getName() + ").");
          return false;
        }
      } catch (NoSuchMethodException e) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": try to find field getter ("
            + toJavaBean("get", sf.name)
            + ") for an Id field - does not exist in IdClass ("
            + sc.identity.objectidJClass.getName() + ").");
        return false;
      }
      // The getter is OK. Look for the setter.
      String mn = toJavaBean("set", sf.name);
      for (Method m : sc.identity.objectidJClass.getDeclaredMethods()) {
        if (mn.equals(m.getName())) {
          if (m.getParameterTypes().length != 1) {
            continue;
          }
          if (! sf.type.equals(Type.getDescriptor(m.getParameterTypes()[0]))) {
            continue;
          }
          if (m.getReturnType() != Void.TYPE) {
            continue;
          }
          // The field is a valid primary key element with well-formed
          // getter/setter.
          sf.primaryKey = true;
          return false;
        }
      }
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": try to find field setter (" + mn
          + ") for an Id field - does not exist in IdClass ("
          + sc.identity.objectidJClass.getName() + ").");
      return false;
    }
    // There is no IdClass. Then it should be a unique Long identifier.
    if (!sf.type.equals("Ljava/lang/Long;")) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": try to define field (" + sf.name
          + ") as an Id - should be 'java.lang.Long'.");
      return false;
    }
    if (sc.identity != null) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": try to define field (" + sf.name
          + ") as an Id - an Id already exist.");
      return false;
    }
    sc.identity = new SpeedoIdentity();
    sc.identity.objectidClass = null;
    sf.primaryKey = true;
    logger.log(BasicLevel.DEBUG, sc.getSourceDescShort()
        + ": define field (" + sf.name
        + ") as an Id - strategy = " + sc.identity.getStrategyName() + ".");
    return true;
  }
 
  private void parseGeneratedValue(GeneratedValue a, SpeedoClass sc) {
    if (a == null) {
      sc.identity.strategy = SpeedoIdentity.USER_ID;
      return;
    }
    switch (a.strategy()) {
    case TABLE:
      sc.identity.strategy = SpeedoIdentity.DATASTORE_OLONG;
      break;
    case SEQUENCE:
      sc.identity.strategy = SpeedoIdentity.DATASTORE_SEQUENCE;
      sc.identity.sequenceName = a.generator();
      break;
    case IDENTITY:
      sc.identity.strategy = SpeedoIdentity.DATASTORE_AUTO_ASSIGN;
      break;
    case AUTO:
      sc.identity.strategy = SpeedoIdentity.DATASTORE_OLONG;
      break;
    default:
      sc.identity.strategy = SpeedoIdentity.USER_ID;
      break;
    }   
  }
 
  /**
   * Parse a Basic annotation to define complementary information associated
   * with a Field.
   *
   * @param b    The Basic annotation.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   * @param jb  true if the field is accessed using the JavaBean model.
   */
  private void parseBasic(Basic b, SpeedoClass sc, SpeedoField sf, boolean jb) {
    if (b == null) {
      return;
    }
    if (sf.relationType != SpeedoField.NO_BI_RELATION) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": an association has already been defined for field ("
          + sf.name + ") - cannot define a Basic mapping.");
      return;
    }
    if (b.fetch() == FetchType.LAZY) {
      if (jb) {
        parseFetchType(b.fetch(), sc, sf);
      }
    }
    if (! b.optional()) {
      if (isValidType(sf.type, PRIMITIVETYPES)) {
        sf.nullValue = SpeedoNullValue.NONE;
        logger.log(BasicLevel.WARN, sc.getSourceDescShort()
            + ": try to define not null constraint on field (" + sf.name
            + ") - cannot apply to primitive types.");
      } else {
        sf.nullValue = SpeedoNullValue.EXCEPTION;
      }
    } else {
      sf.nullValue = SpeedoNullValue.NONE;
    }
  }
 
  /**
   * Parse a FetchType associated to a given field.
   *
   * @param ft  The FetchType to parse.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   */
  void parseFetchType(FetchType ft, SpeedoClass sc, SpeedoField sf) {
    //TODO: support of fetch type for field (LAZY or EAGER).
  }
 
  /**
   * Parse a Lob annotation to define complementary information associated
   * with a Field.
   *
   * @param l    The Lob annotation.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   * @param jb  true if the field is accessed using the JavaBean model.
   */
  private void parseLob(Lob l, SpeedoClass sc, SpeedoField sf, boolean jb) {
    if (l == null) {
      return;
    }
  }
 
  /**
   * Parse a CascadeType annotation associated to a given field.
   *
   * @param a    The CascadeType.
   * @param sf  The SpeedoField under construction.
   */
  private void parseCascadeType(CascadeType aa[], SpeedoField sf) {
    for (CascadeType a : aa) {
      switch (a) {
      case ALL:
        sf.propagate |= SpeedoField.PROPAG_ALL;
        return;
      case PERSIST:
        sf.propagate |= SpeedoField.PROPAG_PERSIST;
        break;
      case MERGE:
        sf.propagate |= SpeedoField.PROPAG_MERGE;
        break;
      case REMOVE:
        sf.propagate |= SpeedoField.PROPAG_REMOVE;
        break;
      case REFRESH:
        sf.propagate |= SpeedoField.PROPAG_REFRESH;
        break;
      default:
        break;
      }
    }
  }
 
  /**
   * Parse a ManyToOne annotation to define information related to the
   * association.
   *
   * @param a    The ManyToOne annotation to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   */
  private void parseManyToOne(ManyToOne a, SpeedoClass sc, SpeedoField sf) {
    if (a == null) {
      return;
    }
    if (a.targetEntity() != null) {
      // Temporarily stores type name of target association entity into
      // field type for verification purpose at type parsing.
      sf.type = a.targetEntity().getName();
    }
    parseCascadeType(a.cascade(), sf);
    if (a.fetch() == FetchType.LAZY) {
      parseFetchType(a.fetch(), sc, sf);
    }
    if (! a.optional()) {
      sf.nullValue = SpeedoNullValue.EXCEPTION;
    } else {
      sf.nullValue = SpeedoNullValue.NONE;
    }   
  }
 
  /**
   * Parse a OneToOne annotation to define information related to the
   * association.
   *
   * @param a    The OneToOne annotation to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   */
  private void parseOneToOne(OneToOne a, SpeedoClass sc, SpeedoField sf) {
    if (a == null) {
      return;
    }
    if (a.targetEntity() != null) {
      // Temporarily stores type name of target association entity into
      // field type for verification purpose at type parsing.
      sf.type = a.targetEntity().getName();
    }
    parseCascadeType(a.cascade(), sf);
    if (a.fetch() == FetchType.LAZY) {
      parseFetchType(a.fetch(), sc, sf);
    }
    if (! a.optional()) {
      sf.nullValue = SpeedoNullValue.EXCEPTION;
    } else {
      sf.nullValue = SpeedoNullValue.NONE;
    }   
    if (! a.mappedBy().equals("")) {
      sf.reverseField = a.mappedBy();
      sf.mappedByReversefield = true;     
    }
  }
 
  /**
   * Parse a OneToMany annotation to define information related to the
   * association.
   *
   * @param a    The OneToMany annotation to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   */
  private void parseOneToMany(OneToMany a, SpeedoClass sc, SpeedoField sf) {
    if (a == null) {
      return;
    }
    if (a.targetEntity() != null) {
      // Temporarily stores type name of target association entity into
      // field type for verification purpose at type parsing.
      sf.type = a.targetEntity().getName();
    }
    parseCascadeType(a.cascade(), sf);
    if (a.fetch() == FetchType.LAZY) {
      parseFetchType(a.fetch(), sc, sf);
    }
    sf.nullValue = SpeedoNullValue.NONE;
    if (a.mappedBy().equals("")) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": OneToMany association must define the reverse field that specify the mapping for field ("
          + sf.name + ").");
      return;
    }
    sf.reverseField = a.mappedBy();
    sf.mappedByReversefield = true;     
  }
 
  /**
   * Parse a ManyToMany annotation to define information related to the
   * association.
   *
   * @param a    The ManyToMany annotation to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   */
  private void parseManyToMany(ManyToMany a, SpeedoClass sc, SpeedoField sf) {
    if (a == null) {
      return;
    }
    if (a.targetEntity() != null) {
      // Temporarily stores type name of target association entity into
      // field type for verification purpose at type parsing.
      sf.type = a.targetEntity().getName();
    }
    parseCascadeType(a.cascade(), sf);
    if (a.fetch() == FetchType.LAZY) {
      parseFetchType(a.fetch(), sc, sf);
    }
    sf.nullValue = SpeedoNullValue.NONE;
    if (! a.mappedBy().equals("")) {
      sf.reverseField = a.mappedBy();
      sf.mappedByReversefield = true;     
    }
  }
 
  /**
   * Parse a JoinTable annotation to define the mapping of a "m-n"
   * association field.
   *
   * @param jt  The JoinTable annotation to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   */
  private void parseJoinTable(JoinTable jt, SpeedoClass sc, SpeedoField sf) {
    if (sf.mappedByReversefield) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": mapping is supposed to be defined by the reverse field for field ("
          + sf.name + ") - mapping information ignored.");
      return;
    }
    sf.join = new SpeedoJoin();
    /*TODO sf.join.extTable = parseTable(jt.table(), sc, null);*/
    sf.join.mainTable = sc.mainTable;
    // Parse joinColumns from JoinTable (may be empty)
    for (JoinColumn jc : jt.joinColumns()) {
      SpeedoJoinColumn sjcol = new SpeedoJoinColumn();
      sjcol.column = new SpeedoColumn();
      if (jc.referencedColumnName().equals("")) {
        if (jc.name().equals("")) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": must define at least columnName or referencedColumnName for m-n relationship field ("
              + sf.name + ") - mapping information ignored.");
          continue;
        }
        sjcol.column.name = jc.name();
        sjcol.column.targetColumn = sjcol.column.name;
      } else {
        sjcol.column.targetColumn = jc.referencedColumnName();
        if (jc.name().equals("")) {
          sjcol.column.name = sjcol.column.targetColumn;
        } else {
          sjcol.column.name = jc.name();
        }
      }
      sjcol.targetColumn = sjcol.column.targetColumn;
      sjcol.column.table = sf.join.mainTable;
      sf.join.columns.add(sjcol);
    }
    // parse inverseJoinColumns from JoinTable
    for (JoinColumn jc : jt.inverseJoinColumns()) {
      SpeedoColumn scol = new SpeedoColumn();
      if (jc.referencedColumnName().equals("")) {
        if (jc.name().equals("")) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": must define at least columnName or referencedColumnName for m-n relationship field ("
              + sf.name + ") - mapping information ignored.");
          continue;
        }
        scol.name = jc.name();
        scol.targetColumn = scol.name;
      } else {
        scol.targetColumn = jc.referencedColumnName();
        if (jc.name().equals("")) {
          scol.name = scol.targetColumn;
        } else {
          scol.name = jc.name();
        }
      }
      sf.addColumn(scol);
    }
  }
 
  /**
   * Parse an array of JoinColumn annotations to define the mapping of a
   * reference or association field with a one or many cardinality.
   *
   * @param jcs  The array of JoinColumn annotation to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   */
  private void parseJoinColumns(JoinColumn[] jcs, SpeedoClass sc, SpeedoField sf) {
    if (sf.mappedByReversefield) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": mapping is supposed to be defined by the reverse field for field ("
          + sf.name + ") - mapping information ignored.");
      return;
    }
    if (sf.relationType == SpeedoField.MANY_REFERENCE) {
      sf.join = new SpeedoJoin();
    }
    for (JoinColumn jc : jcs) {
      SpeedoColumn scol = new SpeedoColumn();
      scol.targetColumn = jc.referencedColumnName();
      if (scol.targetColumn.equals("")) {
        if (jcs.length > 1) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": try to define the join of a reference or association field ("
              + sf.name + ") without defining names of referenced columns.");
          continue;
        }
      }
      if (jc.name().equals("")) {
        if (jcs.length > 1) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": try to define the join of a reference or association field ("
              + sf.name + ") without defining names of join columns.");
          continue;
        }
        scol.name = sf.name + "_" + scol.targetColumn;
      } else {
        scol.name = jc.name();
      }
      if (! jc.table().equals("")) {
        scol.table = sc.getExtTable(jc.table(), true);
      } else {
        scol.table = sc.mainTable;
      }
      if (sf.relationType == SpeedoField.MANY_REFERENCE) {
        if (sf.join.mainTable == null) {
          sf.join.mainTable = scol.table;
        } else if (sf.join.mainTable != scol.table) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": cannot map the reference field ("
              + sf.name + ") to columns belonging to several tables.");
          continue;
        }
      }
      SpeedoColumn fscol = sc.getColumn(scol.name, false);
      if ((fscol != null) && (fscol.table == scol.table)) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": cannot define a column twice for column named ("
            + scol.name + ").");
        continue;
      }
      if (sf.relationType == SpeedoField.MANY_REFERENCE) {
        sf.join.columns.add(scol);
      } else {
        sf.addColumn(scol);
      }
    }
  }
 
  /**
   * Parse an EmbeddedId annotation to define the SpeedoIdentifier associated with a
   * SpeedoClass.
   *
   * @param ida  The Id annotation to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param sf  The SpeedoField under construction.
   */
  private void parseEmbeddedId(EmbeddedId ida, SpeedoClass sc, SpeedoField sf) {
    if (ida == null) {
      return;
    }
    if (sc.identity != null) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": try to define field (" + sf.name
          + ") as an EmbeededId - an Id already exist.");
      return;
    }
    try {
      sc.identity = new SpeedoIdentity();
      sc.identity.objectidClass = sf.getClassName();
      sc.identity.objectidJClass = Class.forName(
          Type.getType(sf.type).getClassName(), false, annotCL);
      sc.identity.strategy = SpeedoIdentity.USER_ID;
      if (parsePkClass(sc)) {
        logger.log(BasicLevel.DEBUG, sc.getSourceDescShort()
            + ": define field (" + sf.name
            + ") as an EmbeddedId - Id class= '"
            + sc.identity.objectidClass + "'.");
      }
    } catch (ClassNotFoundException e) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": try to define field (" + sf.name
          + ") as an EmbeddedId - Id class not found("
          + Type.getType(sf.type).getClassName() + ").");
      return;
    }
  }

  /**
   * Parse the unique constraints definition for a particular table.
   * @param ucs  The constraints to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param t    The SpeedoTable to which the constraints should be associated.
   */
  private void parseUniqueConstraints(UniqueConstraint[]ucs, SpeedoClass sc,
      SpeedoTable t) {
    for (int i = 0; i < ucs.length; i++) { // iterate over constraints
      ArrayList cols = new ArrayList();
      int j;
      for (j = 0; j < ucs[i].columnNames().length; j++) {
        // Look for the column in those defined within the SpeedoClass
        SpeedoColumn c = searchColumn(sc, ucs[i].columnNames()[j], t);
        // Did not find the column
        if (c == null) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": try to associate a unique constraint with a column ("
              + ucs[i].columnNames()[j]
              + ") that does not exist (ignored).");
          break;
        }
        // Insert (sorted) the column into the list specifying the constraint
        Iterator itc = cols.iterator();
        int pos = 0;
        while (itc.hasNext()) {
          SpeedoColumn ct = (SpeedoColumn) itc.next();
          if (c == ct) { // already present: ignored
            pos = -1;
            break;
          }
          if (c.name.compareTo(ct.name) > 0) {
            pos++;
            continue;
          }
          break;
        }
        if (pos != -1) {
          cols.add(pos, c);
        }
      }
      if (cols.size() == 0) { // empty constraint: ignore
        logger.log(BasicLevel.WARN, sc.getSourceDescShort()
            + ": table (" + t.name
            + ") has an empty unique constraint (ignored).");
        continue;
      }
      if (j == ucs[i].columnNames().length) {
        // The list of columns is consistent: create the constraint.
        if (!t.addUniqueConstraint(cols)) {
          logger.log(BasicLevel.WARN, sc.getSourceDescShort()
              + ": unique constraint "
              + cols
              + " already defined (ignored).");
        } else {
          logger.log(BasicLevel.DEBUG, sc.getSourceDescShort()
              + ": add a unique constraint "
              + cols
              + " to table (" + t.name + ").");
        }
      }
    }
  }
 
  /**
   * Parse a table definition.
   *
   * @param t    The Table annotation to be parsed.
   * @param sc  The SpeedoClass under construction.
   * @param st  The SpeedoTable under construction.
   */
  private SpeedoTable parseTable(Table t, SpeedoClass sc, SpeedoTable st) {
    if (st == null) {
      st = new SpeedoTable();
    }
    if (!t.name().equals("")) {
      st.name = t.name();
    }
    if (!t.catalog().equals("")) {
      st.catalog = t.catalog();
    }
    if (!t.schema().equals("")) {
      st.schema = t.schema();
    }
    parseUniqueConstraints(t.uniqueConstraints(), sc, st);
    return st;
  }
 
  /**
   * Parse a secondary table definition.
   *
   * @param st  The SecondaryTable annotation definition.
   * @param sc  The SpeedoClass under construction.
   */
  private void parseSecondaryTable(SecondaryTable st, SpeedoClass sc) {
    for (SpeedoJoin sj : sc.joinToExtTables) {
      if (!sj.extTable.name.equals(st.name())) {
        continue;
      }
      if (!st.catalog().equals("")) {
        sj.extTable.catalog = st.catalog();
      }
      if (!st.schema().equals("")) {
        sj.extTable.schema = st.schema();
      }
      for (PrimaryKeyJoinColumn jc : st.pkJoinColumns()) {
        SpeedoJoinColumn sjc = new SpeedoJoinColumn();
        // Parse the colum name within the secondary table under definition
        if (jc.name().equals("")) {
          nbErrors++;
          logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
              + ": try to define the secondary table ("
              + st.name()
              + ") without defining names of FK join columns.");
          continue;
        } else {
          sjc.column = searchColumn(sc, jc.name(), sj.extTable);
          if (sjc.column == null) {
            // Unknown column: define a new one
            sjc.column = new SpeedoColumn();
            sjc.column.name = jc.name();
            sjc.column.sqlType = jc.columnDefinition();
            sjc.column.table = sj.extTable;
          }
        }
        // Parse the target column within the main table
        sjc.targetColumn = jc.referencedColumnName();
        if (sjc.targetColumn.equals("")) {
          if (st.pkJoinColumns().length > 1) {
            nbErrors++;
            logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
                + ": try to define a secondary table ("
                + st.name()
                + ") without defining names of referenced columns in main table.");
            continue;
          }
          SpeedoField sf;
          try {
            sf = sc.getUniquePKField();
          } catch (SpeedoException e) {
            nbErrors++;
            logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
                + ": cannot find a unique PK column to define referenced column for secondary table ("
                + st.name() + ").");
            continue;
          }
          if (sf.columns.length != 1) {
            nbErrors++;
            logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
                + ": cannot find a unique PK column to define referenced column for secondary table ("
                + st.name() + ").");
            continue;
          }
          sjc.targetColumn = sf.columns[0].name;
        } else {
          if (sc.getColumn(sjc.targetColumn, true) == null) {
            nbErrors++;
            logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
                + ": referenced column (" + sjc.targetColumn
                + ") is not a column of the main table for secondary table ("
                + st.name() + ").");
            continue;
          }
        }
        sj.columns.add(sjc);
      }
      logger.log(BasicLevel.DEBUG, sc.getSourceDescShort()
          + ": secondary table (" + sj.extTable.name
          + ") - define join columns [" + sj.columns + "].");
      parseUniqueConstraints(st.uniqueConstraints(), sc, sj.extTable);
      return;
    }
    logger.log(BasicLevel.WARN, sc.getSourceDescShort()
        + ": try to define a secondary table ("
        + st.name()
        + ") that is unused for the mapping of this class - ignored.");
  }
 
  /**
   *
   *
   * @param ao
   * @param aos
   * @param sc
   */
  private void parseAttributeOverriding(
      AttributeOverride ao,
      AttributeOverrides aos,
      SpeedoClass sc) {
    if ((aos == null) && (ao == null)) {
      return;
    }
    if (sc.inheritance == null) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
          + ": cannot override attribute if no inheritance defined for this class - overriding ignored");
      return;
    }
    AttributeOverride[] target; // The array of AttributeOverride to be annalysed
    if (aos == null) {
      target = new AttributeOverride[] {ao};
    } else {
      if (ao != null) {
        target = new AttributeOverride[aos.value().length + 1];
        System.arraycopy(aos.value(), 0, target, aos.value().length, 0);
        target[aos.value().length] = ao;
      } else {
        target = aos.value();
      }
    }
    // target is completed; perform analysis
    for (AttributeOverride a : target) {
      // TODO:
      // Look for the field in inherited classes
      SpeedoField inhsf = sc.getInheritedField(a.name());
      if (inhsf == null) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort()
            + ": overriden attribute does not exist in inherited class - overriding ignored");
        continue;
      }
            SpeedoInheritedField sif = sc.inheritance.newSpeedoInheritedField(inhsf);
      parseOverrideColumn(a.column(), sc, sif);
    }
  }
 
  /**
   *
   *
   * @param ao
   * @param aos
   * @param sc
   * @param sf
   */
  private void parseEmbeddedOverriding(
      AttributeOverride ao,
      AttributeOverrides aos,
      SpeedoClass sc,
      SpeedoField sf) {
    // TODO:
  }
 
  public static final String[] CBNAMES = {
    "PrePersist",
    "PostPersist",
    "PreRemove",
    "PostRemove",
    "PreUpdate",
    "PostUpdate",
    "PostLoad"
  };
  public static final int[] CBIDS = {
    HomeItf.PRE_NEW,
    HomeItf.POST_CREATE,
    HomeItf.PRE_REMOVE,
    HomeItf.POST_DELETE,
    HomeItf.PRE_UPDATE,
    HomeItf.POST_UPDATE,
    HomeItf.POST_LOAD
  };
  public static final Class[] CBCLASSES = {
    PrePersist.class,
    PostPersist.class,
    PreRemove.class,
    PostRemove.class,
    PreUpdate.class,
    PostUpdate.class,
    PostLoad.class
  };
  /**
   * Parse the callbacks defined either within the class or within an external
   * listener class.
   *
   * @param meths    The methods from which to look for callbacks.
   * @param sc    The SpeedoClass under construction.
   * @param listener  The listener class if the callback belongs to such a class.
   */
  private void parseCallBacks(Method[] meths, SpeedoClass sc, Class listener) {
    // Look for callback methods
    SpeedoCallback scb = null;
    for (Method m : meths) {
      int i = 0;
      for (Class annotc : CBCLASSES) {
        if (m.getAnnotation(annotc) == null) {
          i++;
          continue;
        }
        ArrayList cbl = (ArrayList) sc.callBacks.get(CBIDS[i]);
        if (scb == null) {
          scb = new SpeedoCallback();
        }
        if (cbl != null) {
          logger.log(BasicLevel.WARN, sc.getSourceDescShort() + ": "
              + CBNAMES[i] + " callback - "
              + "already defined.");
        } else {
          cbl = new ArrayList();
          sc.callBacks.put(CBIDS[i], cbl);
        }
        scb.methodByteCodeSignature = isWellFormedCallback(m, listener, sc, CBNAMES[i]);
        if (scb.methodByteCodeSignature == null) {
          continue;
        }
        logger.log(BasicLevel.DEBUG, sc.getSourceDescShort()
            + ": new callback defined (" + CBNAMES[i] + ", "
            + ((listener == null) ? "" : listener.getName())
            + m.getName() + ")");
        scb.callbackName = m.getName();
        scb.callbackType = CBIDS[i];
        scb.listenerClassName = (listener != null) ? listener.getName() : null;
        cbl.add(scb);
        scb = null;
      }
    }
  }

  /**
   * Search a column within those associated to this class.
   *
   * @param sc  The SpeedoClass under construction.
   * @param cname  The name of the column to be searched.
   * @param t    The SpeedoTable to which it should belong.
   * @return  The column found or null if none.
   */
  private SpeedoColumn searchColumn(SpeedoClass sc, String cn, SpeedoTable t) {
    SpeedoColumn c = null;
    Iterator itf = sc.fields.values().iterator();
    while (itf.hasNext()) {
      SpeedoField ft = (SpeedoField) itf.next();
      for (int k = 0; k < ft.columns.length; k++) {
        if (ft.columns[k].table != t) {
          continue;
        }
        if (ft.columns[k].name.equals(cn)) {
          c = ft.columns[k];
          break;
        }
      }
    }
    return c;
  }
 
  /**
   * Verify if the callback method is well formed. If this is a callback from
   * the class (prefix is ""), it should be "void CALLBACK()". If it is a
   * callback from the listener class, it should be "void CALLBACK(Object)".
   *
   * @param m      The method associated to the callback event.
   * @param listener  The listener class if the callback belongs to such a class.
   * @param sc    The SpeedoClass under construction.
   * @param cbe    The callback event under definition.
   * @return      Null if malformed. Otherwise, it defines the byte code
   *           signature of the callback method.
   */
  private String isWellFormedCallback(Method m, Class listener,
      SpeedoClass sc, String cbe) {
    String res = "(";
    if (m.getReturnType() != Void.TYPE) {
      nbErrors++;
      logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": " + cbe
          + " callback malformed - return type should be void.");
      return null;
    }
    if (listener == null) {
      if (m.getParameterTypes().length != 0) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": " + cbe
            + " class callback malformed - should not have parameters.");
        return null;
      }
    } else {
      if (m.getParameterTypes().length != 1) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": " + cbe
            + " listener class callback malformed - should have a unique parameter.");
        return null;
      }
      Class scc;
      try {
        scc = (Class) Class.forName(sc.getFQName(), false, annotCL);
      } catch (ClassNotFoundException e) {
        // Sure to find it there !!!! NPE otherwise...
        scc = null;
      }
      if (! m.getParameterTypes()[0].isAssignableFrom(scc)) {
        nbErrors++;
        logger.log(BasicLevel.ERROR, sc.getSourceDescShort() + ": " + cbe
            + " listener class callback malformed - should have a parameter compliant with type ("
            + scc.getName() + "), found ("
            + m.getParameterTypes()[0].getName() + ").");
        return null;
      }
      res += Type.getDescriptor(m.getParameterTypes()[0]);
    }
    return res + ")V";
  }

  private String toJavaBean(String prefix, String fieldname) {
    prefix += Character.toUpperCase(fieldname.charAt(0));
    prefix += fieldname.substring(1, fieldname.length());
    return prefix;
  }
 
  /**
   * Look if the given type belongs to a set of valid ones.
   *
   * @param type      The typeto be verified.
   * @param validtypes  The set of valid types.
   * @return  true if it belongs to the given set of types.
   */
  private boolean isValidType(String type, String[]validtypes) {
    for (String t : validtypes) {
      if (t.equals(type)) {
        return true;
      }
    }
    return false;
  }
 
  /**
   * Log a warning for each defined annotations in the annots array.
   *
   * @param annots  The array of annotation that shoudl not be used at this place.
   * @param sc    The SpeedoClass under construction.
   * @param fieldname  The name of the field with which these annotations are associated.
   * @param kind    The kind of field under parsing.
   */
  private void excludeAnnotation(Object[] annots, SpeedoClass sc, String fieldname, String kind) {
    for (Object annot : annots) {
      if (annot != null) {
        logger.log(BasicLevel.WARN, sc.getSourceDescShort() + ": annotation ("
            + annot.getClass().getName() + ") invalid for "
            + kind + " (" + fieldname + ") - ignored.");
      }
    }
  }
}

/**
* This ClassLoader is used to load classes that we want to extract the
* annotations from.
*
* @author P. Dechamboux
*
*/
class LoaderForAnnotAccess extends URLClassLoader {
  Logger logger;

  LoaderForAnnotAccess(Logger log, ClassLoader pcl) {
    super(new URL[0], pcl);
    logger = log;
  }

  void addDirURL(String dir) throws SpeedoException {
    try {
      String furl = "file://" + dir + "/";
      logger.log(BasicLevel.DEBUG,
          "Adding URL to compiler class loader: " + furl);
      addURL(new URL(furl));
    } catch (MalformedURLException e) {
      throw new SpeedoXMLError(e);
    }
  }
}
TOP

Related Classes of org.objectweb.speedo.generation.parser.ejb.EJBAnnotationParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.