/*******************************************************************************
* Copyright (c) 2008 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.seam.ui.internal.reveng;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author Vitali
*
* This is specific efficient storage for names of catalogs, schemas, tables, columns and
* it's relations.
*/
public class TablesColumnsCollector {
// filter for lower part of long
public static final long LOWER_MASK = 0x00000000FFFFFFFFL;
// filter for upper part of long
public static final long UPPER_MASK = 0xFFFFFFFF00000000L;
// if true - collect full exact hierarchical structure - full structure info with redundancy
// ---------- (useful to get the tree structure easily)
// ---------- all relations one-to-many
// if false - result structure has no duplicate strings - structure without redundancy
// ---------- (useful for context help)
// ---------- all relations many-to-many
protected boolean fullStructure = false;
// sorted in alphabetic order list of catalogs
protected List catalogs;
// sorted in alphabetic order list of schemas
protected List schemas;
// sorted in alphabetic order list of tables
protected List tables;
// sorted in alphabetic order list of columns
protected List columns;
/**
* Index relation array-maps.
* Maps which are used to save relations between items in both directions.
* Here is a short description:
* left <-> right;
* left - could be catalogs, schemas, tables;
* right - could be schemas, tables, columns;
* xMap[i] & UPPER_MASK -> this is index in left array;
* xMap[i] & LOWER_MASK -> this is index in right array
**/
// catalogs <-> schemas index map
protected long[] csMap;
// schemas <-> tables index map
protected long[] stMap;
// tables <-> columns index map
protected long[] tcMap;
// temporary structures
protected List tempCATList;
protected List tempCSTList;
protected List tempTblList;
protected List tempClnList;
// simple protection of duplicates
protected String strCAT;
protected String strCST;
protected String strTbl;
protected String strCln;
public TablesColumnsCollector() {
}
/**
* @param fullStructure
* if true - collect full exact hierarchical structure - (useful to get the tree structure easily)
* if false - result structure has no duplicate strings - (useful for context help)
*/
public TablesColumnsCollector(boolean fullStructure) {
this.fullStructure = fullStructure;
}
/**
* initialize collection
*/
public void init() {
catalogs = null;
schemas = null;
tables = null;
columns = null;
csMap = null;
stMap = null;
tcMap = null;
tempCATList = new ArrayList();
tempCSTList = new ArrayList();
tempTblList = new ArrayList();
tempClnList = new ArrayList();
strCAT = "%%%%";
strCST = "%%%%";
strTbl = "%%%%";
strCln = "%%%%";
}
public String updateNullValue(String str) {
if (null == str) {
return "";
}
return str;
}
public void addCatalogName(String catalogName) {
catalogName = updateNullValue(catalogName);
String strCurr = "" + catalogName;
if (!strCAT.equalsIgnoreCase(strCurr)) {
tempCATList.add(strCurr);
strCAT = strCurr;
}
}
public void addSchemaName(String catalogName, String schemaName) {
catalogName = updateNullValue(catalogName);
schemaName = updateNullValue(schemaName);
addCatalogName(catalogName);
String strCurr = schemaName + "%" + catalogName;
if (!strCST.equalsIgnoreCase(strCurr)) {
tempCSTList.add(strCurr);
strCST = strCurr;
}
}
public void addTableName(String catalogName, String schemaName, String tableName) {
catalogName = updateNullValue(catalogName);
schemaName = updateNullValue(schemaName);
tableName = updateNullValue(tableName);
addSchemaName(catalogName, schemaName);
String strCurr = fullStructure ?
tableName + "%" + schemaName + "%" + catalogName :
tableName + "%" + schemaName;
if (!strTbl.equalsIgnoreCase(strCurr)) {
tempTblList.add(strCurr);
strTbl = strCurr;
}
}
public void addColumnName(String catalogName, String schemaName, String tableName, String columnName) {
catalogName = updateNullValue(catalogName);
schemaName = updateNullValue(schemaName);
tableName = updateNullValue(tableName);
columnName = updateNullValue(columnName);
addTableName(catalogName, schemaName, tableName);
String strCurr = fullStructure ?
columnName + "%" + tableName + "%" + schemaName + "%" + catalogName :
columnName + "%" + tableName;
if (!strCln.equalsIgnoreCase(strCurr)) {
tempClnList.add(strCurr);
strCln = strCurr;
}
}
protected void copyWithoutDuplicates(List arrTmp, List formList) {
String strPrev = null, strCurr = null;
for (int i = 0; i < arrTmp.size(); i++) {
strCurr = (String)arrTmp.get(i);
if (strPrev == strCurr) {
continue;
}
if (null != strPrev && strPrev.equalsIgnoreCase(strCurr)) {
continue;
}
strPrev = strCurr;
formList.add(strCurr);
}
}
protected void copyIncludeDuplicates(List arrTmp, List formList) {
for (int i = 0; i < arrTmp.size(); i++) {
String strCurr = (String)arrTmp.get(i);
formList.add(strCurr);
}
}
/**
* @param tempList - relation description list (with duplicates)
* @param formList
* @return
* size of tempList without duplicates
* in case of fullStructure: tempList.size() == formList.size() & tempList <-> formList by elements
*/
protected int adjustList(List tempList, List formList) {
ArrayList arrTmp = new ArrayList();
Collections.sort(tempList, String.CASE_INSENSITIVE_ORDER);
String strPrev = null, strCurr = null;
int i, j;
// remove duplicates
for (i = 0, j = 0; i < tempList.size(); i++) {
strCurr = (String)tempList.get(i);
if (strPrev == strCurr) {
continue;
}
if (null != strPrev && strPrev.equalsIgnoreCase(strCurr)) {
continue;
}
strPrev = strCurr;
String[] atc = strCurr.split("%", 2);
tempList.set(j++, strCurr);
arrTmp.add(fullStructure ? strCurr : atc[0]);
}
if (fullStructure) {
copyIncludeDuplicates(arrTmp, formList);
}
else {
Collections.sort(arrTmp, String.CASE_INSENSITIVE_ORDER);
copyWithoutDuplicates(arrTmp, formList);
}
arrTmp = null;
return j;
}
protected void adjustMap(long[] xMap, List tempList, List formList0, List formList1) {
// in case of fullStructure:
// tempList.size() == formList0.size() &
// tempList <-> formList0 by elements
int i, j = xMap.length;
for (i = 0; i < j; i++) {
String strCurr = (String)tempList.get(i);
String[] atc = strCurr.split("%", 2);
String tmp = atc[1];
int keyT = Collections.binarySearch(formList1, tmp, String.CASE_INSENSITIVE_ORDER);
if (keyT < 0 || keyT >= formList1.size() || !tmp.equalsIgnoreCase((String)formList1.get(keyT))) {
keyT = Integer.MAX_VALUE;
}
tmp = atc[0];
int keyC = fullStructure ?
i : Collections.binarySearch(formList0, tmp, String.CASE_INSENSITIVE_ORDER);
tmp = fullStructure ? strCurr : tmp;
if (keyC < 0 || keyC >= formList0.size() || !tmp.equalsIgnoreCase((String)formList0.get(keyC))) {
keyC = Integer.MAX_VALUE;
}
xMap[i] = ( ((long)keyT) << 32 ) | ((long)keyC);
}
Arrays.sort(xMap);
}
/**
* adjust collected results
*/
public void adjust() {
strCAT = null;
strCST = null;
strTbl = null;
strCln = null;
int size;
catalogs = new ArrayList();
if (null != tempCATList) {
Collections.sort(tempCATList, String.CASE_INSENSITIVE_ORDER);
copyWithoutDuplicates(tempCATList, catalogs);
tempCATList = null;
}
schemas = new ArrayList();
csMap = new long[0];
if (null != tempCSTList) {
size = adjustList(tempCSTList, schemas);
csMap = new long[size];
adjustMap(csMap, tempCSTList, schemas, catalogs);
tempCSTList = null;
}
tables = new ArrayList();
stMap = new long[0];
if (null != tempTblList) {
size = adjustList(tempTblList, tables);
stMap = new long[size];
adjustMap(stMap, tempTblList, tables, schemas);
tempTblList = null;
}
columns = new ArrayList();
tcMap = new long[0];
if (null != tempClnList) {
size = adjustList(tempClnList, columns);
tcMap = new long[size];
adjustMap(tcMap, tempClnList, columns, tables);
tempClnList = null;
}
}
public static final class Bounds {
public int nL = 0;
public int nH = 0;
}
/**
* binaryBoundsSearch is a method to find lower and upper bounds
* in sorted array of strings for some string prefix.
* @param list - search array of strings (should be sorted in ascending case insensitive order);
* @param prefix - matching prefix;
* @param bounds - result interval in the list - all items in interval start from prefix.
* bounds.nL <= interval < bounds.nH
*/
public static final Bounds binaryBoundsSearch(List list, String prefix) {
Bounds bounds = new Bounds();
if (list.isEmpty()) {
bounds.nL = bounds.nH = 0;
return bounds;
}
prefix = prefix.toUpperCase();
int low = 0;
int high = list.size() - 1;
int mid = (low + high) >> 1;
// looking for mid - some element in the interval,
// which is matched for criteria (has the prefix)
while (low <= high) {
mid = (low + high) >> 1;
String midVal = ((String)list.get(mid)).toUpperCase();
int cmp = midVal.compareToIgnoreCase(prefix);
cmp = midVal.startsWith(prefix) ? 0 : cmp;
if (cmp < 0) {
low = mid + 1;
}
else if (cmp > 0) {
high = mid - 1;
}
else {
break;
}
}
int low2 = mid;
int high2 = mid;
// looking for low bound - indicates minimal index of element in the interval,
// which is matched for criteria (has the prefix)
while (low <= high2) {
mid = (low + high2) >> 1;
String midVal = ((String)list.get(mid)).toUpperCase();
int cmp = midVal.compareToIgnoreCase(prefix);
cmp = midVal.startsWith(prefix) ? 0 : cmp;
if (cmp < 0) {
low = mid + 1;
}
else if (cmp >= 0) {
high2 = mid - 1;
}
}
// looking for high bound - indicates maximal index of element in the interval,
// which is matched for criteria (has the prefix)
while (low2 <= high) {
mid = (low2 + high) >> 1;
String midVal = ((String)list.get(mid)).toUpperCase();
int cmp = midVal.compareToIgnoreCase(prefix);
cmp = midVal.startsWith(prefix) ? 0 : cmp;
if (cmp <= 0) {
low2 = mid + 1;
}
else if (cmp > 0) {
high = mid - 1;
}
}
// adjust low bound
if (low < list.size()) {
while (0 <= low && ((String)list.get(low)).toUpperCase().startsWith(prefix)) {
low--;
}
low++;
}
if (high < low) {
high = low;
}
// adjust high bound
while (high < list.size() && ((String)list.get(high)).toUpperCase().startsWith(prefix)) {
high++;
}
bounds.nL = low;
bounds.nH = high;
return bounds;
}
/**
* Clear string from redundant information.
* @param res
*/
protected String correctName(String input) {
if (!fullStructure) {
return input;
}
String[] atc = input.split("%", 2);
return atc[0];
}
/**
* Clear list of string from redundant information.
* @param res
*/
protected void correctNames(List res) {
if (!fullStructure) {
return;
}
for (int i = 0; i < res.size(); i++) {
String strCurr = (String)res.get(i);
String[] atc = strCurr.split("%", 2);
res.set(i, atc[0]);
}
}
protected List getXNames(List xArray, String prefix) {
List res = new ArrayList();
if (null == xArray) {
return res;
}
if (null != prefix) {
prefix = prefix.toUpperCase();
}
if (null == prefix) {
res.addAll(xArray);
}
else {
Bounds bounds = binaryBoundsSearch(xArray, prefix);
for (; bounds.nL < bounds.nH; bounds.nL++) {
res.add(xArray.get(bounds.nL));
}
}
return res;
}
public List getMatchingCatalogNames(String prefix) {
List res = getXNames(catalogs, prefix);
correctNames(res);
return res;
}
public List getMatchingSchemaNames(String prefix) {
List res = getXNames(schemas, prefix);
correctNames(res);
return res;
}
public List getMatchingTableNames(String prefix) {
List res = getXNames(tables, prefix);
correctNames(res);
return res;
}
public List getMatchingColumnNames(String prefix) {
List res = getXNames(columns, prefix);
correctNames(res);
return res;
}
/**
* return list of matching strings.
* Here is a short description what is happen here.
* the same schema with left <-> right which was above:
* xArray1 - right;
* xArray2 - left;
* xxMap - <-> index relation array-map;
* name - exactly string in xArray2;
* prefix - prefix string in xArray1.
* Matching criteria:
* list of strings from xArray1 which start from prefix and which is in relation with
* item from xArray2 which is exactly match the name.
**/
protected List getXNames(List xArray1, long[] xxMap, List xArray2, String name, String prefix) {
List res = new ArrayList();
if (null == xArray1) {
return res;
}
if (null != name) {
name = name.toUpperCase();
}
if (null != prefix) {
prefix = prefix.toUpperCase();
}
if (null == name && null == prefix) {
res.addAll(xArray1);
}
else if (null == name) {
Bounds bounds = binaryBoundsSearch(xArray1, prefix);
for (; bounds.nL < bounds.nH; bounds.nL++) {
res.add(xArray1.get(bounds.nL));
}
}
else {
if (null == xArray2 || null == xxMap) {
return res;
}
Bounds bounds = binaryBoundsSearch(xArray2, name);
for (; bounds.nL < bounds.nH; bounds.nL++) {
int keyT = bounds.nL;
String itemT = (String)xArray2.get(keyT);
if (fullStructure) {
if (!itemT.toUpperCase().startsWith(name)) {
continue;
}
name = correctName(name);
itemT = correctName(itemT);
}
if (!name.equalsIgnoreCase(itemT)) {
continue;
}
long keyTlong = ((long)keyT) << 32;
int i = Arrays.binarySearch(xxMap, keyTlong);
if (i < 0) {
i = - i - 1;
}
if (null == prefix) {
for ( ; i < xxMap.length; i++) {
if (keyTlong != (xxMap[i] & UPPER_MASK)) {
break;
}
int keyC = (int)(xxMap[i] & LOWER_MASK);
if (0 <= keyC && keyC < xArray1.size()) {
res.add(xArray1.get(keyC));
}
}
}
else {
for ( ; i < xxMap.length; i++) {
if (keyTlong != (xxMap[i] & UPPER_MASK)) {
break;
}
int keyC = (int)(xxMap[i] & LOWER_MASK);
if (0 <= keyC && keyC < xArray1.size() && ((String)xArray1.get(keyC)).toUpperCase().startsWith(prefix)) {
res.add(xArray1.get(keyC));
}
}
}
}
}
return res;
}
public List getCatalogNames() {
List res = new ArrayList();
if (null != catalogs) {
res.addAll(catalogs);
}
return res;
}
public List getMatchingSchemaNames(String catalogName, String prefix) {
List res = getXNames(schemas, csMap, catalogs, catalogName, prefix);
correctNames(res);
return res;
}
public List getMatchingTablesNames(String schemaName, String prefix) {
List res = getXNames(tables, stMap, schemas, schemaName, prefix);
correctNames(res);
return res;
}
public List getMatchingColumnNames(String tableName, String prefix) {
List res = getXNames(columns, tcMap, tables, tableName, prefix);
correctNames(res);
return res;
}
public List getMatchingTablesNames(String catalogName, String schemaName, String prefix) {
catalogName = updateNullValue(catalogName);
schemaName = updateNullValue(schemaName);
schemaName = "" + schemaName + "%" + catalogName;
List res = getXNames(tables, stMap, schemas, schemaName, prefix);
correctNames(res);
return res;
}
public List getMatchingColumnNames(String schemaName, String tableName, String prefix) {
schemaName = updateNullValue(schemaName);
tableName = updateNullValue(tableName);
tableName = "" + tableName + "%" + schemaName;
List res = getXNames(columns, tcMap, tables, tableName, prefix);
correctNames(res);
return res;
}
public List getMatchingColumnNames(String catalogName, String schemaName, String tableName, String prefix) {
catalogName = updateNullValue(catalogName);
schemaName = updateNullValue(schemaName);
tableName = updateNullValue(tableName);
tableName = "" + tableName + "%" + schemaName + "%" + catalogName;
List res = getXNames(columns, tcMap, tables, tableName, prefix);
correctNames(res);
return res;
}
}