Home » apache-openjpa-1.1.0-source » org.apache.openjpa » kernel » exps » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
   10    * http://www.apache.org/licenses/LICENSE-2.0
   11    *
   12    * Unless required by applicable law or agreed to in writing,
   13    * software distributed under the License is distributed on an
   14    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   15    * KIND, either express or implied.  See the License for the
   16    * specific language governing permissions and limitations
   17    * under the License.    
   18    */
   19   package org.apache.openjpa.kernel.exps;
   20   
   21   import java.util.Collection;
   22   import java.util.HashMap;
   23   import java.util.HashSet;
   24   import java.util.Iterator;
   25   import java.util.Map;
   26   import java.util.Set;
   27   
   28   import org.apache.openjpa.kernel.Filters;
   29   import org.apache.openjpa.lib.util.Localizer;
   30   import org.apache.openjpa.lib.util.Localizer.Message;
   31   import org.apache.openjpa.meta.ClassMetaData;
   32   import org.apache.openjpa.meta.FieldMetaData;
   33   import org.apache.openjpa.meta.JavaTypes;
   34   import org.apache.openjpa.meta.XMLMetaData;
   35   import org.apache.openjpa.util.InternalException;
   36   import org.apache.openjpa.util.OpenJPAException;
   37   import org.apache.openjpa.util.UnsupportedException;
   38   import org.apache.openjpa.util.UserException;
   39   import serp.util.Strings;
   40   
   41   /**
   42    * Abstract base class to help build expressions. Provides
   43    * generic language-independent support for variable resolution,
   44    * path traversal, and error messages.
   45    *
   46    * @author Marc Prud'hommeaux
   47    * @nojavadoc
   48    */
   49   public abstract class AbstractExpressionBuilder {
   50   
   51       // used for error messages
   52       protected static final int EX_USER = 0;
   53       protected static final int EX_FATAL = 1;
   54       protected static final int EX_UNSUPPORTED = 2;
   55   
   56       // common implicit type settings
   57       protected static final Class TYPE_OBJECT = Object.class;
   58       protected static final Class TYPE_STRING = String.class;
   59       protected static final Class TYPE_CHAR_OBJ = Character.class;
   60       protected static final Class TYPE_NUMBER = Number.class;
   61       protected static final Class TYPE_COLLECTION = Collection.class;
   62       protected static final Class TYPE_MAP = Map.class;
   63   
   64       // contains types for setImplicitTypes
   65       protected static final int CONTAINS_TYPE_ELEMENT = 1;
   66       protected static final int CONTAINS_TYPE_KEY = 2;
   67       protected static final int CONTAINS_TYPE_VALUE = 3;
   68   
   69       private static final Localizer _loc = Localizer.forPackage
   70           (AbstractExpressionBuilder.class);
   71   
   72       protected final Resolver resolver;
   73       protected ExpressionFactory factory;
   74   
   75       private final Set _accessPath = new HashSet();
   76       private Map _seenVars = null;
   77       private Set _boundVars = null;
   78   
   79       /**
   80        * Constructor.
   81        *
   82        * @param factory the expression factory to use
   83        * @param resolver used to resolve variables, parameters, and class
   84        * names used in the query
   85        */
   86       public AbstractExpressionBuilder(ExpressionFactory factory,
   87           Resolver resolver) {
   88           this.factory = factory;
   89           this.resolver = resolver;
   90       }
   91   
   92       /**
   93        * Returns the class loader that should be used for resolving
   94        * class names (in addition to the resolver in the query).
   95        */
   96       protected abstract ClassLoader getClassLoader();
   97   
   98       /**
   99        * Create a proper parse exception for the given reason.
  100        */
  101       protected OpenJPAException parseException(int e, String token,
  102           Object[] args,
  103           Exception nest) {
  104           String argStr;
  105           if (args == null)
  106               argStr = getLocalizer().get(token).getMessage();
  107           else
  108               argStr = getLocalizer().get(token, args).getMessage();
  109   
  110           Message msg = _loc.get("parse-error", argStr, currentQuery());
  111   
  112           switch (e) {
  113               case EX_FATAL:
  114                   throw new InternalException(msg, nest);
  115               case EX_UNSUPPORTED:
  116                   throw new UnsupportedException(msg, nest);
  117               default:
  118                   throw new UserException(msg, nest);
  119           }
  120       }
  121   
  122       /**
  123        * Register the specified metadata as being in the query's access path.
  124        */
  125       protected ClassMetaData addAccessPath(ClassMetaData meta) {
  126           _accessPath.add(meta);
  127           return meta;
  128       }
  129   
  130       /**
  131        * Return the recorded query access path.
  132        */
  133       protected ClassMetaData[] getAccessPath() {
  134           return (ClassMetaData[]) _accessPath.toArray
  135               (new ClassMetaData[_accessPath.size()]);
  136       }
  137   
  138       /**
  139        * Return true if the given variable has been bound.
  140        */
  141       protected boolean isBound(Value var) {
  142           return _boundVars != null && _boundVars.contains(var);
  143       }
  144   
  145       /**
  146        * Record that the given variable is bound.
  147        */
  148       protected void bind(Value var) {
  149           if (_boundVars == null)
  150               _boundVars = new HashSet();
  151           _boundVars.add(var);
  152       }
  153   
  154       /**
  155        * Returns a value for the given id.
  156        */
  157       protected Value getVariable(String id, boolean bind) {
  158           // check for already constructed var
  159           if (isSeenVariable(id))
  160               return (Value) _seenVars.get(id);
  161   
  162           // create and cache var
  163           Class type = getDeclaredVariableType(id);
  164   
  165           // add this type to the set of classes in the filter's access path
  166           ClassMetaData meta = null;
  167           if (type == null)
  168               type = TYPE_OBJECT;
  169           else
  170               meta = getMetaData(type, false);
  171           if (meta != null)
  172               _accessPath.add(meta);
  173   
  174           Value var;
  175           if (bind)
  176               var = factory.newBoundVariable(id, type);
  177           else
  178               var = factory.newUnboundVariable(id, type);
  179           var.setMetaData(meta);
  180   
  181           if (_seenVars == null)
  182               _seenVars = new HashMap();
  183           _seenVars.put(id, var);
  184           return var;
  185       }
  186   
  187       /**
  188        * Validate that all unbound variables are of a PC type. If not, assume
  189        * that the user actually made a typo that we took for an implicit
  190        * unbound variable.
  191        */
  192       protected void assertUnboundVariablesValid() {
  193           if (_seenVars == null)
  194               return;
  195   
  196           Map.Entry entry;
  197           Value var;
  198           for (Iterator itr = _seenVars.entrySet().iterator(); itr.hasNext();) {
  199               entry = (Map.Entry) itr.next();
  200               var = (Value) entry.getValue();
  201               if (var.getMetaData() == null && !isBound(var)
  202                   && !isDeclaredVariable((String) entry.getKey())) {
  203                   throw parseException(EX_USER, "not-unbound-var",
  204                       new Object[]{ entry.getKey() }, null);
  205               }
  206           }
  207       }
  208   
  209       /**
  210        * Returns whether the specified variable name has been explicitly
  211        * declared. Note all query languages necessarily support declaring
  212        * variables.
  213        *
  214        * @param id the variable to check
  215        * @return true if the variabe has been explicitely declared
  216        */
  217       protected abstract boolean isDeclaredVariable(String id);
  218   
  219       /**
  220        * Return whether the given id has been used as a variable.
  221        */
  222       protected boolean isSeenVariable(String id) {
  223           return _seenVars != null && _seenVars.containsKey(id);
  224       }
  225   
  226       /**
  227        * Convenience method to get metadata for the given type.
  228        */
  229       protected ClassMetaData getMetaData(Class c, boolean required) {
  230           return getMetaData(c, required, getClassLoader());
  231       }
  232   
  233       /**
  234        * Convenience method to get metadata for the given type.
  235        */
  236       protected ClassMetaData getMetaData(Class c, boolean required,
  237           ClassLoader loader) {
  238           return resolver.getConfiguration().getMetaDataRepositoryInstance().
  239               getMetaData(c, loader, required);
  240       }
  241   
  242       /**
  243        * Traverse the given field in the given path.
  244        */
  245       protected Value traversePath(Path path, String field) {
  246           return traversePath(path, field, false, false);
  247       }
  248       
  249       protected Value traverseXPath(Path path, String field) {
  250           XMLMetaData meta = path.getXmlMapping();
  251           if (meta.getFieldMapping(field) == null) {
  252               throw parseException(EX_USER, "no-field",
  253                       new Object[]{ meta.getType(), field }, null);
  254           }
  255           else {
  256               // collection-valued xpath is not allowed
  257               int type = meta.getFieldMapping(field).getTypeCode();
  258               switch (type) {
  259                   case JavaTypes.ARRAY:
  260                   case JavaTypes.COLLECTION:
  261                   case JavaTypes.MAP:
  262                       throw new UserException(_loc.get("collection-valued-path",
  263                               field));
  264               }
  265           }
  266           path.get(meta, field);
  267           return path;
  268       }
  269   
  270       /**
  271        * Traverse the given field in the given path.
  272        */
  273       protected Value traversePath(Path path, String field, boolean pcOnly,
  274           boolean allowNull) {
  275           ClassMetaData meta = path.getMetaData();
  276           if (meta == null)
  277               throw parseException(EX_USER, "path-no-meta",
  278                   new Object[]{ field, path.getType() }, null);
  279   
  280           FieldMetaData fmd = meta.getField(field);
  281           if (fmd == null) {
  282               Object val = traverseStaticField(meta.getDescribedType(), field);
  283               if (val == null)
  284                   throw parseException(EX_USER, "no-field",
  285                       new Object[]{ meta.getDescribedType(), field }, null);
  286   
  287               return factory.newLiteral(val, Literal.TYPE_UNKNOWN);
  288           }
  289   
  290           if (fmd.isEmbedded())
  291               meta = fmd.getEmbeddedMetaData();
  292           else
  293               meta = fmd.getDeclaredTypeMetaData();
  294           if (meta != null) {
  295               addAccessPath(meta);
  296               path.setMetaData(meta);
  297           }
  298           else {
  299               // xmlsupport xpath
  300               XMLMetaData xmlmeta = fmd.getRepository().getXMLMetaData(fmd);
  301               if (xmlmeta != null) {
  302                   path.get(fmd, xmlmeta);
  303                   return path;
  304               }
  305           }
  306   
  307           if (meta != null || !pcOnly)
  308               path.get(fmd, allowNull);
  309   
  310           return path;
  311       }
  312   
  313       /**
  314        * Return a constant containing the value of the given static field.
  315        */
  316       protected Object traverseStaticField(Class cls, String field) {
  317           try {
  318               return cls.getField(field).get(null);
  319           } catch (Exception e) {
  320               // count not locate the field: return null
  321               return null;
  322           }
  323       }
  324   
  325       /**
  326        * Returns the type of the named variabe if it has been declared.
  327        */
  328       protected abstract Class getDeclaredVariableType(String name);
  329   
  330       /**
  331        * Set the implicit types of the given values based on the fact that
  332        * they're used together, and based on the operator type.
  333        */
  334       protected void setImplicitTypes(Value val1, Value val2,
  335           Class expected) {
  336           Class c1 = val1.getType();
  337           Class c2 = val2.getType();
  338           boolean o1 = c1 == TYPE_OBJECT;
  339           boolean o2 = c2 == TYPE_OBJECT;
  340   
  341           if (o1 && !o2) {
  342               val1.setImplicitType(c2);
  343               if (val1.getMetaData() == null && !val1.isXPath())
  344                   val1.setMetaData(val2.getMetaData());
  345           } else if (!o1 && o2) {
  346               val2.setImplicitType(c1);
  347               if (val2.getMetaData() == null && !val1.isXPath())
  348                   val2.setMetaData(val1.getMetaData());
  349           } else if (o1 && o2 && expected != null) {
  350               // we never expect a pc type, so don't bother with metadata
  351               val1.setImplicitType(expected);
  352               val2.setImplicitType(expected);
  353           } else if (isNumeric(val1.getType()) != isNumeric(val2.getType())) {
  354               if (resolver.getConfiguration().getCompatibilityInstance().
  355                   getQuotedNumbersInQueries())
  356                   convertTypesQuotedNumbers(val1, val2);
  357               else
  358                   convertTypes(val1, val2);
  359           }
  360       }
  361   
  362       /**
  363        * Perform conversions to make values compatible.
  364        */
  365       private void convertTypes(Value val1, Value val2) {
  366           Class t1 = val1.getType();
  367           Class t2 = val2.getType();
  368   
  369           // allow string-to-char conversions
  370           if (t1 == TYPE_STRING && (Filters.wrap(t2) == TYPE_CHAR_OBJ
  371               && !(val2 instanceof Path))) {
  372               val2.setImplicitType(String.class);
  373               return;
  374           }
  375           if (t2 == TYPE_STRING && (Filters.wrap(t1) == TYPE_CHAR_OBJ)
  376               && !(val1 instanceof Path)) {
  377               val1.setImplicitType(String.class);
  378               return;
  379           }
  380   
  381           // if the non-numeric side is a string of length 1, cast it
  382           // to a character
  383           if (t1 == TYPE_STRING && val1 instanceof Literal
  384               && ((String) ((Literal) val1).getValue()).length() == 1) {
  385               val1.setImplicitType(Character.class);
  386               return;
  387           }
  388           if (t2 == TYPE_STRING && val2 instanceof Literal
  389               && ((String) ((Literal) val2).getValue()).length() == 1) {
  390               val2.setImplicitType(Character.class);
  391               return;
  392           }
  393   
  394           // error
  395           String left;
  396           String right;
  397           if (val1 instanceof Path && ((Path) val1).last() != null)
  398               left = _loc.get("non-numeric-path", ((Path) val1).last().
  399                   getName(), t1.getName()).getMessage();
  400           else
  401               left = _loc.get("non-numeric-value", t1.getName()).getMessage();
  402           if (val2 instanceof Path && ((Path) val2).last() != null)
  403               right = _loc.get("non-numeric-path", ((Path) val2).last().
  404                   getName(), t2.getName()).getMessage();
  405           else
  406               right = _loc.get("non-numeric-value", t2.getName()).getMessage();
  407           throw new UserException(_loc.get("non-numeric-comparison",
  408               left, right));
  409       }
  410   
  411       /**
  412        * Perform conversions to make values compatible.
  413        */
  414       private void convertTypesQuotedNumbers(Value val1, Value val2) {
  415           Class t1 = val1.getType();
  416           Class t2 = val2.getType();
  417   
  418           // if we're comparing to a single-quoted string, convert
  419           // the value according to the 3.1 rules.
  420           if (t1 == TYPE_STRING && val1 instanceof Literal
  421               && ((Literal) val1).getParseType() == Literal.TYPE_SQ_STRING) {
  422               String s = (String) ((Literal) val1).getValue();
  423               if (s.length() > 1) {
  424                   ((Literal) val1).setValue(s.substring(0, 1));
  425                   val1.setImplicitType(Character.TYPE);
  426               }
  427           }
  428           if (t2 == TYPE_STRING && val2 instanceof Literal
  429               && ((Literal) val2).getParseType() == Literal.TYPE_SQ_STRING) {
  430               String s = (String) ((Literal) val2).getValue();
  431               if (s.length() > 1) {
  432                   ((Literal) val2).setValue(s.substring(0, 1));
  433                   val2.setImplicitType(Character.TYPE);
  434               }
  435           }
  436   
  437           // if we're comparing to a double-quoted string, convert the
  438           // value directly to a number
  439           if (t1 == TYPE_STRING && val1 instanceof Literal
  440               && ((Literal) val1).getParseType() == Literal.TYPE_STRING) {
  441               String s = (String) ((Literal) val1).getValue();
  442               ((Literal) val1).setValue(Strings.parse(s, Filters.wrap(t2)));
  443               val1.setImplicitType(t2);
  444           }
  445           if (t2 == TYPE_STRING && val2 instanceof Literal
  446               && ((Literal) val2).getParseType() == Literal.TYPE_STRING) {
  447               String s = (String) ((Literal) val2).getValue();
  448               ((Literal) val2).setValue(Strings.parse(s, Filters.wrap(t1)));
  449               val2.setImplicitType(t1);
  450           }
  451       }
  452   
  453       /**
  454        * Return true if given class can be used as a number.
  455        */
  456       private static boolean isNumeric(Class type) {
  457           type = Filters.wrap(type);
  458           return Number.class.isAssignableFrom(type)
  459               || type == Character.TYPE || type == TYPE_CHAR_OBJ;
  460       }
  461   
  462       /**
  463        * Set the implicit types of the given values based on the fact that
  464        * the first is supposed to contain the second.
  465        */
  466       protected void setImplicitContainsTypes(Value val1, Value val2, int op) {
  467           if (val1.getType() == TYPE_OBJECT) {
  468               if (op == CONTAINS_TYPE_ELEMENT)
  469                   val1.setImplicitType(Collection.class);
  470               else
  471                   val1.setImplicitType(Map.class);
  472           }
  473   
  474           if (val2.getType() == TYPE_OBJECT && val1 instanceof Path) {
  475               FieldMetaData fmd = ((Path) val1).last();
  476               ClassMetaData meta;
  477               if (fmd != null) {
  478                   if (op == CONTAINS_TYPE_ELEMENT || op == CONTAINS_TYPE_VALUE) {
  479                       val2.setImplicitType(fmd.getElement().getDeclaredType());
  480                       meta = fmd.getElement().getDeclaredTypeMetaData();
  481                       if (meta != null) {
  482                           val2.setMetaData(meta);
  483                           addAccessPath(meta);
  484                       }
  485                   } else {
  486                       val2.setImplicitType(fmd.getKey().getDeclaredType());
  487                       meta = fmd.getKey().getDeclaredTypeMetaData();
  488                       if (meta != null) {
  489                           val2.setMetaData(meta);
  490                           addAccessPath(meta);
  491                       }
  492                   }
  493               }
  494           }
  495       }
  496   
  497       /**
  498        * Set the implicit type of the given value to the given class.
  499        */
  500       protected static void setImplicitType(Value val, Class expected) {
  501           // we never expect a pc type, so no need to worry about metadata
  502           if (val.getType() == TYPE_OBJECT)
  503               val.setImplicitType(expected);
  504       }
  505   
  506       /**
  507        * Used for obtaining the {@link Localizer} to use for translating
  508        * error messages.
  509        */
  510       protected abstract Localizer getLocalizer();
  511   
  512       /**
  513        * Returns the current string being parsed; used for error messages.
  514   	 */
  515   	protected abstract String currentQuery ();
  516   }
  517   

Save This Page
Home » apache-openjpa-1.1.0-source » org.apache.openjpa » kernel » exps » [javadoc | source]