com.healthmarketscience.jackcess.impl.IndexImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.healthmarketscience.jackcess.impl.IndexImpl.java

Source

/*
Copyright (c) 2005 Health Market Science, Inc.
    
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
    
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.
    
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
USA
    
You can contact Health Market Science at info@healthmarketscience.com
or at the following address:
    
Health Market Science
2700 Horizon Drive
Suite 200
King of Prussia, PA 19406
*/

package com.healthmarketscience.jackcess.impl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.healthmarketscience.jackcess.CursorBuilder;
import com.healthmarketscience.jackcess.Index;
import com.healthmarketscience.jackcess.IndexBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Access table (logical) index.  Logical indexes are backed for IndexData,
 * where one or more logical indexes could be backed by the same data.
 * 
 * @author Tim McCune
 */
public class IndexImpl implements Index, Comparable<IndexImpl> {
    protected static final Log LOG = LogFactory.getLog(IndexImpl.class);

    /** index type for primary key indexes */
    public static final byte PRIMARY_KEY_INDEX_TYPE = (byte) 1;

    /** index type for foreign key indexes */
    public static final byte FOREIGN_KEY_INDEX_TYPE = (byte) 2;

    /** flag for indicating that updates should cascade in a foreign key index */
    private static final byte CASCADE_UPDATES_FLAG = (byte) 1;
    /** flag for indicating that deletes should cascade in a foreign key index */
    private static final byte CASCADE_DELETES_FLAG = (byte) 1;

    /** index table type for the "primary" table in a foreign key index */
    private static final byte PRIMARY_TABLE_TYPE = (byte) 1;

    /** indicate an invalid index number for foreign key field */
    private static final int INVALID_INDEX_NUMBER = -1;

    /** the actual data backing this index (more than one index may be backed by
        the same data */
    private final IndexData _data;
    /** 0-based index number */
    private final int _indexNumber;
    /** the type of the index */
    private final byte _indexType;
    /** Index name */
    private String _name;
    /** foreign key reference info, if any */
    private final ForeignKeyReference _reference;

    protected IndexImpl(ByteBuffer tableBuffer, List<IndexData> indexDatas, JetFormat format) throws IOException {

        ByteUtil.forward(tableBuffer, format.SKIP_BEFORE_INDEX_SLOT); //Forward past Unknown
        _indexNumber = tableBuffer.getInt();
        int indexDataNumber = tableBuffer.getInt();

        // read foreign key reference info
        byte relIndexType = tableBuffer.get();
        int relIndexNumber = tableBuffer.getInt();
        int relTablePageNumber = tableBuffer.getInt();
        byte cascadeUpdatesFlag = tableBuffer.get();
        byte cascadeDeletesFlag = tableBuffer.get();

        _indexType = tableBuffer.get();

        if ((_indexType == FOREIGN_KEY_INDEX_TYPE) && (relIndexNumber != INVALID_INDEX_NUMBER)) {
            _reference = new ForeignKeyReference(relIndexType, relIndexNumber, relTablePageNumber,
                    (cascadeUpdatesFlag == CASCADE_UPDATES_FLAG), (cascadeDeletesFlag == CASCADE_DELETES_FLAG));
        } else {
            _reference = null;
        }

        ByteUtil.forward(tableBuffer, format.SKIP_AFTER_INDEX_SLOT); //Skip past Unknown

        _data = indexDatas.get(indexDataNumber);

        _data.addIndex(this);
    }

    public IndexData getIndexData() {
        return _data;
    }

    public TableImpl getTable() {
        return getIndexData().getTable();
    }

    public JetFormat getFormat() {
        return getTable().getFormat();
    }

    public PageChannel getPageChannel() {
        return getTable().getPageChannel();
    }

    public int getIndexNumber() {
        return _indexNumber;
    }

    public byte getIndexFlags() {
        return getIndexData().getIndexFlags();
    }

    public int getUniqueEntryCount() {
        return getIndexData().getUniqueEntryCount();
    }

    public int getUniqueEntryCountOffset() {
        return getIndexData().getUniqueEntryCountOffset();
    }

    public String getName() {
        return _name;
    }

    void setName(String name) {
        _name = name;
    }

    public boolean isPrimaryKey() {
        return _indexType == PRIMARY_KEY_INDEX_TYPE;
    }

    public boolean isForeignKey() {
        return _indexType == FOREIGN_KEY_INDEX_TYPE;
    }

    public ForeignKeyReference getReference() {
        return _reference;
    }

    public IndexImpl getReferencedIndex() throws IOException {

        if (_reference == null) {
            return null;
        }

        TableImpl refTable = getTable().getDatabase().getTable(_reference.getOtherTablePageNumber());

        if (refTable == null) {
            throw new IOException("Reference to missing table " + _reference.getOtherTablePageNumber());
        }

        IndexImpl refIndex = null;
        int idxNumber = _reference.getOtherIndexNumber();
        for (IndexImpl idx : refTable.getIndexes()) {
            if (idx.getIndexNumber() == idxNumber) {
                refIndex = idx;
                break;
            }
        }

        if (refIndex == null) {
            throw new IOException("Reference to missing index " + idxNumber + " on table " + refTable.getName());
        }

        // finally verify that we found the expected index (should reference this
        // index)
        ForeignKeyReference otherRef = refIndex.getReference();
        if ((otherRef == null) || (otherRef.getOtherTablePageNumber() != getTable().getTableDefPageNumber())
                || (otherRef.getOtherIndexNumber() != _indexNumber)) {
            throw new IOException("Found unexpected index " + refIndex.getName() + " on table " + refTable.getName()
                    + " with reference " + otherRef);
        }

        return refIndex;
    }

    public boolean shouldIgnoreNulls() {
        return getIndexData().shouldIgnoreNulls();
    }

    public boolean isUnique() {
        return getIndexData().isUnique();
    }

    public List<IndexData.ColumnDescriptor> getColumns() {
        return getIndexData().getColumns();
    }

    public CursorBuilder newCursor() {
        return getTable().newCursor().setIndex(this);
    }

    /**
     * Whether or not the complete index state has been read.
     */
    public boolean isInitialized() {
        return getIndexData().isInitialized();
    }

    /**
     * Forces initialization of this index (actual parsing of index pages).
     * normally, the index will not be initialized until the entries are
     * actually needed.
     */
    public void initialize() throws IOException {
        getIndexData().initialize();
    }

    /**
     * Gets a new cursor for this index.
     * <p>
     * Forces index initialization.
     */
    public IndexData.EntryCursor cursor() throws IOException {
        return cursor(null, true, null, true);
    }

    /**
     * Gets a new cursor for this index, narrowed to the range defined by the
     * given startRow and endRow.
     * <p>
     * Forces index initialization.
     * 
     * @param startRow the first row of data for the cursor, or {@code null} for
     *                 the first entry
     * @param startInclusive whether or not startRow is inclusive or exclusive
     * @param endRow the last row of data for the cursor, or {@code null} for
     *               the last entry
     * @param endInclusive whether or not endRow is inclusive or exclusive
     */
    public IndexData.EntryCursor cursor(Object[] startRow, boolean startInclusive, Object[] endRow,
            boolean endInclusive) throws IOException {
        return getIndexData().cursor(startRow, startInclusive, endRow, endInclusive);
    }

    /**
     * Constructs an array of values appropriate for this index from the given
     * column values, expected to match the columns for this index.
     * @return the appropriate sparse array of data
     * @throws IllegalArgumentException if the wrong number of values are
     *         provided
     */
    public Object[] constructIndexRowFromEntry(Object... values) {
        return getIndexData().constructIndexRowFromEntry(values);
    }

    /**
     * Constructs an array of values appropriate for this index from the given
     * column value.
     * @return the appropriate sparse array of data or {@code null} if not all
     *         columns for this index were provided
     */
    public Object[] constructIndexRow(String colName, Object value) {
        return constructIndexRow(Collections.singletonMap(colName, value));
    }

    /**
     * Constructs an array of values appropriate for this index from the given
     * column values.
     * @return the appropriate sparse array of data or {@code null} if not all
     *         columns for this index were provided
     */
    public Object[] constructIndexRow(Map<String, ?> row) {
        return getIndexData().constructIndexRow(row);
    }

    @Override
    public String toString() {
        ToStringBuilder sb = CustomToStringStyle.builder(this)
                .append("name", "(" + getTable().getName() + ") " + _name).append("number", _indexNumber)
                .append("isPrimaryKey", isPrimaryKey()).append("isForeignKey", isForeignKey());
        if (_reference != null) {
            sb.append("foreignKeyReference", _reference);
        }
        sb.append("data", _data);
        return sb.toString();
    }

    public int compareTo(IndexImpl other) {
        if (_indexNumber > other.getIndexNumber()) {
            return 1;
        } else if (_indexNumber < other.getIndexNumber()) {
            return -1;
        } else {
            return 0;
        }
    }

    /**
     * Writes the logical index definitions into a table definition buffer.
     * @param buffer Buffer to write to
     * @param indexes List of IndexBuilders to write definitions for
     */
    protected static void writeDefinitions(TableCreator creator, ByteBuffer buffer) throws IOException {
        // write logical index information
        for (IndexBuilder idx : creator.getIndexes()) {
            TableCreator.IndexState idxState = creator.getIndexState(idx);
            buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def
            buffer.putInt(idxState.getIndexNumber()); // index num
            buffer.putInt(idxState.getIndexDataNumber()); // index data num
            buffer.put((byte) 0); // related table type
            buffer.putInt(INVALID_INDEX_NUMBER); // related index num
            buffer.putInt(0); // related table definition page number
            buffer.put((byte) 0); // cascade updates flag
            buffer.put((byte) 0); // cascade deletes flag
            buffer.put(idx.getType()); // index type flags
            buffer.putInt(0); // unknown
        }

        // write index names
        for (IndexBuilder idx : creator.getIndexes()) {
            TableImpl.writeName(buffer, idx.getName(), creator.getCharset());
        }
    }

    /**
     * Information about a foreign key reference defined in an index (when
     * referential integrity should be enforced).
     */
    public static class ForeignKeyReference {
        private final byte _tableType;
        private final int _otherIndexNumber;
        private final int _otherTablePageNumber;
        private final boolean _cascadeUpdates;
        private final boolean _cascadeDeletes;

        public ForeignKeyReference(byte tableType, int otherIndexNumber, int otherTablePageNumber,
                boolean cascadeUpdates, boolean cascadeDeletes) {
            _tableType = tableType;
            _otherIndexNumber = otherIndexNumber;
            _otherTablePageNumber = otherTablePageNumber;
            _cascadeUpdates = cascadeUpdates;
            _cascadeDeletes = cascadeDeletes;
        }

        public byte getTableType() {
            return _tableType;
        }

        public boolean isPrimaryTable() {
            return (getTableType() == PRIMARY_TABLE_TYPE);
        }

        public int getOtherIndexNumber() {
            return _otherIndexNumber;
        }

        public int getOtherTablePageNumber() {
            return _otherTablePageNumber;
        }

        public boolean isCascadeUpdates() {
            return _cascadeUpdates;
        }

        public boolean isCascadeDeletes() {
            return _cascadeDeletes;
        }

        @Override
        public String toString() {
            return CustomToStringStyle.builder(this).append("otherIndexNumber", _otherIndexNumber)
                    .append("otherTablePageNum", _otherTablePageNumber).append("isPrimaryTable", isPrimaryTable())
                    .append("isCascadeUpdates", isCascadeUpdates()).append("isCascadeDeletes", isCascadeDeletes())
                    .toString();
        }
    }
}