/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.core.index;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.teiid.core.designer.TeiidDesignerException;
import org.teiid.core.designer.util.CharOperation;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.core.designer.util.CoreStringUtil;
import org.teiid.core.designer.util.FileUtils;
import org.teiid.designer.metadata.runtime.impl.RecordFactory;
/**
* IndexUtil
*
* @since 8.0
*/
public class SimpleIndexUtil {
public static interface ProgressMonitor {
public void beginTask(String name, int totalWork);
public void worked(int work);
}
//############################################################################################################################
//# Constants #
//############################################################################################################################
public static final boolean CASE_SENSITIVE_INDEX_FILE_NAMES = false;
//############################################################################################################################
//# Indexing Methods #
//############################################################################################################################
public static String getIndexFilePath(final String indexDirectoryPath, final String indexFileName) {
StringBuffer sb = new StringBuffer(100);
sb.append(indexDirectoryPath);
if (!indexDirectoryPath.endsWith(File.separator)) {
sb.append(File.separator);
}
sb.append(indexFileName);
return sb.toString();
}
//############################################################################################################################
//# Methods to query indexes #
//############################################################################################################################
/**
* Return all index file records that match the specified record pattern.
* The pattern can be constructed from any combination of characters
* including the multiple character wildcard '*' and single character
* wildcard '?'. The field delimiter is used to tokenize both the pattern
* and the index record so that individual fields can be matched. The method
* assumes that the first occurrence of the delimiter in the record alligns
* with the first occurrence in the pattern. Any wildcard characters in the
* pattern cannot represent a delimiter character.
* @param indexes the array of MtkIndex instances to query
* @param pattern
* @param fieldDelimiter
* @return results
* @throws TeiidDesignerException
*/
public static IEntryResult[] queryIndex(final Index[] indexes, final char[] pattern, final char fieldDelimiter) throws TeiidDesignerException {
final boolean isCaseSensitive = false;
final List<IEntryResult> queryResult = new ArrayList<IEntryResult>();
try {
for (int i = 0; i < indexes.length; i++) {
// Search for index records matching the specified pattern
IEntryResult[] partialResults = indexes[i].queryEntriesMatching(pattern,isCaseSensitive);
// If any of these IEntryResults represent an index record that is continued
// across multiple entries within the index file then we must query for those
// records and build the complete IEntryResult
if (partialResults != null) {
partialResults = addContinuationRecords(indexes[i], partialResults);
}
if (partialResults != null) {
queryResult.addAll(Arrays.asList(partialResults));
}
}
} catch(IOException e) {
throw new TeiidDesignerException(e);
}
// Remove any results that do not match after tokenizing the record
for (int i = 0, n = queryResult.size(); i < n; i++) {
IEntryResult record = queryResult.get(i);
if ( record == null || !entryMatches(record.getWord(),pattern,fieldDelimiter) ) {
queryResult.remove(record);
}
}
return queryResult.toArray(new IEntryResult[queryResult.size()]);
}
/**
* Return true if the record matches the pattern after being tokenized using
* the specified delimiter. The method assumes that the first occurrence of
* the delimiter in the record alligns with the first occurrence in the pattern.
* Any wildcard characters in the pattern cannot represent a delimiter character.
* @param record
* @param pattern
* @param fieldDelimiter
* @return
*/
private static boolean entryMatches(final char[] record, final char[] pattern, final char fieldDelimiter) {
final boolean isCaseSensitive = false;
if (record == null)
return false; // null record cannot match
if (pattern == null)
return true; // null pattern is equivalent to '*'
String delimiter = String.valueOf(fieldDelimiter);
List recordTokens = CoreStringUtil.split(new String(record),delimiter);
List patternTokens = CoreStringUtil.split(new String(pattern),delimiter);
if (patternTokens.size() > recordTokens.size()) {
return false;
}
for (int i = 0, n = patternTokens.size(); i < n; i++) {
char[] patternToken = ((String)patternTokens.get(i)).toCharArray();
char[] recordToken = ((String)recordTokens.get(i)).toCharArray();
if (!CharOperation.match(patternToken,recordToken,isCaseSensitive)) {
return false;
}
}
return true;
}
/**
* Return all index file records that match the specified record prefix
* or pattern. The pattern can be constructed from any combination of characters
* including the multiple character wildcard '*' and single character
* wildcard '?'. The prefix may be constructed from any combination of
* characters excluding the wildcard characters. The prefix specifies a fixed
* number of characters that the index record must start with.
* @param indexes the array of MtkIndex instances to query
* @param pattern
* @return results
* @throws TeiidDesignerException
*/
public static IEntryResult[] queryIndex(final Index[] indexes, final char[] pattern, final boolean isPrefix, final boolean returnFirstMatch) throws TeiidDesignerException {
return queryIndex(null, indexes, pattern, isPrefix, true, returnFirstMatch);
}
/**
* Return all index file records that match the specified record prefix
* or pattern. The pattern can be constructed from any combination of characters
* including the multiple character wildcard '*' and single character
* wildcard '?'. The prefix may be constructed from any combination of
* characters excluding the wildcard characters. The prefix specifies a fixed
* number of characters that the index record must start with.
* @param monitor an optional ProgressMonitor
* @param indexes the array of MtkIndex instances to query
* @param pattern
* @return results
* @throws TeiidDesignerException
*/
public static IEntryResult[] queryIndex(ProgressMonitor monitor, final Index[] indexes, final char[] pattern, final boolean isPrefix, final boolean returnFirstMatch) throws TeiidDesignerException {
return queryIndex(monitor, indexes, pattern, isPrefix, true, returnFirstMatch);
}
/**
* Return all index file records that match the specified record prefix
* or pattern. The pattern can be constructed from any combination of characters
* including the multiple character wildcard '*' and single character
* wildcard '?'. The prefix may be constructed from any combination of
* characters excluding the wildcard characters. The prefix specifies a fixed
* number of characters that the index record must start with.
* @param monitor an optional ProgressMonitor
* @param indexes the array of MtkIndex instances to query
* @param pattern
* @return results
* @throws TeiidDesignerException
*/
public static IEntryResult[] queryIndex(ProgressMonitor monitor, final Index[] indexes, final char[] pattern, final boolean isPrefix, final boolean isCaseSensitive, final boolean returnFirstMatch) throws TeiidDesignerException {
final List<IEntryResult> queryResult = new ArrayList<IEntryResult>();
if ( monitor != null ) {
monitor.beginTask( null, indexes.length );
}
try {
for (int i = 0; i < indexes.length; i++) {
if ( monitor != null ) {
monitor.worked( 1 );
}
IEntryResult[] partialResults = null;
if(isPrefix) {
// Query based on prefix. This uses a fast binary search
// based on matching the first n characters in the index record.
// The index files contain records that are sorted alphabetically
// by fullname such that the search algorithm can quickly determine
// which index block(s) contain the matching prefixes.
partialResults = indexes[i].queryEntries(pattern, isCaseSensitive);
} else {
// Search for index records matching the specified pattern
partialResults = indexes[i].queryEntriesMatching(pattern, isCaseSensitive);
}
// If any of these IEntryResults represent an index record that is continued
// across multiple entries within the index file then we must query for those
// records and build the complete IEntryResult
if (partialResults != null) {
partialResults = addContinuationRecords(indexes[i], partialResults);
}
// Process these results against the specified pattern and return
// only the subset entries that match both criteria
if (partialResults != null) {
for (int j = 0; j < partialResults.length; j++) {
// filter out any continuation records, they should already appended
// to index record thet is continued
IEntryResult result = partialResults[j];
if(result != null && result.getWord()[0] != IndexConstants.RECORD_TYPE.RECORD_CONTINUATION) {
queryResult.add(partialResults[j]);
}
}
}
if (returnFirstMatch && queryResult.size() > 0) {
break;
}
}
} catch(IOException e) {
throw new TeiidDesignerException(e);
}
return queryResult.toArray(new IEntryResult[queryResult.size()]);
}
/**
* Return all index file records that match the specified record prefix
* or pattern. The pattern can be constructed from any combination of characters
* including the multiple character wildcard '*' and single character
* wildcard '?'. The prefix may be constructed from any combination of
* characters excluding the wildcard characters. The prefix specifies a fixed
* number of characters that the index record must start with.
* @param monitor an optional ProgressMonitor
* @param indexes the array of MtkIndex instances to query
* @param pattern
* @return results
* @throws TeiidDesignerException
*/
public static IEntryResult[] queryIndex(ProgressMonitor monitor, final Index[] indexes, final Collection patterns, final boolean isPrefix, final boolean isCaseSensitive, final boolean returnFirstMatch) throws TeiidDesignerException {
final List<IEntryResult> queryResult = new ArrayList<IEntryResult>();
if ( monitor != null ) {
monitor.beginTask( null, indexes.length );
}
// index file input
BlocksIndexInput input = null;
try {
for (int i = 0; i < indexes.length; i++) {
if ( monitor != null ) {
monitor.worked( 1 );
}
// initialize input for the index file
input = new BlocksIndexInput(indexes[i].getIndexFile());
IEntryResult[] partialResults = null;
for(final Iterator patternIter = patterns.iterator(); patternIter.hasNext();) {
char[] pattern = ((String) patternIter.next()).toCharArray();
if(isPrefix) {
// Query based on prefix. This uses a fast binary search
// based on matching the first n characters in the index record.
// The index files contain records that are sorted alphabetically
// by fullname such that the search algorithm can quickly determine
// which index block(s) contain the matching prefixes.
partialResults = input.queryEntriesPrefixedBy(pattern, isCaseSensitive);
} else {
// Search for index records matching the specified pattern
partialResults = input.queryEntriesMatching(pattern, isCaseSensitive);
}
// If any of these IEntryResults represent an index record that is continued
// across multiple entries within the index file then we must query for those
// records and build the complete IEntryResult
if (partialResults != null) {
partialResults = addContinuationRecords(indexes[i], partialResults);
}
// Process these results against the specified pattern and return
// only the subset entries that match both criteria
if (partialResults != null) {
for (int j = 0; j < partialResults.length; j++) {
IEntryResult record = partialResults[j];
if(record != null) {
char[] recordWord = partialResults[j].getWord();
// filter out any continuation records, they should already appended
// to index record thet is continued
if(recordWord[0] != IndexConstants.RECORD_TYPE.RECORD_CONTINUATION) {
if (!isPrefix) {
// filter results that do not match after tokenizing the record
if(entryMatches(recordWord,pattern,IndexConstants.RECORD_STRING.RECORD_DELIMITER) ) {
queryResult.add(partialResults[j]);
}
} else {
queryResult.add(partialResults[j]);
}
}
}
}
}
if (returnFirstMatch && queryResult.size() > 0) {
break;
}
// close file input
input.close();
}
}
} catch(IOException e) {
throw new TeiidDesignerException(e);
} finally {
// close file input
try {
if(input != null) {
input.close();
}
} catch(IOException io) {}
}
return queryResult.toArray(new IEntryResult[queryResult.size()]);
}
private static IEntryResult[] addContinuationRecords(final Index index, final IEntryResult[] partialResults) throws IOException {
final int blockSize = RecordFactory.INDEX_RECORD_BLOCK_SIZE;
IEntryResult[] results = partialResults;
for (int i = 0; i < results.length; i++) {
IEntryResult partialResult = results[i];
char[] word = partialResult.getWord();
// If this IEntryResult is not continued on another record then skip to the next result
if (word.length < blockSize || word[blockSize-1] != IndexConstants.RECORD_TYPE.RECORD_CONTINUATION) {
continue;
}
// Extract the UUID from the IEntryResult to use when creating the prefix string
String objectID = RecordFactory.extractUUIDString(partialResult);
String patternStr = "" //$NON-NLS-1$
+ IndexConstants.RECORD_TYPE.RECORD_CONTINUATION
+ word[0]
+ IndexConstants.RECORD_STRING.RECORD_DELIMITER
+ objectID
+ IndexConstants.RECORD_STRING.RECORD_DELIMITER;
// Query the index file for any continuation records
IEntryResult[] continuationResults = index.queryEntries(patternStr.toCharArray(), true);
// If found the continued records then join to the original result and stop searching
if (continuationResults != null && continuationResults.length > 0) {
results[i] = RecordFactory.joinEntryResults(partialResult, continuationResults, blockSize);
}
}
return results;
}
//############################################################################################################################
//# Helper methods #
//############################################################################################################################
/**
* Return true if the specifed index file exists on the file system
* otherwise return false.
*/
public static boolean indexFileExists(final String indexFilePath) {
if (indexFilePath == null) {
return false;
}
String filePath = indexFilePath.replace(FileUtils.SEPARATOR, File.separatorChar);
final File indexFile = new File(filePath);
return indexFileExists(indexFile);
}
/**
* Return true if the specifed index file exists on the file system
* otherwise return false.
*/
public static boolean indexFileExists(final File indexFile) {
if ( !indexFile.isDirectory() && indexFile.exists() ) {
return isIndexFile(indexFile.getName());
}
return false;
}
/**
* Return true if the specifed index file represents a known index file
* on the file system otherwise return false.
*/
public static boolean isModelIndex(final String indexFileName) {
if (!isIndexFile(indexFileName)) {
return false;
}
return !IndexConstants.INDEX_NAME.isKnownIndex(indexFileName);
}
/**
* Return true if the specifed index file represents a index file
* on the file system otherwise return false.
*/
public static boolean isIndexFile(final String indexFileName) {
if (!CoreStringUtil.isEmpty(indexFileName)) {
String extension = FileUtils.getExtension(indexFileName);
if(extension != null ) {
if( extension.equals(IndexConstants.INDEX_EXT) || extension.equals(IndexConstants.SEARCH_INDEX_EXT)) {
return true;
}
}
}
return false;
}
/**
* Return true if the specifed index file represents a index file
* on the file system otherwise return false.
*/
public static boolean isIndexFile(final File indexFile) {
if (indexFile != null && indexFile.isFile()) {
return isIndexFile(indexFile.getName());
}
return false;
}
/**
* Return an array of indexes given a indexName.
* @param indexName The shortName of the index file
* @param selector The indexSelector to lookup indexes
* @return An array of indexes, may be duplicates depending on index selector.
* @throws TeiidDesignerException If there is an error looking up indexes
* @since 4.2
*/
public static Index[] getIndexes(final String indexName, final IndexSelector selector) throws TeiidDesignerException {
CoreArgCheck.isNotEmpty(indexName);
// The the index file name for the record type
try {
final Index[] indexes = selector.getIndexes();
final List<Index> tmp = new ArrayList<Index>(indexes.length);
for (int i = 0; i < indexes.length; i++) {
Index coreIndex = indexes[i];
if(coreIndex != null) {
final String indexFileName = indexes[i].getIndexFile().getName();
if(indexName.equals(indexFileName)) {
tmp.add(coreIndex);
}
}
}
return tmp.toArray(new Index[tmp.size()]);
} catch(IOException e) {
throw new TeiidDesignerException(e);
}
}
/**
* Return the name of the index file to use for the specified record type, applies only for sever and vdb
* index files.
* @param recordType
* @return
*/
public static String getIndexFileNameForRecordType(final char recordType) {
switch (recordType) {
case IndexConstants.RECORD_TYPE.COLUMN: return IndexConstants.INDEX_NAME.COLUMNS_INDEX;
case IndexConstants.RECORD_TYPE.TABLE: return IndexConstants.INDEX_NAME.TABLES_INDEX;
case IndexConstants.RECORD_TYPE.MODEL: return IndexConstants.INDEX_NAME.MODELS_INDEX;
case IndexConstants.RECORD_TYPE.CALLABLE:
case IndexConstants.RECORD_TYPE.CALLABLE_PARAMETER:
case IndexConstants.RECORD_TYPE.RESULT_SET: return IndexConstants.INDEX_NAME.PROCEDURES_INDEX;
case IndexConstants.RECORD_TYPE.INDEX:
case IndexConstants.RECORD_TYPE.ACCESS_PATTERN:
case IndexConstants.RECORD_TYPE.PRIMARY_KEY:
case IndexConstants.RECORD_TYPE.FOREIGN_KEY:
case IndexConstants.RECORD_TYPE.UNIQUE_KEY: return IndexConstants.INDEX_NAME.KEYS_INDEX;
case IndexConstants.RECORD_TYPE.SELECT_TRANSFORM: return IndexConstants.INDEX_NAME.SELECT_TRANSFORM_INDEX;
case IndexConstants.RECORD_TYPE.INSERT_TRANSFORM: return IndexConstants.INDEX_NAME.INSERT_TRANSFORM_INDEX;
case IndexConstants.RECORD_TYPE.UPDATE_TRANSFORM: return IndexConstants.INDEX_NAME.UPDATE_TRANSFORM_INDEX;
case IndexConstants.RECORD_TYPE.DELETE_TRANSFORM: return IndexConstants.INDEX_NAME.DELETE_TRANSFORM_INDEX;
case IndexConstants.RECORD_TYPE.PROC_TRANSFORM: return IndexConstants.INDEX_NAME.PROC_TRANSFORM_INDEX;
case IndexConstants.RECORD_TYPE.MAPPING_TRANSFORM: return IndexConstants.INDEX_NAME.MAPPING_TRANSFORM_INDEX;
case IndexConstants.RECORD_TYPE.DATATYPE: return IndexConstants.INDEX_NAME.DATATYPES_INDEX;
//case IndexConstants.RECORD_TYPE.DATATYPE_ELEMENT:
//case IndexConstants.RECORD_TYPE.DATATYPE_FACET:
case IndexConstants.RECORD_TYPE.VDB_ARCHIVE: return IndexConstants.INDEX_NAME.VDBS_INDEX;
case IndexConstants.RECORD_TYPE.ANNOTATION: return IndexConstants.INDEX_NAME.ANNOTATION_INDEX;
case IndexConstants.RECORD_TYPE.PROPERTY: return IndexConstants.INDEX_NAME.PROPERTIES_INDEX;
//case IndexConstants.RECORD_TYPE.JOIN_DESCRIPTOR: return null;
case IndexConstants.RECORD_TYPE.FILE: return IndexConstants.INDEX_NAME.FILES_INDEX;
}
throw new IllegalArgumentException("Unkown record type " + recordType);
}
/**
* Return the name of the index file to use for the specified record type, applies only for sever and vdb
* index files.
* @param recordType
* @return
*/
public static String getRecordTypeForIndexFileName(final String indexName) {
char recordType;
if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.COLUMNS_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.COLUMN;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.TABLES_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.TABLE;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.MODELS_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.MODEL;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.DATATYPES_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.DATATYPE;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.VDBS_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.VDB_ARCHIVE;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.ANNOTATION_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.ANNOTATION;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.PROPERTIES_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.PROPERTY;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.SELECT_TRANSFORM_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.SELECT_TRANSFORM;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.INSERT_TRANSFORM_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.INSERT_TRANSFORM;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.UPDATE_TRANSFORM_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.UPDATE_TRANSFORM;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.DELETE_TRANSFORM_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.DELETE_TRANSFORM;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.PROC_TRANSFORM_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.PROC_TRANSFORM;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.MAPPING_TRANSFORM_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.MAPPING_TRANSFORM;
} else if(indexName.equalsIgnoreCase(IndexConstants.INDEX_NAME.FILES_INDEX)) {
recordType = IndexConstants.RECORD_TYPE.FILE;
} else {
return null;
}
return CoreStringUtil.Constants.EMPTY_STRING + recordType;
}
/**
* Return the prefix match string that could be used to exactly match a fully
* qualified entity name in an index record. All index records
* contain a header portion of the form:
* recordType|name|
* @param name The fully qualified name for which the prefix match
* string is to be constructed.
* @return The pattern match string of the form: recordType|name|
*/
public static String getPrefixPattern(final char recordType, final String uuid) {
// construct the pattern string
String patternStr = "" //$NON-NLS-1$
+ recordType
+ IndexConstants.RECORD_STRING.RECORD_DELIMITER;
if(uuid != null && !uuid.equals(CoreStringUtil.Constants.EMPTY_STRING)) {
patternStr = patternStr + uuid.trim() + IndexConstants.RECORD_STRING.RECORD_DELIMITER;
}
return patternStr;
}
}