001: /*
002: * BaseAnalyzer.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.gui.completion;
013:
014: import java.awt.Toolkit;
015: import java.io.InputStream;
016: import java.util.ArrayList;
017: import java.util.Collections;
018: import java.util.List;
019: import java.util.Set;
020: import workbench.db.ColumnIdentifier;
021: import workbench.db.TableIdentifier;
022: import workbench.db.WbConnection;
023: import workbench.resource.ResourceMgr;
024: import workbench.util.StringUtil;
025: import workbench.util.TableAlias;
026: import workbench.util.TextlistReader;
027:
028: /**
029: * Base class to analyze a SQL statement to find out what kind and which
030: * objects should be included in the auto-completion window
031: * @author support@sql-workbench.net
032: */
033: public abstract class BaseAnalyzer {
034: public static final String QUALIFIER_DELIM = "\\?*=<>!/{}\\#%[]'\"(),:;";
035: public static final String WORD_DELIM = QUALIFIER_DELIM + "@";
036: public static final String SELECT_WORD_DELIM = WORD_DELIM + ".";
037:
038: protected static final int NO_CONTEXT = -1;
039:
040: // List the available tables
041: protected static final int CONTEXT_TABLE_LIST = 1;
042:
043: // List the columns for a table
044: protected static final int CONTEXT_COLUMN_LIST = 2;
045:
046: // List the tables that are available in the FROM list
047: protected static final int CONTEXT_FROM_LIST = 3;
048:
049: protected static final int CONTEXT_TABLE_OR_COLUMN_LIST = 4;
050:
051: // List keywords available at this point
052: protected static final int CONTEXT_KW_LIST = 5;
053:
054: // List parameters for WB commands
055: protected static final int CONTEXT_WB_PARAMS = 6;
056:
057: private final SelectAllMarker allColumnsMarker = new SelectAllMarker();
058: private String typeFilter;
059: protected String keywordFile;
060: protected WbConnection dbConnection;
061: protected final String sql;
062: protected final int cursorPos;
063: protected int context;
064: protected TableIdentifier tableForColumnList;
065: protected String schemaForTableList;
066: protected boolean addAllMarker = false;
067: protected List elements;
068: protected String title;
069: private boolean overwriteCurrentWord;
070: private boolean appendDot;
071: private String columnPrefix;
072: protected BaseAnalyzer parentAnalyzer;
073:
074: public BaseAnalyzer(WbConnection conn, String statement,
075: int cursorPos) {
076: this .dbConnection = conn;
077: this .sql = statement;
078: this .cursorPos = cursorPos;
079: }
080:
081: public char quoteCharForValue(String value) {
082: return 0;
083: }
084:
085: public boolean isWbParam() {
086: return false;
087: }
088:
089: public String getSelectionPrefix() {
090: return this .columnPrefix;
091: }
092:
093: /**
094: * Set a prefix for columns that are added.
095: * If this value is set, any column that the user
096: * selects, will be prefixed with this string (plus a dot)
097: * This is used when the FROM list in a SELECT statement
098: * contains more than one column
099: */
100: protected void setColumnPrefix(String prefix) {
101: this .columnPrefix = prefix;
102: }
103:
104: public String getColumnPrefix() {
105: return this .columnPrefix;
106: }
107:
108: protected void setOverwriteCurrentWord(boolean flag) {
109: this .overwriteCurrentWord = flag;
110: }
111:
112: protected void setAppendDot(boolean flag) {
113: this .appendDot = flag;
114: }
115:
116: public boolean appendDotToSelection() {
117: return this .appendDot;
118: }
119:
120: public boolean isKeywordList() {
121: return this .context == CONTEXT_KW_LIST;
122: }
123:
124: public boolean getOverwriteCurrentWord() {
125: return this .overwriteCurrentWord;
126: }
127:
128: public void retrieveObjects() {
129: // reset current state
130: this .elements = null;
131: this .context = NO_CONTEXT;
132: this .typeFilter = null;
133: this .keywordFile = null;
134:
135: checkOverwrite();
136: this .addAllMarker = false;
137:
138: // this should not be done in the constructor as the
139: // sub-classes might need to do important initializations durin initialization
140: // and before checkContext is called
141: this .checkContext();
142: this .buildResult();
143: }
144:
145: public String getTitle() {
146: return this .title;
147: }
148:
149: public List getData() {
150: return this .elements;
151: }
152:
153: protected abstract void checkContext();
154:
155: // For use with hierarchical Analyzers (so that a child
156: // analyzer can ask its parent directly for a table list
157: // This should be overwritten by any Analyzer supporting
158: // Sub-Selects
159: protected List<TableAlias> getTables() {
160: return Collections.emptyList();
161: }
162:
163: public void setParent(BaseAnalyzer analyzer) {
164: this .parentAnalyzer = analyzer;
165: }
166:
167: protected boolean between(int toTest, int start, int end) {
168: if (start == -1 || end == -1)
169: return false;
170: return (toTest > start && toTest < end);
171: }
172:
173: @SuppressWarnings("unchecked")
174: private void buildResult() {
175: if (context == CONTEXT_TABLE_OR_COLUMN_LIST
176: && tableForColumnList != null) {
177: if (!retrieveColumns()) {
178: retrieveTables();
179: }
180: } else if (context == CONTEXT_TABLE_LIST) {
181: if (this .elements == null)
182: retrieveTables();
183: } else if (context == CONTEXT_COLUMN_LIST) {
184: retrieveColumns();
185: } else if (context == CONTEXT_FROM_LIST) {
186: // element list has already been filled
187: this .title = ResourceMgr
188: .getString("LblCompletionListTables");
189: } else if (context == CONTEXT_KW_LIST) {
190: this .title = ResourceMgr.getString("LblCompletionListKws");
191: this .elements = readKeywords();
192: } else if (context == CONTEXT_WB_PARAMS) {
193: // element list has already been filled
194: if (isWbParam()) {
195: this .title = ResourceMgr
196: .getString("LblCompletionListParams");
197: } else {
198: this .title = ResourceMgr
199: .getString("LblCompletionListParmValues");
200: }
201: }
202: // else if (context == CONTEXT_INDEX_LIST)
203: // {
204: // this.title = ResourceMgr.getString("LblCompletionListIndex");
205: // IndexDefinition[] idx = this.dbConnection.getMetadata().getIndexList(this.schemaForTableList);
206: // this.elements = new ArrayList();
207: // for (int i = 0; i < idx.length; i++)
208: // {
209: // this.elements.add(idx[i].getName());
210: // }
211: // }
212: else {
213: // no proper sql found
214: this .elements = Collections.emptyList();
215: this .title = null;
216: Toolkit.getDefaultToolkit().beep();
217: }
218: if (this .addAllMarker && this .elements != null) {
219: this .elements.add(0, this .allColumnsMarker);
220: }
221: }
222:
223: @SuppressWarnings("unchecked")
224: private List readKeywords() {
225: if (this .keywordFile == null)
226: return null;
227: InputStream in = getClass().getResourceAsStream(keywordFile);
228: TextlistReader reader = new TextlistReader(in);
229: List result = new ArrayList(reader.getValues());
230: return result;
231: }
232:
233: @SuppressWarnings("unchecked")
234: private void retrieveTables() {
235: Set<TableIdentifier> tables = this .dbConnection
236: .getObjectCache().getTables(schemaForTableList,
237: typeFilter);
238: if (schemaForTableList == null) {
239: this .title = ResourceMgr
240: .getString("LblCompletionListTables");
241: } else {
242: this .title = schemaForTableList + ".*";
243: }
244: this .elements = new ArrayList(tables.size());
245: this .elements.addAll(tables);
246: }
247:
248: @SuppressWarnings("unchecked")
249: private boolean retrieveColumns() {
250: if (tableForColumnList == null)
251: return false;
252: String s = tableForColumnList.getSchema();
253: if (s == null) {
254: tableForColumnList.setSchema(this .dbConnection
255: .getMetadata().getCurrentSchema());
256: }
257: TableIdentifier toCheck = this .dbConnection.getMetadata()
258: .resolveSynonym(tableForColumnList);
259: List<ColumnIdentifier> cols = this .dbConnection
260: .getObjectCache().getColumns(toCheck);
261: if (cols != null && cols.size() > 0) {
262: this .title = tableForColumnList.getTableName() + ".*";
263:
264: this .elements = new ArrayList(cols.size() + 1);
265: this .elements.addAll(cols);
266: }
267: return (elements == null ? false : (elements.size() > 0));
268: }
269:
270: protected void setTableTypeFilter(String filter) {
271: this .typeFilter = filter;
272: }
273:
274: protected String getQualifierLeftOfCursor() {
275: int len = this .sql.length();
276: int start = this .cursorPos - 1;
277:
278: if (this .cursorPos > len) {
279: start = len - 1;
280: }
281:
282: char c = this .sql.charAt(start);
283: //if (Character.isWhitespace(c)) return null;
284:
285: // if no dot is present, then the current word is not a qualifier (e.g. a table name or alias)
286: if (c != '.')
287: return null;
288:
289: String word = StringUtil.getWordLeftOfCursor(this .sql, start,
290: QUALIFIER_DELIM + ".");
291: if (word == null)
292: return null;
293: int dotPos = word.indexOf('.');
294:
295: if (dotPos > -1) {
296: return word.substring(0, dotPos);
297: }
298: return word;
299: }
300:
301: public boolean isColumnList() {
302: return this .context == CONTEXT_COLUMN_LIST;
303: }
304:
305: protected String getCurrentWord() {
306: return StringUtil.getWordLeftOfCursor(this .sql, cursorPos,
307: WORD_DELIM);
308: }
309:
310: protected void checkOverwrite() {
311: String currentWord = getCurrentWord();
312: if (!StringUtil.isEmptyString(currentWord)) {
313: boolean keyWord = this .dbConnection.getMetadata()
314: .isKeyword(currentWord);
315: setOverwriteCurrentWord(!keyWord);
316: } else {
317: setOverwriteCurrentWord(false);
318: }
319: }
320:
321: // Used by JUnit tests
322: TableIdentifier getTableForColumnList() {
323: return tableForColumnList;
324: }
325: }
|