/*

   Derby - Class org.apache.derby.iapi.sql.dictionary.IndexRowGenerator

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to you under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package org.apache.derby.iapi.sql.dictionary;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.security.AccessController;
import java.security.PrivilegedAction;

import org.apache.derby.catalog.IndexDescriptor;
import org.apache.derby.catalog.types.IndexDescriptorImpl;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.services.context.Context;
import org.apache.derby.iapi.services.context.ContextService;
import org.apache.derby.iapi.services.io.Formatable;
import org.apache.derby.iapi.services.io.FormatableBitSet;
import org.apache.derby.iapi.services.io.StoredFormatIds;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.execute.ExecIndexRow;
import org.apache.derby.iapi.sql.execute.ExecRow;
import org.apache.derby.iapi.sql.execute.ExecutionContext;
import org.apache.derby.iapi.sql.execute.ExecutionFactory;
import org.apache.derby.iapi.types.RowLocation;
import org.apache.derby.iapi.types.StringDataValue;
import org.apache.derby.iapi.types.DataTypeDescriptor;

/**
 * This class extends IndexDescriptor for internal use by the
 * DataDictionary.
 * <p>
 * For a description of how deferrable and non-deferrable constraints
 * are backed differently, including the meaning of the
 * boolean attributes used here, see {@link
 * org.apache.derby.catalog.IndexDescriptor}.
 */
public class IndexRowGenerator implements IndexDescriptor, Formatable
{
	private IndexDescriptor	id;
	private ExecutionFactory ef;

    /**
     * Constructor for an IndexRowGeneratorImpl
     * 
     * @param indexType		The type of index
     * @param isUnique		True means the index is unique
     * @param isUniqueWithDuplicateNulls means the index is almost unique
     *                              i.e. unique only for non null keys
     * @param isUniqueDeferrable    True means the index represents a PRIMARY
     *                              KEY or a UNIQUE NOT NULL constraint which
     *                              is deferrable.
     * @param hasDeferrableChecking True if the index is used to back a
     *                              deferrable constraint
     * @param baseColumnPositions	An array of column positions in the base
     * 								table.  Each index column corresponds to a
     * 								column position in the base table.
     * @param isAscending	An array of booleans telling asc/desc on each
     * 						column.
     * @param numberOfOrderedColumns	In the future, it will be possible
     * 									to store non-ordered columns in an
     * 									index.  These will be useful for
     * 									covered queries.
     */
	public IndexRowGenerator(String indexType,
								boolean isUnique,
								boolean isUniqueWithDuplicateNulls,
                                boolean isUniqueDeferrable,
                                boolean hasDeferrableChecking,
								int[] baseColumnPositions,
								boolean[] isAscending,
								int numberOfOrderedColumns)
	{
		id = new IndexDescriptorImpl(indexType,
									isUnique,
									isUniqueWithDuplicateNulls,
                                    isUniqueDeferrable,
                                    hasDeferrableChecking,
									baseColumnPositions,
									isAscending,
									numberOfOrderedColumns);

		if (SanityManager.DEBUG)
		{
			SanityManager.ASSERT(baseColumnPositions != null,
				"baseColumnPositions are null");
		}
	}

	/**
	 * Constructor for an IndexRowGeneratorImpl
	 *
	 * @param indexDescriptor		An IndexDescriptor to delegate calls to
	 */
	public IndexRowGenerator(IndexDescriptor indexDescriptor)
	{
		id = indexDescriptor;
	}

	/**
	 * Get a template for the index row, to be used with getIndexRow.
	 *
	 * @return	A row template for the index row.
	 */
	public ExecIndexRow getIndexRowTemplate()
	{
		return getExecutionFactory().getIndexableRow(
										id.baseColumnPositions().length + 1);
	}

    /**
     * Get a NULL Index Row for this index. This is useful to create objects
     * that need to be passed to ScanController.
     *
     * @param columnList ColumnDescriptors describing the base table.
     * @param rowLocation   empty row location.
     *
     * @exception StandardException thrown on error.
     */
    public ExecIndexRow getNullIndexRow(ColumnDescriptorList columnList,
    		RowLocation rowLocation)
    throws StandardException
    {
            int[] baseColumnPositions = id.baseColumnPositions();
            ExecIndexRow indexRow = getIndexRowTemplate();

            for (int i = 0; i < baseColumnPositions.length; i++)
            {
                    DataTypeDescriptor dtd =
                            columnList.elementAt(baseColumnPositions[i] - 1).getType();
                    indexRow.setColumn(i + 1, dtd.getNull());
            }

            indexRow.setColumn(baseColumnPositions.length + 1, rowLocation);
            return indexRow;
    }

	/**
	 * Get an index row for this index given a row from the base table
	 * and the RowLocation of the base row.  This method can be used
	 * to get the new index row for inserts, and the old and new index
	 * rows for deletes and updates.  For updates, the result row has
	 * all the old column values followed by all of the new column values,
	 * so you must form a row using the new column values to pass to
	 * this method to get the new index row.
	 *
	 * @param baseRow	A row in the base table
	 * @param rowLocation	The RowLocation of the row in the base table
	 * @param indexRow	A template for the index row.  It must have the
	 *					correct number of columns.
	 * @param bitSet	If non-null, then baseRow is a partial row and the
	 *					set bits in bitSet represents the column mapping for
	 *					the partial row to the complete base row. <B> WARNING:
	 *					</B> ONE based!!!
	 *
	 * @exception StandardException		Thrown on error
	 */
	public void getIndexRow(ExecRow baseRow,
							RowLocation rowLocation,
							ExecIndexRow indexRow,
							FormatableBitSet bitSet)
						throws StandardException
	{
		/*
		** Set the columns in the index row that are based on columns in
		** the base row.
		*/
		int[] baseColumnPositions = id.baseColumnPositions();
		int colCount = baseColumnPositions.length;

		if (bitSet == null)
		{
			/*
			** Set the columns in the index row that are based on columns in
			** the base row.
			*/
			for (int i = 0; i < colCount ; i++)
			{
				indexRow.setColumn(i + 1,
						baseRow.getColumn(baseColumnPositions[i]));
			}
		}
		else
		{
			if (SanityManager.DEBUG)
			{
				SanityManager.ASSERT(!bitSet.get(0), "element zero of the bitSet passed into getIndexRow() is not false, bitSet should be 1 based");
			}
 
			/*
			** Set the columns in the index row that are based on columns in
			** the base row.
			*/
			for (int i = 0; i < colCount; i++)
			{
				int fullColumnNumber = baseColumnPositions[i];
				int partialColumnNumber = 0;
				for (int index = 1; index <= fullColumnNumber; index++)
				{
					if (bitSet.get(index))
					{
						partialColumnNumber++;
					}
				}
				indexRow.setColumn(i + 1,
							baseRow.getColumn(partialColumnNumber));
			}
		}

		/* Set the row location in the last column of the index row */
		indexRow.setColumn(colCount + 1, rowLocation);
	}

    /**
     * Return an array of collation ids for this table.
     * <p>
     * Return an array of collation ids, one for each column in the
     * columnDescriptorList.  This is useful for passing collation id info
     * down to store, for instance in createConglomerate() to create
     * the index.
     *
     * This is only expected to get called during ddl, so object allocation
     * is ok. 
     *
	 * @param columnList ColumnDescriptors describing the base table.
     *
	 * @exception  StandardException  Standard exception policy.
     **/
    public int[] getColumnCollationIds(ColumnDescriptorList columnList)
		throws StandardException
    {
        int[] base_cols     = id.baseColumnPositions();
        int[] collation_ids = new int[base_cols.length + 1];

		for (int i = 0; i < base_cols.length; i++)
		{
            collation_ids[i] =
				columnList.elementAt(
                    base_cols[i] - 1).getType().getCollationType();
		}

        // row location column at end is always basic collation type.
        collation_ids[collation_ids.length - 1] = 
            StringDataValue.COLLATION_TYPE_UCS_BASIC; 

		return(collation_ids);
    }

		 
	/**
	 * Get the IndexDescriptor that this IndexRowGenerator is based on.
	 */
	public IndexDescriptor getIndexDescriptor()
	{
		return id;
	}

	/** Zero-argument constructor for Formatable interface */
	public IndexRowGenerator()
	{
	}

	/**
     * @see IndexDescriptor#isUniqueWithDuplicateNulls
     */
	public boolean isUniqueWithDuplicateNulls()
	{
		return id.isUniqueWithDuplicateNulls();
	}

    public boolean hasDeferrableChecking()
    {
        return id.hasDeferrableChecking();
    }


    public boolean isUniqueDeferrable()
    {
        return id.isUniqueDeferrable();
    }


	/** @see IndexDescriptor#isUnique */
	public boolean isUnique()
	{
		return id.isUnique();
	}

	/** @see IndexDescriptor#baseColumnPositions */
	public int[] baseColumnPositions()
	{
		return id.baseColumnPositions();
	}

	/** @see IndexDescriptor#getKeyColumnPosition */
	public int getKeyColumnPosition(int heapColumnPosition)
	{
		return id.getKeyColumnPosition(heapColumnPosition);
	}

	/** @see IndexDescriptor#numberOfOrderedColumns */
	public int numberOfOrderedColumns()
	{
		return id.numberOfOrderedColumns();
	}

	/** @see IndexDescriptor#indexType */
	public String indexType()
	{
		return id.indexType();
	}

	public String toString()
	{
		return id.toString();
	}

	/** @see IndexDescriptor#isAscending */
	public boolean			isAscending(Integer keyColumnPosition)
	{
		return id.isAscending(keyColumnPosition);
	}

	/** @see IndexDescriptor#isDescending */
	public boolean			isDescending(Integer keyColumnPosition)
	{
		return id.isDescending(keyColumnPosition);
	}

	/** @see IndexDescriptor#isAscending */
	public boolean[]		isAscending()
	{
		return id.isAscending();
	}

	/** @see IndexDescriptor#setBaseColumnPositions */
	public void		setBaseColumnPositions(int[] baseColumnPositions)
	{
		id.setBaseColumnPositions(baseColumnPositions);
	}

	/** @see IndexDescriptor#setIsAscending */
	public void		setIsAscending(boolean[] isAscending)
	{
		id.setIsAscending(isAscending);
	}

	/** @see IndexDescriptor#setNumberOfOrderedColumns */
	public void		setNumberOfOrderedColumns(int numberOfOrderedColumns)
	{
		id.setNumberOfOrderedColumns(numberOfOrderedColumns);
	}

	/**
	 * Test for value equality
	 *
	 * @param other		The other indexrowgenerator to compare this one with
	 *
	 * @return	true if this indexrowgenerator has the same value as other
	 */

	public boolean equals(Object other)
	{
		return id.equals(other);
	}

	/**
	  @see java.lang.Object#hashCode
	  */
	public int hashCode()
	{
		return id.hashCode();
	}

	private ExecutionFactory getExecutionFactory()
	{
		if (ef == null)
		{
			ExecutionContext	ec;

			ec = (ExecutionContext)
					getContext(ExecutionContext.CONTEXT_ID);
			ef = ec.getExecutionFactory();
		}
		return ef;
	}

	////////////////////////////////////////////////////////////////////////////
	//
	// EXTERNALIZABLE
	//
	////////////////////////////////////////////////////////////////////////////

	/**
	 * @see java.io.Externalizable#readExternal
	 *
	 * @exception IOException	Thrown on read error
	 * @exception ClassNotFoundException	Thrown on read error
	 */
	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
	{
		id = (IndexDescriptor)in.readObject();
	}

	/**
	 *
	 * @exception IOException	Thrown on write error
	 */
	public void writeExternal(ObjectOutput out) throws IOException
	{
		out.writeObject(id);
	}

	/* TypedFormat interface */
	public int getTypeFormatId()
	{
		return StoredFormatIds.INDEX_ROW_GENERATOR_V01_ID;
	}

    
    /**
     * Privileged lookup of a Context. Must be private so that user code
     * can't call this entry point.
     */
    private  static  Context    getContext( final String contextID )
    {
        if ( System.getSecurityManager() == null )
        {
            return ContextService.getContext( contextID );
        }
        else
        {
            return AccessController.doPrivileged
                (
                 new PrivilegedAction<Context>()
                 {
                     public Context run()
                     {
                         return ContextService.getContext( contextID );
                     }
                 }
                 );
        }
    }

}
