Author: max...@jb... Date: 2006-05-19 10:44:38 -0400 (Fri, 19 May 2006) New Revision: 9938 Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/AntlrSimpleHQLLexer.java trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/CompletionHelper.java trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/ConfigurationCompletion.java trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/EntityNameReference.java trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLAnalyzer.java trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLCodeAssist.java trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLCompletionProposal.java trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/IHQLCodeAssist.java trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/IHQLCompletionRequestor.java trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/SimpleHQLLexer.java trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/SimpleLexerException.java trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/ trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/CompletionHelperTest.java trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/HQLCompletionProposalComparator.java trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/HqlAnalyzerTest.java trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/Model.java trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/ModelCompletionTest.java trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/Product.hbm.xml trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/Product.java trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/ProductOwner.java trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/ProductOwnerAddress.hbm.xml trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/ProductOwnerAddress.java trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/Store.hbm.xml trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/Store.java trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/StoreCity.hbm.xml trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/StoreCity.java Log: hql code completion api Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/AntlrSimpleHQLLexer.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/AntlrSimpleHQLLexer.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/AntlrSimpleHQLLexer.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,59 @@ +package org.hibernate.tool.ide.completion; + +import java.io.CharArrayReader; + +import org.hibernate.hql.antlr.HqlBaseLexer; + +import antlr.Token; +import antlr.TokenStreamException; + +/** + * A lexer implemented on top of the Antlr grammer implemented in core. + * + * @author Max Rydahl Andersen + * + */ +public class AntlrSimpleHQLLexer implements SimpleHQLLexer { + + private HqlBaseLexer lexer; + private Token token; + + public AntlrSimpleHQLLexer(char[] cs, int length) { + lexer = new HqlBaseLexer(new CharArrayReader(cs, 0, length)) { + public void newline() { + //super.newline(); + } + + public int getColumn() { + return super.getColumn()-1; + } + + + }; + } + + public int getTokenLength() { + if(token.getText()==null) { + return 0; + } + return token.getText().length(); + } + + public int getTokenOffset() { + return token.getColumn()-1; + } + + public int nextTokenId() { + try { + token = lexer.nextToken(); + if(token==null) { + System.out.println(token); + } + } + catch (TokenStreamException e) { + throw new SimpleLexerException(e); + } + return token.getType(); + } + +} Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/CompletionHelper.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/CompletionHelper.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/CompletionHelper.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,74 @@ +package org.hibernate.tool.ide.completion; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Helper class for completion. + * Package private, not to be used externally. + * + * @author leon, max...@jb... + */ +class CompletionHelper { + + private CompletionHelper() { + } + + public static String getCanonicalPath(List qts, String name) { + Map alias2Type = new HashMap(); + for (Iterator iter = qts.iterator(); iter.hasNext();) { + EntityNameReference qt = (EntityNameReference) iter.next(); + alias2Type.put(qt.getAlias(), qt.getEntityName()); + } + if (qts.size() == 1) { + EntityNameReference visible = (EntityNameReference) qts.get(0); + String alias = visible.getAlias(); + if (name.equals(alias)) { + return visible.getEntityName(); + } else if (alias == null || alias.length() == 0 || alias.equals(visible.getEntityName())) { + return visible.getEntityName() + "/" + name; + } + } + return getCanonicalPath(new HashSet(), alias2Type, name); + } + + + private static String getCanonicalPath(Set resolved, Map alias2Type, String name) { + if (resolved.contains(name)) { + // To prevent a stack overflow + return name; + } + resolved.add(name); + String type = (String) alias2Type.get(name); + if (type != null) { + return name.equals(type) ? name : getCanonicalPath(resolved, alias2Type, type); + } + int idx = name.lastIndexOf('.'); + if (idx == -1) { + return type != null ? type : name; + } + String baseName = name.substring(0, idx); + String prop = name.substring(idx + 1); + if (isAliasNown(alias2Type, baseName)) { + return getCanonicalPath(resolved, alias2Type, baseName) + "/" + prop; + } else { + return name; + } + } + + private static boolean isAliasNown(Map alias2Type, String alias) { + if (alias2Type.containsKey(alias)) { + return true; + } + int idx = alias.lastIndexOf('.'); + if (idx == -1) { + return false; + } + return isAliasNown(alias2Type, alias.substring(0, idx)); + } + +} Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/ConfigurationCompletion.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/ConfigurationCompletion.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/ConfigurationCompletion.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,341 @@ +package org.hibernate.tool.ide.completion; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import org.hibernate.cfg.Configuration; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.Component; +import org.hibernate.mapping.OneToMany; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.ToOne; +import org.hibernate.mapping.Value; +import org.hibernate.tool.hbm2x.Cfg2JavaTool; +import org.hibernate.tool.hbm2x.pojo.EntityPOJOClass; + +/** + * Completion based on a Configuration. + * package protected for now - not meant to be used externally. + * + * @author Max Rydahl Andersen + * + */ +class ConfigurationCompletion { + + private final Configuration cfg; + + public ConfigurationCompletion(Configuration cfg) { + this.cfg = cfg; + + } + + public void getMatchingImports(String prefix , IHQLCompletionRequestor collector) { + getMatchingImports( prefix, prefix.length() , collector ); + } + + public void getMatchingImports(String prefix, int cursorPosition, IHQLCompletionRequestor collector) { + Iterator iterator = cfg.getImports().entrySet().iterator(); + while ( iterator.hasNext() ) { + Map.Entry entry = (Entry) iterator.next(); + String entityImport = (String) entry.getKey(); + String entityName = (String) entry.getValue(); + + if(entityImport.startsWith(prefix)) { + String remaining = entityImport.substring( prefix.length() ); + HQLCompletionProposal proposal = new HQLCompletionProposal( + HQLCompletionProposal.ENTITY_NAME, + cursorPosition); + proposal.setCompletion( remaining ); + proposal.setSimpleName( entityImport ); + proposal.setReplaceStart( cursorPosition ); + proposal.setReplaceEnd( cursorPosition+0 ); // we don't replace anything here + + proposal.setShortEntityName( entityImport ); + proposal.setEntityName( entityName ); + collector.accept(proposal); + + } + } + } + + public void getMatchingKeywords(String prefix, int cursorPosition, IHQLCompletionRequestor collector) { + findMatchingWords( cursorPosition, prefix, HQLAnalyzer.getHQLKeywords(), HQLCompletionProposal.KEYWORD, collector); + } + + public void getMatchingFunctions(String prefix, int cursorPosition, IHQLCompletionRequestor collector) { + findMatchingWords( cursorPosition, prefix, HQLAnalyzer.getHQLFunctionNames(), HQLCompletionProposal.FUNCTION, collector); + } + + public void getMatchingProperties(String path, String prefix, IHQLCompletionRequestor hcc) { + getMatchingProperties( path, prefix, prefix.length(), hcc ); + } + + public void getMatchingProperties(String path, String prefix, int cursorPosition, IHQLCompletionRequestor hcc) { + int idx = path.indexOf('/'); + if (idx == -1) { // root name + PersistentClass cmd = getPersistentClass(path); + if (cmd == null) { + return; + } + addPropertiesToList(cmd, prefix, cursorPosition, hcc); + } else { + String baseEntityName = path.substring(0, idx); + String propertyPath = path.substring(idx + 1); + Value value = getNextAttributeType(baseEntityName, propertyPath); + if (value == null) { + return; + } + + // Go to the next property (get the y of x/y/z when root is x) + idx = propertyPath.indexOf('/'); + if (idx == -1) { + path = ""; + } else { + path = propertyPath.substring(idx + 1); + } + if (path.length() == 0) { + // No properties left + if (value instanceof Component) { + addPropertiesToList((Component) value, prefix, cursorPosition, hcc); + } else { + addPropertiesToList(getPersistentClass( getReferencedEntityName( value ) ), prefix, cursorPosition, hcc); + } + } else { + // Nested properties + if (value instanceof Component) { + // We need to find the first non-component type + while (value instanceof Component && path.length() > 0) { + value = getNextAttributeType((Component) value, path); + if (value != null) { + // Consume part of the canonical path + idx = path.indexOf('/'); + if (idx != -1) { + path = path.substring(idx + 1); + } else { + path = ""; + } + } + } + if (value instanceof Component) { + addPropertiesToList((Component) value, prefix, cursorPosition, hcc); + } else if (value != null) { + if (path.length() > 0) { + path = getReferencedEntityName( value ) + "/" + path; + } else { + path = getReferencedEntityName( value ); + } + getMatchingProperties( path, prefix, cursorPosition, hcc ); + } + } else { + // Just call the method recursively to add our new type + getMatchingProperties(getReferencedEntityName( value ) + "/" + path, prefix, cursorPosition, hcc); + } + } + } + } + + private String getReferencedEntityName(Value value) { + if(value instanceof ToOne) { + return ((ToOne)value).getReferencedEntityName(); + } + if ( value instanceof Collection ) { + Collection collection = ((Collection)value); + Value element = collection.getElement(); + String elementType = getReferencedEntityName( element ); + if(collection.isIndexed()) { + //TODO..list/map + /*IndexedCollection idxCol = (IndexedCollection) collection; + if(!idxCol.isList()) { + Value idxElement = idxCol.getIndex(); + String indexType = getReferencedEntityName( value ); + genericDecl = indexType + "," + elementType; + }*/ + } + return elementType; + } + + if(value instanceof OneToMany) { + return ((OneToMany)value).getReferencedEntityName(); + } + + return null; + } + + private void addPropertiesToList(PersistentClass cmd, String prefix, int cursorPosition, IHQLCompletionRequestor hcc) { + if (cmd == null) { + return; + } + if (prefix == null) { + prefix = ""; + } + + EntityPOJOClass pc = new EntityPOJOClass(cmd, new Cfg2JavaTool()); // TODO: we should extract the needed functionallity from this hbm2java class. + + Iterator allPropertiesIterator = pc.getAllPropertiesIterator(); + while ( allPropertiesIterator.hasNext() ) { + Property property = (Property) allPropertiesIterator.next(); + String candidate = property.getName(); + if (prefix.length() == 0 || candidate.startsWith(prefix)) { + HQLCompletionProposal proposal = createStartWithCompletionProposal( prefix, cursorPosition, HQLCompletionProposal.PROPERTY, candidate ); + proposal.setEntityName( cmd.getEntityName() ); + proposal.setPropertyName( candidate ); + hcc.accept( proposal); + } + } + } + + private HQLCompletionProposal createStartWithCompletionProposal(String prefix, int cursorPosition, int kind, String candidate) { + HQLCompletionProposal proposal = new HQLCompletionProposal(kind, cursorPosition); + proposal.setCompletion( candidate.substring( prefix.length() ) ); + proposal.setSimpleName( candidate ); + proposal.setReplaceEnd( cursorPosition ); + proposal.setReplaceStart( cursorPosition ); + return proposal; + } + + /** returns PersistentClass for path. Can be null if path is an imported non-mapped class */ + private PersistentClass getPersistentClass(String path) { + if(path==null) return null; + String entityName = (String) cfg.getImports().get( path ); + if(entityName==null) { + return null; + } else { + return cfg.getClassMapping( entityName ); + } + } + + public String getCanonicalPath(List qts, String name) { + Map alias2Type = new HashMap(); + for (Iterator iter = qts.iterator(); iter.hasNext();) { + EntityNameReference qt = (EntityNameReference) iter.next(); + alias2Type.put(qt.getAlias(), qt.getEntityName()); + } + if (qts.size() == 1) { + EntityNameReference visible = (EntityNameReference) qts.get(0); + String alias = visible.getAlias(); + if (name.equals(alias)) { + return visible.getEntityName(); + } else if (alias == null || alias.length() == 0 || alias.equals(visible.getEntityName())) { + return visible.getEntityName() + "/" + name; + } + } + return getCanonicalPath(new HashSet(), alias2Type, name); + } + + private String getCanonicalPath(Set resolved, Map alias2Type, String name) { + if (resolved.contains(name)) { + // To prevent a stack overflow + return name; + } + resolved.add(name); + String type = (String) alias2Type.get(name); + if (type != null) { + return name.equals(type) ? name : getCanonicalPath(resolved, alias2Type, type); + } + int idx = name.lastIndexOf('.'); + if (idx == -1) { + return type != null ? type : name; + } + String baseName = name.substring(0, idx); + String prop = name.substring(idx + 1); + if (isAliasKnown(alias2Type, baseName)) { + return getCanonicalPath(resolved, alias2Type, baseName) + "/" + prop; + } else { + return name; + } + } + + private static boolean isAliasKnown(Map alias2Type, String alias) { + if (alias2Type.containsKey(alias)) { + return true; + } + int idx = alias.lastIndexOf('.'); + if (idx == -1) { + return false; + } + return isAliasKnown(alias2Type, alias.substring(0, idx)); + } + + private Value getNextAttributeType(String type, String attributePath) { + PersistentClass cmd = getPersistentClass( type ); + if (cmd == null) { + return null; + } + String attribute; + int idx = attributePath.indexOf('/'); + if (idx == -1) { + attribute = attributePath; + } else { + attribute = attributePath.substring(0, idx); + } + + String idName = cmd.getIdentifierProperty()==null?null:cmd.getIdentifierProperty().getName(); + if (attribute.equals(idName)) { + return cmd.getIdentifierProperty().getValue(); + } + Property property = cmd.getProperty( attribute ); + return property==null?null:property.getValue(); + } + + private Value getNextAttributeType(Component t, String attributeName) { + int idx = attributeName.indexOf('/'); + if (idx != -1) { + attributeName = attributeName.substring(0, idx); + } + Iterator names = t.getPropertyIterator(); + int i = 0; + while ( names.hasNext() ) { + Property element = (Property) names.next(); + String name = element.getName(); + if (attributeName.equals(name)) { + return element.getValue(); + } + i++; + } + return null; + } + + void addPropertiesToList(Component t, String prefix, int cursorPosition, IHQLCompletionRequestor hcc) { + if (t == null) { + return; + } + Iterator props = t.getPropertyIterator(); + int i = 0; + while ( props.hasNext() ) { + Property element = (Property) props.next(); + String candidate = element.getName(); + if (candidate.startsWith(prefix)) { + HQLCompletionProposal proposal = createStartWithCompletionProposal( prefix, cursorPosition, HQLCompletionProposal.PROPERTY, candidate ); + //proposal.setEntityName( cmd.getEntityName() ); ...we don't know here..TODO: pass in the "path" + proposal.setPropertyName( candidate ); + hcc.accept( proposal); + } + i++; + } + } + + private void findMatchingWords(int cursorPosition, String prefix, String[] words, int kind, IHQLCompletionRequestor hcc) { + int i = Arrays.binarySearch(words, prefix); + if(i<0) { + i = Math.abs(i+1); + } + + for (int cnt = i; cnt < words.length; cnt++) { + String word = words[cnt]; + if(word.startsWith(prefix)) { + HQLCompletionProposal proposal = createStartWithCompletionProposal( prefix, cursorPosition, kind, word ); + hcc.accept( proposal); + } else { + break; + } + } + } + +} Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/EntityNameReference.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/EntityNameReference.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/EntityNameReference.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,43 @@ +package org.hibernate.tool.ide.completion; + +/** + * Class that represents an alias to some entityname in a HQL statement. e.g. "Product as p" or "Product p" + * + * Should not be used by external clients. + * + * @author leon, Max Rydahl Andersen + */ +public class EntityNameReference { + + private String alias; + + private String entityName; + + public EntityNameReference(String type, String alias) { + this.entityName = type; + this.alias = alias; + } + + /** + * + * @return The alias, the "p" in "Product as p" + */ + public String getAlias() { + return alias; + } + + /** + * + * @return the entityname, the "Product" in "Product as b" + */ + public String getEntityName() { + return entityName; + } + + public String toString() { + return alias + ":" + entityName; + } + + + +} Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLAnalyzer.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLAnalyzer.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLAnalyzer.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,388 @@ +package org.hibernate.tool.ide.completion; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.hibernate.hql.antlr.HqlSqlTokenTypes; + +/** + * The HQLAnalyzer can answer certain questions about a HQL String. + * + * @author leon, max...@jb... + */ +public class HQLAnalyzer { + + /** Defines the HQL keywords. Based on hql.g antlr grammer in 2005 ;) */ + private static String[] hqlKeywords = { "between", "class", "delete", + "desc", "distinct", "elements", "escape", "exists", "false", + "fetch", "from", "full", "group", "having", "in", "indices", + "inner", "insert", "into", "is", "join", "left", "like", "new", + "not", "null", "or", "order", "outer", "properties", "right", + "select", "set", "some", "true", "union", "update", "versioned", + "where", "and", "or", "as","on", "with", + + // -- SQL tokens -- + // These aren't part of HQL, but recognized by the lexer. Could be + // usefull for having SQL in the editor..but for now we keep them out + // "case", "end", "else", "then", "when", + + + // -- EJBQL tokens -- + "both", "empty", "leading", "member", "object", "of", "trailing", + }; + + + /** + * built-in function names. Various normal builtin functions in SQL/HQL. + * Maybe sShould try and do this dynamically based on dialect or + * sqlfunctionregistry + */ + private static String[] builtInFunctions = { + // standard sql92 functions + "substring", "locate", "trim", "length", "bit_length", "coalesce", + "nullif", "abs", "mod", "sqrt", + "upper", + "lower", + "cast", + "extract", + + // time functions mapped to ansi extract + "second", "minute", "hour", "day", + "month", + "year", + + "str", + + // misc functions - based on oracle dialect + "sign", "acos", "asin", "atan", "cos", "cosh", "exp", "ln", "sin", + "sinh", "stddev", "sqrt", "tan", "tanh", "variance", + + "round", "trunc", "ceil", "floor", + + "chr", "initcap", "lower", "ltrim", "rtrim", "soundex", "upper", + "ascii", "length", "to_char", "to_date", + + "current_date", "current_time", "current_timestamp", "lastday", + "sysday", "systimestamp", "uid", "user", + + "rowid", "rownum", + + "concat", "instr", "instrb", "lpad", "replace", "rpad", "substr", + "substrb", "translate", + + "substring", "locate", "bit_length", "coalesce", + + "atan2", "log", "mod", "nvl", "nvl2", "power", + + "add_months", "months_between", "next_day", + + "max", "min", }; + + static { + // to allow binary search + Arrays.sort(builtInFunctions); + Arrays.sort(hqlKeywords); + } + + protected SimpleHQLLexer getLexer(char chars[], int end) { + return new AntlrSimpleHQLLexer(chars,end); + } + + protected SimpleHQLLexer getLexer(char chars[]) { + return new AntlrSimpleHQLLexer(chars,chars.length); + } + + /** + * Returns true if the position is at a location where an entityname makes sense. + * e.g. "from Pr| where x" + * @param query + * @param cursorPosition + * @return + */ + public boolean shouldShowEntityNames(String query, int cursorPosition) { + return shouldShowEntityNames( query.toCharArray(), cursorPosition ); + } + + public boolean shouldShowEntityNames(char chars[], int cursorPosition) { + SimpleHQLLexer lexer = getLexer( chars, cursorPosition ); + int tokenId = -1; + boolean show = false; + while ((tokenId = lexer.nextTokenId()) != HqlSqlTokenTypes.EOF) { + if ((tokenId == HqlSqlTokenTypes.FROM || + tokenId == HqlSqlTokenTypes.DELETE || + tokenId == HqlSqlTokenTypes.UPDATE) && + (lexer.getTokenOffset() + lexer.getTokenLength()) < cursorPosition) { + show = true; + } else if (tokenId != HqlSqlTokenTypes.DOT && tokenId != HqlSqlTokenTypes.AS && tokenId != HqlSqlTokenTypes.COMMA && tokenId != HqlSqlTokenTypes.IDENT && tokenId != HqlSqlTokenTypes.WS) { + show = false; + } + } + return show; + } + + public List getVisibleSubQueries(char[] chars, int position) { + SubQueryList sqList = getSubQueries(chars, position); + List visible = new ArrayList(); + for (Iterator iter = sqList.subQueries.iterator(); iter.hasNext();) { + SubQuery sq = (SubQuery) iter.next(); + if (sqList.caretDepth >= sq.depth && (sq.startOffset <= position || sq.endOffset >= position)) { + visible.add(sq); + } + } + return visible; + } + + public List getVisibleEntityNames(char[] chars, int position) { + List sqs = getVisibleSubQueries(chars, position); + List entityReferences = new ArrayList(); + for (Iterator iter = sqs.iterator(); iter.hasNext();) { + SubQuery sq = (SubQuery) iter.next(); + entityReferences.addAll(sq.getEntityNames()); + } + return entityReferences; + } + + protected SubQueryList getSubQueries(char[] query, int position) { + SimpleHQLLexer syntax = getLexer( query ); + int numericId = -1; + List subQueries = new ArrayList(); + int depth = 0; + int caretDepth = 0; + Map level2SubQuery = new HashMap(); + SubQuery current = null; + while ((numericId = syntax.nextTokenId()) != HqlSqlTokenTypes.EOF) { + boolean tokenAdded = false; + if (numericId == HqlSqlTokenTypes.OPEN) { + depth++; + if (position > syntax.getTokenOffset()) { + caretDepth = depth; + } + } else if (numericId == HqlSqlTokenTypes.CLOSE) { + SubQuery currentDepthQuery = (SubQuery) level2SubQuery.get(new Integer(depth)); + // We check if we have a query on the current depth. + // If yes, we'll have to close it + if (currentDepthQuery != null && currentDepthQuery.depth == depth) { + currentDepthQuery.endOffset = syntax.getTokenOffset(); + currentDepthQuery.tokenIds.add(new Integer(numericId)); + currentDepthQuery.tokenText.add(String.valueOf(query, syntax.getTokenOffset(), syntax.getTokenLength())); + subQueries.add(currentDepthQuery); + level2SubQuery.remove(new Integer(depth)); + tokenAdded = true; + } + depth--; + if (position > syntax.getTokenOffset()) { + caretDepth = depth; + } + } + switch (numericId) { + case HqlSqlTokenTypes.FROM: + case HqlSqlTokenTypes.UPDATE: + case HqlSqlTokenTypes.DELETE: + case HqlSqlTokenTypes.SELECT: + if (!level2SubQuery.containsKey(new Integer(depth))) { + current = new SubQuery(); + current.depth = depth; + current.startOffset = syntax.getTokenOffset(); + level2SubQuery.put(new Integer(depth), current); + } + current.tokenIds.add(new Integer(numericId)); + current.tokenText.add(String.valueOf(query, syntax.getTokenOffset(), syntax.getTokenLength())); + break; + default: + if (!tokenAdded) { + SubQuery sq = (SubQuery) level2SubQuery.get(new Integer(depth)); + int i = depth; + while (sq == null && i >= 0) { + sq = (SubQuery) level2SubQuery.get(new Integer(i--)); + } + if (sq != null) { + sq.tokenIds.add(new Integer(numericId)); + sq.tokenText.add(String.valueOf(query, syntax.getTokenOffset(), syntax.getTokenLength())); + } + } + } + } + for (Iterator iter = level2SubQuery.values().iterator(); iter.hasNext();) { + SubQuery sq = (SubQuery) iter.next(); + sq.endOffset = syntax.getTokenOffset() + syntax.getTokenLength(); + subQueries.add(sq); + } + Collections.sort(subQueries); + SubQueryList sql = new SubQueryList(); + sql.caretDepth = caretDepth; + sql.subQueries = subQueries; + return sql; + } + + + /** Returns reference name found from position and backwards in the array. + **/ + public static String getEntityNamePrefix(char[] chars, int position) { + StringBuffer buff = new StringBuffer(); + for (int i = position - 1; i >= 0; i--) { + char c = chars[i]; + if (c == '.' || Character.isJavaIdentifierPart(c)) { + buff.insert(0, c); + } else { + break; + } + } + return buff.toString(); + } + + public static class SubQuery implements Comparable { + + public int compareTo(Object s) { + return startOffset - ((SubQuery)s).startOffset; + } + + private List tokenIds = new ArrayList(); + + private List tokenText = new ArrayList(); + + private int startOffset; + + private int endOffset; + + private int depth; + + public int getTokenCount() { + return tokenIds.size(); + } + + public int getToken(int i) { + return ((Number)tokenIds.get(i)).intValue(); + } + + public String getTokenText(int i) { + return (String) tokenText.get(i); + } + + public List getEntityNames() { + boolean afterFrom = false; + boolean afterJoin = false; + StringBuffer tableNames = new StringBuffer(); + StringBuffer joins = new StringBuffer(); + int i = 0; + boolean cont = true; + int lastToken = HqlSqlTokenTypes.EOF; + for (Iterator iter = tokenIds.iterator(); iter.hasNext();) { + Integer typeInteger = (Integer) iter.next(); + int type = typeInteger.intValue(); + if (!cont) { + break; + } + if (!afterFrom && + (type == HqlSqlTokenTypes.FROM || + type == HqlSqlTokenTypes.UPDATE || + type == HqlSqlTokenTypes.DELETE)) { + afterFrom = true; + } else if (afterJoin) { + switch (type) { + case HqlSqlTokenTypes.ORDER: + case HqlSqlTokenTypes.WHERE: + case HqlSqlTokenTypes.GROUP: + case HqlSqlTokenTypes.HAVING: + cont = false; + break; + case HqlSqlTokenTypes.INNER: + case HqlSqlTokenTypes.OUTER: + case HqlSqlTokenTypes.LEFT: + case HqlSqlTokenTypes.RIGHT: + case HqlSqlTokenTypes.JOIN: + joins.append(","); + break; + case HqlSqlTokenTypes.COMMA: + joins.append(","); //TODO: we should detect this and create the list directly instead of relying on the tokenizer + break; + case HqlSqlTokenTypes.DOT: + joins.append("."); + break; + case HqlSqlTokenTypes.IDENT: + if(lastToken!=HqlSqlTokenTypes.DOT) { + joins.append(" "); + } + joins.append(tokenText.get(i)); + break; + } + } else if (afterFrom) { + switch (type) { + case HqlSqlTokenTypes.ORDER: + case HqlSqlTokenTypes.WHERE: + case HqlSqlTokenTypes.GROUP: + case HqlSqlTokenTypes.HAVING: + case HqlSqlTokenTypes.SET: + cont = false; + break; + case HqlSqlTokenTypes.COMMA: + tableNames.append(","); //TODO: we should detect this and create the list directly instead of relying on the tokenizer + break; + case HqlSqlTokenTypes.DOT: + tableNames.append("."); + break; + case HqlSqlTokenTypes.IDENT: + if(lastToken!=HqlSqlTokenTypes.DOT) { + tableNames.append(" "); + } + tableNames.append(tokenText.get(i)); + break; + case HqlSqlTokenTypes.JOIN: + tableNames.append(","); + afterJoin = true; + break; + default: + break; + } + } + i++; + lastToken = type; + } + List tables = new ArrayList(); + addEntityReferences(tables, tableNames); + addEntityReferences(tables, joins); + return tables; + } + + private void addEntityReferences(final List tables, final StringBuffer tableNames) { + StringTokenizer tableTokenizer = new StringTokenizer(tableNames.toString(), ","); + while (tableTokenizer.hasMoreTokens()) { + String table = tableTokenizer.nextToken().trim(); + if (table.indexOf(' ') == -1 && table.length() > 0) { + tables.add(new EntityNameReference(table, table)); + } else { + StringTokenizer aliasTokenizer = new StringTokenizer(table, " "); + if (aliasTokenizer.countTokens() >= 2) { + String type = aliasTokenizer.nextToken().trim(); + String alias = aliasTokenizer.nextToken().trim(); + if (type.length() > 0 && alias.length() > 0) { + tables.add(new EntityNameReference(type, alias)); + } + } + } + } + } + } + + static class SubQueryList { + + int caretDepth; + + List subQueries; + } + + + static String[] getHQLKeywords() { + return hqlKeywords; + } + + static String[] getHQLFunctionNames() { + return builtInFunctions; + } + +} Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLCodeAssist.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLCodeAssist.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLCodeAssist.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,103 @@ +package org.hibernate.tool.ide.completion; + +import java.util.Iterator; +import java.util.List; + +import org.hibernate.cfg.Configuration; + +public class HQLCodeAssist implements IHQLCodeAssist { + + private Configuration configuration; + private ConfigurationCompletion completion; + + public HQLCodeAssist(Configuration configuration) { + this.configuration = configuration; + completion = new ConfigurationCompletion(configuration); + } + + public void codeComplete(String query, int position, IHQLCompletionRequestor collector) { + + int prefixStart = findNearestWhiteSpace(query, position); + String prefix = query.substring( prefixStart, position ); + + boolean showEntityNames; + try { + showEntityNames = new HQLAnalyzer().shouldShowEntityNames( query, position ); + + if(showEntityNames) { + if(hasConfiguration()) { + completion.getMatchingImports( prefix, position, collector ); + } else { + collector.completionFailure("Configuration not available nor open"); + } + } else { + List visible = new HQLAnalyzer().getVisibleEntityNames( query.toCharArray(), position ); + int dotIndex = prefix.lastIndexOf("."); + if (dotIndex == -1) { + // It's a simple path, not a dot separated one (find aliases that matches) + for (Iterator iter = visible.iterator(); iter.hasNext();) { + EntityNameReference qt = (EntityNameReference) iter.next(); + String alias = qt.getAlias(); + if (alias.startsWith(prefix)) { + HQLCompletionProposal completionProposal = new HQLCompletionProposal(HQLCompletionProposal.ALIAS_REF, position); + completionProposal.setCompletion( alias.substring( prefix.length() ) ); + completionProposal.setReplaceStart( position ); + completionProposal.setReplaceEnd( position+0 ); + completionProposal.setSimpleName( alias ); + completionProposal.setShortEntityName( qt.getEntityName() ); + if(hasConfiguration()) { + String importedName = (String) getConfiguration().getImports().get( qt.getEntityName() ); + completionProposal.setEntityName( importedName ); + } + collector.accept( completionProposal ); + } + } + } else { + if(hasConfiguration()) { + String path = CompletionHelper.getCanonicalPath(visible, prefix.substring(0, dotIndex)); + String propertyPrefix = prefix.substring(dotIndex + 1); + completion.getMatchingProperties( path, propertyPrefix, position, collector ); + } else { + collector.completionFailure("Configuration not available nor open"); + } + } + + completion.getMatchingFunctions( prefix, position, collector ); + completion.getMatchingKeywords( prefix, position, collector ); + + + } + } catch(SimpleLexerException sle) { + collector.completionFailure( "Syntax error: " + sle.getMessage() ); + } + + } + + private boolean hasConfiguration() { + return configuration!=null; + } + + private Configuration getConfiguration() { + return configuration; + } + + public int findNearestWhiteSpace( CharSequence doc, int start ) { + boolean loop = true; + + int offset = 0; + + int tmpOffset = start - 1; + while (loop && tmpOffset >= 0) { + char c = doc.charAt(tmpOffset); + if(Character.isWhitespace(c)) { + loop = false; + } else { + tmpOffset--; + } + } + offset = tmpOffset + 1; + + return offset; + } + +} Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLCompletionProposal.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLCompletionProposal.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/HQLCompletionProposal.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,193 @@ +package org.hibernate.tool.ide.completion; + + + +public class HQLCompletionProposal { + + static final char[] NO_CHAR = new char[0]; + + public static final int ENTITY_NAME = 1; + public static final int PROPERTY = 2; + public static final int KEYWORD = 3; + public static final int FUNCTION = 4; + public static final int ALIAS_REF = 5; // ref to an alias name, e.g. "bar" in "from Bar as bar where b|" + + protected static final int FIRST_KIND = ENTITY_NAME; + protected static final int LAST_KIND = ALIAS_REF; + + /** + * Kind of completion request. + */ + private int completionKind; + + /** + * Offset in original buffer where ICodeAssist.codeComplete() was + * requested. + */ + private int completionLocation; + + /** + * Completion string; defaults to empty string. + */ + private String completion = ""; + + /** + * Start position (inclusive) of source range in original buffer + * to be replaced by completion string; + * defaults to empty subrange at [0,0). + */ + private int replaceStart = 0; + + /** + * End position (exclusive) of source range in original buffer + * to be replaced by completion string; + * defaults to empty subrange at [0,0). + */ + private int replaceEnd = 0; + + /** + * Relevance rating; positive; higher means better; + * defaults to minimum rating. + */ + private int relevance = 1; + + /** The default name for the entityname, keyword, property etc. */ + private String simpleName = ""; + + /** The full related entity name, the resolved shortEntityName. Can be null */ + private String entityName = null; + + /** + * A short entity name. e.g. the imported name. + * e.g. "Product" instead of "org.hibernate.model.Product" + * (note: a imported name can also be the long version) + **/ + private String shortEntityName = null; + + /** + * The propertyName, can be null. + */ + private String propertyName = null; + + public String getCompletion() { + return completion; + } + + public void setCompletion(String completion) { + this.completion = completion; + } + + public int getCompletionKind() { + return completionKind; + } + + public void setCompletionKind(int completionKind) { + this.completionKind = completionKind; + } + + public int getCompletionLocation() { + return completionLocation; + } + + public void setCompletionLocation(int completionLocation) { + this.completionLocation = completionLocation; + } + + public int getRelevance() { + return relevance; + } + + public void setRelevance(int relevance) { + this.relevance = relevance; + } + + public int getReplaceEnd() { + return replaceEnd; + } + + public void setReplaceEnd(int replaceEnd) { + this.replaceEnd = replaceEnd; + } + + public int getReplaceStart() { + return replaceStart; + } + + public void setReplaceStart(int replaceStart) { + this.replaceStart = replaceStart; + } + + public HQLCompletionProposal(int kind, int cursorPosition) { + this.completionKind = kind; + this.completionLocation = cursorPosition; + } + + public String getSimpleName() { + return simpleName; + } + + public void setSimpleName(String simpleName) { + this.simpleName = simpleName; + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append('['); + switch(this.completionKind) { + case ENTITY_NAME : + buffer.append("ENTITY_NAME"); + break; + case PROPERTY: + buffer.append("PROPERTY"); + break; + case KEYWORD: + buffer.append("KEYWORD"); + break; + default : + buffer.append("<Unknown type>"); + break; + + } + buffer.append("]{completion:"); //$NON-NLS-1$ + if (this.completion != null) buffer.append(this.completion); + buffer.append(", simpleName:"); //$NON-NLS-1$ + if (this.simpleName != null) buffer.append(this.simpleName); + buffer.append(", ["); //$NON-NLS-1$ + buffer.append(this.replaceStart); + buffer.append(','); + buffer.append(this.replaceEnd); + buffer.append("], relevance="); //$NON-NLS-1$ + buffer.append(this.relevance); + buffer.append('}'); + return buffer.toString(); + } + + public String getEntityName() { + return entityName; + } + + public void setEntityName(String entityName) { + this.entityName = entityName; + } + + public String getShortEntityName() { + return shortEntityName; + } + + public void setShortEntityName(String shortEntityName) { + this.shortEntityName = shortEntityName; + } + + public String getPropertyName() { + return propertyName; + } + + public void setPropertyName(String propertyName) { + this.propertyName = propertyName; + } + + + + + +} Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/IHQLCodeAssist.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/IHQLCodeAssist.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/IHQLCodeAssist.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,19 @@ +package org.hibernate.tool.ide.completion; + +/** + * Interface for code assist on HQL strings. + * + * @author Max Rydahl Andersen + * + */ +public interface IHQLCodeAssist { + + /** + * + * @param query the query string (full or partial) + * @param position the cursor position inside the query string + * @param requestor requestor on which the codeassist will call methods with information about proposals. + */ + void codeComplete(String query, int position, IHQLCompletionRequestor requestor); + +} Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/IHQLCompletionRequestor.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/IHQLCompletionRequestor.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/IHQLCompletionRequestor.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,16 @@ +package org.hibernate.tool.ide.completion; + + +/** + * The interface to implement to collect completion proposals. + * + * @author Max Rydahl Andersen + * + */ +public interface IHQLCompletionRequestor { + + boolean accept(HQLCompletionProposal proposal); + + void completionFailure(String errorMessage); + +} Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/SimpleHQLLexer.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/SimpleHQLLexer.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/SimpleHQLLexer.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,17 @@ +package org.hibernate.tool.ide.completion; + +/** + * Minimal lexer interface that allows HqlAnalyzer to work. + * + * @author Max Rydahl Andersen + */ +public interface SimpleHQLLexer { + + + int nextTokenId() throws SimpleLexerException; + + int getTokenOffset(); + + int getTokenLength(); + +} Added: trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/SimpleLexerException.java =================================================================== --- trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/SimpleLexerException.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/java/org/hibernate/tool/ide/completion/SimpleLexerException.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,27 @@ +package org.hibernate.tool.ide.completion; + +/** + * Exception that can be thrown when the lexer encounters errors (such as syntax errors etc.) + * + * @author Max Rydahl Andersen + * + */ +public class SimpleLexerException extends RuntimeException { + + public SimpleLexerException() { + super(); + } + + public SimpleLexerException(String message, Throwable cause) { + super( message, cause ); + } + + public SimpleLexerException(String message) { + super( message ); + } + + public SimpleLexerException(Throwable cause) { + super( cause ); + } + +} Added: trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/CompletionHelperTest.java =================================================================== --- trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/CompletionHelperTest.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/CompletionHelperTest.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,60 @@ +/* + * This 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 software 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 software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.hibernate.tool.ide.completion; + +import java.util.ArrayList; +import java.util.List; +import junit.framework.TestCase; + +/** + * @author leon + */ +public class CompletionHelperTest extends TestCase { + + public CompletionHelperTest() { + } + + public void testGetCanonicalPath() { + List qts = new ArrayList(); + qts.add(new EntityNameReference("Article", "art")); + qts.add(new EntityNameReference("art.descriptions", "descr")); + qts.add(new EntityNameReference("descr.name", "n")); + assertEquals("Invalid path", "Article/descriptions/name/locale", CompletionHelper.getCanonicalPath(qts, "n.locale")); + assertEquals("Invalid path", "Article/descriptions", CompletionHelper.getCanonicalPath(qts, "descr")); + // + qts.clear(); + qts.add(new EntityNameReference("com.company.Clazz", "clz")); + qts.add(new EntityNameReference("clz.attr", "a")); + assertEquals("Invalid path", "com.company.Clazz/attr", CompletionHelper.getCanonicalPath(qts, "a")); + // + qts.clear(); + qts.add(new EntityNameReference("Agga", "a")); + assertEquals("Invalid path", "Agga", CompletionHelper.getCanonicalPath(qts, "a")); + } + + public void testStackOverflowInGetCanonicalPath() { + List qts = new ArrayList(); + qts.add(new EntityNameReference("Article", "art")); + qts.add(new EntityNameReference("art.stores", "store")); + qts.add(new EntityNameReference("store.articles", "art")); + // This should not result in a stack overflow + CompletionHelper.getCanonicalPath(qts, "art"); + } + + +} Added: trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/HQLCompletionProposalComparator.java =================================================================== --- trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/HQLCompletionProposalComparator.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/HQLCompletionProposalComparator.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,13 @@ +package org.hibernate.tool.ide.completion; + +import java.util.Comparator; + +public class HQLCompletionProposalComparator implements Comparator { + + public int compare(Object o1, Object o2) { + HQLCompletionProposal p1 = (HQLCompletionProposal) o1; + HQLCompletionProposal p2 = (HQLCompletionProposal) o2; + return p1.getSimpleName().compareTo( p2.getSimpleName() ); + } + +} Added: trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/HqlAnalyzerTest.java =================================================================== --- trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/HqlAnalyzerTest.java 2006-05-19 14:44:33 UTC (rev 9937) +++ trunk/HibernateExt/tools/src/test/org/hibernate/tool/ide/completion/HqlAnalyzerTest.java 2006-05-19 14:44:38 UTC (rev 9938) @@ -0,0 +1,260 @@ +/* + * This 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 software 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 software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.hibernate.tool.ide.completion; + +import java.util.Iterator; +import java.util.List; +import junit.framework.TestCase; + +/** + * @author leon + */ +public class HqlAnalyzerTest extends TestCase { + + public HqlAnalyzerTest() { + } + + public void testShouldShowTables() { + String query = "select | from"; + doTestShouldShowTables(query, false); + query = "select art from | Article1, Article2"; + doTestShouldShowTables(query, true); + query = "from Article1, | Article2"; + doTestShouldShowTables(query, true); + query = "select a, b, c | from Article a"; + doTestShouldShowTables(query, false); + query = "select a, b, c from Article a where a in (select | from"; + doTestShouldShowTables(query, false); + query = "select a, b, c from Article a where a in (select a from |"; + doTestShouldShowTables(query, true); + query = "select a, b, c from Article a where a in (select a from C c where c.id in (select t from | G"; + doTestShouldShowTables(query, true); + query = "select a from|"; + doTestShouldShowTables(query, false); + query = "\n\nfrom Article art where art.|"; + doTestShouldShowTables(query, false); + query = "update |"; + doTestShouldShowTables(query, true); + query = "delete |"; + doTestShouldShowTables(query, true); + query = "select new map(item.id as id, item.description as d, bid.amount as a) from |Item item join item.bids bid\r\n" + + " where bid.amount > 100"; + doTestShouldShowTables( query, true ); + + query = "select new map(item.id| as id, item.description as d, bid.amount as a) from |Item item join item.bids bid\r\n" + + " where bid.amount > 100"; + doTestShouldShowTables( query, false ); + + query = "select new map(item.id as id, item.description as d, bid.amount as a) from Item item join item|.bids bid\r\n" + + " where bid.amount > 100"; + doTestShouldShowTables( query, false ); + + query = "from org.|hibernate"; + doTestShouldShowTables( query, true ); + + query = "from \n\r\r\n" + + "org.|hibernate"; + doTestShouldShowTables( query, true ); + + query = "from \n\r\r\n" + + "org.hibernate \n\r where |"; + doTestShouldShowTables( query, false ); + + query = "from \n\r\r\n" + + "org.hibernate \n\r | where "; + doTestShouldShowTables( query, true ); + + } + + public void testTableNamePrefix() { + doTestPrefix("select a fromtable.substring(0, i0) Art|, Bart", "Art"); + doTestPrefix("from |", ""); + doTestPrefix("select a, b, c from Art,|", ""); + doTestPrefix("select u from | Garga", ""); + doTestPrefix("select t from A.B.C.D.|", "A.B.C.D."); + doTestPrefix("from Goro|boro, Zoroor", "Goro"); + } + + public void testSubQueries() { + doTestSubQueries("select a", 1); + doTestSubQueries("fr", 0); + doTestSubQueries("from Article a", 1); + doTestSubQueries("select a from A, B, C", 1); + doTestSubQueries("select a from T a where a.id in ( select c from C c)", 2); + doTestSubQueries("select c where c.id in (select D from D D)", 2); + doTestSubQueries("select d from D d where d.id in (select a.id from A a where a.id in (select b.id from B b", 3); + } + + public void testVisibleSubQueries() { + doTestVisibleSubQueries("select | from A a join a.b b", 1); + doTestVisibleSubQueries("select | from A a join a.b b where b.id in (select c.id from C c)", 1); + doTestVisibleSubQueries("select a from A a join a.b b where b.id in (select c.id from | C c)", 2); + doTestVisibleSubQueries("select a from A a join a.b b where b.id in (select c.id from C c) and b.| > 2", 1); + doTestVisibleSubQueries("select a from A a where | a.id in (select b.id from B b where b.id in (select c.id", 1); + doTestVisibleSubQueries("select a from A a where a.id in (select | b.id from B b where b.id in (select c.id", 2); + doTestVisibleSubQueries("select a from A a where a.id in (select b.id from B b where b.id in (select c.id |", 3); + } + + public void doTestVisibleSubQueries(String query, int size) { + char[] cs = query.replaceAll("\\|", "").toCharArray(); + List visible = new HQLAnalyzer().getVisibleSubQueries(cs, query.indexOf("|")); + assertEquals("Invalid visible query size", size, visible.size()); + } + + private void doTestSubQueries(String query, int size) { + char[] cs = query.toCharArray(); + List l = new HQLAnalyzer().getSubQueries(query.toCharArray(), 0).subQueries; + assertEquals("Incorrent subqueries count", size, l.size()); + } + + private void doTestPrefix(String query, String prefix) { + assertEquals(prefix, HQLAnalyzer.getEntityNamePrefix(query.toCharArray(), query.indexOf("|"))); + } + + private void doTestShouldShowTables(String query, boolean expectedValue) { + char[] ch = query.replaceAll("\\|", "").toCharArray(); + if (expectedValue) { + assertTrue(new HQLAnalyzer().shouldShowEntityNames(ch, getCaretPosition(query))); + } else { + assertFalse(new HQLAnalyzer().shouldShowEntityNames(ch, getCaretPosition(query)... [truncated message content] |