001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.lib.meta;
020:
021: import java.io.BufferedReader;
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.FileNotFoundException;
025: import java.io.FileReader;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.io.InputStreamReader;
029: import java.io.Reader;
030: import java.security.AccessController;
031: import java.security.PrivilegedActionException;
032: import java.util.ArrayList;
033: import java.util.Collection;
034: import java.util.Collections;
035: import java.util.HashMap;
036: import java.util.Iterator;
037: import java.util.List;
038: import java.util.Map;
039:
040: import org.apache.commons.lang.exception.NestableRuntimeException;
041: import org.apache.openjpa.lib.util.Files;
042: import org.apache.openjpa.lib.util.J2DoPrivHelper;
043: import org.apache.openjpa.lib.util.Localizer;
044: import serp.bytecode.lowlevel.ConstantPoolTable;
045: import serp.util.Strings;
046:
047: /**
048: * Parser used to resolve arguments into java classes.
049: * Interprets command-line args as either class names, .class files or
050: * resources, .java files or resources, or metadata files or resources
051: * conforming to the common format defined by {@link CFMetaDataParser}.
052: * Transforms the information in these args into {@link Class} instances.
053: * Note that when parsing .java files, only the main class in the file
054: * is detected. Other classes defined in the file, such as inner classes,
055: * are not added to the returned classes list.
056: *
057: * @author Abe White
058: * @nojavadoc
059: */
060: public class ClassArgParser {
061:
062: private static final int TOKEN_EOF = -1;
063: private static final int TOKEN_NONE = 0;
064: private static final int TOKEN_PACKAGE = 1;
065: private static final int TOKEN_CLASS = 2;
066: private static final int TOKEN_PACKAGE_NOATTR = 3;
067: private static final int TOKEN_CLASS_NOATTR = 4;
068:
069: private static final Localizer _loc = Localizer
070: .forPackage(ClassArgParser.class);
071:
072: private ClassLoader _loader = null;
073: private char[] _packageAttr = "name".toCharArray();
074: private char[] _classAttr = "name".toCharArray();
075: private char[][] _beginElements = { { 'p' }, { 'c' } };
076: private char[][] _endElements = { "ackage".toCharArray(),
077: "lass".toCharArray() };
078:
079: /**
080: * The class loader with which to load parsed classes.
081: */
082: public ClassLoader getClassLoader() {
083: return _loader;
084: }
085:
086: /**
087: * The class loader with which to load parsed classes.
088: */
089: public void setClassLoader(ClassLoader loader) {
090: _loader = loader;
091: }
092:
093: /**
094: * Set the the relevant metadata file structure so that metadata files
095: * containing class names can be parsed. Null attribute names indicate
096: * that the text content of the element contains the data.
097: */
098: public void setMetaDataStructure(String packageElementName,
099: 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
111: .get("cant-diff-elems").getMessage());
112: buf[i] = packageElementName.charAt(charIdx);
113: } else {
114: if (charIdx == classElementNames[i - 1].length())
115: throw new UnsupportedOperationException(_loc
116: .get("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
131: .substring(0, charIdx + 1).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].substring(0,
136: charIdx + 1).toCharArray();
137: _endElements[i + 1] = classElementNames[i].substring(
138: 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(J2DoPrivHelper
218: .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(_loc.get("class-arg",
224: 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(_loc.get("class-arg",
247: 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,
257: List names) 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
287: .toArray(new String[names.size()]));
288: names.clear();
289: }
290: } catch (Exception e) {
291: throw new NestableRuntimeException(_loc.get("class-arg",
292: 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
304: .doPrivileged(J2DoPrivHelper
305: .newFileInputStreamAction(file));
306: return getFromClass(fin);
307: } catch (PrivilegedActionException pae) {
308: throw (FileNotFoundException) pae.getException();
309: } finally {
310: if (fin != null)
311: try {
312: fin.close();
313: } catch (IOException ioe) {
314: }
315: }
316: }
317:
318: /**
319: * Returns the class name in the given .class bytecode.
320: */
321: private String getFromClass(InputStream in) throws IOException {
322: ConstantPoolTable table = new ConstantPoolTable(in);
323: int idx = table.getEndIndex();
324: idx += 2; // access flags
325: int clsEntry = table.readUnsignedShort(idx);
326: int utfEntry = table.readUnsignedShort(table.get(clsEntry));
327: return table.readString(table.get(utfEntry)).replace('/', '.');
328: }
329:
330: /**
331: * Returns the class named in the given .java file.
332: */
333: private String getFromJavaFile(File file) throws IOException {
334: BufferedReader in = null;
335: try {
336: // find the line with the package declaration
337: in = new BufferedReader(new FileReader(file));
338: String line;
339: StringBuffer pack = null;
340: while ((line = in.readLine()) != null) {
341: line = line.trim();
342: if (line.startsWith("package ")) {
343: line = line.substring(8).trim();
344:
345: // strip off anything beyond the package declaration
346: pack = new StringBuffer();
347: for (int i = 0; i < line.length(); i++) {
348: if (Character.isJavaIdentifierPart(line
349: .charAt(i))
350: || line.charAt(i) == '.')
351: pack.append(line.charAt(i));
352: else
353: break;
354: }
355: break;
356: }
357: }
358:
359: // strip '.java'
360: String clsName = file.getName();
361: clsName = clsName.substring(0, clsName.length() - 5);
362:
363: // prefix with package
364: if (pack != null && pack.length() > 0)
365: clsName = pack + "." + clsName;
366:
367: return clsName;
368: } finally {
369: if (in != null)
370: try {
371: in.close();
372: } catch (IOException ioe) {
373: }
374: }
375: }
376:
377: /**
378: * Returns the classes named in the given common format metadata file.
379: */
380: private Collection getFromMetaDataFile(File file)
381: throws IOException {
382: FileReader in = null;
383: try {
384: in = new FileReader(file);
385: return getFromMetaData(in);
386: } finally {
387: if (in != null)
388: try {
389: in.close();
390: } catch (IOException ioe) {
391: }
392: }
393: }
394:
395: /**
396: * Returns the classes named in the given common format metadata stream.
397: */
398: private Collection getFromMetaData(Reader xml) throws IOException {
399: Collection names = new ArrayList();
400: BufferedReader in = new BufferedReader(xml);
401:
402: boolean comment = false;
403: int token = TOKEN_NONE;
404: String pkg = "";
405: String name;
406: read: for (int ch = 0, last = 0, last2 = 0; ch == '<'
407: || (ch = in.read()) != -1; last2 = last, last = ch) {
408: // handle comments
409: if (comment && last2 == '-' && last == '-' && ch == '>') {
410: comment = false;
411: continue;
412: }
413: if (comment) {
414: if (ch == '<') {
415: ch = in.read();
416: if (ch == -1)
417: break read;
418: }
419: continue;
420: }
421: if (last2 == '<' && last == '!' && ch == '-') {
422: comment = true;
423: continue;
424: }
425:
426: // if not an element start, skip it
427: if (ch != '<')
428: continue;
429: token = TOKEN_NONE; // reset token
430: last = ch; // update needed for comment detection
431: ch = readThroughWhitespace(in);
432: if (ch == '/' || ch == '!' || ch == '?')
433: continue;
434:
435: // read element name; look for packages and classes
436: token = readElementToken(ch, in);
437: switch (token) {
438: case TOKEN_EOF:
439: break read;
440: case TOKEN_PACKAGE:
441: pkg = readAttribute(in, _packageAttr);
442: if (pkg == null)
443: break read;
444: break;
445: case TOKEN_PACKAGE_NOATTR:
446: pkg = readElementText(in);
447: if (pkg == null)
448: break read;
449: ch = '<'; // reading element text reads to next '<'
450: break;
451: case TOKEN_CLASS:
452: name = readAttribute(in, _classAttr);
453: if (name == null)
454: break read;
455: if (pkg.length() > 0 && name.indexOf('.') == -1)
456: names.add(pkg + "." + name);
457: else
458: names.add(name);
459: break;
460: case TOKEN_CLASS_NOATTR:
461: name = readElementText(in);
462: if (name == null)
463: break read;
464: ch = '<'; // reading element text reads to next '<'
465: if (pkg.length() > 0 && name.indexOf('.') == -1)
466: names.add(pkg + "." + name);
467: else
468: names.add(name);
469: break;
470: }
471: }
472: return names;
473: }
474:
475: /**
476: * Read the name of the current XML element and return the matching token.
477: */
478: private int readElementToken(int ch, Reader in) throws IOException {
479: // look through the beginning element names to find what element this
480: // might be(if any)
481: int matchIdx = -1;
482: int matched = 0;
483: int dq = 0;
484: for (int beginIdx = 0; beginIdx < _beginElements[0].length; beginIdx++) {
485: if (beginIdx != 0)
486: ch = in.read();
487: if (ch == -1)
488: return TOKEN_EOF;
489:
490: matched = 0;
491: for (int i = 0; i < _beginElements.length; i++) {
492: if ((dq & (2 << i)) != 0)
493: continue;
494:
495: if (ch == _beginElements[i][beginIdx]) {
496: matchIdx = i;
497: matched++;
498: } else
499: dq |= 2 << i;
500: }
501:
502: if (matched == 0)
503: break;
504: }
505: if (matched != 1)
506: return TOKEN_NONE;
507:
508: // make sure the rest of the element name matches
509: char[] match = _endElements[matchIdx];
510: for (int i = 0; i < match.length; i++) {
511: ch = in.read();
512: if (ch == -1)
513: return TOKEN_EOF;
514: if (ch != match[i])
515: return TOKEN_NONE;
516: }
517:
518: // read the next char to make sure we finished the element name
519: ch = in.read();
520: if (ch == -1)
521: return TOKEN_EOF;
522: if (ch == '>') {
523: if (matchIdx == 0 && _packageAttr == null)
524: return TOKEN_PACKAGE_NOATTR;
525: if (matchIdx != 0 && _classAttr == null)
526: return TOKEN_CLASS_NOATTR;
527: } else if (Character.isWhitespace((char) ch)) {
528: if (matchIdx == 0 && _packageAttr != null)
529: return TOKEN_PACKAGE;
530: if (matchIdx != 0 && _classAttr != null)
531: return TOKEN_CLASS;
532: }
533: return TOKEN_NONE;
534: }
535:
536: /**
537: * Read the attribute with the given name in chars of the current XML
538: * element.
539: */
540: private String readAttribute(Reader in, char[] name)
541: throws IOException {
542: int expected = 0;
543: for (int ch, last = 0; true; last = ch) {
544: ch = in.read();
545: if (ch == -1)
546: return null;
547: if (ch == '>')
548: return "";
549:
550: // if not expected char or still looking for 'n' and previous
551: // char is not whitespace, keep looking
552: if (ch != name[expected]
553: || (expected == 0 && last != 0 && !Character
554: .isWhitespace((char) last))) {
555: expected = 0;
556: continue;
557: }
558:
559: // found expected char; have we found the whole "name"?
560: expected++;
561: if (expected == name.length) {
562: // make sure the next char is '='
563: ch = readThroughWhitespace(in);
564: if (ch == -1)
565: return null;
566: if (ch != '=') {
567: expected = 0;
568: continue;
569: }
570:
571: // toss out any subsequent whitespace and the next char, which
572: // is the opening quote for the attr value, then read until the
573: // closing quote
574: readThroughWhitespace(in);
575: return readAttributeValue(in);
576: }
577: }
578: }
579:
580: /**
581: * Read the current text value until the next element.
582: */
583: private String readElementText(Reader in) throws IOException {
584: StringBuffer buf = null;
585: int ch;
586: while (true) {
587: ch = in.read();
588: if (ch == -1)
589: return null;
590: if (ch == '<')
591: break;
592: if (Character.isWhitespace((char) ch))
593: continue;
594: if (buf == null)
595: buf = new StringBuffer();
596: buf.append((char) ch);
597: }
598: return (buf == null) ? "" : buf.toString();
599: }
600:
601: /**
602: * Read until the next non-whitespace character.
603: */
604: private int readThroughWhitespace(Reader in) throws IOException {
605: int ch;
606: while (true) {
607: ch = in.read();
608: if (ch == -1 || !Character.isWhitespace((char) ch))
609: return ch;
610: }
611: }
612:
613: /**
614: * Return the current attribute value.
615: */
616: private String readAttributeValue(Reader in) throws IOException {
617: StringBuffer buf = new StringBuffer();
618: int ch;
619: while (true) {
620: ch = in.read();
621: if (ch == -1)
622: return null;
623: if (ch == '\'' || ch == '"')
624: return buf.toString();
625: buf.append((char) ch);
626: }
627: }
628: }
|