Package com.sun.jdo.api.persistence.model.util

Source Code of com.sun.jdo.api.persistence.model.util.ModelValidator$ValidationComponent

/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License").  You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code.  If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license."  If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above.  However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/

/*
* ModelValidator.java
*
* Created on September 22, 2000, 12:49 PM
*/

package com.sun.jdo.api.persistence.model.util;

import java.util.*;
import java.lang.reflect.Modifier;

import org.netbeans.modules.dbschema.*;
import org.netbeans.modules.dbschema.util.NameUtil;
import org.netbeans.modules.dbschema.util.SQLTypeUtil;

import com.sun.jdo.api.persistence.model.Model;
import com.sun.jdo.api.persistence.model.jdo.*;
import com.sun.jdo.api.persistence.model.mapping.*;
import com.sun.jdo.spi.persistence.utility.*;
import com.sun.jdo.spi.persistence.utility.logging.Logger;

/**
*
* @author Rochelle Raccah
* @version %I%
*/
public class ModelValidator
{
  /** This field holds the model object used for validation */
  private Model _model;

  /** This field holds the name of the class being validated */
  private String _className;

  /** This field holds the class loader used to load class
   * being validated (if available).
   */
  private ClassLoader _classLoader;

  /** I18N message handler */
  private ResourceBundle _messages;

  public ModelValidator (Model model, String className, ResourceBundle bundle)
  {
    this(model, className, null, bundle);
  }

   /** Create a new model validator object.
   * @param model model object used for validation
   * @param className the name of the class being validated
   */
  public ModelValidator (Model model, String className,
    ClassLoader classLoader, ResourceBundle bundle)
  {
    _model = model;
    _className = className;
    _classLoader = classLoader;
    _messages = bundle;
  }

  /**
   * Get the model object used for validation.
   * @return the model object used for validation
   */
  public Model getModel () { return _model; }

  /**
   * Get the name of the class being validated.
   * @return the name of the class being validated
   */
  public String getClassName () { return _className; }

  /**
   * Get the class loader used to load the class being validated.
   * @return the class loader of the class being validated
   */
  public ClassLoader getClassLoader () { return _classLoader; }

  /** @return I18N message handler for this element
   */
  protected ResourceBundle getMessages () { return _messages; }

  /** Main method used for parsing the combination of java (or class)
   * information and mapping/jdo information by running through a subset
   * of the full validation check and aborting (and returning
   * <code>false</code> at the first error or warning.
   * @return <code>true</code> if no errors or warnings occur,
   * <code>false</code> otherwise.
   * @see #getBasicValidationList
   */
  public boolean parseCheck ()
  {
    Iterator iterator = getBasicValidationList().iterator();

    try
    {
      while (iterator.hasNext())
        ((ValidationComponent)iterator.next()).validate();
    }
    catch (ModelValidationException e)
    {
      LogHelperModel.getLogger().log(Logger.FINER,
        "model.parse_error", e)// NOI18N

      return false;
    }

    return true;
  }

  /** Main method used for validating the combination of java (or class)
   * information and mapping/jdo information by running through the full
   * validation check and returning a collection of
   * ModelValidationExceptions containing any errors or warnings encountered.
   * @return a collection of ModelValidationExceptions containing any
   * errors or warnings encountered.  If no errors or warnings were
   * encountered, the collection will be empty, not <code>null</code>.
   * @see #getFullValidationList
   */
  public Collection fullValidationCheck ()
  {
    ArrayList list = new ArrayList();
    Iterator iterator = getFullValidationList().iterator();

    while (iterator.hasNext())
    {
      try
      {
        ((ValidationComponent)iterator.next()).validate();
      }
      catch (ModelValidationException e)
      {
        list.add(e);
      }
    }

    return Collections.unmodifiableCollection(list);
  }

  // ================ Validation list construction methods ===============

  /** Computes and returns a collection of ValidationComponents
   * representing the tests to be performed during parse.
   * @return a collection of ValidationComponents representing the
   * tests to be performed during parse.
   * @see #getDatabaseValidationList
   * @see #getFieldsValidationList
   * @see #getFullValidationList
   */
  public Collection getBasicValidationList ()
  {
    ArrayList list = new ArrayList();
    String className = getClassName();

    list.add(createClassExistenceComponent(className));
    list.add(createClassPersistenceComponent(className));

    list.addAll(getDatabaseValidationList());
    list.addAll(getFieldsValidationList());

    return Collections.unmodifiableCollection(list);
  }
 
  /** Computes and returns a collection of ValidationComponents
   * representing the tests to be performed during validation.  These
   * include all those in the basic list plus those which check
   * cardinality and the related classes in more detail.
   * @return a collection of ValidationComponents representing the
   * tests to be performed during validation.
   * @see #getRelatedClassValidationList
   * @see #getBasicValidationList
   */
  public Collection getFullValidationList ()
  {
    ArrayList list = new ArrayList(getBasicValidationList());
    String className = getClassName();
    PersistenceClassElement persistenceClass =
      getPersistenceClass(className);

    if (persistenceClass != null)
    {
      PersistenceFieldElement[] fields = persistenceClass.getFields();
      int i, count = ((fields != null) ? fields.length : 0);

      list.add(createSerializableClassComponent(className));
      list.add(createKeyClassComponent(persistenceClass.getKeyClass()));
      list.add(createClassMappingComponent(persistenceClass));
      list.add(createKeyColumnMappingComponent(persistenceClass));

      for (i = 0; i < count; i++)
      {
        PersistenceFieldElement field = fields[i];

        list.add(createFieldCardinalityComponent(field));
        list.add(createFieldMappingComponent(field));
        list.add(createFieldBlobMappingComponent(field));
        list.addAll(getRelatedClassValidationList(field));
      }
    }

    return Collections.unmodifiableCollection(list);
  }

  // ============= Validation list construction suppport methods ============

  /** Computes and returns a collection of ValidationComponents
   * representing the database tests to be performed.
   * @return a collection of ValidationComponents representing the
   * database tests to be performed.
   */
  private Collection getDatabaseValidationList ()
  {
    ArrayList list = new ArrayList();
    String className = getClassName();
    MappingClassElement mappingClass = getMappingClass(className);

    if (mappingClass != null)
    {
      ArrayList tables = mappingClass.getTables();
      int i, count = ((tables != null) ? tables.size() : 0);
      MappingTableElement primaryTable = null;
      Iterator iterator = null;

      list.add(createSchemaExistenceComponent(className));

      for (i = 0; i < count; i++)
      {
        MappingTableElement nextTable =
          (MappingTableElement)tables.get(i);

        list.add(createTableExistenceComponent(nextTable.getTable()));

        if (i == 0)
        {
          primaryTable = nextTable;
          list.add(createPrimaryTableComponent(primaryTable));
        }
        else
        {
          MappingReferenceKeyElement referenceKey =
            findReferenceKey(primaryTable, nextTable);

          if (referenceKey != null)
          {
            iterator = referenceKey.getColumnPairNames().iterator();
            while (iterator.hasNext())
            {
              list.add(createColumnExistenceComponent(
                (String)iterator.next()));
            }
          }
        }
      }

      list.add(createVersionConsistencyComponent(mappingClass));

      iterator = mappingClass.getFields().iterator();
      while (iterator.hasNext())
      {
        MappingFieldElement nextField =
          (MappingFieldElement)iterator.next();
        ArrayList allColumns = new ArrayList();
        Iterator columnIterator = null;

        if (isRelationship(nextField))
        {
          allColumns.addAll(((MappingRelationshipElement)nextField).
            getAssociatedColumns());
        }

        allColumns.addAll(nextField.getColumns());

        columnIterator = allColumns.iterator();
        while (columnIterator.hasNext())
        {
          list.add(createColumnExistenceComponent(
            (String)columnIterator.next(), nextField));
        }
      }
    }

    return list;
  }

  /** Computes and returns a collection of ValidationComponents
   * representing the field and relationship tests to be performed.
   * @return a collection of ValidationComponents representing the
   * field and relationship tests to be performed.
   */
  private Collection getFieldsValidationList ()
  {
    ArrayList list = new ArrayList();
    Model model = getModel();
    String className = getClassName();
    PersistenceClassElement persistenceClass =
      getPersistenceClass(className);

    if (persistenceClass != null)
    {
      PersistenceFieldElement[] fields = persistenceClass.getFields();
      int i, count = ((fields != null) ? fields.length : 0);
      Iterator iterator =
        getMappingClass(className).getFields().iterator();

      for (i = 0; i < count; i++)
      {
        PersistenceFieldElement field = fields[i];

        list.add(createFieldExistenceComponent(field));

        // even though this is really the validation step, we
        // only want to add the others if the field exists
        if (model.hasField(className, field.getName()))
        {
          list.add(createFieldPersistenceComponent(field));
          list.add(createFieldPersistenceTypeComponent(field));
          list.add(createFieldConsistencyComponent(field));

          if (isLegalRelationship(field))
          {
            RelationshipElement rel = (RelationshipElement)field;

            /* user modifiable collection class not yet supported
            list.add(createCollectionClassComponent(rel));*/
            list.add(createElementClassComponent(rel));
            list.add(createRelatedClassMatchesComponent(rel));
          }
        }
      }

      while (iterator.hasNext())
      {
        MappingFieldElement field =
          (MappingFieldElement)iterator.next();
        String fieldName = field.getName();

        // only check this if it is not in the jdo model
        if (persistenceClass.getField(fieldName) == null)
        {
          list.add(createFieldExistenceComponent(field));

          // even though this is really the validation step, we
          // only want to add the others if the field exists
          if (model.hasField(className, fieldName))
            list.add(createFieldConsistencyComponent(field));
        }

        if (!isRelationship(field))
          list.add(createColumnOverlapComponent(field));

        // preliminary fix for CR6239630
        if (Boolean.getBoolean("AllowManagedFieldsInDefaultFetchGroup")) // NOI18N
         {
                                    // Do nothing - AllowManagedFieldsInDefaultFetchGroup:
                                    // disabled single model validation test;
                                    // may use checked read/write access to managed fields
        }
        else
        {
          list.add(createFieldDefaultFetchGroupComponent(field));
        }
      }
    }

    return list;
  }

  /** Computes and returns a collection of ValidationComponents
   * representing the related class tests to be performed.  Right now,
   * these are only included as part of full validation, as they may
   * be somewhat time intensive since they compute information about
   * other classes as well as this class.
   * @return a collection of ValidationComponents representing the
   * related class tests to be performed.
   */
  private Collection getRelatedClassValidationList (
    PersistenceFieldElement field)
  {
    String relatedClass = getRelatedClass(field);
    ArrayList list = new ArrayList();

    // even though this is really already included in the validation
    // step, we only want to add the extra steps if the field exists
    if ((relatedClass != null) &&
      getModel().hasField(getClassName(), field.getName()))
    {
      MappingClassElement relatedClassElement =
        getMappingClass(relatedClass);

      list.add(createClassExistenceComponent(relatedClass, field));
      list.add(createClassPersistenceComponent(relatedClass, field));
      list.add(createSchemaExistenceComponent(relatedClass, field));
      list.add(createRelatedSchemaMatchesComponent(relatedClass, field));

      if (relatedClassElement != null)
      {
        ArrayList tables = relatedClassElement.getTables();
        MappingTableElement primaryTable = null;
        boolean hasTables = ((tables != null) && (tables.size() > 0));

        if (hasTables)
        {
          primaryTable = (MappingTableElement)tables.get(0);
          list.add(createTableExistenceComponent(
            primaryTable.getTable(), field));
        }

        if (isRelationship(field))
        {
          RelationshipElement relElement = (RelationshipElement)field;
          Object rel = getMappingClass(getClassName()).
            getField(field.getName());

          list.add(createInverseFieldComponent(relElement));
          list.add(createInverseMappingComponent(relElement));

          // verify that the columns from the primary table
          // of the related class are actually from that table
          // since it could have been changed
          if ((rel != null) && isRelationship(rel))
          {
            MappingRelationshipElement relationship =
              (MappingRelationshipElement)rel;
            ArrayList columns =
              relationship.getAssociatedColumns();
            Iterator iterator = null;

            if ((columns == null) || (columns.size() == 0))
              columns = relationship.getColumns();

            if (columns != null)
            {
              List tableNames = new ArrayList();

              if (hasTables)
              {
                Iterator tableIterator = tables.iterator();

                while (tableIterator.hasNext())
                {
                  tableNames.add(((MappingTableElement)
                    tableIterator.next()).getName());
                }
              }

              iterator = columns.iterator();

              while (iterator.hasNext())
              {
                list.add(createRelatedTableMatchesComponent(
                  relatedClass, field, tableNames,
                  (String)iterator.next()));
              }
            }
          }
        }
      }
    }

    return list;
  }

  // ================ Validation Component inner classes ===============

  /** Create a validation component which can check whether the class exists.
   * @param className the class whose existence is being checked
   * @param relatedField the relationship field whose class is being checked,
   * may be <code>null</code> in which case we are probably checking the
   * same class as the validator is checking overall
   * @return the validation component
   */
  protected ValidationComponent createClassExistenceComponent (
    final String className, final PersistenceFieldElement relatedField)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        if ((className == null) ||
          !getModel().hasClass(className, getClassLoader()))
        {
          throw constructClassException(className, relatedField,
            "util.validation.class_not_found");    //NOI18N
        }
      }
    };
  }

  /** Create a validation component which can check whether the class exists.
   * @param className the class whose existence is being checked
   * @return the validation component
   */
  protected ValidationComponent createClassExistenceComponent (
    final String className)
  {
    return createClassExistenceComponent(className, null);
  }

  /** Create a validation component which can check the class persistence.
   * @param className the class whose persistence is being checked
   * @param relatedField the relationship field whose class is being checked,
   * may be <code>null</code> in which case we are probably checking the
   * same class as the validator is checking overall
   * @return the validation component
   */
  protected ValidationComponent createClassPersistenceComponent (
    final String className, final PersistenceFieldElement relatedField)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        Model model = getModel();
 
        if ((className != null) &&
           model.hasClass(className, getClassLoader()))
        {
          String key = null;

          if (!isPersistent(className))
            key = "util.validation.class_not_persistence_capable";//NOI18N
          else if (!model.isPersistenceCapableAllowed(className))
            key = "util.validation.class_not_allowed";//NOI18N

          if (key != null)
          {
            throw constructClassException(
              className, relatedField, key);
          }
        }
      }
    };
  }

  /** Create a validation component which can check the class persistence.
   * @param className the class whose persistence is being checked
   * @return the validation component
   */
  protected ValidationComponent createClassPersistenceComponent (
    final String className)
  {
    return createClassPersistenceComponent(className, null);
  }

  /** Create a validation component which can check whether the field exists.
   * @param fieldName the field whose existence is being checked
   * @return the validation component
   */
  protected ValidationComponent createFieldExistenceComponent (
    final String fieldName)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        if (!getModel().hasField(getClassName(), fieldName))
        {
          throw constructFieldException(fieldName,
            "util.validation.field_not_found");      //NOI18N
        }
      }
    };
  }

  /** Create a validation component which can check whether the field exists.
   * @param field the field whose existence is being checked
   * @return the validation component
   */
  protected ValidationComponent createFieldExistenceComponent (Object field)
  {
    return createFieldExistenceComponent(field.toString());
  }

  /** Create a validation component which can check whether the field is
   * persistent.
   * @param field the field whose persistence is being checked
   * @return the validation component
   */
  protected ValidationComponent createFieldPersistenceComponent (
    final PersistenceFieldElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        boolean isPersistent = (PersistenceFieldElement.PERSISTENT ==
          field.getPersistenceType());
        String fieldName = field.getName();

        if (isPersistent &&
          !isPersistentAllowed(getClassName(), fieldName))
        {
          throw constructFieldException(fieldName,
            "util.validation.field_persistent_not_allowed");//NOI18N
        }
      }
    };
  }

  /** Create a validation component which can check whether the field is
   * consistent (field in both models or relationship in both).
   * @param field the field whose consistency is being checked
   * @return the validation component
   */
  protected ValidationComponent createFieldConsistencyComponent (
    final PersistenceFieldElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        String fieldName = field.getName();
        String className = getClassName();
        boolean isLegallyPersistent =
          isPersistentAllowed(className, fieldName);

        if (isLegallyPersistent)
        {
          MappingClassElement mappingClass =
            getMappingClass(className);
          MappingFieldElement mappingElement =
            ((mappingClass != null) ?
            mappingClass.getField(fieldName) : null);

          if (mappingElement != null)
          {
            boolean jdoIsRelationship = isLegalRelationship(field);

            if (jdoIsRelationship != isRelationship(mappingElement))
            {
              throw constructFieldException(fieldName,
                "util.validation.field_type_inconsistent")//NOI18N
            }
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether the field is
   * consistent (if in mapping model but not jdo, it is a problem).
   * @param field the field whose consistency is being checked
   * @return the validation component
   */
  protected ValidationComponent createFieldConsistencyComponent (
    final MappingFieldElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        if (field != null)
        {
          String fieldName = field.getName();
          PersistenceClassElement persistenceClass =
            getPersistenceClass(getClassName());
          PersistenceFieldElement persistenceElement =
            ((persistenceClass != null) ?
            persistenceClass.getField(fieldName) : null);

          if (persistenceElement == null)
          {
            throw constructFieldException(fieldName,
              "util.validation.field_model_inconsistent");//NOI18N
          }
        }
      }
    };
  }

  /** Create a validation component which can check the persistence type
   * of the field (whether it is a relationship or not).
   * @param field the field whose persistence type is being checked
   * @return the validation component
   */
  protected ValidationComponent createFieldPersistenceTypeComponent (
    final PersistenceFieldElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        String fieldName = field.getName();
        String className = getClassName();
        boolean isLegallyPersistent =
          isPersistentAllowed(className, fieldName);

        if (isLegallyPersistent)
        {
          boolean isRelationship = isRelationship(field);
          boolean mustBeRelationship = shouldBeRelationship(field);

          if (isRelationship && !mustBeRelationship)
          {
            throw constructFieldException(fieldName,
              "util.validation.field_relationship_not_allowed");//NOI18N
          }
          else if (!isRelationship && mustBeRelationship)
          {
            throw constructFieldException(fieldName,
              "util.validation.field_type_not_allowed")//NOI18N
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether the cardinality
   * bounds are semantically valid given the relationship field type.
   * @param field the relationship whose cardinality bounds are being checked
   * @return the validation component
   */
  protected ValidationComponent createFieldCardinalityComponent (
    final PersistenceFieldElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        if (isLegalRelationship(field))
        {
          RelationshipElement relationship =
            (RelationshipElement)field;
          String fieldName = field.getName();
          boolean nonCollectionRelationship =
            !isCollection(getClassName(), fieldName);
          int upperBound = (nonCollectionRelationship ?
            1 : relationship.getUpperBound());
          int lowerBound = relationship.getLowerBound();
          MappingRelationshipElement mapping = null;

          if ((lowerBound < 0) || (upperBound <= 0) ||
            (lowerBound > upperBound))
          {
            throw constructFieldException(fieldName,
              "util.validation.cardinality_invalid")//NOI18N
          }

          // now check specific lower bound requirements imposed
          // by the mapping
          mapping = getMappingRelationship(relationship);
          if (nonCollectionRelationship && (lowerBound != 1) &&
            (mapping != null) && !isJoin(mapping))
          {
            // If the non-collection relationship field is exactly
            // mapped to a FK, we need to check the nullability
            // of the columns. If there are any non-nullable
            // columns the lower bound must be 1.
            ForeignKeyElement fk = getMatchingFK(mapping);

            if ((fk != null) && hasNonNullableColumn(fk))
            {
              throw constructFieldException(fieldName,
                "util.validation.lower_bound_invalid"); //NOI18N
            }
          }
        }
      }

      /** Returns <code>true</code> if the specified FK has at least
       * one non-nullable column. Please note that the caller is
       * responsible for passing a non-null fk argument.
       */
      private boolean hasNonNullableColumn (ForeignKeyElement fk)
      {
        ColumnElement[] localColumns = fk.getLocalColumns();
        int count = ((localColumns != null) ? localColumns.length : 0);
       
        for (int i = 0; i < count; i++)
        {
          if (!localColumns[i].isNullable())
            return true;
        }

        return false;
      }

      /** Checks whether the specified relationship is exactly mapped
       * to a FK. If yes, the method returns the matching FK. If not,
       * it returns <code>null</code>. Please note that the caller is
       * responsible for passing a non-null mapping argument.
       */
       private ForeignKeyElement getMatchingFK (
        MappingRelationshipElement mapping)
      {
        MappingClassElement mappingClass = mapping.
          getDeclaringClass();
        String databaseRoot = getSchemaForClass(getClassName());
        List pairNames = mapping.getColumns();
        List tables = mappingClass.getTables();
         
        if (tables != null)
        {
          for (Iterator i = tables.iterator(); i.hasNext();)
          {
            String tableName = ((MappingTableElement)i.next()).
              getName();
            TableElement table = getTable(tableName, databaseRoot);
            ForeignKeyElement fk = getMatchingFK(pairNames, table);
           
            if (fk != null)
              return fk;
          }
        }

        return null;
      }

      /** Checks whether the specified TableElement has a FK that
       * exactly matches the list of column pair names.
       * @return the matching FK if it exactly matches the list
       * of column pairs; <code>null</code> otherwise.
       */
      private ForeignKeyElement getMatchingFK (List pairNames,
        TableElement table)
      {
        ForeignKeyElement[] foreignKeys = (table != null) ?
          table.getForeignKeys() : null;
        int count = ((foreignKeys != null) ? foreignKeys.length : 0);

        for (int i = 0; i < count; i++)
        {
          if (matchesFK(pairNames, foreignKeys[i]))
            return foreignKeys[i];
        }

        return null;
      }

      /** Returns <code>true</code> if the specified list of column
       * pair names matches exactly the specified FK.
       */
      private boolean matchesFK (List pairNames,
        ForeignKeyElement foreignKey)
      {
        ColumnPairElement[] fkPairs = foreignKey.getColumnPairs();
        int fkCount = ((fkPairs != null) ? fkPairs.length : 0);
        int count = ((pairNames != null) ? pairNames.size() : 0);

        // First check whether the list of fk column pairs has the
        // same size than the specified list of columns.
        if (fkCount == count)
        {
          // Now check whether each fk column is included in the
          // specified list of columns.
          for (int i = 0; i < fkCount; i++)
          {
            String fkPairName = NameUtil.getRelativeMemberName(
              fkPairs[i].getName().getFullName());

            if (!pairNames.contains(fkPairName))
              return false;
          }

          return true;
        }

        return false;
      }
    };
  }

  /** Create a validation component which can check whether the field is 
   * unmapped.
   * @param field the field whose mapping is being checked
   * @return the validation component
   */
  protected ValidationComponent createFieldMappingComponent (
    final PersistenceFieldElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        String fieldName = field.getName();
        MappingClassElement mappingClass =
          getMappingClass(getClassName());
        if ((mappingClass != null) &&
          (mappingClass.getTables().size() > 0))
        {
          MappingFieldElement mappingField =
            mappingClass.getField(fieldName);

          if ((mappingField == null) ||
            (mappingField.getColumns().size() == 0))
          {
            throw constructFieldException(
              ModelValidationException.WARNING, fieldName,
              "util.validation.field_not_mapped")//NOI18N
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether the field is 
   * mapped to a blob type and if so, whether it is a key field or belongs
   * to the default fetch group.  Note that it's somewhat important to check
   * for the key field first because if a field is key, its fetch group 
   * value in the model is ignored.
   * @param field the field whose mapping/key field/fetch group consistency
   * is being checked
   * @return the validation component
   */
  protected ValidationComponent createFieldBlobMappingComponent (
    final PersistenceFieldElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        String className = getClassName();
        String fieldName = field.getName();
        MappingClassElement mappingClass = getMappingClass(className);
        MappingFieldElement mappingField = ((mappingClass != null) ?
          mappingClass.getField(fieldName) : null);

        if (mappingField != null)
        {
          boolean isKey = field.isKey();

           if (isKey || (MappingFieldElement.GROUP_DEFAULT ==
            mappingField.getFetchGroup()))
          {
            if (isMappedToBlob(mappingField,
              getSchemaForClass(className)))
            {
              throw constructFieldException(fieldName, (isKey ?
                "util.validation.field_key_field_not_allowed" : //NOI18N
                "util.validation.field_fetch_group_not_allowed")); // NOI18N
            }
          }
        }
      }
      private boolean isMappedToBlob (MappingFieldElement mappingField,
        String schema)
      {
        if (mappingField instanceof MappingRelationshipElement)
        {
          return isMappedToBlob(
            (MappingRelationshipElement)mappingField, schema);
        }
        else
        {
          Iterator iterator = mappingField.getColumns().iterator();

          while (iterator.hasNext())
          {
            String absoluteName = NameUtil.getAbsoluteMemberName(
              schema, (String)iterator.next());
            TableElement table = TableElement.forName(
              NameUtil.getTableName(absoluteName));
            ColumnElement columnElement = ((table != null) ?
              (ColumnElement)table.getMember(
              DBIdentifier.create(absoluteName)) : null);

            if (isMappedToBlob(columnElement))
              return true;
          }
        }

        return false;
      }
      private boolean isMappedToBlob (MappingRelationshipElement rel,
        String schema)
      {
        Iterator iterator = rel.getColumns().iterator();

        while (iterator.hasNext())
        {
          ColumnPairElement pair =
            getPair((String)iterator.next(), schema);

          if (isMappedToBlob(pair))
            return true;
        }

        // now check join columns
        iterator = rel.getAssociatedColumns().iterator();
        while (iterator.hasNext())
        {
          ColumnPairElement pair =
            getPair((String)iterator.next(), schema);

          if (isMappedToBlob(pair))
            return true;
        }

        return false;
      }
      private boolean isMappedToBlob (ColumnPairElement pair)
      {
        return ((pair == null) ? false :
          isMappedToBlob(pair.getLocalColumn()) &&
          isMappedToBlob(pair.getReferencedColumn()));
      }
      private boolean isMappedToBlob (ColumnElement column)
      {
        return ((column != null) &&
          SQLTypeUtil.isBlob(column.getType()));
      }
    };
  }

  /** Create a validation component which can check whether the collection
   * class is valid given the relationship field type.
   * @param field the relationship whose collection class is being checked
   * @return the validation component
   */
  protected ValidationComponent createCollectionClassComponent (
    final RelationshipElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        String className = getClassName();
        String fieldName = field.getName();

        if (isCollection(className, fieldName))
        {
          Model model = getModel();
          String collectionClass = field.getCollectionClass();
          String fieldType = model.getFieldType(className, fieldName);
          boolean missingCollectionClass =
            StringHelper.isEmpty(collectionClass);

          if (!missingCollectionClass &&
            !model.getSupportedCollectionClasses(fieldType).
            contains(collectionClass))
          {
            throw constructFieldException(fieldName,
              "util.validation.collection_class_invalid");//NOI18N
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether the
   * relationship is mapped to columns even though the element class is null.
   * @param field the relationship whose element class is being checked
   * @return the validation component
   */
  protected ValidationComponent createElementClassComponent (
    final RelationshipElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        String className = getClassName();
        String fieldName = field.getName();

        if (isCollection(className, fieldName))
        {
          String elementClass = field.getElementClass();

          if (StringHelper.isEmpty(elementClass))
          {
            MappingClassElement mappingClass =
              getMappingClass(className);
            MappingFieldElement mappingElement =
              ((mappingClass != null) ?
              mappingClass.getField(fieldName) : null);

            if ((mappingElement != null) &&
              (mappingElement.getColumns().size() > 0))
            {
              throw constructFieldException(fieldName,
                "util.validation.element_class_not_found");//NOI18N
            }
          }
        }
      }
    };
  }

  /** Create a validation component which checks whether the rules for
   * version consistency are followed.  This includes:
   * <ul>
   * <li> There must be exactly one version field defined.
   * <li> The version field must not be a relationship.
   * <li> The version field must not be a key field.
   * <li> The version field must be of java type (primitive) long.
   * <li> The version field must be in the default fetch group.
   * <li> The version field must be mapped to exactly 1 column from the
   * primary table.
   * <li> The column to which the version field is mapped must be of a
   * numeric type and non-nullable.
   * <li> The column to which the version field is mapped must not be a PK or
   * FK column.
   * </ul>
   * @param mappingClass the mapping class element whose consistency is being
   * checked
   * @return the validation component
   */
  protected ValidationComponent createVersionConsistencyComponent (
    final MappingClassElement mappingClass)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        // only bother to check for classes with version consistency
        if (MappingClassElement.VERSION_CONSISTENCY ==
          mappingClass.getConsistencyLevel())
        {
          MappingFieldElement versionField =
             validateVersionFieldExistence();
          String className = mappingClass.getName();
          String fieldName = versionField.getName();
          String columnName = null;
          ColumnElement column = null;

          if (versionField instanceof MappingRelationshipElement)
          {
            throw constructFieldException(fieldName,
              "util.validation.version_field_relationship_not_allowed");//NOI18N
          }
          else if (MappingFieldElement.GROUP_DEFAULT !=
            versionField.getFetchGroup()) // must be in DFG
          {
            throw constructFieldException(fieldName,
              "util.validation.version_field_fetch_group_invalid");//NOI18N
          }

          validatePersistenceFieldAttributes(className, fieldName);
          columnName = validateVersionFieldMapping(versionField);
          column = validateTableMatch(className, fieldName, columnName);
          validateColumnAttributes(className, fieldName, column);
        }
      }
      /** Helper method validating the existence of the exactly one
       * version field.
       */
      private MappingFieldElement validateVersionFieldExistence ()
        throws ModelValidationException
      {
        List versionFields = mappingClass.getVersionFields();

        // must have exactly 1 version field (for this release)
        if (versionFields.size() != 1)
        {
          throw constructClassException(mappingClass.getName(),
            null, "util.validation.version_field_cardinality")//NOI18N
        }

        return (MappingFieldElement)versionFields.get(0);
      }
      /** Helper method validating the attributes of the field in the 
       * jdo model which corresponds to the version field.
       */
      private void validatePersistenceFieldAttributes (String className,
        String fieldName) throws ModelValidationException
      {
        Class fieldType = JavaTypeHelper.getPrimitiveClass(
          getModel().getFieldType(className, fieldName));
        String keyName = null;

        // must not be a key field
        if (getPersistenceClass(className).getField(fieldName).isKey())
          keyName = "util.validation.version_field_key_field_not_allowed";//NOI18N
        else if (Long.TYPE != fieldType// must be type long
          keyName = "util.validation.version_field_type_not_allowed";//NOI18N

        if (keyName != null)
          throw constructFieldException(fieldName, keyName);
      }
      /** Helper method validating the column name of the 
       * version field mapping.
       */
      private String validateVersionFieldMapping (
        MappingFieldElement versionField)
        throws ModelValidationException
      {
        List columns = versionField.getColumns();

        // must be mapped to exactly 1 column (for this release)
        if (columns.size() != 1)
        {
          throw constructFieldException(versionField.getName(),
            "util.validation.version_field_not_mapped")//NOI18N
        }

        return (String)columns.get(0);
      }
      /** Helper method validating the column mapping of the version
       * field is from the primary table.
       */
      private ColumnElement validateTableMatch (String className,
        String fieldName, String columnName)
        throws ModelValidationException
      {
        String schema = getSchemaForClass(className);
        String absoluteName =
          NameUtil.getAbsoluteMemberName(schema, columnName);
        TableElement table =
          TableElement.forName(NameUtil.getTableName(absoluteName));
        String primaryName = ((MappingTableElement)mappingClass.
          getTables().get(0)).getName();
        TableElement pTable = getTable(primaryName, schema);

        // column must be from the PT
        if (table != pTable)
        {
          throw new ModelValidationException(
            getModel().getField(className, fieldName),
            I18NHelper.getMessage(getMessages(),
            "util.validation.version_field_table_mismatch", //NOI18N
            new Object[]{columnName, fieldName, className}));
        }

        return ((table != null) ? (ColumnElement)table.getMember(
          DBIdentifier.create(absoluteName)) : null);
      }
      /** Helper method validating the attributes of the column of the 
       * version field mapping.
       */
      private void validateColumnAttributes (String className,
        String fieldName, ColumnElement column)
        throws ModelValidationException
      {
        String keyName = null;

        // column must be numeric type and non-nullable
        if (column.isNullable() || !column.isNumericType())
          keyName = "util.validation.version_field_column_type_invalid";    // NOI18N
        else  // column must be non-PK and non-FK column
        {
          TableElement table = column.getDeclaringTable();
          UniqueKeyElement[] uks = table.getUniqueKeys();
          ForeignKeyElement[] fks = table.getForeignKeys();
          int i, count = ((uks != null) ? uks.length : 0);

          for (i = 0; i < count; i++)
          {
            UniqueKeyElement uk = uks[i];

            if (uk.isPrimaryKey() && Arrays.asList(
              uk.getColumns()).contains(column))
            {
              keyName = "util.validation.version_field_column_pk_invalid";    // NOI18N
              break;
            }
          }

          count = ((fks != null) ? fks.length : 0);
          for (i = 0; i < count; i++)
          {
            ForeignKeyElement fk = fks[i];

            if (Arrays.asList(fk.getLocalColumns()).
              contains(column))
            {
              keyName = "util.validation.version_field_column_fk_invalid";    // NOI18N
              break;
            }
          }
        }

        if (keyName != null)
        {
          throw new ModelValidationException(
            getModel().getField(className, fieldName),
            I18NHelper.getMessage(getMessages(), keyName,
            new Object[]{column.getName(), fieldName, className}));
        }
      }
    };
  }

  /** Create a validation component which can check whether the inverse of
   * the inverse of the relationship is the relationship itself.
   * @param field the relationship whose inverse relationship is being checked
   * @return the validation component
   */
  protected ValidationComponent createInverseFieldComponent (
    final RelationshipElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        Model model = getModel();
        RelationshipElement inverse =
          field.getInverseRelationship(model);
        RelationshipElement inverseInverse = ((inverse != null) ?
          inverse.getInverseRelationship(model) : null);

        if ((inverse != null) &&
          (!field.equals(inverseInverse) || (inverseInverse == null)))
        {
          String fieldName = field.getName();

          throw new ModelValidationException(
            model.getField(getClassName(), fieldName),
            I18NHelper.getMessage(getMessages(),
            "util.validation.inverse_field_invalid", //NOI18N
            new Object[]{fieldName, inverse.getName()}));
        }
      }
    };
  }

  /** Create a validation component which can check whether the inverse of
   * the relationship belongs to the related class (type or element class
   * depending on cardinality).
   * @param field the relationship whose inverse relationship is being checked
   * @return the validation component
   */
  protected ValidationComponent createRelatedClassMatchesComponent (
    final RelationshipElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        String inverseName =
          field.getInverseRelationshipName();

        if (!StringHelper.isEmpty(inverseName))
        {
          Model model = getModel();
          RelationshipElement inverse =
            field.getInverseRelationship(model);

          if (inverse == null) // no such field in that related class
          {
            String relatedClass = getRelatedClass(field);
            String fieldName = field.getName();
            String key = ((relatedClass != null) ?
              "util.validation.related_class_mismatch" : //NOI18N
              "util.validation.related_class_not_found");//NOI18N
            Object[] args = ((relatedClass != null) ?
              new Object[]{fieldName, inverseName, relatedClass}
              : new Object[]{fieldName, inverseName});
             
            throw new ModelValidationException(
              model.getField(getClassName(), fieldName),
              I18NHelper.getMessage(getMessages(), key, args));
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether the mapping of
   * the relationship and the mapping of its inverse are inverses of each
   * other.
   * @param field the relationship whose inverse relationship is being checked
   * @return the validation component
   */
  protected ValidationComponent createInverseMappingComponent (
    final RelationshipElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        Model model = getModel();
        RelationshipElement inverse =
          field.getInverseRelationship(model);

        if ((inverse != null) && !isInverseMapping(field, inverse))
        {
          String fieldName = field.getName();

          throw new ModelValidationException(
            model.getField(getClassName(), fieldName),
            I18NHelper.getMessage(getMessages(),
            "util.validation.inverse_mapping_mismatch", //NOI18N
            new Object[]{fieldName, inverse.getName()}));
        }
      }
      private boolean hasMappingRows (MappingRelationshipElement field2)
      {
        if (field2 != null)
        {
          ArrayList columns = field2.getColumns();
         
          return ((columns != null) && !columns.isEmpty());
        }

        return false;
      }
      private boolean isInverseMapping (RelationshipElement jdoField1,
        RelationshipElement jdoField2)
      {
        MappingRelationshipElement field1 =
          getMappingRelationship(jdoField1);
        MappingRelationshipElement field2 =
          getMappingRelationship(jdoField2);
        boolean field1HasMapping = hasMappingRows(field1);
        boolean field2HasMapping = hasMappingRows(field2);

        // if both have rows, they must be exact inverses
        if (field1HasMapping && field2HasMapping)
        {
          boolean field1IsJoin = isJoin(field1);

          if (field1IsJoin == isJoin(field2))
          {
            ArrayList pairs1 = field1.getColumns();
            ArrayList pairs2 = field2.getColumns();

            return ((!field1IsJoin) ? isInverse(pairs1, pairs2) :
              (isInverse(pairs1,
              field2.getAssociatedColumns()) &&
              isInverse(field1.getAssociatedColumns(), pairs2)));
          }

          return false;
        }

        // if neither have rows that's fine
        return (field1HasMapping == field2HasMapping);
      }
      private boolean isInverse (ArrayList pairs1, ArrayList pairs2)
      {
        int i, size1 = pairs1.size(), size2 = pairs2.size();

        if (size1 == size2)
        {
          for (i = 0; i < size1; i++)
          {
            String nextPair = (String)pairs1.get(i);
            String inversePair = (String)pairs2.get(i);
            int semicolonIndex1 = nextPair.indexOf(';');
            int semicolonIndex2 = inversePair.indexOf(';');

            if (((semicolonIndex1 == -1) || (semicolonIndex2 == -1))
              || (!nextPair.substring(0, semicolonIndex1).equals(
              inversePair.substring(semicolonIndex2 + 1)) ||
              !nextPair.substring(semicolonIndex1 + 1).equals(
              inversePair.substring(0, semicolonIndex2))))
            {
              return false;
            }
          }

          return true;
        }

        return false;
      }
    };
  }

  /** Create a validation component which can check whether the field is
   * part of a managed (multiple fields to same column) group and in an
   * illegal fetch group.  If the field is in one of these groups, it is
   * not allowed to be in the default fetch group.
   * @param field the field whose fetch group is being checked
   * @return the validation component
   */
  protected ValidationComponent createFieldDefaultFetchGroupComponent (
    final MappingFieldElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        if (field != null)
        {
          String fieldName = field.getName();
          PersistenceClassElement persistenceClass =
            getPersistenceClass(getClassName());
          PersistenceFieldElement pElement =
            ((persistenceClass != null) ?
            persistenceClass.getField(fieldName) : null);

          if ((pElement != null) && !pElement.isKey() &&
            (MappingFieldElement.GROUP_DEFAULT ==
            field.getFetchGroup()))
          {
            MappingClassElement mappingClass =
              field.getDeclaringClass();
            boolean isVersionField =
              ((MappingClassElement.VERSION_CONSISTENCY ==
                mappingClass.getConsistencyLevel()) &&
                field.isVersion());
            Iterator iterator = mappingClass.getFields().iterator();
            String exceptionKey = (!isVersionField ?
              "util.validation.field_fetch_group_invalid"://NOI18N
              "util.validation.version_field_column_invalid");//NOI18N

            /* rules:
             *  primitive, primitive -> error if exact match of
             *    columns
             *  primitive, relationship OR
             *  relationship, primitive -> error if non-collection
             *    relationship and none of the relationship's
             *    columns are PK columns and any are present in
             *    the primitive's list
             *  relationship, relationship -> error if exact
             *    match of mapping (local, join, associated),
             *    but order is not important
             */
            while (iterator.hasNext())
            {
              MappingFieldElement testField =
                (MappingFieldElement)iterator.next();

              if (isManaged(field, testField) ||
                isManaged(testField, field))
              {
                throw constructFieldException(
                  fieldName, exceptionKey);
              }
              else if (!testField.equals(field) && isExactMatch(
                field, testField))
              {
                throw constructFieldException(
                  fieldName, exceptionKey);
              }
            }
          }
        }
      }
      private boolean isManaged (MappingFieldElement primField,
        MappingFieldElement relField)       
      {
        String className = getClassName();

        if (!isRelationship(primField) && isRelationship(relField) &&
          !isCollection(className, relField.getName()))
        {
          ArrayList columns = primField.getColumns();
          Iterator iterator = relField.getColumns().iterator();
          String databaseRoot = getSchemaForClass(className);

          while (iterator.hasNext())
          {
            if (!testColumn(getLocalColumn((String)iterator.next(),
              databaseRoot), columns))
            {
              return true;
            }
          }
        }

        return false;
      }
      private boolean testColumn (ColumnElement column,
        ArrayList masterList)
      {
        if ((column != null) && !isPrimaryKeyColumn(column))
        {
          return !masterList.contains(NameUtil.
            getRelativeMemberName(column.getName().getFullName()));
        }

        return true;
      }
      private ColumnElement getLocalColumn (String pairName,
        String databaseRoot)
      {
        ColumnPairElement pair = getPair(pairName, databaseRoot);

        return ((pair != null) ? pair.getLocalColumn() : null);
      }
      private boolean isPrimaryKeyColumn (ColumnElement column)
      {
        if (column != null)
        {
          KeyElement key = column.getDeclaringTable().getPrimaryKey();

          return ((key != null) &&
            (key.getColumn(column.getName()) != null));
        }

        return false;
      }
      private boolean isExactMatch (ArrayList columns1,
        ArrayList columns2)
      {
        int count = columns1.size();

        if ((count > 0) && (count == columns2.size()))
          return getDifference(columns1, columns2).isEmpty();

        return false;
      }
      private boolean isExactMatch (MappingFieldElement field1,
        MappingFieldElement field2)
      {
        boolean field1IsRel = isRelationship(field1);
        boolean match = false;

        // both primitives, or both relationships
        if (field1IsRel == isRelationship(field2))
        {
          match = isExactMatch(field1.getColumns(),
            field2.getColumns());

          if (match && field1IsRel)
          {
            MappingRelationshipElement rel1 =
              (MappingRelationshipElement)field1;
            MappingRelationshipElement rel2 =
              (MappingRelationshipElement)field2;
            boolean field1IsJoin = isJoin(rel1);

            // both join relationships or both direct
            if (field1IsJoin == isJoin(rel2))
            {
              if (field1IsJoin)
              {
                match = isExactMatch(
                  rel1.getAssociatedColumns(),
                  rel2.getAssociatedColumns());
              }
            }
            else
              match = false;
          }
        }
       
        return match;
      }
    };
  }

  /** Create a validation component which can check whether the schema of  
   * the related class matches that of the class we are checking.
   * @param relatedClass the class whose schema is being checked
   * @param relatedField the relationship field whose schema is being
   * compared
   * @return the validation component
   */
  protected ValidationComponent createRelatedSchemaMatchesComponent (
    final String relatedClass, final PersistenceFieldElement relatedField)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        if (relatedClass != null)
        {
          String className = getClassName();
          String mySchema = getSchemaForClass(className);
          String relatedSchema = getSchemaForClass(relatedClass);

          if ((mySchema != null) && (relatedSchema != null) &&
            !(relatedSchema.equals(mySchema)))
          {
            String fieldName = relatedField.getName();

            throw new ModelValidationException(
              getModel().getField(className, fieldName),
              I18NHelper.getMessage(getMessages(),
              "util.validation.schema_mismatch", //NOI18N
              new Object[]{className, relatedClass, fieldName}));
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether any of
   * the supplied tables of the related class (which includes primary
   * and secondary tables) contains the table of the column stored in
   * the relationship definition.
   * @param relatedClass the class whose table is being checked
   * @param relatedField the relationship field whose table is being compared
   * @param tableNames the list of names of the tables we expect the
   * column to match
   * @param pairName the name of the pair whose reference column is to
   * be checked
   * @return the validation component
   */
  protected ValidationComponent createRelatedTableMatchesComponent (
    final String relatedClass, final PersistenceFieldElement relatedField,
    final List tableNames, final String pairName)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        ColumnPairElement pair = getPair(pairName,
          getSchemaForClass(relatedClass));

        if (pair != null)
        {
          ColumnElement column = pair.getReferencedColumn();

          if (!matchesTable(tableNames, column))
          {
            String fieldName = relatedField.getName();

            throw new ModelValidationException(
              getModel().getField(getClassName(), fieldName),
              I18NHelper.getMessage(getMessages(),
              getKey(
              "util.validation.table_mismatch", //NOI18N
              relatedField),
              new Object[]{column.getName().getFullName(),
              fieldName, relatedClass}));
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether the schema of  
   * the given class exists.
   * @param className the class whose mapped schema's existence is
   * being checked
   * @return the validation component
   */
  protected ValidationComponent createSchemaExistenceComponent (
    final String className)
  {
    return createSchemaExistenceComponent(className, null);
  }

  /** Create a validation component which can check whether the schema of  
   * the given class exists.
   * @param className the class whose mapped schema's existence is
   * being checked
   * @param relatedField the relationship field whose class'
   * mapped schema is being checked, may be <code>null</code> in which
   * case we are probably checking the same class as the validator is
   * checking overall
   * @return the validation component
   */
  protected ValidationComponent createSchemaExistenceComponent (
    final String className, final PersistenceFieldElement relatedField)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        String schemaName = getSchemaForClass(className);

        if ((schemaName != null) &&
          (SchemaElement.forName(schemaName) == null))
        {
          Object[] args = (relatedField == null) ?
            new Object[]{schemaName, className} :
            new Object[]{schemaName, className, relatedField};

          throw new ModelValidationException(
            ModelValidationException.WARNING,
            getOffendingObject(relatedField),
            I18NHelper.getMessage(getMessages(), getKey(
              "util.validation.schema_not_found", //NOI18N
              relatedField), args));
        }
      }
    };
  }

  /** Create a validation component which can check whether the
   * class is mapped to tables even though the schema is null or the
   * class is mapped to a primary table without a primary key.
   * @param primaryTable the primary table for the class
   * @return the validation component
   */
  protected ValidationComponent createPrimaryTableComponent (
    final MappingTableElement primaryTable)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        if (primaryTable != null)
        {
          String className = getClassName();
          String schemaName = getSchemaForClass(className);

          if (schemaName == null)
          {
            throw constructClassException(className, null,
              "util.validation.schema_not_set");    //NOI18N
          }
          else
          {
            String tableName = primaryTable.getName();
            TableElement table = getTable(tableName, schemaName);

            if ((table != null) && (table.getPrimaryKey() == null))
            {
              throw new ModelValidationException(
                getOffendingObject(null),
                I18NHelper.getMessage(getMessages(),
                  "util.validation.table_no_primarykey", //NOI18N
                  new Object[]{tableName, className}));
            }
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether the given table
   * exists.
   * @param tableName the table whose existence is being checked
   * @return the validation component
   */
  protected ValidationComponent createTableExistenceComponent (
    final String tableName)
  {
    return createTableExistenceComponent(tableName, null);
  }

  /** Create a validation component which can check whether the given table
   * exists.
   * @param tableName the table whose existence is being checked
   * @param relatedField the relationship field whose class'
   * table is being checked, may be <code>null</code> in which
   * case we are probably checking the same class as the validator is
   * checking overall
   * @return the validation component
   */
  protected ValidationComponent createTableExistenceComponent (
    final String tableName, final PersistenceFieldElement relatedField)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        if (tableName != null)
        {
          String className = getClassName();
          boolean noRelated = (relatedField == null);
          TableElement table = getTable(tableName,
            getSchemaForClass((noRelated ? className :
            getRelatedClass(relatedField))));

          if (table == null)
          {
            Object[] args = noRelated ?
              new Object[]{tableName, className} :
              new Object[]{tableName, relatedField};

            throw new ModelValidationException(
              ModelValidationException.WARNING,
              getOffendingObject(relatedField),
              I18NHelper.getMessage(getMessages(), getKey(
                "util.validation.table_not_found", //NOI18N
                relatedField), args));
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether the given column
   * exists.
   * @param columnName the column whose existence is being checked
   * @return the validation component
   */
  protected ValidationComponent createColumnExistenceComponent (
    final String columnName)
  {
    return createColumnExistenceComponent(columnName, null);
  }

  /** Create a validation component which can check whether the given 
   * column or column pair exists.
   * @param columnName the column or pair whose existence is being checked
   * @param relatedField the field whose class' column is being checked,
   * may be <code>null</code> in which case we are probably checking the
   * same secondary table setup
   * @return the validation component
   */
  protected ValidationComponent createColumnExistenceComponent (
    final String columnName, final MappingFieldElement relatedField)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        if (columnName != null)
        {
          String className = getClassName();
          String absoluteName = NameUtil.getAbsoluteMemberName(
            getSchemaForClass(className), columnName);
          TableElement table = TableElement.forName(
            NameUtil.getTableName(absoluteName));
          boolean foundTable = (table != null);
          DBMemberElement columnElement = ((foundTable) ?
            table.getMember(DBIdentifier.create(absoluteName)) :
            null);
          boolean noRelated = (relatedField == null);

          if (foundTable)
          {
            boolean isRelationship =
              (!noRelated && isRelationship(relatedField));
            boolean noColumn = (columnElement == null);

            if (!isRelationship && noColumn)
            {
              Object[] args = (noRelated) ?
                new Object[]{columnName, className} :
                new Object[]{columnName, relatedField,
                className};

              throw new ModelValidationException(
                ModelValidationException.WARNING,
                getOffendingObject(relatedField),
                I18NHelper.getMessage(getMessages(), getKey(
                  "util.validation.column_not_found", //NOI18N
                  relatedField), args));
            }
            else if (isRelationship &&
              (noColumn || !isPairComplete(columnElement)))
            {
              throw new ModelValidationException(
                ModelValidationException.WARNING,
                getOffendingObject(relatedField),
                I18NHelper.getMessage(getMessages(),
                  "util.validation.column_invalid", //NOI18N
                  new Object[]{columnName, relatedField,
                  className}));
            }
          }
        }
      }
      private boolean isPairComplete (DBMemberElement member)
      {
        return ((member instanceof ColumnPairElement) && 
          (((ColumnPairElement)member).getLocalColumn() != null) &&
          (((ColumnPairElement)member).getReferencedColumn()
          != null));
      }
    };
  }

  /** Create a validation component which can check whether the field is  
   * one of a set mapped to overlapping columns
   * @param field the field whose column mapping is being checked
   * @return the validation component
   */
  protected ValidationComponent createColumnOverlapComponent (
    final MappingFieldElement field)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        MappingClassElement mappingClass = field.getDeclaringClass();
        Iterator iterator = mappingClass.getFields().iterator();
        ArrayList myColumns = field.getColumns();

        while (iterator.hasNext())
        {
          MappingFieldElement testField =
            (MappingFieldElement)iterator.next();

          if (!testField.equals(field) && !isRelationship(testField)
            && isPartialMatch(myColumns, testField.getColumns()))
          {
            String fieldName = field.getName();

            throw new ModelValidationException(getModel().getField(
              getClassName(), fieldName),
              I18NHelper.getMessage(getMessages(),
              "util.validation.field_mapping_invalid", //NOI18N
              new Object[]{fieldName, testField.getName()}));
          }
        }
      }
      private boolean isPartialMatch (ArrayList columns1,
        ArrayList columns2)
      {
        int count = columns1.size();

        if (count > 0)
        {
          ArrayList difference = getDifference(columns1, columns2);

          return (!difference.isEmpty() &&
            (columns2.size() != difference.size()));
        }

        return false;
      }
    };
  }
 
  /** Create a validation component which can check whether the key class
   * of the persistence capable class is valid. This includes:
   * <ul>
   * <li> The key class must be public.
   * <li> The key class must implement Serializable.
   * <li> If the key class is an inner class, it must be static.
   * <li> The key class must have a public constructor, which might be
   * the default constructor or a no-arg constructor.
   * <li> The field types of all non-static fields in the key class must be
   * of valid types.
   * <li> All serializable non-static fields in the key class must be public.
   * <li> The names of the non-static fields in the key class must include the
   * names of the primary key fields in the JDO class, and the types of the
   * common fields must be identical
   * <li> The key class must redefine equals and hashCode.
   * </ul>
   */
   protected ValidationComponent createKeyClassComponent (
    final String className)
  {
    return new ValidationComponent ()
    {
      /** The class element of the key class */
      private Object keyClass;

      /** The fully qualified name of the key class */
      private String keyClassName;

      public void validate () throws ModelValidationException
      {
        // checks the key class name
        keyClassName = validateKeyClassName(className);
        // initilialize keyClass field
        keyClass = getModel().getClass(keyClassName, getClassLoader());
        validateClass();
        validateConstructor();
        validateFields();
        validateMethods();
      }

      /** Helper method validating the key class itself:
       * public, serializable, static.
       */
      private void validateClass () throws ModelValidationException
      {
        Model model = getModel();
        int modifiers = model.getModifiersForClass(keyClassName);
        boolean hasKeyClassName = !StringHelper.isEmpty(keyClassName);
        boolean isInnerClass =
          (hasKeyClassName && (keyClassName.indexOf('$') != -1));
        String pcClassName = getClassName();

        // check for key class existence
        if (keyClass == null)
        {
          throw new ModelValidationException(
            ModelValidationException.WARNING,
            model.getClass(pcClassName),
            I18NHelper.getMessage(getMessages(),
            "util.validation.key_class_missing", //NOI18N
            keyClassName, pcClassName));
        }

        // check for public class modifier
        if (!Modifier.isPublic(modifiers))
        {
          throw new ModelValidationException(keyClass,
            I18NHelper.getMessage(getMessages(),
            "util.validation.key_class_public", //NOI18N
            keyClassName, pcClassName));
        }
       
        // check for Serializable
        /* This check is disabled because of Boston backward
           compatibility. In Boston there was no requirement for a
           key class being serializable, thus key classes from pc
           classes mapped with boston are not serializable.

        if (!model.implementsInterface(keyClass,
          "java.io.Serializable")) //NOI18N
        {
          throw new ModelValidationException(keyClass,
            I18NHelper.getMessage(getMessages(),
            "util.validation.key_class_serializable", //NOI18N
            keyClassName, pcClassName));
        }
        */

        // if inner class it must be static
        if (isInnerClass && !Modifier.isStatic(modifiers))
        {
          throw new ModelValidationException(keyClass,
            I18NHelper.getMessage(getMessages(),
            "util.validation.key_class_static", //NOI18N
            keyClassName, pcClassName));
        }
      }

      /** Helper method validating the fields of the key class.
       */
      private void validateFields () throws ModelValidationException
      {
        String pcClassName = getClassName();
        Model model = getModel();
        // check for valid typed public non-static fields
        List keyClassFieldNames = model.getAllFields(keyClassName);
        Map keyFields = getKeyFields();

        for (Iterator i = keyClassFieldNames.iterator(); i.hasNext();)
        {
          String keyClassFieldName = (String)i.next();
          Object keyClassField =
            getKeyClassField(keyClassName, keyClassFieldName);
          int keyClassFieldModifiers =
            model.getModifiers(keyClassField);
          String keyClassFieldType = model.getType(keyClassField);
          Object keyField = keyFields.get(keyClassFieldName);

          if (Modifier.isStatic(keyClassFieldModifiers))
            // we are not interested in static fields
            continue;

          if (!model.isValidKeyType(keyClassName, keyClassFieldName))
          {
            throw new ModelValidationException(keyClassField,
              I18NHelper.getMessage(getMessages(),
              "util.validation.key_field_type_invalid", //NOI18N
              keyClassFieldName, keyClassName));
          }
         
          if (!Modifier.isPublic(keyClassFieldModifiers))
          {
            throw new ModelValidationException(keyClassField,
              I18NHelper.getMessage(getMessages(),
              "util.validation.key_field_public", //NOI18N
              keyClassFieldName, keyClassName));
          }

          if (keyField == null)
            continue;
         
          if (!keyClassFieldType.equals(model.getType(keyField)))
          {
            throw new ModelValidationException(keyClassField,
              I18NHelper.getMessage(getMessages(),
              "util.validation.key_field_type_mismatch", //NOI18N
              keyClassFieldName, keyClassName, pcClassName));
          }

          // remove handled keyField from the list of keyFields
          keyFields.remove(keyClassFieldName);
        }

        // check whether there are any unhandled key fields
        if (!keyFields.isEmpty())
        {
          Object pcClass = model.getClass(pcClassName);
          String fieldNames = StringHelper.arrayToSeparatedList(
            new ArrayList(keyFields.keySet()));

          throw new ModelValidationException(pcClass,
            I18NHelper.getMessage(getMessages(),
            "util.validation.key_field_missing", //NOI18N
            pcClassName, keyClassName, fieldNames));
        }
      }

      /** Helper method validating the key class constructors.
       */
      private void validateConstructor () throws ModelValidationException
      {
        // no constructor or no arg constructor
        Model model = getModel();
        boolean hasConstr = model.hasConstructor(keyClassName);
        Object noArgConstr =
          model.getConstructor(keyClassName, Model.NO_ARGS);
        int modifiers = model.getModifiers(noArgConstr);

        if (hasConstr &&
          ((noArgConstr == null) || !Modifier.isPublic(modifiers)))
        {
          throw new ModelValidationException(keyClass,
            I18NHelper.getMessage(getMessages(),
            "util.validation.key_class_constructor", //NOI18N
            keyClassName, getClassName()));
        }
      }

      /** Helper method validating the key class methods.
       */
      private void validateMethods () throws ModelValidationException
      {
        Model model = getModel();
        Object equalsMethod = getNonObjectMethod(keyClassName,
          "equals", Model.EQUALS_ARGS); //NOI18N
        Object hashCodeMethod = getNonObjectMethod(keyClassName,
          "hashCode", Model.NO_ARGS); //NOI18N

        // check equals method
        if (!matchesMethod(equalsMethod, Modifier.PUBLIC,
          0, "boolean")) //NOI18N
        {
          throw new ModelValidationException(keyClass,
            I18NHelper.getMessage(getMessages(),
            "util.validation.key_class_equals", //NOI18N
            keyClassName, getClassName()));
        }
       
        // check hashCode method
        if (!matchesMethod(hashCodeMethod, Modifier.PUBLIC,
          0, "int")) //NOI18N
        {
          throw new ModelValidationException(keyClass,
            I18NHelper.getMessage(getMessages(),
            "util.validation.key_class_hashcode", //NOI18N
            keyClassName, getClassName()));
        }
      }
     
      /** Helper method validating the name of the key class.
       */
      private String validateKeyClassName (String keyClassName)
        throws ModelValidationException
      {
        String pcClassName = getClassName();
        Model model = getModel();
        boolean hasKeyClassName = !StringHelper.isEmpty(keyClassName);
        boolean hasPrefix;
        String nameSuffix;
        boolean isOIDNameSuffix;

        // check for existence of key class name
        if (!hasKeyClassName)
        {
          throw new ModelValidationException(
            ModelValidationException.WARNING,
            model.getClass(pcClassName),
            I18NHelper.getMessage(getMessages(),
            "util.validation.key_class_unset", //NOI18N
            pcClassName));
        }

        keyClassName = keyClassName.trim();
        hasPrefix = keyClassName.startsWith(pcClassName);
        nameSuffix = (hasPrefix ?
           keyClassName.substring(pcClassName.length()) : keyClassName);
        isOIDNameSuffix =
          (nameSuffix.equalsIgnoreCase(".OID") || // NOI18N
           nameSuffix.equalsIgnoreCase("$OID")); // NOI18N

        if (!hasPrefix ||
          (!nameSuffix.equalsIgnoreCase("Key") &&   // NOI18N
           !isOIDNameSuffix))
        {
          Object pcClass = getModel().getClass(pcClassName);
          throw new ModelValidationException(pcClass,
            I18NHelper.getMessage(getMessages(),
            "util.validation.key_class_invalid", //NOI18N
            keyClassName, pcClassName));
        }
        if (isOIDNameSuffix)
        {
          StringBuffer buf = new StringBuffer(keyClassName);
          buf.setCharAt(keyClassName.length() - 4, '$');
          return buf.toString()
        }
        return keyClassName;
      }

      // helper method which returns a field object from the
      // given class or one of its superclasses
      private Object getKeyClassField (String keyClassName,
        String keyClassFieldName)
      {
        Model model = getModel();
        Object keyClassField =
          model.getField(keyClassName, keyClassFieldName);

        if (keyClassField == null// this is an inherited field
        {
          keyClassField = model.getInheritedField(
            keyClassName, keyClassFieldName);
        }

        return keyClassField;
      }

      /** Helper method returning the key fields of the pc class as a map.
       */
      private Map getKeyFields ()
      {
        Model model = getModel();
        String pcClassName = getClassName();
        PersistenceClassElement pce =
          model.getPersistenceClass(pcClassName);
        PersistenceFieldElement[] fields = pce.getFields();
        Map keyFields = new HashMap();

        if (fields != null)
        {
          for (int i = 0; i < fields.length; i++)
          {
            PersistenceFieldElement pfe = fields[i];
            if (pfe.isKey())
            {
              String name = pfe.getName();
              keyFields.put(name,
                model.getField(pcClassName, name));
            }
          }
        }
       
        return keyFields;
      }
      // helper method which returns a method object from the
      // given class or one of its superclasses provided it
      // is not java.lang.Object
      private Object getNonObjectMethod (String className,
        String methodName, String[] argTypeNames)
      {
        Model model = getModel();
        Object method =
          model.getMethod(className, methodName, argTypeNames);

        if (method == null// look for an inherited method
        {
          method = model.getInheritedMethod(
            className, methodName, argTypeNames);

          if ((method != null) && model.getDeclaringClass(method).
            equals("java.lang.Object"))    // NOI18N
          {
            method = null;
          }
        }

        return method;
      }
    };
  }

  /** Create a validation component which can check that the persistence
   * capable class implement methods readObject and writeObject, if the class
   * implements the intreface java.io.Serializable
   * @param className the class whose methods are checked
   * @return the validation component
   */
  protected ValidationComponent createSerializableClassComponent (
    final String className)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        Model model = getModel();
        Object pcClass = null;

        if (className == null)
          return;
        pcClass = model.getClass(className);
        if (pcClass == null)
          return;

        if (model.implementsInterface(pcClass, "java.io.Serializable")) //NOI18N
        {
          // check readObject method
          Object readMethod = model.getMethod(className,
            "readObject", Model.READ_OBJECT_ARGS); //NOI18N

          if (!matchesMethod(readMethod, Modifier.PRIVATE,
            Modifier.SYNCHRONIZED, "void")) // NOI18N
          {
            throw new ModelValidationException(pcClass,
              I18NHelper.getMessage(getMessages(),
              "util.validation.class_readobject", //NOI18N
              className));
          }
         
          // check writeObject method
          Object writeMethod = model.getMethod(className,
            "writeObject", Model.WRITE_OBJECT_ARGS); //NOI18N

          if (!matchesMethod(writeMethod, Modifier.PRIVATE,
            Modifier.SYNCHRONIZED, "void")) // NOI18N
          {
            throw new ModelValidationException(pcClass,
              I18NHelper.getMessage(getMessages(),
              "util.validation.class_writeobject", //NOI18N
              className));
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether the class is 
   * unmapped.
   * @param persistenceClass the class whose mapping is being checked
   * @return the validation component
   */
  protected ValidationComponent createClassMappingComponent (
    final PersistenceClassElement persistenceClass)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        PersistenceFieldElement[] fields = persistenceClass.getFields();
        String className = getClassName();

        if ((fields == null) || fields.length == 0)
        {
          throw constructClassException(
            ModelValidationException.WARNING, className, null,
            "util.validation.class_no_fields");    //NOI18N
        }
        else  // has fields, check for primary table
        {
          MappingClassElement mappingClass =
            getMappingClass(className);

          if ((mappingClass == null) ||
            (mappingClass.getTables().size() == 0))
          {
            throw constructClassException(
              ModelValidationException.WARNING, className, null,
              "util.validation.class_not_mapped");    //NOI18N
          }
        }
      }
    };
  }

  /** Create a validation component which can check whether the class  
   * contains field mappings for all primary key columns.
   * @param persistenceClass the class whose mapping is being checked
   * @return the validation component
   */
  protected ValidationComponent createKeyColumnMappingComponent (
    final PersistenceClassElement persistenceClass)
  {
    return new ValidationComponent ()
    {
      public void validate () throws ModelValidationException
      {
        String className = getClassName();
        MappingClassElement mappingClass = getMappingClass(className);

        if (mappingClass != null)
        {
          List tables = mappingClass.getTables();

          if (tables.size() > 0)
          {
            String tableName =
              ((MappingTableElement)tables.get(0)).getName();
            TableElement table = getTable(tableName,
              getSchemaForClass(className));
            List columns = getUnmappedColumnNames(
              ((table != null) ? table.getPrimaryKey() : null),
              mappingClass);

            if ((columns != null) && (columns.size() > 0))
            {
              throw new ModelValidationException(
                ModelValidationException.WARNING,
                getOffendingObject(null),
                I18NHelper.getMessage(getMessages(),
                "util.validation.class_key_column_missing", //NOI18N
                className, tableName,
                StringHelper.arrayToSeparatedList(columns)));
            }
          }
        }
      }
      private List getUnmappedColumnNames (KeyElement primaryKey,
        MappingClassElement mappingClass)
      {
        List unmappedColumns = null;

        if (primaryKey != null// check if primary table has a pk
        {
          ColumnElement[] columns = primaryKey.getColumns();
          int count = ((columns != null) ? columns.length : 0);

          // all columns in the pk should be mapped to key fields
          if (count > 0)
          {
            List mappingFields = mappingClass.getFields();
            Iterator iterator = mappingFields.iterator();

            unmappedColumns = getRelativeColumnNames(columns);

            while (iterator.hasNext())
            {
              MappingFieldElement field =
                (MappingFieldElement)iterator.next();

              if (isKeyField(field))
                unmappedColumns.removeAll(field.getColumns());
            }
          }
        }

        return unmappedColumns;
      }
      private List getRelativeColumnNames (ColumnElement[] columns)
      {
        int i, count = ((columns != null) ? columns.length : 0);
        List columnNames = new ArrayList(count);
               
        for (i = 0; i < count; i++)
        {
          columnNames.add(NameUtil.getRelativeMemberName(
            columns[i].getName().getFullName()));
        }

        return columnNames;
      }
      private boolean isKeyField (MappingFieldElement field)
      {
        PersistenceFieldElement persistenceField =
          persistenceClass.getField(field.getName());

        return ((persistenceField != null) && persistenceField.isKey());
      }
    };
  }

  //========== Convenience methods for exception construction ============

  /** Computes the offending object to be used in the construction of a
   * ModelValidationException as follows: if a non-null field is supplied,
   * the corresponding org.openide.src.FieldElement is returned, otherwise,
   * corresponding org.openide.src.ClassElement is returned.
   * @param field the field object which caused the problem - may be
   * <code>null</code>
   * @return the offending object
   */
  private Object getOffendingObject (Object field)
  {
    return ((field == null) ?
      getModel().getClass(getClassName(), getClassLoader()) :
      getModel().getField(getClassName(), field.toString()));
  }

  /** Computes the key for the i18n string to be used in the construction
   * of a ModelValidationException as follows: if a non-null field is
   * supplied, "_related" is appending to the supplied key base, otherwise,
   * the key base is returned as is.
   * @param keyBase the base key to be used for the i18n string
   * @param field the field object which caused the problem - may be
   * <code>null</code>
   * @return the key
   */
  private String getKey (String keyBase, Object field)
  {
    return ((field == null) ? keyBase : (keyBase + "_related"))//NOI18N
  }

  /** Computes the arguments for the i18n string to be used in the
   * construction of a ModelValidationException as follows: if a
   * non-null field is supplied, an array containing the supplied className
   * and field is returned, otherwise, an array containing only the supplied
   * className is returned.
   * @param className the name of the class which caused the problem
   * @param field the field object which caused the problem - may be
   * <code>null</code>
   * @return the argument array
   */
  private Object[] getArguments (String className, Object field)
  {
    return ((field == null) ? new Object[]{className} :
      new Object[]{className, field});
  }

  /** Constructs a ModelValidationException for class validation tests
   * using the supplied class name, related field, and key base.
   * @param className the name of the class which caused the problem
   * @param field the field object which caused the problem - may be
   * <code>null</code>
   * @param keyBase the base key to be used for the i18n string
   * @return the ModelValidationException
   * @see #getOffendingObject
   * @see #getKey
   * @see #getArguments
   * @see #constructFieldException
   */
  private ModelValidationException constructClassException (String className,
    Object relatedField, String keyBase)
  {
    return constructClassException(ModelValidationException.ERROR,
      className, relatedField, keyBase);
  }

  /** Constructs a ModelValidationException for class validation tests
   * using the supplied class name, related field, and key base.
   * @param errorType the type of error -- one of
   * {@link ModelValidationException#ERROR} or
   * {@link ModelValidationException#WARNING}.
   * @param className the name of the class which caused the problem
   * @param field the field object which caused the problem - may be
   * <code>null</code>
   * @param keyBase the base key to be used for the i18n string
   * @return the ModelValidationException
   * @see #getOffendingObject
   * @see #getKey
   * @see #getArguments
   * @see #constructFieldException
   */
  private ModelValidationException constructClassException (int errorType,
    String className, Object relatedField, String keyBase)
  {
    return new ModelValidationException(errorType,
      getOffendingObject(relatedField), I18NHelper.getMessage(
      getMessages(), getKey(keyBase, relatedField),
      getArguments(className, relatedField)));
  }

  /** Constructs a ModelValidationException for field validation tests
   * using the supplied field name and key.
   * @param fieldName the name of the field which caused the problem
   * @param keyBase the base key to be used for the i18n string
   * @return the ModelValidationException
   * @see #constructClassException
   */
  private ModelValidationException constructFieldException (String fieldName,
    String key)
  {
    return constructFieldException(ModelValidationException.ERROR,
      fieldName, key);
  }

  /** Constructs a ModelValidationException for field validation tests
   * using the supplied field name and key.
   * @param errorType the type of error -- one of
   * {@link ModelValidationException#ERROR} or
   * {@link ModelValidationException#WARNING}.
   * @param fieldName the name of the field which caused the problem
   * @param keyBase the base key to be used for the i18n string
   * @return the ModelValidationException
   * @see #constructClassException
   */
  private ModelValidationException constructFieldException (int errorType,
    String fieldName, String key)
  {
    return new ModelValidationException(errorType,
      getModel().getField(getClassName(), fieldName),
      I18NHelper.getMessage(getMessages(), key, fieldName));
  }

  //=============== Misc. private convenience methods  ================

  /** Checks whether the specified method element exists and if so whether it
   * has the expected modifiers and the expected return type.
   * @param method the method element to be checked
   * @param expectedModifiers the modifiers the method should have
   * @param optionalModifiers additional modifiers the method might have
   * @param expectedReturnType the return type the method should have
   * @return <code>true</code> if the method matches,
   * <code>false</code> otherwise.
   */
  private boolean matchesMethod (final Object method,
    final int expectedModifiers, final int optionalModifiers,
    final String expectedReturnType)
  {
    boolean matches = false

    if (method != null)
    {
      Model model = getModel();
      int modifiers = model.getModifiers(method);

      matches = (((modifiers == expectedModifiers) ||
        (modifiers == (expectedModifiers | optionalModifiers))) &&
        expectedReturnType.equals(model.getType(method)));
    }

    return matches;
  }

  /** Check if the table of the column matches one of the list of tables.
   * @param tableNames A list of table names in which to check for a match
   * @param column A ColumnElement object to be checked
   * @return <code>true</code> if the column belongs to a table found
   * in the supplied list of table names, <code>false</code> otherwise
   */
  private boolean matchesTable (List tableNames, ColumnElement column)
  { 
    return ((column == null) ? true : tableNames.contains(
      column.getDeclaringTable().getName().getName()));
  }

  private boolean isRelationship (Object field)
  {
    return ((field instanceof RelationshipElement) ||
      (field instanceof MappingRelationshipElement));
  }

  private boolean shouldBeRelationship (PersistenceFieldElement field)
  {
    Model model = getModel();
    String fieldType = model.getFieldType(getClassName(), field.getName());

    return (isPersistent(fieldType) || model.isCollection(fieldType));
  }

  private boolean isLegalRelationship (PersistenceFieldElement field)
  {
    return (isRelationship(field) ? shouldBeRelationship(field) : false);
  }

  private boolean isCollection (String className, String fieldName)
  {
    Model model = getModel();

    return model.isCollection(model.getFieldType(className, fieldName));
  }

  private String getRelatedClass (PersistenceFieldElement field)
  {
    if (isLegalRelationship(field))
      return getModel().getRelatedClass((RelationshipElement)field);

    return null;
  }

  private String getSchemaForClass (String className)
  {
    MappingClassElement mappingClass = getMappingClass(className);
    String schema = ((mappingClass != null) ?
      mappingClass.getDatabaseRoot() : null);

    return (StringHelper.isEmpty(schema) ? null : schema.trim());
  }

  private MappingRelationshipElement getMappingRelationship (
    RelationshipElement jdoElement)
  {
    MappingRelationshipElement mappingElement = null;

    if (jdoElement != null)
    {
      MappingClassElement mappingClass = getMappingClass(
        jdoElement.getDeclaringClass().getName());

      if (mappingClass != null)
      {
        MappingFieldElement fieldElement =
          mappingClass.getField(jdoElement.getName());

        if (isRelationship(fieldElement))
          mappingElement = (MappingRelationshipElement)fieldElement;
      }
    }

    return mappingElement;
  }

  private boolean isJoin (MappingRelationshipElement field)
  {
    if (field != null)
    {
      ArrayList columns = field.getAssociatedColumns();
     
      return ((columns != null) && !columns.isEmpty());
    }
   
    return false;
  }

  private MappingReferenceKeyElement findReferenceKey (
    MappingTableElement primaryTable, MappingTableElement secondaryTable)
  {
    if ((primaryTable != null) && (secondaryTable != null))
    {
      Iterator iterator = primaryTable.getReferencingKeys().iterator();

      while (iterator.hasNext())
      {
        MappingReferenceKeyElement testKey =
          (MappingReferenceKeyElement)iterator.next();

        if (testKey.getTable().equals(secondaryTable))
          return testKey;
      }
    }

    return null;
  }

  private TableElement getTable (String tableName, String databaseRoot)
  {
    String absoluteName = NameUtil.getAbsoluteTableName(databaseRoot,
      tableName);
    return TableElement.forName(absoluteName);
  }

  private ColumnPairElement getPair (String pairName, String databaseRoot)
  {
    String absoluteName = NameUtil.getAbsoluteMemberName(
      databaseRoot, pairName);
    TableElement tableElement = TableElement.forName(
      NameUtil.getTableName(absoluteName));
    DBMemberElement pair = ((tableElement == null) ? null :
      tableElement.getMember(DBIdentifier.create(absoluteName)));

    return ((pair instanceof ColumnPairElement) ?
      ((ColumnPairElement)pair) : null);
  }

  private ArrayList getDifference (ArrayList columns1, ArrayList columns2)
  {
    ArrayList differenceColumns = new ArrayList(columns2);

    differenceColumns.removeAll(columns1);

    return differenceColumns;
  }

  /**
   * Convenience method to call Model.getMappingClass.
   */
  private MappingClassElement getMappingClass (String className)
  {
    return getModel().getMappingClass(className, getClassLoader());
  }

  /**
   * Convenience method to call Model.getPersistenceClass.
   */
  private PersistenceClassElement getPersistenceClass (String className)
  {
    return getModel().getPersistenceClass(className, getClassLoader());
  }

  /**
   * Convenience method to call Model.isPersistent
   */
  private boolean isPersistent (String className)
  {
    return getModel().isPersistent(className, getClassLoader());
  }

  /**
   * Convenience method to call Model.isPersistentAllowed
   */
  private boolean isPersistentAllowed (String className, String fieldName)
  {
    return getModel().isPersistentAllowed(className, getClassLoader(),
      fieldName);
  }
  // ================== Validation component support =================

  /** Abstraction of component tests for validation.
   */
  static abstract class ValidationComponent
  {
    /** Constructs a new ValidationComponent
     */
    public ValidationComponent ()
    {
    }
    /** Method which validates this component
     * @exception ModelValidationException when the validation fails.
     */
    public abstract void validate () throws ModelValidationException;
  }
}
TOP

Related Classes of com.sun.jdo.api.persistence.model.util.ModelValidator$ValidationComponent

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.