Home » apache-openjpa-1.1.0-source » org.apache.openjpa.lib » meta » [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.lib.meta;
   20   
   21   import java.io.BufferedReader;
   22   import java.io.File;
   23   import java.io.FileInputStream;
   24   import java.io.FileNotFoundException;
   25   import java.io.FileReader;
   26   import java.io.IOException;
   27   import java.io.InputStream;
   28   import java.io.InputStreamReader;
   29   import java.io.Reader;
   30   import java.security.AccessController;
   31   import java.security.PrivilegedActionException;
   32   import java.util.ArrayList;
   33   import java.util.Collection;
   34   import java.util.Collections;
   35   import java.util.HashMap;
   36   import java.util.Iterator;
   37   import java.util.List;
   38   import java.util.Map;
   39   
   40   import org.apache.commons.lang.exception.NestableRuntimeException;
   41   import org.apache.openjpa.lib.util.Files;
   42   import org.apache.openjpa.lib.util.J2DoPrivHelper;
   43   import org.apache.openjpa.lib.util.Localizer;
   44   import serp.bytecode.lowlevel.ConstantPoolTable;
   45   import serp.util.Strings;
   46   
   47   /**
   48    * Parser used to resolve arguments into java classes.
   49    * Interprets command-line args as either class names, .class files or
   50    * resources, .java files or resources, or metadata files or resources
   51    * conforming to the common format defined by {@link CFMetaDataParser}.
   52    * Transforms the information in these args into {@link Class} instances.
   53    * Note that when parsing .java files, only the main class in the file
   54    * is detected. Other classes defined in the file, such as inner classes,
   55    * are not added to the returned classes list.
   56    *
   57    * @author Abe White
   58    * @nojavadoc
   59    */
   60   public class ClassArgParser {
   61   
   62       private static final int TOKEN_EOF = -1;
   63       private static final int TOKEN_NONE = 0;
   64       private static final int TOKEN_PACKAGE = 1;
   65       private static final int TOKEN_CLASS = 2;
   66       private static final int TOKEN_PACKAGE_NOATTR = 3;
   67       private static final int TOKEN_CLASS_NOATTR = 4;
   68   
   69       private static final Localizer _loc = Localizer.forPackage
   70           (ClassArgParser.class);
   71   
   72       private ClassLoader _loader = null;
   73       private char[] _packageAttr = "name".toCharArray();
   74       private char[] _classAttr = "name".toCharArray();
   75       private char[][] _beginElements = { { 'p' }, { 'c' } };
   76       private char[][] _endElements = { "ackage".toCharArray(),
   77           "lass".toCharArray() };
   78   
   79       /**
   80        * The class loader with which to load parsed classes.
   81        */
   82       public ClassLoader getClassLoader() {
   83           return _loader;
   84       }
   85   
   86       /**
   87        * The class loader with which to load parsed classes.
   88        */
   89       public void setClassLoader(ClassLoader loader) {
   90           _loader = loader;
   91       }
   92   
   93       /**
   94        * Set the the relevant metadata file structure so that metadata files
   95        * containing class names can be parsed. Null attribute names indicate
   96        * that the text content of the element contains the data.
   97        */
   98       public void setMetaDataStructure(String packageElementName,
   99           String packageAttributeName, String[] classElementNames,
  100           String classAttributeName) {
  101           // calculate how many chars deep we have to go to identify each element
  102           // name as unique.  this is extremely inefficient for large N, but
  103           // should never be called for more than a few elements
  104           char[] buf = new char[classElementNames.length + 1];
  105           int charIdx = 0;
  106           for (; true; charIdx++) {
  107               for (int i = 0; i < buf.length; i++) {
  108                   if (i == 0) {
  109                       if (charIdx == packageElementName.length())
  110                           throw new UnsupportedOperationException(_loc.get
  111                               ("cant-diff-elems").getMessage());
  112                       buf[i] = packageElementName.charAt(charIdx);
  113                   } else {
  114                       if (charIdx == classElementNames[i - 1].length())
  115                           throw new UnsupportedOperationException(_loc.get
  116                               ("cant-diff-elems").getMessage());
  117                       buf[i] = classElementNames[i - 1].charAt(charIdx);
  118                   }
  119               }
  120               if (charsUnique(buf))
  121                   break;
  122           }
  123   
  124           _packageAttr = (packageAttributeName == null) ? null
  125               : packageAttributeName.toCharArray();
  126           _classAttr = (classAttributeName == null) ? null
  127               : classAttributeName.toCharArray();
  128           _beginElements = new char[classElementNames.length + 1][];
  129           _endElements = new char[classElementNames.length + 1][];
  130           _beginElements[0] = packageElementName.substring(0, charIdx + 1).
  131               toCharArray();
  132           _endElements[0] = packageElementName.substring(charIdx + 1).
  133               toCharArray();
  134           for (int i = 0; i < classElementNames.length; i++) {
  135               _beginElements[i + 1] = classElementNames[i].
  136                   substring(0, charIdx + 1).toCharArray();
  137               _endElements[i + 1] = classElementNames[i].
  138                   substring(charIdx + 1).toCharArray();
  139           }
  140       }
  141   
  142       /**
  143        * Return true if all characters in given buffer are unique.
  144        */
  145       private static boolean charsUnique(char[] buf) {
  146           for (int i = buf.length - 1; i >= 0; i--)
  147               for (int j = 0; j < i; j++)
  148                   if (buf[j] == buf[i])
  149                       return false;
  150           return true;
  151       }
  152   
  153       /**
  154        * Return the {@link Class} representation of the class(es) named in the
  155        * given arg.
  156        *
  157        * @param arg a class name, .java file, .class file, or metadata
  158        * file naming the type(s) to act on
  159        */
  160       public Class[] parseTypes(String arg) {
  161           String[] names = parseTypeNames(arg);
  162           Class[] objs = new Class[names.length];
  163           for (int i = 0; i < names.length; i++)
  164               objs[i] = Strings.toClass(names[i], _loader);
  165           return objs;
  166       }
  167   
  168       /**
  169        * Return the {@link Class} representation of the class(es) named in the
  170        * given metadatas.
  171        */
  172       public Class[] parseTypes(MetaDataIterator itr) {
  173           String[] names = parseTypeNames(itr);
  174           Class[] objs = new Class[names.length];
  175           for (int i = 0; i < names.length; i++)
  176               objs[i] = Strings.toClass(names[i], _loader);
  177           return objs;
  178       }
  179   
  180       /**
  181        * Return a mapping of each metadata resource to an array of its
  182        * contained classes.
  183        */
  184       public Map mapTypes(MetaDataIterator itr) {
  185           Map map = mapTypeNames(itr);
  186           Map.Entry entry;
  187           String[] names;
  188           Class[] objs;
  189           for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
  190               entry = (Map.Entry) i.next();
  191               names = (String[]) entry.getValue();
  192               objs = new Class[names.length];
  193               for (int j = 0; j < names.length; j++)
  194                   objs[j] = Strings.toClass(names[j], _loader);
  195               entry.setValue(objs);
  196           }
  197           return map;
  198       }
  199   
  200       /**
  201        * Return the names of the class(es) from the given arg.
  202        *
  203        * @param arg a class name, .java file, .class file, or metadata
  204        * file naming the type(s) to act on
  205        * @throws IllegalArgumentException with appropriate message on error
  206        */
  207       public String[] parseTypeNames(String arg) {
  208           if (arg == null)
  209               return new String[0];
  210   
  211           try {
  212               File file = Files.getFile(arg, _loader);
  213               if (arg.endsWith(".class"))
  214                   return new String[]{ getFromClassFile(file) };
  215               if (arg.endsWith(".java"))
  216                   return new String[]{ getFromJavaFile(file) };
  217               if (((Boolean) AccessController.doPrivileged(
  218                   J2DoPrivHelper.existsAction(file))).booleanValue()) {
  219                   Collection col = getFromMetaDataFile(file);
  220                   return (String[]) col.toArray(new String[col.size()]);
  221               }
  222           } catch (Exception e) {
  223               throw new NestableRuntimeException(
  224                   _loc.get("class-arg", arg).getMessage(), e);
  225           }
  226   
  227           // must be a class name
  228           return new String[]{ arg };
  229       }
  230   
  231       /**
  232        * Return the names of the class(es) from the given metadatas.
  233        */
  234       public String[] parseTypeNames(MetaDataIterator itr) {
  235           if (itr == null)
  236               return new String[0];
  237   
  238           List names = new ArrayList();
  239           Object source = null;
  240           try {
  241               while (itr.hasNext()) {
  242                   source = itr.next();
  243                   appendTypeNames(source, itr.getInputStream(), names);
  244               }
  245           } catch (Exception e) {
  246               throw new NestableRuntimeException(
  247                   _loc.get("class-arg", source).getMessage(), e);
  248           }
  249           return (String[]) names.toArray(new String[names.size()]);
  250       }
  251   
  252       /**
  253        * Parse the names in the given metadata iterator stream, closing the
  254        * stream on completion.
  255        */
  256       private void appendTypeNames(Object source, InputStream in, List names)
  257           throws IOException {
  258           try {
  259               if (source.toString().endsWith(".class"))
  260                   names.add(getFromClass(in));
  261               names.addAll(getFromMetaData(new InputStreamReader(in)));
  262           } finally {
  263               try {
  264                   in.close();
  265               } catch (IOException ioe) {
  266               }
  267           }
  268       }
  269   
  270       /**
  271        * Return a mapping of each metadata resource to an array of its contained
  272        * class names.
  273        */
  274       public Map mapTypeNames(MetaDataIterator itr) {
  275           if (itr == null)
  276               return Collections.EMPTY_MAP;
  277   
  278           Map map = new HashMap();
  279           Object source = null;
  280           List names = new ArrayList();
  281           try {
  282               while (itr.hasNext()) {
  283                   source = itr.next();
  284                   appendTypeNames(source, itr.getInputStream(), names);
  285                   if (!names.isEmpty())
  286                       map.put(source, (String[]) names.toArray
  287                           (new String[names.size()]));
  288                   names.clear();
  289               }
  290           } catch (Exception e) {
  291               throw new NestableRuntimeException(
  292                   _loc.get("class-arg", source).getMessage(), e);
  293           }
  294           return map;
  295       }
  296   
  297       /**
  298        * Returns the class named in the given .class file.
  299        */
  300       private String getFromClassFile(File file) throws IOException {
  301           FileInputStream fin = null;
  302           try {
  303               fin = (FileInputStream) AccessController.doPrivileged(
  304                   J2DoPrivHelper.newFileInputStreamAction(file));
  305               return getFromClass(fin);
  306           } catch (PrivilegedActionException pae) {
  307               throw (FileNotFoundException) pae.getException();
  308           } finally {
  309               if (fin != null)
  310                   try {
  311                       fin.close();
  312                   } catch (IOException ioe) {
  313                   }
  314           }
  315       }
  316   
  317       /**
  318        * Returns the class name in the given .class bytecode.
  319        */
  320       private String getFromClass(InputStream in) throws IOException {
  321           ConstantPoolTable table = new ConstantPoolTable(in);
  322           int idx = table.getEndIndex();
  323           idx += 2; // access flags
  324           int clsEntry = table.readUnsignedShort(idx);
  325           int utfEntry = table.readUnsignedShort(table.get(clsEntry));
  326           return table.readString(table.get(utfEntry)).replace('/', '.');
  327       }
  328   
  329       /**
  330        * Returns the class named in the given .java file.
  331        */
  332       private String getFromJavaFile(File file) throws IOException {
  333           BufferedReader in = null;
  334           try {
  335               // find the line with the package declaration
  336               in = new BufferedReader(new FileReader(file));
  337               String line;
  338               StringBuffer pack = null;
  339               while ((line = in.readLine()) != null) {
  340                   line = line.trim();
  341                   if (line.startsWith("package ")) {
  342                       line = line.substring(8).trim();
  343   
  344                       // strip off anything beyond the package declaration
  345                       pack = new StringBuffer();
  346                       for (int i = 0; i < line.length(); i++) {
  347                           if (Character.isJavaIdentifierPart(line.charAt(i))
  348                               || line.charAt(i) == '.')
  349                               pack.append(line.charAt(i));
  350                           else
  351                               break;
  352                       }
  353                       break;
  354                   }
  355               }
  356   
  357               // strip '.java'
  358               String clsName = file.getName();
  359               clsName = clsName.substring(0, clsName.length() - 5);
  360   
  361               // prefix with package
  362               if (pack != null && pack.length() > 0)
  363                   clsName = pack + "." + clsName;
  364   
  365               return clsName;
  366           } finally {
  367               if (in != null)
  368                   try { in.close(); } catch (IOException ioe) {}
  369           }
  370       }
  371   
  372       /**
  373        * Returns the classes named in the given common format metadata file.
  374        */
  375       private Collection getFromMetaDataFile(File file) throws IOException {
  376           FileReader in = null;
  377           try {
  378               in = new FileReader(file);
  379               return getFromMetaData(in);
  380           } finally {
  381               if (in != null)
  382                   try {
  383                       in.close();
  384                   } catch (IOException ioe) {
  385                   }
  386           }
  387       }
  388   
  389       /**
  390        * Returns the classes named in the given common format metadata stream.
  391        */
  392       private Collection getFromMetaData(Reader xml) throws IOException {
  393           Collection names = new ArrayList();
  394           BufferedReader in = new BufferedReader(xml);
  395   
  396           boolean comment = false;
  397           int token = TOKEN_NONE;
  398           String pkg = "";
  399           String name;
  400           read:
  401           for (int ch = 0, last = 0, last2 = 0;
  402               ch == '<' || (ch = in.read()) != -1; last2 = last, last = ch) {
  403               // handle comments
  404               if (comment && last2 == '-' && last == '-' && ch == '>') {
  405                   comment = false;
  406                   continue;
  407               }
  408               if (comment) {
  409                   if (ch == '<') {
  410                       ch = in.read();
  411                       if (ch == -1)
  412                           break read;
  413                   }
  414                   continue;
  415               }
  416               if (last2 == '<' && last == '!' && ch == '-') {
  417                   comment = true;
  418                   continue;
  419               }
  420   
  421               // if not an element start, skip it
  422               if (ch != '<')
  423                   continue;
  424               token = TOKEN_NONE; // reset token
  425               last = ch; // update needed for comment detection
  426               ch = readThroughWhitespace(in);
  427               if (ch == '/' || ch == '!' || ch == '?')
  428                   continue;
  429   
  430               // read element name; look for packages and classes
  431               token = readElementToken(ch, in);
  432               switch (token) {
  433                   case TOKEN_EOF:
  434                       break read;
  435                   case TOKEN_PACKAGE:
  436                       pkg = readAttribute(in, _packageAttr);
  437                       if (pkg == null)
  438                           break read;
  439                       break;
  440                   case TOKEN_PACKAGE_NOATTR:
  441                       pkg = readElementText(in);
  442                       if (pkg == null)
  443                           break read;
  444                       ch = '<'; // reading element text reads to next '<'
  445                       break;
  446                   case TOKEN_CLASS:
  447                       name = readAttribute(in, _classAttr);
  448                       if (name == null)
  449                           break read;
  450                       if (pkg.length() > 0 && name.indexOf('.') == -1)
  451                           names.add(pkg + "." + name);
  452                       else
  453                           names.add(name);
  454                       break;
  455                   case TOKEN_CLASS_NOATTR:
  456                       name = readElementText(in);
  457                       if (name == null)
  458                           break read;
  459                       ch = '<'; // reading element text reads to next '<'
  460                       if (pkg.length() > 0 && name.indexOf('.') == -1)
  461                           names.add(pkg + "." + name);
  462                       else
  463                           names.add(name);
  464                       break;
  465               }
  466           }
  467           return names;
  468       }
  469   
  470       /**
  471        * Read the name of the current XML element and return the matching token.
  472        */
  473       private int readElementToken(int ch, Reader in) throws IOException {
  474           // look through the beginning element names to find what element this
  475           // might be(if any)
  476           int matchIdx = -1;
  477           int matched = 0;
  478           int dq = 0;
  479           for (int beginIdx = 0; beginIdx < _beginElements[0].length; beginIdx++)
  480           {
  481               if (beginIdx != 0)
  482                   ch = in.read();
  483               if (ch == -1)
  484                   return TOKEN_EOF;
  485   
  486               matched = 0;
  487               for (int i = 0; i < _beginElements.length; i++) {
  488                   if ((dq & (2 << i)) != 0)
  489                       continue;
  490   
  491                   if (ch == _beginElements[i][beginIdx]) {
  492                       matchIdx = i;
  493                       matched++;
  494                   } else
  495                       dq |= 2 << i;
  496               }
  497   
  498               if (matched == 0)
  499                   break;
  500           }
  501           if (matched != 1)
  502               return TOKEN_NONE;
  503   
  504           // make sure the rest of the element name matches
  505           char[] match = _endElements[matchIdx];
  506           for (int i = 0; i < match.length; i++) {
  507               ch = in.read();
  508               if (ch == -1)
  509                   return TOKEN_EOF;
  510               if (ch != match[i])
  511                   return TOKEN_NONE;
  512           }
  513   
  514           // read the next char to make sure we finished the element name
  515           ch = in.read();
  516           if (ch == -1)
  517               return TOKEN_EOF;
  518           if (ch == '>') {
  519               if (matchIdx == 0 && _packageAttr == null)
  520                   return TOKEN_PACKAGE_NOATTR;
  521               if (matchIdx != 0 && _classAttr == null)
  522                   return TOKEN_CLASS_NOATTR;
  523           } else if (Character.isWhitespace((char) ch)) {
  524               if (matchIdx == 0 && _packageAttr != null)
  525                   return TOKEN_PACKAGE;
  526               if (matchIdx != 0 && _classAttr != null)
  527                   return TOKEN_CLASS;
  528           }
  529           return TOKEN_NONE;
  530       }
  531   
  532       /**
  533        * Read the attribute with the given name in chars of the current XML
  534        * element.
  535        */
  536       private String readAttribute(Reader in, char[] name) throws IOException {
  537           int expected = 0;
  538           for (int ch, last = 0; true; last = ch) {
  539               ch = in.read();
  540               if (ch == -1)
  541                   return null;
  542               if (ch == '>')
  543                   return "";
  544   
  545               // if not expected char or still looking for 'n' and previous
  546               // char is not whitespace, keep looking
  547               if (ch != name[expected] || (expected == 0 && last != 0
  548                   && !Character.isWhitespace((char) last))) {
  549                   expected = 0;
  550                   continue;
  551               }
  552   
  553               // found expected char; have we found the whole "name"?
  554               expected++;
  555               if (expected == name.length) {
  556                   // make sure the next char is '='
  557                   ch = readThroughWhitespace(in);
  558                   if (ch == -1)
  559                       return null;
  560                   if (ch != '=') {
  561                       expected = 0;
  562                       continue;
  563                   }
  564   
  565                   // toss out any subsequent whitespace and the next char, which
  566                   // is the opening quote for the attr value, then read until the
  567                   // closing quote
  568                   readThroughWhitespace(in);
  569                   return readAttributeValue(in);
  570               }
  571           }
  572       }
  573   
  574       /**
  575        * Read the current text value until the next element.
  576        */
  577       private String readElementText(Reader in) throws IOException {
  578           StringBuffer buf = null;
  579           int ch;
  580           while (true) {
  581               ch = in.read();
  582               if (ch == -1)
  583                   return null;
  584               if (ch == '<')
  585                   break;
  586               if (Character.isWhitespace((char) ch))
  587                   continue;
  588               if (buf == null)
  589                   buf = new StringBuffer();
  590               buf.append((char) ch);
  591           }
  592           return (buf == null) ? "" : buf.toString();
  593       }
  594   
  595       /**
  596        * Read until the next non-whitespace character.
  597        */
  598       private int readThroughWhitespace(Reader in) throws IOException {
  599           int ch;
  600           while (true) {
  601               ch = in.read();
  602               if (ch == -1 || !Character.isWhitespace((char) ch))
  603                   return ch;
  604           }
  605       }
  606   
  607       /**
  608        * Return the current attribute value.
  609        */
  610       private String readAttributeValue(Reader in) throws IOException {
  611           StringBuffer buf = new StringBuffer();
  612           int ch;
  613           while (true) {
  614               ch = in.read();
  615               if (ch == -1)
  616                   return null;
  617               if (ch == '\'' || ch == '"')
  618                   return buf.toString();
  619               buf.append((char) ch);
  620           }
  621       }
  622   }

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