001    package railo.runtime.search.lucene2.query;
002    
003    import java.util.ArrayList;
004    import java.util.Iterator;
005    import java.util.List;
006    
007    import railo.commons.lang.ParserString;
008    
009    
010    /**
011     * The simple query is the default query type and is appropriate for the vast majority of searches. 
012     * When entering text on a search form, you perform a simple query by entering a word or comma-delimited strings, 
013     * with optional wildcard characters. 
014     * Verity treats each comma as a logical OR. If you omit the commas, Verity treats the expression as a phrase.
015     */
016    public final class QueryParser {
017        private static final String OR="or";
018        private static final String AND="and";
019        private static final String NOT="not";
020        private static final char QUOTER='"';
021        
022            private static final String STAR = "*";
023            private List list=new ArrayList();
024    
025    
026            /**
027         * parse given string query
028         * @param criteria
029         * @return matching Query
030         */
031        public String parse(String criteria) {
032            Op op = parseOp(criteria);
033            if(op==null) return STAR;
034            return op.toString();
035        }
036        public Op parseOp(String criteria) {
037            if(criteria.length()>0) {
038                char first=criteria.charAt(0);
039                // start with operator
040                while(first=='*' || first=='~' || first=='?') {
041                    criteria=criteria.substring(1);
042                    if(criteria.length()==0) break;
043                    first=criteria.charAt(0);
044                } 
045            }
046            
047            // make never foud query if quey is empty
048            if(criteria.length()==0) {
049                    return null;
050            }
051            
052            //StringBuffer str=new StringBuffer();
053            ParserString ps=new ParserString(criteria);
054            Op op=null;
055            while(!ps.isAfterLast()) {
056                    if(op==null)op=orOp(ps);
057                    else op=new Concator(op,orOp(ps));
058            }
059            return op;
060        }
061        
062        
063    
064        private Op orOp(ParserString ps) {
065            Op op=andOp(ps);
066            ps.removeSpace();
067                   
068            // OR
069            while(ps.isValidIndex() && (ps.forwardIfCurrent(OR) || ps.forwardIfCurrent(','))) {
070                ps.removeSpace();
071                if(ps.isAfterLast()) op=new Concator(op,new Literal("OR") );
072                else op=new Or(op,andOp(ps));
073            }
074            return op;
075        }
076    
077        private Op andOp(ParserString ps) {
078            Op op = notOp(ps);
079            ps.removeSpace();
080            
081            // AND
082            while(ps.isValidIndex() && ps.forwardIfCurrent(AND)) {
083                ps.removeSpace();
084                if(ps.isAfterLast()) op=new Concator(op,new Literal("AND") );
085                else op=new And(op,notOp(ps));
086            }
087            return op;
088        }
089        
090        private Op notOp(ParserString ps) {
091            Op op = spaceOp(ps);
092            ps.removeSpace();
093            
094            // NOT
095            while(ps.isValidIndex() && ps.forwardIfCurrent(NOT)) {
096                ps.removeSpace();
097                if(ps.isAfterLast()) op=new Concator(op,new Literal("NOT") );
098                else {
099                    Op r;
100                                    op=new Not(op,r=clip(ps));
101                                    this.list.remove(r);
102                }
103            }
104            return op;
105        }
106        
107        private Op spaceOp(ParserString ps) {
108            Op op = clip(ps);
109            //ps.removeSpace();
110            
111            // Concat
112            while(ps.isValidIndex() && isSpace(ps.getCurrent()) && !(ps.isCurrentIgnoreSpace(OR) || ps.isCurrentIgnoreSpace(',') || ps.isCurrentIgnoreSpace(AND) || ps.isCurrentIgnoreSpace(NOT))) {
113                ps.removeSpace();
114                op=new Concator(op,clip(ps));
115            }
116            return op;
117        }
118    
119        private Op clip(ParserString ps) {
120            // ()
121            if(ps.isValidIndex() && ps.forwardIfCurrent('(')) {
122                Op op=orOp(ps);
123                ps.removeSpace();
124                ps.forwardIfCurrent(')');
125                ps.removeSpace();
126                return op;
127            }
128            return literal(ps);
129        }
130        
131        private Op literal(ParserString ps) {
132            ps.removeSpace();
133            
134            if(ps.isCurrent(QUOTER)) return quotedLiteral(ps);
135            return notQuotedLiteral(ps);
136        }
137     
138    
139        private Op quotedLiteral(ParserString ps) {
140            StringBuffer str=new StringBuffer();
141                    ps.next();
142                    char c;
143                    while(!ps.isAfterLast()) {
144                            c=ps.getCurrent();
145                            if(c==QUOTER) {
146                                    ps.next();
147                                    if(ps.isCurrent(QUOTER))        str.append(QUOTER);
148                    else break;
149                }
150                else {
151                    str.append(c);  
152                }
153                            ps.next();
154                    }
155                    
156                    return register(new Literal(str.toString()));
157        }
158     
159        private Op notQuotedLiteral(ParserString ps) {
160            
161                    StringBuffer str=new StringBuffer();
162                    ps.removeSpace();
163                    
164                    char c;
165                    
166                    while(!ps.isAfterLast()) {
167                            c=ps.getCurrent();
168                            if(isSpace(c) || c==',') break;
169                            str.append(c);
170                            ps.next();
171                    }
172                    return register(new Literal(str.toString()));
173            
174        }
175        
176        private boolean isSpace(char c) {
177                    return c==' ' || c=='\t' || c=='\n' || c=='\b';
178            }
179    
180    
181    
182            /*public static void main(String[] args) {
183            QueryParser qp = new QueryParser();
184            
185            qp.parseOp("aaa zzz not bbb and ccc");
186                    print.out(qp.getLiteralSearchedTerms());
187            if(true) return;
188                    print.out(qp.parse("\"abc\""));
189                    print.out(qp.parse("abc"));
190                    print.out(qp.parse("abc def"));
191                    print.out(qp.parse("abc def"));
192                    print.out(qp.parse("abc and def"));
193                    print.out(qp.parse("\"\"\"abc\"\"\""));
194                    print.out(qp.parse("\"abc\" susi or peter"));
195                    print.out(qp.parse("abc susi or peter"));
196                    print.out(qp.parse("abc or susi or peter"));
197                    print.out(qp.parse("*abc susi and peter or \"abc\"* , xxx,yy*"));
198                    print.out(qp.parse("xxx,y\"y*"));
199                    print.out(qp.parse("xxx y\"y*"));
200                    print.out(qp.parse(""));
201                    print.out(qp.parse("per or"));
202                    print.out(qp.parse("per and"));
203                    print.out(qp.parse("per not"));
204                    print.out(qp.parse("andi per not susi"));
205                    print.out(qp.parse("\"kinderhort test\""));
206            }*/
207            public Literal register(Literal literal) {
208                    list.add(literal);
209                    return literal;
210            }
211    
212            public Literal[] getLiteralSearchedTerms() {
213                    return (Literal[]) list.toArray(new Literal[list.size()]);
214            }
215            
216            public String[] getStringSearchedTerms() {
217                    Iterator it = list.iterator();
218                    String[] rtn=new String[list.size()];
219                    int i=0;
220                    while(it.hasNext()) {
221                            rtn[i++]=it.next().toString();
222                    }
223                    
224                    return rtn;
225            }
226        
227    }