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.meta;
020:
021: import java.lang.reflect.Method;
022: import java.io.ByteArrayInputStream;
023: import java.security.AccessController;
024: import java.security.PrivilegedActionException;
025: import java.util.Set;
026: import java.util.HashSet;
027: import java.util.Map;
028: import java.util.WeakHashMap;
029:
030: import org.apache.commons.lang.StringUtils;
031: import org.apache.openjpa.enhance.PCEnhancer;
032: import org.apache.openjpa.util.InternalException;
033: import org.apache.openjpa.lib.util.J2DoPrivHelper;
034: import org.apache.openjpa.lib.util.Localizer;
035: import serp.bytecode.BCClass;
036: import serp.bytecode.BCClassLoader;
037: import serp.bytecode.BCField;
038: import serp.bytecode.BCMethod;
039: import serp.bytecode.Code;
040: import serp.bytecode.Constants;
041: import serp.bytecode.Project;
042:
043: /**
044: * Creates implementations of managed interfaces. Will throw exceptions
045: * on unknown properties.
046: *
047: * @author Steve Kim
048: */
049: class InterfaceImplGenerator {
050: private static final Localizer _loc = Localizer
051: .forPackage(InterfaceImplGenerator.class);
052: private static final String POSTFIX = "openjpaimpl";
053:
054: private final MetaDataRepository _repos;
055: private final Map _impls = new WeakHashMap();
056: private final Project _project = new Project();
057:
058: // distinct project / loader for enhanced version of class
059: private final Project _enhProject = new Project();
060:
061: /**
062: * Constructor. Supply repository.
063: */
064: public InterfaceImplGenerator(MetaDataRepository repos) {
065: _repos = repos;
066: }
067:
068: /**
069: * Create a concrete implementation of the given type, possibly
070: * returning a cached version of the class.
071: */
072: public synchronized Class createImpl(ClassMetaData meta) {
073: Class iface = meta.getDescribedType();
074:
075: // check cache.
076: Class impl = (Class) _impls.get(iface);
077: if (impl != null)
078: return impl;
079:
080: ClassLoader parentLoader = (ClassLoader) AccessController
081: .doPrivileged(J2DoPrivHelper
082: .getClassLoaderAction(iface));
083: BCClassLoader loader = (BCClassLoader) AccessController
084: .doPrivileged(J2DoPrivHelper.newBCClassLoaderAction(
085: _project, parentLoader));
086: BCClassLoader enhLoader = (BCClassLoader) AccessController
087: .doPrivileged(J2DoPrivHelper.newBCClassLoaderAction(
088: _enhProject, parentLoader));
089: BCClass bc = _project.loadClass(getClassName(meta));
090: bc.declareInterface(iface);
091: ClassMetaData sup = meta.getPCSuperclassMetaData();
092: if (sup != null) {
093: bc.setSuperclass(sup.getInterfaceImpl());
094: enhLoader = (BCClassLoader) AccessController
095: .doPrivileged(J2DoPrivHelper
096: .newBCClassLoaderAction(
097: _enhProject,
098: (ClassLoader) AccessController
099: .doPrivileged(J2DoPrivHelper
100: .getClassLoaderAction(sup
101: .getInterfaceImpl()))));
102: }
103:
104: FieldMetaData[] fields = meta.getDeclaredFields();
105: Set methods = new HashSet();
106: for (int i = 0; i < fields.length; i++)
107: addField(bc, iface, fields[i], methods);
108: invalidateNonBeanMethods(bc, iface, methods);
109:
110: // first load the base class as the enhancer requires the class
111: // to be available
112: try {
113: meta.setInterfaceImpl(Class.forName(bc.getName(), true,
114: loader));
115: } catch (Throwable t) {
116: throw new InternalException(_loc.get("interface-load",
117: iface, loader), t).setFatal(true);
118: }
119: // copy the BCClass into the enhancer project.
120: bc = _enhProject.loadClass(new ByteArrayInputStream(bc
121: .toByteArray()), loader);
122: PCEnhancer enhancer = new PCEnhancer(_repos, bc, meta);
123:
124: int result = enhancer.run();
125: if (result != PCEnhancer.ENHANCE_PC)
126: throw new InternalException(_loc.get(
127: "interface-badenhance", iface)).setFatal(true);
128: try {
129: // load the class for real.
130: impl = Class.forName(bc.getName(), true, enhLoader);
131: } catch (Throwable t) {
132: throw new InternalException(_loc.get("interface-load2",
133: iface, enhLoader), t).setFatal(true);
134: }
135: // cache the generated impl.
136: _impls.put(iface, impl);
137: return impl;
138: }
139:
140: /**
141: * Add bean getters and setters, also recording seen methods
142: * into the given set.
143: */
144: private void addField(BCClass bc, Class iface, FieldMetaData fmd,
145: Set methods) {
146: String name = fmd.getName();
147: Class type = fmd.getDeclaredType();
148: BCField field = bc.declareField(name, type);
149: field.setAccessFlags(Constants.ACCESS_PRIVATE);
150:
151: // getter
152: name = StringUtils.capitalize(name);
153: String prefix = isGetter(iface, fmd) ? "get" : "is";
154: BCMethod meth = bc.declareMethod(prefix + name, type, null);
155: meth.makePublic();
156: Code code = meth.getCode(true);
157: code.aload().setThis();
158: code.getfield().setField(field);
159: code.xreturn().setType(type);
160: code.calculateMaxStack();
161: code.calculateMaxLocals();
162: methods.add(getMethodSafe(iface, meth.getName(), null));
163:
164: // setter
165: meth = bc.declareMethod("set" + name, void.class,
166: new Class[] { type });
167: meth.makePublic();
168: code = meth.getCode(true);
169: code.aload().setThis();
170: code.xload().setParam(0).setType(type);
171: code.putfield().setField(field);
172: code.vreturn();
173: code.calculateMaxStack();
174: code.calculateMaxLocals();
175: methods.add(getMethodSafe(iface, meth.getName(), type));
176: }
177:
178: /**
179: * Invalidate methods on the interface which are not managed.
180: */
181: private void invalidateNonBeanMethods(BCClass bc, Class iface,
182: Set methods) {
183: Method[] meths = (Method[]) AccessController
184: .doPrivileged(J2DoPrivHelper
185: .getDeclaredMethodsAction(iface));
186: BCMethod meth;
187: Code code;
188: Class type = _repos.getMetaDataFactory().getDefaults()
189: .getUnimplementedExceptionType();
190: for (int i = 0; i < meths.length; i++) {
191: if (methods.contains(meths[i]))
192: continue;
193: meth = bc.declareMethod(meths[i].getName(), meths[i]
194: .getReturnType(), meths[i].getParameterTypes());
195: meth.makePublic();
196: code = meth.getCode(true);
197: code.anew().setType(type);
198: code.dup();
199: code.invokespecial().setMethod(type, "<init>", void.class,
200: null);
201: code.athrow();
202: code.calculateMaxLocals();
203: code.calculateMaxStack();
204: }
205: }
206:
207: /**
208: * Return a unique class name.
209: */
210: protected final String getClassName(ClassMetaData meta) {
211: Class iface = meta.getDescribedType();
212: return iface.getName() + "$" + System.identityHashCode(iface)
213: + POSTFIX;
214: }
215:
216: /**
217: * Convenience method to return the given method / arg.
218: */
219: private static Method getMethodSafe(Class iface, String name,
220: Class arg) {
221: try {
222: return (Method) AccessController
223: .doPrivileged(J2DoPrivHelper
224: .getDeclaredMethodAction(iface, name,
225: arg == null ? null
226: : new Class[] { arg }));
227: } catch (PrivilegedActionException pae) {
228: throw new InternalException(_loc.get("interface-mismatch",
229: name));
230: }
231: }
232:
233: private static boolean isGetter(Class iface, FieldMetaData fmd) {
234: if (fmd.getType() != boolean.class
235: && fmd.getType() != Boolean.class)
236: return true;
237: try {
238: Method meth = (Method) AccessController
239: .doPrivileged(J2DoPrivHelper
240: .getDeclaredMethodAction(iface, "is"
241: + StringUtils.capitalize(fmd
242: .getName()), (Class[]) null));
243: return meth == null;
244: } catch (PrivilegedActionException pae) {
245: }
246: return true;
247: }
248:
249: boolean isImplType(Class cls) {
250: return (cls.getName().endsWith(POSTFIX) && cls.getName()
251: .indexOf('$') != -1);
252: }
253:
254: public Class toManagedInterface(Class cls) {
255: Class[] ifaces = cls.getInterfaces();
256: for (int i = 0; i < ifaces.length; i++) {
257: if (_impls.get(ifaces[i]) == cls)
258: return ifaces[i];
259: }
260: throw new IllegalArgumentException(cls.getName());
261: }
262: }
|