LDAPExpr.java

00001 /*
00002  * Copyright (c) 2003-2006, KNOPFLERFISH project
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following
00007  * conditions are met:
00008  *
00009  * - Redistributions of source code must retain the above copyright
00010  *   notice, this list of conditions and the following disclaimer.
00011  *
00012  * - Redistributions in binary form must reproduce the above
00013  *   copyright notice, this list of conditions and the following
00014  *   disclaimer in the documentation and/or other materials
00015  *   provided with the distribution.
00016  *
00017  * - Neither the name of the KNOPFLERFISH project nor the names of its
00018  *   contributors may be used to endorse or promote products derived
00019  *   from this software without specific prior written permission.
00020  *
00021  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00027  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
00028  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00029  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
00030  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00031  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
00032  * OF THE POSSIBILITY OF SUCH DAMAGE.
00033  */
00034 
00035 package org.knopflerfish.framework;
00036 
00037 import org.osgi.framework.InvalidSyntaxException;
00038 import java.util.Dictionary;
00039 import java.util.Vector;
00040 import java.util.ArrayList;
00041 import java.util.HashMap;
00042 import java.util.List;
00043 import java.util.Enumeration;
00044 import java.lang.reflect.Array;
00045 import java.lang.reflect.Constructor;
00046 import java.lang.reflect.Method;
00047 //import java.math.BigInteger;
00048 
00049 public class LDAPExpr {
00050   public static final int AND     =  0;
00051   public static final int OR      =  1;
00052   public static final int NOT     =  2;
00053   public static final int EQ      =  4;
00054   public static final int LE      =  8;
00055   public static final int GE      = 16;
00056   public static final int APPROX  = 32;
00057   public static final int COMPLEX = AND | OR | NOT;
00058   public static final int SIMPLE  = EQ | LE | GE | APPROX;
00059   
00060   private static final char WILDCARD = 65535;
00061   private static final String WILDCARD_STRING = 
00062     new String(new char [] { WILDCARD });
00063 
00064   private static final String NULL      = "Null query";
00065   private static final String GARBAGE   = "Trailing garbage";
00066   private static final String EOS       = "Unexpected end of query";
00067   private static final String MALFORMED = "Malformed query";
00068   private static final String EMPTY     = "Empty list";
00069   private static final String SUBEXPR   = "No subexpression";
00070   private static final String OPERATOR  = "Undefined operator";
00071 
00072   private static Class classBigDecimal;
00073   private static Constructor consBigDecimal;
00074   private static Method compBigDecimal;
00075 
00076   private static Class classBigInteger;
00077   private static Constructor consBigInteger;
00078   private static Method compBigInteger;
00079 
00080 
00081   public int operator;
00082   public LDAPExpr[] args;
00083   public String attrName;
00084   public String attrValue;
00085 
00086 
00087   public LDAPExpr(String filter) throws InvalidSyntaxException {
00088 
00089     ParseState ps = new ParseState(filter);
00090     LDAPExpr expr = null;
00091     try {
00092       expr = parseExpr(ps);
00093     } catch (StringIndexOutOfBoundsException e) {
00094       ps.error(EOS);
00095     }
00096     if (ps.rest().trim().length() != 0)
00097       ps.error(GARBAGE + " '" + ps.rest() + "'");
00098     operator = expr.operator;
00099     args = expr.args;
00100     attrName = expr.attrName;
00101     attrValue = expr.attrValue;
00102   }
00103 
00104   private LDAPExpr(int operator, LDAPExpr[] args) {
00105     this.operator = operator;
00106     this.args = args;
00107     this.attrName = null;
00108     this.attrValue = null;
00109   }
00110   
00111   private LDAPExpr(int operator, String attrName, String attrValue) {
00112     this.operator = operator;
00113     this.args = null;
00114     this.attrName = attrName;
00115     this.attrValue = attrValue;    
00116   }
00117   
00140   public boolean isSimple(List keywords, List[] cache) {
00141     if (operator == EQ) {
00142       int index;
00143       if ((index = keywords.indexOf(attrName)) >= 0 && 
00144           attrValue.indexOf(WILDCARD) < 0) {
00145         if (cache[index] == null) {
00146           cache[index] = new ArrayList();
00147         }
00148         cache[index].add(attrValue);
00149         return true;
00150       }
00151     } else if (operator == OR) {
00152       for (int i = 0; i < args.length; i++) {
00153         if (!args[i].isSimple(keywords, cache))
00154           return false;
00155       }
00156       return true;
00157     }
00158     return false;
00159   }
00160 
00161 
00162   public static boolean query(String filter, Dictionary pd) 
00163     throws InvalidSyntaxException {
00164     return new LDAPExpr(filter).evaluate(pd, true);
00165   }
00166 
00170   public boolean evaluate(Dictionary p, boolean matchCase) {
00171     if ((operator & SIMPLE) != 0) {      
00172       return compare(p.get(attrName), operator, attrValue, matchCase); 
00173     } else { // (operator & COMPLEX) != 0
00174       switch (operator) {
00175       case AND:
00176         for (int i = 0; i < args.length; i++) {
00177           if (!args[i].evaluate(p, matchCase))
00178             return false;
00179         }
00180         return true;
00181       case OR:
00182         for (int i = 0; i < args.length; i++) {
00183           if (args[i].evaluate(p, matchCase))
00184             return true;
00185         }
00186         return false;
00187       case NOT:
00188         return !args[0].evaluate(p, matchCase);
00189       default:
00190         return false; // Cannot happen
00191       }
00192     }
00193   }
00194   
00195   
00196 
00197   /**** Private methods ****/
00198 
00199   protected boolean compare(Object obj, int op, String s, boolean matchCase) {
00200     if (obj == null) 
00201       return false;
00202     if (op == EQ && s.equals(WILDCARD_STRING)) 
00203       return true;
00204     try {
00205       if (obj instanceof String) {
00206                 return compareString((String)obj, op, s, matchCase);
00207       } else if (obj instanceof Character) {  
00208                 return compareString(obj.toString(), op, s, matchCase);
00209       } else if (obj instanceof Boolean) {
00210         if (op==LE || op==GE)
00211           return false;
00212         return ((Boolean)obj).equals(new Boolean(s));
00213       } else if (obj instanceof Number) {
00214         if (obj instanceof Byte) {
00215           switch(op) {
00216           case LE:
00217             return ((Byte)obj).byteValue() <= Byte.parseByte(s);
00218           case GE:
00219             return ((Byte)obj).byteValue() >= Byte.parseByte(s);
00220           default: /*APPROX and EQ*/
00221             return (new Byte(s)).equals(obj);
00222           }
00223         } else if (obj instanceof Integer) {
00224           switch(op) {
00225           case LE:
00226             return ((Integer)obj).intValue() <= Integer.parseInt(s);
00227           case GE:
00228             return ((Integer)obj).intValue() >= Integer.parseInt(s);
00229           default: /*APPROX and EQ*/
00230             return (new Integer(s)).equals(obj);
00231           }
00232         } else if (obj instanceof Short) {
00233           switch(op) {
00234           case LE:
00235             return ((Short)obj).shortValue() <= Short.parseShort(s);
00236           case GE:
00237             return ((Short)obj).shortValue() >= Short.parseShort(s);
00238           default: /*APPROX and EQ*/
00239             return (new Short(s)).equals(obj);
00240           }
00241         } else if (obj instanceof Long) {
00242           switch(op) {
00243           case LE:
00244             return ((Long)obj).longValue() <= Long.parseLong(s);
00245           case GE:
00246             return ((Long)obj).longValue() >= Long.parseLong(s);
00247           default: /*APPROX and EQ*/
00248             return (new Long(s)).equals(obj);
00249           }
00250         } else if (obj instanceof Float) {
00251           switch(op) {
00252           case LE:
00253             return ((Float)obj).floatValue() <= (new Float(s)).floatValue();
00254           case GE:
00255             return ((Float)obj).floatValue() >= (new Float(s)).floatValue();
00256           default: /*APPROX and EQ*/
00257             return (new Float(s)).equals(obj);
00258           }
00259         } else if (obj instanceof Double) {
00260           switch(op) {
00261           case LE:
00262             return ((Double)obj).doubleValue() <= (new Double(s)).doubleValue();
00263           case GE:
00264             return ((Double)obj).doubleValue() >= (new Double(s)).doubleValue();
00265           default: /*APPROX and EQ*/
00266             return (new Double(s)).equals(obj);
00267           }
00268         } else if (classBigInteger != null && classBigInteger.isInstance(obj)) {
00269           Object n = consBigInteger.newInstance(new Object [] { s });
00270           int c = ((Integer)compBigInteger.invoke(obj, new Object [] { n })).intValue();
00271 
00272           switch(op) {
00273           case LE:
00274             return c <= 0;
00275           case GE:
00276             return c >= 0;
00277           default: /*APPROX and EQ*/
00278             return c == 0;
00279           }
00280         } else if (classBigDecimal != null && classBigDecimal.isInstance(obj)) {
00281           Object n = consBigDecimal.newInstance(new Object [] { s });
00282           int c = ((Integer)compBigDecimal.invoke(obj, new Object [] { n })).intValue();
00283 
00284           switch(op) {
00285           case LE:
00286             return c <= 0;
00287           case GE:
00288             return c >= 0;
00289           default: /*APPROX and EQ*/
00290             return c == 0;
00291           }
00292         } 
00293       } else if (obj instanceof Vector) {
00294         for (Enumeration e=((Vector)obj).elements(); e.hasMoreElements();)
00295           if (compare(e.nextElement(), op, s, matchCase)) 
00296             return true;
00297       } else if (obj.getClass().isArray()) {
00298         int len = Array.getLength(obj);
00299         for(int i=0; i<len; i++)
00300           if (compare(Array.get(obj, i), op, s, matchCase)) 
00301             return true;
00302       } else {
00303         // Extended comparison
00304         // Allow simple EQ comparison on all classes having
00305         // a string constructor, and use compareTo if they
00306         // implement Comparable
00307         Class       clazz = obj.getClass();
00308         Constructor cons  = getConstructor(clazz);
00309 
00310         if(cons != null) {
00311           Object     other = cons.newInstance(new Object [] { s } );
00312           if(obj instanceof Comparable) {
00313             int c = ((Comparable)obj).compareTo(other);
00314             switch(op) {
00315             case LE:
00316               return c <= 0;
00317             case GE:
00318               return c >= 0;
00319             default: /*APPROX and EQ*/
00320               return c == 0;
00321             }
00322           } else {
00323                 boolean b = false;
00324             if(op == LE || op == GE ||op == EQ ||op == APPROX){
00325                 b = obj.equals(other);
00326             }
00327             return b;
00328           }
00329         }
00330       }
00331     } catch (Exception e) { 
00332       e.printStackTrace();
00333     }
00334     return false;
00335   }
00336   
00337   // Clazz -> Constructor(String)
00338   private static HashMap constructorMap = new HashMap();
00339 
00343   private static Constructor getConstructor(Class clazz) {
00344     synchronized(constructorMap) {
00345 
00346       // This might be null
00347       Constructor cons = (Constructor)constructorMap.get(clazz);
00348       
00349       // ...check if we have tried before. A failed try
00350       // is stored as null
00351       if(!constructorMap.containsKey(clazz)) {
00352         try {
00353           cons = clazz.getConstructor(new Class [] { String.class });
00354         } catch (Exception e) {
00355           // remember by storing null in map
00356         }
00357         constructorMap.put(clazz, cons);
00358       }
00359       return cons;
00360     }
00361   }
00362 
00363   static {
00364     try {
00365       classBigDecimal = Class.forName("java.math.BigDecimal");
00366       consBigDecimal = getConstructor(classBigDecimal);
00367       compBigDecimal = classBigDecimal.getMethod("compareTo", new Class [] { classBigDecimal });
00368     } catch (Exception ignore) {
00369       classBigDecimal = null;
00370     }
00371     try {
00372       classBigInteger = Class.forName("java.math.BigInteger");
00373       consBigInteger = getConstructor(classBigInteger);
00374       compBigInteger = classBigInteger.getMethod("compareTo", new Class [] { classBigInteger });
00375     } catch (Exception ignore) {
00376       classBigInteger = null;
00377     }
00378   }
00379 
00380 
00381   private static boolean compareString(String s1, int op, String s2, boolean matchCase) {
00382     switch(op) {
00383     case LE:
00384       return s1.compareTo(s2) <= 0;
00385     case GE:
00386       return s1.compareTo(s2) >= 0;
00387     case EQ:
00388       return patSubstr(s1,s2, matchCase);
00389     case APPROX:
00390       return fixupString(s2).equals(fixupString(s1));
00391     default:
00392       return false;
00393     }
00394   }
00395 
00396   private static String fixupString(String s) {
00397     StringBuffer sb = new StringBuffer();
00398     int len = s.length();
00399     boolean isStart = true;
00400     boolean isWhite = false;
00401     for(int i=0; i<len; i++) {
00402       char c = s.charAt(i);
00403       if (Character.isWhitespace(c)) {
00404         isWhite = true;
00405       } else {
00406         if (!isStart && isWhite) 
00407           sb.append(' ');
00408         if (Character.isUpperCase(c)) 
00409           c = Character.toLowerCase(c);
00410         sb.append(c);
00411         isStart = false;
00412         isWhite = false;
00413       }
00414     }
00415     return sb.toString();
00416   }
00417 
00418   private static boolean patSubstr(String s, String pat, boolean matchCase) {
00419     return s==null ? false : patSubstr(s.toCharArray(),0,pat.toCharArray(),0, matchCase);
00420   }
00421   
00422   private static boolean patSubstr(char[] s, int si, char[] pat, int pi, boolean matchCase) {
00423     if (pat.length-pi == 0) 
00424       return s.length-si == 0;
00425     if (pat[pi] == WILDCARD) {
00426       pi++;
00427       for (;;) {
00428         if (patSubstr( s, si, pat, pi, matchCase))
00429           return true;
00430         if (s.length-si == 0)
00431           return false;
00432         si++;
00433       }
00434     } else {
00435         if (s.length-si==0){
00436                 return false;
00437         }
00438         if(matchCase){
00439                 if(s[si]!=pat[pi]){
00440                         return false;
00441                 }
00442         }
00443         else{
00444                 if(Character.toLowerCase(s[si]) != pat[pi] &&
00445                    Character.toUpperCase(s[si]) != pat[pi]){
00446                         return false;
00447                 }
00448         }
00449       return patSubstr( s, ++si, pat, ++pi, matchCase);
00450     }
00451   }
00452 
00453   private static LDAPExpr parseExpr(ParseState ps) 
00454     throws InvalidSyntaxException {
00455     ps.skipWhite();
00456     if (!ps.prefix("(")) 
00457       ps.error(MALFORMED);
00458 
00459     int operator;
00460     ps.skipWhite();
00461     switch(ps.peek()) {
00462     case '&': operator = AND; break;
00463     case '|': operator = OR; break;
00464     case '!': operator = NOT; break;
00465     default: return parseSimple(ps);
00466     }
00467     ps.skip(1); // Ignore the operator
00468     List v = new ArrayList();
00469     do {
00470       v.add(parseExpr(ps));
00471       ps.skipWhite();
00472     } while (ps.peek() == '(');
00473     int n = v.size();
00474     if (!ps.prefix(")") || n == 0 || (operator == NOT && n > 1))
00475       ps.error(MALFORMED);    
00476     LDAPExpr[] args = new LDAPExpr[n];
00477     v.toArray(args);
00478     return new LDAPExpr(operator, args);
00479   }
00480 
00481   private static LDAPExpr parseSimple(ParseState ps) 
00482     throws InvalidSyntaxException {
00483     String attrName = ps.getAttributeName(); 
00484     int operator = 0;
00485     if (ps.prefix("=")) 
00486       operator = EQ;
00487     else if (ps.prefix("<="))    
00488       operator = LE;
00489     else if(ps.prefix(">=")) 
00490       operator = GE;
00491     else if(ps.prefix("~=")) 
00492       operator = APPROX;
00493     else {
00494       //      System.out.println("undef op='" + ps.peek() + "'");
00495       ps.error(OPERATOR); // Does not return
00496     }
00497     String attrValue = ps.getAttributeValue();
00498     if (!ps.prefix(")"))
00499       ps.error(MALFORMED);        
00500     return new LDAPExpr(operator, attrName, attrValue);
00501   }
00502 
00503   public String toString() {
00504     StringBuffer res = new StringBuffer();
00505     res.append("(");
00506     if ((operator & SIMPLE) != 0) { 
00507       res.append(attrName);
00508       switch (operator) {
00509       case EQ:
00510         res.append("=");
00511         break;
00512       case LE:
00513         res.append("<=");
00514         break;
00515       case GE:
00516         res.append(">=");
00517         break;
00518       case APPROX:
00519         res.append("~=");
00520         break;
00521       }
00522       for (int i = 0; i < attrValue.length(); i++) {
00523         char c = attrValue.charAt(i);
00524         if (c ==  '(' || c == ')' || c == '*' || c == '\\') {
00525           res.append('\\');
00526         } else if (c == WILDCARD) {
00527           c = '*';
00528         }
00529         res.append(c);
00530       }
00531     } else {
00532       switch (operator) {
00533       case AND:
00534         res.append("&");
00535         break;
00536       case OR:
00537         res.append("|");
00538         break;
00539       case NOT:
00540         res.append("!");
00541         break;
00542       }
00543       for (int i = 0; i < args.length; i++) {
00544         res.append(args[i].toString());
00545       }
00546     }
00547     res.append(")");
00548     return res.toString();
00549   }
00550 
00554   private static class ParseState {
00555     int pos;
00556     String str;
00557 
00558     public ParseState(String str) throws InvalidSyntaxException {
00559       this.str = str;
00560       if (str.length() == 0)
00561         error(NULL);
00562       pos = 0;
00563     }
00564 
00565     public int getPos() {
00566       return pos;
00567     }
00568 
00569     public boolean prefix(String pre) {
00570       if (!str.startsWith(pre, pos))
00571         return false;
00572       pos += pre.length();
00573       return true;
00574     }
00575 
00576     public char peek() {
00577       return str.charAt(pos);
00578     }
00579 
00580     public void skip(int n) {
00581       pos += n;
00582     }
00583 
00584     public String rest() {
00585       return str.substring(pos);
00586     }
00587 
00588     public void skipWhite() {
00589       while (Character.isWhitespace(str.charAt(pos))) {
00590         pos++;
00591       }
00592     }
00593 
00594     public String getAttributeName() {
00595       int start = pos;
00596       for(;; pos++) {
00597         char c = str.charAt(pos);
00598         if (Character.isWhitespace(c) ||
00599             c == '(' || c == ')' ||
00600             c == '<' || c == '>' ||
00601             c == '=' || c == '~' ||
00602             c == '*' || c == '\\') {
00603           break;
00604         }
00605       }
00606       String res = str.substring(start, pos).toLowerCase();
00607       skipWhite();
00608       return res;
00609     }
00610 
00611     public String getAttributeValue() {
00612       StringBuffer sb = new StringBuffer();
00613       label:
00614       for(;; pos++) {
00615         char c = str.charAt(pos);
00616         switch(c) {
00617         case '(':
00618         case ')':
00619           break label;
00620         case '*':
00621           sb.append(WILDCARD);
00622           break;
00623         case '\\':
00624           sb.append(str.charAt(++pos));
00625           break;
00626         default:
00627           sb.append(c);
00628           break;
00629         }
00630       }
00631       return sb.toString();
00632     }
00633 
00634     public void error(String m) throws InvalidSyntaxException {
00635       throw new InvalidSyntaxException(m, (str == null) ? "" : str.substring(pos));
00636     }
00637   }
00638 }

Generated on Mon Jan 11 21:19:15 2010 for OpenMobileIS by  doxygen 1.5.4