Package nexj.core.meta.persistence.sql

Source Code of nexj.core.meta.persistence.sql.RelationalMapping

// Copyright 2010 NexJ Systems Inc. This software is licensed under the terms of the Eclipse Public License 1.0
package nexj.core.meta.persistence.sql;

import java.util.Arrays;
import java.util.BitSet;
import java.util.Iterator;

import nexj.core.meta.Aspect;
import nexj.core.meta.Attribute;
import nexj.core.meta.Component;
import nexj.core.meta.Metaclass;
import nexj.core.meta.Metadata;
import nexj.core.meta.MetadataException;
import nexj.core.meta.MetadataValidationException;
import nexj.core.meta.persistence.AttributeMapping;
import nexj.core.meta.persistence.GenericPersistenceMapping;
import nexj.core.meta.persistence.Key;
import nexj.core.meta.persistence.PersistenceMapping;
import nexj.core.scripting.ConstPair;
import nexj.core.scripting.Pair;
import nexj.core.scripting.Symbol;
import nexj.core.util.Logger;

/**
* Mapping of a class to a set of tables.
*/
public final class RelationalMapping extends GenericPersistenceMapping
{
   // constants

   /**
    * Identity key generator.
    */
   public final static Component KEY_GEN_IDENTITY = new Component("identity");

   static
   {
      KEY_GEN_IDENTITY.setType(IdentityKeyGenerator.class);
      KEY_GEN_IDENTITY.makeReadOnly();
   }

   // attributes

   /**
    * The mapped table count.
    */
   private int m_nTableCount;

   /**
    * The denorm count.
    */
   private int m_nDenormCount;

   // associations

   /**
    * The primary key generator component. Can be null.
    */
   private Component m_keyGenerator;

   /**
    * The table containing the primary key.
    */
   private Table m_primaryTable;

   /**
    * The mapped table collection.
    * The first table is the primary one.
    */
   private Table[] m_tableArray = new Table[2];

   /**
    * The source denorm collection.
    */
   private RelationalDenorm[] m_denormArray;

   /**
    * The class logger.
    */
   private final static Logger s_logger = Logger.getLogger(RelationalMapping.class);

   // operations

   /**
    * @see nexj.core.meta.persistence.PersistenceMapping#isCompatible(nexj.core.meta.persistence.PersistenceMapping)
    */
   public boolean isCompatible(PersistenceMapping mapping)
   {
      return mapping instanceof RelationalMapping &&
         ((RelationalMapping)mapping).m_primaryTable == m_primaryTable;
   }

   /**
    * Sets the primary key generator component.
    * @param keyGenerator The primary key generator component to set.
    * @throws MetadataException if the component is not suitable for the mapping.
    */
   public void setKeyGenerator(Component keyGenerator) throws MetadataException
   {
      verifyNotReadOnly();

      if (m_primaryTable != null)
      {
         validateKeyGenerator(keyGenerator);
      }

      m_keyGenerator = keyGenerator;
   }

   /**
    * @return The primary key generator component.
    */
   public Component getKeyGenerator()
   {
      return m_keyGenerator;
   }

   /**
    * Sets the primary key table.
    * @param primaryTable The primary key table to set.
    */
   public void setPrimaryTable(Table primaryTable)
   {
      verifyNotReadOnly();

      if (primaryTable != null)
      {
         if (m_nTableCount != 0)
         {
            throw new IllegalStateException("The primary table must be added first");
         }

         addTable(primaryTable);
      }
      else
      {
         if (m_metaclass != null)
         {
            if (!(m_metaclass instanceof Aspect) || !((Aspect)m_metaclass).isAspect())
            {
               throw new MetadataException("err.meta.persistence.sql.noPrimaryTable",
                  new Object[]{m_metaclass.getName()});
            }
         }
      }

      m_primaryTable = primaryTable;
   }

   /**
    * @return The primary key table.
    */
   public Table getPrimaryTable()
   {
      return m_primaryTable;
   }

   /**
    * Adds a new attribute mapping to the relational mapping.
    * @param mapping The attribute mapping to add.
    * @throws MetadataException if an attribute mapping
    * with the same name already exists.
    */
   public void addAttributeMapping(AttributeMapping mapping)
   {
      super.addAttributeMapping(mapping);

      if (mapping instanceof RelationalPrimitiveMapping)
      {
         RelationalPrimitiveMapping primitiveMapping = (RelationalPrimitiveMapping)mapping;
         Column column = primitiveMapping.getColumn();

         addTable(column.getTable());
         column.getTable().addMapping(primitiveMapping);
      }
      else
      {
         RelationalClassMapping classMapping = (RelationalClassMapping)mapping;

         if (classMapping.getSourceKey() != null)
         {
            addTable(classMapping.getSourceKey().getTable());
         }
      }
   }

   /**
    * Adds a table to the mapping, if it does not exist.
    * @param table The table to add.
    * @return The ordinal number of the added table.
    * @throws MetadataException if the table primary key is
    * incompatible with the primary table primary key.
    */
   public int addTable(Table table) throws MetadataException
   {
      verifyNotReadOnly();

      for (int i = m_nTableCount - 1; i >= 0; --i)
      {
         if (m_tableArray[i] == table)
         {
            return i;
         }
      }

      if (m_metaclass != null &&
         (m_metaclass instanceof Aspect && ((Aspect)m_metaclass).isAspect()) != table.isAspect())
      {
         throw new MetadataException(
            (table.isAspect()) ? "err.meta.persistence.sql.classAspectTableMismatch" :
               "err.meta.persistence.sql.aspectClassTableMismatch",
               new Object[]{m_metaclass.getName(), table.getName()});
      }

      if (m_nTableCount == m_tableArray.length)
      {
         Table[] tableArray = new Table[m_nTableCount << 1];
         System.arraycopy(m_tableArray, 0, tableArray, 0, m_nTableCount);
         m_tableArray = tableArray;
      }

      if (m_primaryTable != null && m_primaryTable.getPrimaryKey() != null && table.getPrimaryKey() != null &&
         !m_primaryTable.getPrimaryKey().isCompatible(table.getPrimaryKey()))
      {
         throw new MetadataException("err.meta.incompatibleTablePrimaryKeys",
            new Object[]{m_primaryTable.getName(), table.getName()});
      }

      m_tableArray[m_nTableCount] = table;

      return m_nTableCount++;
   }

   /**
    * Gets a mapped table by ordinal number.
    * @param nOrdinal The table ordinal number, 0-based.
    * @return The mapped table.
    */
   public Table getTable(int nOrdinal)
   {
      if (nOrdinal >= m_nTableCount)
      {
         throw new IndexOutOfBoundsException("Invalid ordinal number in RelationalMapping.getTable()");
      }

      return m_tableArray[nOrdinal];
   }

   /**
    * Finds the ordinal number of a mapped table.
    * @param table The mapped table.
    * @return The ordinal number of the table, or -1 if not found.
    */
   public int findTableOrdinal(Table table)
   {
      for (int i = 0; i != m_nTableCount; ++i)
      {
         if (m_tableArray[i] == table)
         {
            return i;
         }
      }

      return -1;
   }

   /**
    * @return The mapped table count (at least 1).
    */
   public int getTableCount()
   {
      return m_nTableCount;
   }

   /**
    * Adds a source denorm to the mapping (the source value is usually elsewhere,
    * the destination value is on one of the tables of this class).
    * @param denorm The denorm to add.
    */
   public void addDenorm(RelationalDenorm denorm)
   {
      if (m_denormArray == null)
      {
         m_denormArray = new RelationalDenorm[2];
      }
      else if (m_nDenormCount == m_denormArray.length)
      {
         RelationalDenorm[] denormArray = new RelationalDenorm[m_nDenormCount << 1];

         System.arraycopy(m_denormArray, 0, denormArray, 0, m_nDenormCount);
         m_denormArray = denormArray;
      }

      m_denormArray[m_nDenormCount++] = denorm;
   }

   /**
    * Gets a denorm by ordinal number.
    * @param nOrdinal The denorm ordinal number.
    * @return The denorm.
    */
   public RelationalDenorm getDenorm(int nOrdinal)
   {
      if (nOrdinal >= m_nDenormCount)
      {
         throw new ArrayIndexOutOfBoundsException(nOrdinal);
      }

      return m_denormArray[nOrdinal];
   }

   /**
    * @return The denorm count.
    */
   public int getDenormCount()
   {
      return m_nDenormCount;
   }

   /**
    * @see nexj.core.meta.persistence.PersistenceMapping#getObjectKey()
    */
   public Key getObjectKey()
   {
      return m_primaryTable.getPrimaryKey();
   }

   /**
    * @see nexj.core.meta.persistence.GenericPersistenceMapping#getForeignKey(java.lang.String)
    */
   protected Key getForeignKey(String sName) throws MetadataException
   {
      return ((RelationalSchema)getDataSource().getSchema()).getIndex(sName);
   }

   /**
    * Verifies the locking attribute and performs precomputations.
    * @throws MetadataException if the verification fails.
    */
   public void resolveInheritance() throws MetadataException
   {
      PersistenceMapping baseMapping = getBaseMapping();

      // Inherit the persistence mappings
      if (baseMapping != null && baseMapping.getDataSource() == m_dataSource)
      {
         RelationalMapping mapping = (RelationalMapping)baseMapping;

         if (m_primaryTable == mapping.getPrimaryTable())
         {
            for (int i = 0, nCount = mapping.getTableCount(); i < nCount; ++i)
            {
               addTable(mapping.getTable(i));
            }

            for (int i = 0, nCount = mapping.getMetaclass().getInstanceAttributeCount(); i < nCount; ++i)
            {
               Attribute baseAttribute = mapping.getMetaclass().getInstanceAttribute(i);
               AttributeMapping baseAttrMapping = mapping.getAttributeMapping(baseAttribute);

               if (baseAttrMapping != null)
               {
                  Attribute derivedAttribute = m_metaclass.getDerivedAttribute(baseAttribute);
                  AttributeMapping derivedAttrMapping = getAttributeMapping(derivedAttribute);

                  if (derivedAttrMapping == null)
                  {
                     if (derivedAttribute.getType() == baseAttribute.getType())
                     {
                        derivedAttrMapping = (AttributeMapping)baseAttrMapping.clone(derivedAttribute);
                        derivedAttrMapping.setAttribute(derivedAttribute);
                        addAttributeMapping(derivedAttrMapping);
                     }
                  }
                  else if (m_typeCodeAttribute != null || mapping.m_typeCodeAttribute != null)
                  {
                     boolean bMismatch;

                     if (baseAttrMapping instanceof RelationalPrimitiveMapping)
                     {
                        bMismatch = (((RelationalPrimitiveMapping)derivedAttrMapping).getColumn() !=
                           ((RelationalPrimitiveMapping)baseAttrMapping).getColumn());
                     }
                     else
                     {
                        RelationalClassMapping baseClassMapping = (RelationalClassMapping)baseAttrMapping;
                        RelationalClassMapping classMapping = (RelationalClassMapping)derivedAttrMapping;

                        bMismatch = (classMapping.getDestinationKey() != baseClassMapping.getDestinationKey() ||
                           classMapping.getSourceKey() != baseClassMapping.getSourceKey());
                     }

                     if (bMismatch)
                     {
                        MetadataValidationException e = new MetadataValidationException(
                           "err.meta.persistenceMappingMismatch",
                           new Object[]{derivedAttribute.getName(), m_metaclass.getName(), mapping.getMetaclass().getName()});

                        derivedAttribute.setProperties(e);

                        throw e;
                     }
                  }
               }
            }
         }
         else
         {
            if (s_logger.isDebugEnabled())
            {
               s_logger.debug("Primary table " + mapping.getPrimaryTable().getName() +
                  " overridden by " + m_primaryTable.getName() + " in class " + m_metaclass.getName());
            }
         }
      }

      super.resolveInheritance();

      if (!(m_metaclass instanceof Aspect))
      {
         if (m_lockingAttribute != null)
         {
            if (((RelationalPrimitiveMapping)getAttributeMapping(m_lockingAttribute)).getColumn().getTable() != getPrimaryTable())
            {
               throw new MetadataException("err.meta.lockingAttributeNotInPrimaryTable",
                  new Object[]{m_lockingAttribute.getName(), m_metaclass.getName()});
            }
         }

         if (m_typeCodeAttribute != null)
         {
            ((RelationalPrimitiveMapping)getAttributeMapping(m_typeCodeAttribute)).getColumn().setCaseInsensitive(false);
         }
      }

      if (s_logger.isDebugEnabled())
      {
         StringBuffer buf = new StringBuffer(64);

         buf.append("Tables for class ");
         buf.append(m_metaclass.getName());

         if (isContext())
         {
            buf.append(" (context mapping)");
         }

         buf.append(": ");

         for (int i = 0; i < m_nTableCount; ++i)
         {
            if (i > 0)
            {
               buf.append(", ");
            }

            buf.append(m_tableArray[i].getName());
         }

         s_logger.debug(buf.toString());
      }
   }

   /**
    * @see PersistenceMapping#deriveAttributeMapping(Attribute, Attribute, AttributeMapping)
    */
   protected void deriveAttributeMapping(Attribute attribute, Attribute base, AttributeMapping baseMapping)
   {
      if (attribute.getType() == base.getType())
      {
         if (attribute.getMetaclass() instanceof Aspect)
         {
            if (getAttributeMapping(attribute) == null)
            {
               AttributeMapping mapping = (AttributeMapping)baseMapping.clone();

               mapping.setAttribute(attribute);
               addAttributeMapping(mapping);
            }
         }
         else
         {
            if (baseMapping instanceof RelationalPrimitiveMapping)
            {
               RelationalPrimitiveMapping basePrimitiveMapping = (RelationalPrimitiveMapping)baseMapping;

               for (int nTable = 0, nTableCount = getTableCount(); nTable < nTableCount; ++nTable)
               {
                  Table table = getTable(nTable);

                  if (table.hasAspect(basePrimitiveMapping.getColumn().getTable()))
                  {
                     RelationalPrimitiveMapping primitiveMapping = (RelationalPrimitiveMapping)getAttributeMapping(attribute);

                     if (primitiveMapping == null)
                     {
                        primitiveMapping = (RelationalPrimitiveMapping)basePrimitiveMapping.clone();
                        primitiveMapping.setColumn(table.getColumn(basePrimitiveMapping.getColumn().getName()));
                        primitiveMapping.setAttribute(attribute);

                        addAttributeMapping(primitiveMapping);
                     }
                     else if (table != primitiveMapping.getColumn().getTable())
                     {
                        RelationalPrimitiveDenorm denorm = new RelationalPrimitiveDenorm(primitiveMapping);

                        denorm.setColumn(table.getColumn(basePrimitiveMapping.getColumn().getName()));
                        denorm.getColumn().addAttribute(attribute);
                        primitiveMapping.addDenorm(denorm);
                        addDenorm(denorm);
                     }
                  }
               }
            }
            else
            {
               RelationalClassMapping baseClassMapping = (RelationalClassMapping)baseMapping;
               Index sourceKey = baseClassMapping.getSourceKey();

               if (sourceKey == null)
               {
                  RelationalClassMapping classMapping = (RelationalClassMapping)getAttributeMapping(attribute);

                  if (classMapping == null)
                  {
                     classMapping = (RelationalClassMapping)baseClassMapping.clone(attribute);
                     classMapping.setSourceKey(m_primaryTable.getPrimaryKey());
                     classMapping.setDestinationKey(classMapping.getDestinationKey());
                     addAttributeMapping(classMapping);
                  }
               }
               else
               {
                  for (int nTable = 0, nTableCount = getTableCount(); nTable < nTableCount; ++nTable)
                  {
                     Table table = getTable(nTable);

                     if (table.hasAspect(sourceKey.getTable()))
                     {
                        RelationalClassMapping classMapping = (RelationalClassMapping)getAttributeMapping(attribute);

                        if (classMapping == null)
                        {
                           classMapping = (RelationalClassMapping)baseClassMapping.clone(attribute);
                           classMapping.setSourceKey(table.getIndex(sourceKey.getName(table)));
                           classMapping.setAttribute(attribute);
                           addAttributeMapping(classMapping);
                        }
                        else if (!sourceKey.isObjectKeyPart() && table != classMapping.getSourceKey().getTable())
                        {
                           RelationalClassDenorm denorm = new RelationalClassDenorm(classMapping);

                           denorm.setSourceKey(table.getIndex(sourceKey.getName(table)));
                           classMapping.addDenorm(denorm);
                           addDenorm(denorm);
                        }
                     }
                  }
               }
            }
         }
      }
   }

   /**
    * @see nexj.core.meta.persistence.PersistenceMapping#resolveInheritance2()
    */
   public void resolveInheritance2()
   {
      super.resolveInheritance2();

      // Set up the default reverse association mappings

      for (int i = 0, nCount = m_metaclass.getInstanceAttributeCount(); i < nCount; ++i)
      {
         Attribute attribute = m_metaclass.getInstanceAttribute(i);
         Attribute reverse = attribute.getReverse();

         if (reverse != null && attribute.isSymmetric())
         {
            RelationalClassMapping mapping = (RelationalClassMapping)getAttributeMapping(attribute);

            if (mapping == null)
            {
               AttributeMapping attributeMapping = reverse.findPersistenceMapping(this, true);

               if (attributeMapping instanceof RelationalClassMapping)
               {
                  RelationalClassMapping reverseMapping = (RelationalClassMapping)attributeMapping;

                  if (reverseMapping != null)
                  {
                     mapping = new RelationalClassMapping();
                     mapping.setPersistenceMapping(this);
                     mapping.setAttribute(attribute);
                     mapping.setMapping(attributeMapping.getPersistenceMapping());
                     mapping.setSourceKey((Index)reverseMapping.getDestinationKey());
                     mapping.setDestinationKey(reverseMapping.getSourceKey());
                     addAttributeMapping(mapping);
                  }
               }
            }
         }
      }

      PersistenceMapping baseMapping = getBaseMapping();

      // Inherit the key generators

      if (baseMapping != null && baseMapping.getDataSource() == getDataSource())
      {
         RelationalMapping mapping = (RelationalMapping)baseMapping;

         if (m_keyGenerator == null)
         {
            m_keyGenerator = mapping.getKeyGenerator();
         }
      }
   }

   /**
    * @see nexj.core.meta.persistence.PersistenceMapping#getSortKeys(nexj.core.meta.Attribute[], nexj.core.meta.persistence.PersistenceMapping[], Attribute[])
    */
   public Pair getSortKeys(Attribute[] assocs, PersistenceMapping[] mappings, Attribute[] restrictions)
   {
      Pair sortKeys = null;
      Pair lastKey = null;

      for (int nTable = 0; nTable < m_nTableCount; ++nTable)
      {
         Table table = m_tableArray[nTable];
         RelationalPrimitiveMapping[] primitiveMappings = table.findMappingArray(this);
         BitSet filterSet = null;
         BitSet optionalSet = null;
         int nFilterCount = 0;

         if (primitiveMappings == null)
         {
            continue;
         }

         // Create a set of columns, which participate in equality
         // comparisons due to the associations.
         if (assocs != null && assocs.length > 0)
         {
            filterSet = new BitSet(table.getColumnCount());

            for (int i = 0; i < assocs.length; ++i)
            {
               PersistenceMapping mapping = mappings[i];

               if (mapping == null)
               {
                  continue;
               }

               AttributeMapping attributeMapping = mapping.getAttributeMapping(assocs[i]);

               if (attributeMapping instanceof RelationalClassMapping)
               {
                  RelationalClassMapping classMapping = (RelationalClassMapping)attributeMapping;

                  if (classMapping.getMapping() == this &&
                     classMapping.getDestinationKey() instanceof Index)
                  {
                     Index key = (Index)classMapping.getDestinationKey();

                     if (key.getTable() != table)
                     {
                        key = table.getPrimaryKey();
                     }

                     if (!key.isUnique())
                     {
                        for (int k = key.getIndexColumnCount() - 1; k >= 0; --k)
                        {
                           filterSet.set(key.getIndexColumn(k).getColumn().getOrdinal());
                        }
                     }
                  }
               }
            }

            nFilterCount = filterSet.cardinality();
         }

         // Create a set of columns, which participate in equality
         // comparisons and can be optionally excluded from the index.
         if (restrictions != null && restrictions.length > 0)
         {
            optionalSet = new BitSet(table.getColumnCount());

            for (int i = 0; i < restrictions.length; ++i)
            {
               AttributeMapping attributeMapping = getAttributeMapping(restrictions[i]);

               if (attributeMapping instanceof RelationalClassMapping)
               {
                  Index index = ((RelationalClassMapping)attributeMapping).getSourceKey();

                  for (int k = index.getIndexColumnCount() - 1; k >= 0; --k)
                  {
                     optionalSet.set(index.getIndexColumn(k).getColumn().getOrdinal());
                  }
               }
               else if (attributeMapping instanceof RelationalPrimitiveMapping)
               {
                  optionalSet.set(((RelationalPrimitiveMapping)attributeMapping).getColumn().getOrdinal());
               }
            }
         }

         // Collect the sort keys
         for (int nIndex = 0; nIndex < table.getIndexCount(); ++nIndex)
         {
            Index index = table.getIndex(nIndex);

            if (index.getType() != Index.CLUSTER &&
               index.getType() != Index.BTREE &&
               index.getType() != Index.QUERY)
            {
               continue;
            }

            // Ignore indexes that do not start with the columns specified
            // in the association filter, in arbitrary order
            if (nFilterCount > 0)
            {
               if (index.getIndexColumnCount() < nFilterCount)
               {
                  continue;
               }

               int i;

               for (i = 0; i < nFilterCount; ++i)
               {
                  if (!filterSet.get(index.getIndexColumn(i).getColumn().getOrdinal()))
                  {
                     break;
                  }
               }

               if (i < nFilterCount)
               {
                  continue;
               }
            }

            Index pk = table.getPrimaryKey();
            boolean bMatch = true;
            boolean bAscending = true;
            Pair key = null;

            // Check whether the primary key columns are a subset of the index columns
            for (int i = 0; i < pk.getIndexColumnCount(); ++i)
            {
               IndexColumn pkIndexColumn = pk.getIndexColumn(i);
               Column pkColumn = pkIndexColumn.getColumn();

               if (optionalSet != null &&
                  optionalSet.get(pkColumn.getOrdinal()) &&
                  index.findIndexColumn(pkColumn) != null)
               {
                  continue;
               }

               int k;

               for (k = 0; k < index.getIndexColumnCount(); ++k)
               {
                  IndexColumn indexColumn = index.getIndexColumn(k);

                  if (indexColumn.getColumn() == pkColumn)
                  {
                     if (i == 0)
                     {
                        bAscending = indexColumn.isAscending();
                     }

                     break;
                  }
               }

               if (k == index.getIndexColumnCount())
               {
                  bMatch = false;

                  break;
               }
            }

            // If the above is true, then append the primary key
            // at the end of the sort key
            if (bMatch)
            {
               key = new Pair(new Pair(new Pair(Symbol.AT),
                  Boolean.valueOf(!(pk.getIndexColumn(0).isAscending() ^ bAscending))));
            }
            else
            {
               pk = null;
            }

            int nCount;

            for (nCount = nFilterCount; nCount < index.getIndexColumnCount(); ++nCount)
            {
               int nOrdinal = index.getIndexColumn(nCount).getColumn().getOrdinal();

               if (primitiveMappings[nOrdinal] == null)
               {
                  if (optionalSet == null || !optionalSet.get(nOrdinal))
                  {
                     break;
                  }
               }
            }

            bMatch = true;

            if (pk != null)
            {
               if (nCount == index.getIndexColumnCount())
               {
                  key = null;
               }
               else
               {
                  for (int i = nCount; i < index.getIndexColumnCount(); ++i)
                  {
                     if (!index.getIndexColumn(i).getColumn().isPrimary())
                     {
                        bMatch = false;
                        break;
                     }
                  }
               }
            }

            if (!bMatch)
            {
               continue;
            }

            boolean bPrimitive = false;

            for (int i = nCount - 1; i >= nFilterCount; --i)
            {
               IndexColumn indexColumn = index.getIndexColumn(i);
               int nOrdinal = indexColumn.getColumn().getOrdinal();
               RelationalPrimitiveMapping mapping = primitiveMappings[nOrdinal];

               if (mapping != null)
               {
                  if (optionalSet == null || !optionalSet.get(nOrdinal))
                  {
                     key = new Pair(new Pair(mapping.getAttribute().getSymbol(),
                        Boolean.valueOf(indexColumn.isAscending())), key);
                     bPrimitive = true;
                  }
               }
            }

            // Append the new sort key to the end of the sort key list
            if (key != null)
            {
               lastKey = addKey(new Pair(Boolean.valueOf(pk != null ||
                  index.isUnique() && nCount == index.getIndexColumnCount()), key), sortKeys, lastKey);

               if (sortKeys == null)
               {
                  sortKeys = lastKey;
               }

               if (pk == index && bPrimitive)
               {
                  lastKey = addKey(new Pair(Boolean.TRUE,
                     new Pair(new Pair(new Pair(Symbol.AT), Boolean.TRUE))), sortKeys, lastKey);
               }
            }
         }
      }

      return sortKeys;
   }

   /**
    * Adds a key to the end of a key list.
    * @param key The key to add.
    * @param tail The tail of the key list. Can be null.
    * @param head The head of the key list. Can be null.
    * @return The new tail of the key list.
    */
   protected static Pair addKey(Pair key, Pair head, Pair tail)
   {
      if (tail == null)
      {
         tail = new Pair(key);
      }
      else
      {
         Pair pair;

         // Filter out the duplicates
         for (pair = head; pair != null; pair = pair.getNext())
         {
            if (pair.getHead().equals(key))
            {
               break;
            }
         }

         if (pair == null)
         {
            key = new Pair(key);
            tail.setTail(key);
            tail = key;
         }
      }

      return tail;
   }

   /**
    * @see nexj.core.meta.persistence.PersistenceMapping#getUniqueKeys()
    */
   public Pair getUniqueKeys()
   {
      Pair uniqueKeys = null; // The resulting key list
      int[] ordinalArray = null; // Contains the ordinal numbers of attributes
      boolean[] useArray = null; // Flags index columns as used

      for (int nTable = 0; nTable < m_nTableCount; ++nTable)
      {
         Table table = getTable(nTable);

         for (int nIndex = 0, nIndexCount = table.getIndexCount(); nIndex < nIndexCount; ++nIndex)
         {
            Index index = table.getIndex(nIndex);

            if (!index.isUnique())
            {
               continue;
            }

            int nIndexColumnCount = index.getIndexColumnCount();

            if (nIndexColumnCount == 0)
            {
               continue;
            }

            // Find all the sets of no more than nIndexColumnCount attributes
            // which are mapped to the columns of the unique index, so that
            // no unmapped columns are left and each column is used only once

            // Allocate an array of at least nIndexColumnCount counters
            if (ordinalArray == null || ordinalArray.length < nIndexColumnCount)
            {
               ordinalArray = new int[Math.max(nIndexColumnCount, 4)];
               useArray = new boolean[ordinalArray.length];
            }
            else
            {
               ordinalArray[0] = 0;
               Arrays.fill(useArray, false);
            }

            uniqueKeys = getUniqueKeys(index, uniqueKeys, ordinalArray, useArray);
         }
      }

      return uniqueKeys;
   }

   /**
    * Determines the unique keys for a given index.
    * @param index The index for which to determine the unique keys.
    * @param uniqueKeys The list of unique keys to which to prepend.
    * @return A list of unique keys.
    * @see nexj.core.meta.persistence.PersistenceMapping#getUniqueKeys()
    */
   public Pair getUniqueKeys(Index index)
   {
      if (index.isUnique())
      {
         int nIndexColumnCount = index.getIndexColumnCount();

         if (nIndexColumnCount != 0)
         {
            return getUniqueKeys(index, null, new int[nIndexColumnCount],
               new boolean[nIndexColumnCount]);
         }
      }

      return null;
   }

   /**
    * Helper method determining the unique keys for a given index.
    * @param index The index for which to determine the unique keys.
    * @param uniqueKeys The list of unique keys to which to prepend.
    * @param ordinalArray Work array containing attribute ordinal numbers on return.
    * Must be able to contain at least as many entries as there are index columns.
    * @param useArray Work array containing used index column flags
    * on return, same size as ordinalArray.
    * @return The augmented unique key list.
    * @see nexj.core.meta.persistence.PersistenceMapping#getUniqueKeys()
    */
   protected Pair getUniqueKeys(Index index, Pair uniqueKeys,
      int[] ordinalArray, boolean[] useArray)
   {
      int nIndexColumnCount = index.getIndexColumnCount();
      int nAttributeCount = m_metaclass.getInstanceAttributeCount();
      int nTop = 0; // The current ordinal counter
      int nColumnCount = 0; // The mapped index column count

      for (;;)
      {
         if (ordinalArray[nTop] >= nAttributeCount)
         {
            if (--nTop >= 0)
            {
               nColumnCount -= mark(getAttributeMapping(
                  m_metaclass.getInstanceAttribute(ordinalArray[nTop]++)),
                  index, useArray, false);

               continue;
            }

            break;
         }

         AttributeMapping mapping = getAttributeMapping(m_metaclass.getInstanceAttribute(ordinalArray[nTop]));

         if (mapping != null && !mapping.getAttribute().isCollection())
         {
            if (!(mapping instanceof RelationalClassMapping) ||
               ((RelationalClassMapping)mapping).getDestinationKey().isUnique())
            {
               int nSize =  getSubsetSize(mapping, index, useArray);

               if (nSize > 0)
               {
                  if (nColumnCount + nSize == nIndexColumnCount)
                  {
                     Pair key = null;

                     for (int i = nTop; i >= 0; --i)
                     {
                        key = new ConstPair(m_metaclass.getInstanceAttribute(ordinalArray[i]).getSymbol(), key);
                     }

                     uniqueKeys = new ConstPair(key, uniqueKeys);
                  }
                  else if (nTop < nIndexColumnCount - 1)
                  {
                     nColumnCount += mark(mapping, index, useArray, true);
                     ordinalArray[nTop + 1] = ordinalArray[nTop];
                     ++nTop;
                  }
               }
            }
         }

         ++ordinalArray[nTop];
      }

      return uniqueKeys;
   }

   /**
    * Determines if an attribute mapping columns are a subset of index columns.
    * @param mapping The attribute mapping.
    * @param index The index, a subset of which is sought.
    * @param excludeArray A boolean array with true values set to exclude corresponding index columns.
    * @return The subset size.
    */
   private static int getSubsetSize(AttributeMapping mapping, Index index, boolean excludeArray[])
   {
      if (mapping instanceof RelationalPrimitiveMapping)
      {
         IndexColumn indexColumn = index.findIndexColumn(((RelationalPrimitiveMapping)mapping).getColumn());

         return (indexColumn != null && !excludeArray[indexColumn.getOrdinal()]) ? 1 : 0;
      }

      Index sourceIndex = ((RelationalClassMapping)mapping).getSourceKey();

      for (int i = 0, n = sourceIndex.getIndexColumnCount(); i < n; ++i)
      {
         IndexColumn indexColumn = index.findIndexColumn(sourceIndex.getIndexColumn(i).getColumn());

         if (indexColumn == null || excludeArray[indexColumn.getOrdinal()])
         {
            return 0;
         }
      }

      return sourceIndex.getIndexColumnCount();
   }

   /**
    * Marks the index columns corresponding to attribute mapping columns in a boolean array.
    * @param mapping The attribute mapping.
    * @param index The index.
    * @param flagArray The flag array.
    * @param bFlag The flag to set.
    * @return The count of the marked columns.
    */
   private static int mark(AttributeMapping mapping, Index index, boolean flagArray[], boolean bFlag)
   {
      if (mapping instanceof RelationalPrimitiveMapping)
      {
         flagArray[index.findIndexColumn(((RelationalPrimitiveMapping)mapping).getColumn()).getOrdinal()] = bFlag;

         return 1;
      }

      Index sourceIndex = ((RelationalClassMapping)mapping).getSourceKey();

      for (int i = 0, n = sourceIndex.getIndexColumnCount(); i < n; ++i)
      {
         flagArray[index.findIndexColumn(sourceIndex.getIndexColumn(i).getColumn()).getOrdinal()] = bFlag;
      }

      return sourceIndex.getIndexColumnCount();
   }

   /**
    * @see nexj.core.meta.persistence.PersistenceMapping#create()
    */
   public PersistenceMapping create()
   {
      RelationalMapping mapping = new RelationalMapping();

      mapping.setPrimaryTable(m_primaryTable);

      return mapping;
   }

   /**
    * Computes the unmapped column nullable flag.
    * @param metadata The root metadata object.
    */
   public static void resolve(Metadata metadata)
   {
      for (Iterator itr = metadata.getMetaclassIterator(); itr.hasNext();)
      {
         final Metaclass metaclass = (Metaclass)itr.next();

         metaclass.visit(new PersistenceMapping.Visitor()
         {
            public void visit(PersistenceMapping mapping)
            {
               if (mapping instanceof RelationalMapping)
               {
                  RelationalMapping relMapping = (RelationalMapping)mapping;
                  BitSet[] bitSetArray = new BitSet[relMapping.getTableCount()];

                  for (int i = 0; i != relMapping.getTableCount(); ++i)
                  {
                     bitSetArray[i] = new BitSet(relMapping.getTable(i).getColumnCount());
                  }

                  for (int nAttr = 0; nAttr != metaclass.getInstanceAttributeCount(); ++nAttr)
                  {
                     AttributeMapping attrMapping = relMapping.getAttributeMapping(metaclass.getInstanceAttribute(nAttr));

                     if (attrMapping instanceof RelationalPrimitiveMapping)
                     {
                        RelationalPrimitiveMapping primitiveMapping = (RelationalPrimitiveMapping)attrMapping;

                        Column column = primitiveMapping.getColumn();

                        bitSetArray[relMapping.findTableOrdinal(column.getTable())].set(column.getOrdinal());

                        for (int i = 0; i != primitiveMapping.getDenormCount(); ++i)
                        {
                           column = ((RelationalPrimitiveDenorm)primitiveMapping.getDenorm(i)).getColumn();
                        }

                        bitSetArray[relMapping.findTableOrdinal(column.getTable())].set(column.getOrdinal());
                     }
                     else if (attrMapping instanceof RelationalClassMapping)
                     {
                        Index index = ((RelationalClassMapping)attrMapping).getSourceKey();
                        BitSet bitSet = bitSetArray[relMapping.findTableOrdinal(index.getTable())];

                        for (int i = 0; i != index.getIndexColumnCount(); ++i)
                        {
                           bitSet.set(index.getIndexColumn(i).getColumn().getOrdinal());
                        }
                     }
                  }

                  for (int i = 0; i != relMapping.getTableCount(); ++i)
                  {
                     Table table = relMapping.getTable(i);
                     Index index = table.getPrimaryKey();
                     BitSet bitSet = bitSetArray[i];

                     for (int k = 0; k != index.getIndexColumnCount(); ++k)
                     {
                        bitSet.set(index.getIndexColumn(k).getColumn().getOrdinal());
                     }

                     for (int k = bitSet.nextClearBit(0); k >= 0 && k < table.getColumnCount(); k = bitSet.nextClearBit(k + 1))
                     {
                        table.getColumn(k).setRequired(false, true);
                     }
                  }
               }
            }
         });
      }
   }

   // inner classes

   /**
    * Stub for the identity key generator.
    */
   public final static class IdentityKeyGenerator
   {
      public final static int VALUE_COUNT = 1;
   }
}
TOP

Related Classes of nexj.core.meta.persistence.sql.RelationalMapping

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.