001: /*
002: * Copyright 2002 (C) TJDO.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the TJDO License version 1.0.
006: * See the terms of the TJDO License in the documentation provided with this software.
007: *
008: * $Id: Imports.java,v 1.3 2004/03/22 04:58:13 jackknifebarber Exp $
009: */
010:
011: package com.triactive.jdo.util;
012:
013: import java.util.Arrays;
014: import java.util.Collections;
015: import java.util.HashMap;
016: import java.util.HashSet;
017: import java.util.Iterator;
018: import java.util.Set;
019: import java.util.StringTokenizer;
020: import java.util.WeakHashMap;
021: import javax.jdo.JDOUserException;
022: import org.apache.log4j.Category;
023:
024: public class Imports {
025: private static final Category LOG = Category
026: .getInstance(Imports.class);
027:
028: /** Sorted array of primitive type names. */
029: private static final String[] primitives = { "boolean", "byte",
030: "char", "double", "float", "int", "long", "short" };
031:
032: /** Array of classes for primitive types, parallel with primitives[]. */
033: private static final Class[] primitiveClasses = { boolean.class,
034: byte.class, char.class, double.class, float.class,
035: int.class, long.class, short.class };
036:
037: /** Array of type characters for primitive types, parallel with primitives[]. */
038: private static final char[] primitiveTypeChars = { 'Z', 'B', 'C',
039: 'D', 'F', 'I', 'J', 'S' };
040:
041: private static final WeakHashMap nonExistentClassNamesByLoader = new WeakHashMap();
042:
043: private HashMap importedClassesByName = new HashMap();
044: private HashSet importedPackageNames = new HashSet();
045:
046: public Imports() {
047: importedPackageNames.add("java.lang");
048: }
049:
050: public void importPackage(Class c) {
051: while (c.isArray())
052: c = c.getComponentType();
053:
054: String className = c.getName();
055: int lastDot = className.lastIndexOf('.');
056:
057: if (lastDot > 0)
058: importedPackageNames.add(className.substring(0, lastDot));
059: }
060:
061: public void parseImports(String imports) {
062: StringTokenizer t1 = new StringTokenizer(imports, ";");
063:
064: while (t1.hasMoreTokens()) {
065: String importDecl = t1.nextToken().trim();
066:
067: if (importDecl.length() == 0 && !t1.hasMoreTokens())
068: break;
069:
070: StringTokenizer t2 = new StringTokenizer(importDecl, " ");
071:
072: if (t2.countTokens() != 2
073: || !t2.nextToken().equals("import"))
074: throw new JDOUserException(
075: "Invalid import declaration: " + importDecl);
076:
077: String importName = t2.nextToken();
078: int lastDot = importName.lastIndexOf(".");
079: String lastPart = importName.substring(lastDot + 1);
080:
081: if (lastPart.equals("*")) {
082: if (lastDot < 1)
083: throw new JDOUserException(
084: "Invalid package import: " + importName);
085:
086: importedPackageNames.add(importName.substring(0,
087: lastDot));
088: } else {
089: if (importedClassesByName.get(lastPart) != null)
090: throw new JDOUserException(
091: "Duplicate class import: " + importName);
092:
093: try {
094: importedClassesByName.put(lastPart,
095: classForName(importName));
096: } catch (ClassNotFoundException e) {
097: throw new JDOUserException(
098: "Class named in imports not found: "
099: + importName);
100: }
101: }
102: }
103: }
104:
105: /**
106: * Returns the <tt>Class</tt> object associated with the class or interface
107: * with the given string name.
108: *
109: * <p>This method is functionally equivalent to <tt>Class.forName(name)</tt>
110: * but maintains a cache of class names that are <em>not</em> found. In
111: * many cases, especially in an app server environment, searching for
112: * non-existent classes can be a very slow process. It's important because
113: * some things in TJDO, notably the macro mechanism used in e.g. view
114: * definitions, tend to repeatedly search for class names that don't exist.
115: *
116: * @param className fully qualified name of the desired class.
117: *
118: * @exception ClassNotFoundException
119: * if the class cannot be located.
120: *
121: * @see Class#forName(String)
122: */
123:
124: private Class classForName(String className)
125: throws ClassNotFoundException {
126: ClassLoader loader = getClass().getClassLoader();
127: Set names;
128:
129: synchronized (nonExistentClassNamesByLoader) {
130: names = (Set) nonExistentClassNamesByLoader.get(loader);
131:
132: if (names == null) {
133: names = Collections.synchronizedSet(new HashSet());
134: nonExistentClassNamesByLoader.put(loader, names);
135: } else {
136: if (names.contains(className))
137: throw new ClassNotFoundException(className
138: + " (cached from previous lookup attempt)");
139: }
140: }
141:
142: try {
143: return Class.forName(className, true, loader);
144: } catch (ClassNotFoundException e) {
145: LOG.debug("Caching the fact that " + className
146: + " was not found by " + loader);
147: names.add(className);
148: throw e;
149: }
150: }
151:
152: public Class resolveClassDeclaration(String classDecl)
153: throws ClassNotFoundException {
154: String className;
155: String dims = null;
156: int bktIdx = classDecl.indexOf("[]");
157:
158: if (bktIdx > 0) {
159: className = classDecl.substring(0, bktIdx);
160: StringBuffer dimBuf = new StringBuffer();
161:
162: do {
163: dimBuf.append('[');
164: bktIdx += 2;
165: } while (classDecl.startsWith("[]", bktIdx));
166:
167: if (bktIdx < classDecl.length())
168: throw new ClassNotFoundException(classDecl);
169:
170: dims = dimBuf.toString();
171: } else
172: className = classDecl;
173:
174: Class c;
175: int p = Arrays.binarySearch(primitives, className);
176:
177: if (p >= 0) {
178: if (dims == null)
179: c = primitiveClasses[p];
180: else
181: c = classForName(dims + primitiveTypeChars[p]);
182: } else {
183: c = (Class) importedClassesByName.get(className);
184:
185: if (c == null) {
186: try {
187: c = classForName(className);
188: } catch (ClassNotFoundException e) {
189: Iterator packageNames = importedPackageNames
190: .iterator();
191:
192: while (packageNames.hasNext()) {
193: String packageName = (String) packageNames
194: .next();
195:
196: try {
197: Class c1 = classForName(packageName + '.'
198: + className);
199:
200: if (c != null)
201: throw new JDOUserException(
202: "Ambiguous class declaration, could be "
203: + c.getName() + " or "
204: + c1.getName());
205:
206: c = c1;
207: } catch (ClassNotFoundException e1) {
208: }
209: }
210:
211: if (c == null)
212: throw e;
213:
214: LOG
215: .warn(
216: "Class "
217: + c.getName()
218: + " was not explicitly imported; this may have a noticeable impact on performance",
219: new JDOUserException());
220: }
221: }
222:
223: if (dims != null)
224: c = classForName(dims + 'L' + c.getName() + ';');
225: }
226:
227: return c;
228: }
229: }
|