/* * Copyright 2007-2010 Brian S O'Neill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright 2006 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.cojen.util; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.UndeclaredThrowableException; import java.util.Map; import java.security.AccessController; import java.security.PrivilegedAction; import org.cojen.classfile.CodeBuilder; import org.cojen.classfile.RuntimeClassFile; import org.cojen.classfile.TypeDesc; /** * Generates code to invoke constructors. This is a replacement for {@link * java.lang.reflect.Constructor} which is easier to use and performs * better. In one tested situation, overall performance was improved by about * 10%. * * <p>QuickConstructorGenerator is not general purpose however, as the * parameters to the constructor must be known, and the constructor must be * public. It is intended to be used for constructing instances of * auto-generated classes. The exact parameters may be known at compile time, * but the actual object type is not. * * @author Brian S O'Neill * @since 2.1 */ public class QuickConstructorGenerator { // Map<factory class, Map<object type, factory instance>> @SuppressWarnings("unchecked") private static Map<Class<?>, Map<Class<?>, Object>> cCache = new WeakIdentityMap(); /** * Returns a factory instance for one type of object. Each method in the * interface defines a constructor via its parameters. Any checked * exceptions declared thrown by the constructor must also be declared by * the method. The method return types can be the same type as the * constructed object or a supertype. * * <p>Here is a contrived example for constructing strings. In practice, * such a string factory is useless, since the "new" operator can be * invoked directly. * * <pre> * public interface StringFactory { * String newEmptyString(); * * String newStringFromChars(char[] chars); * * String newStringFromBytes(byte[] bytes, String charsetName) * throws UnsupportedEncodingException; * } * </pre> * * Here's an example of it being used: * * <pre> * StringFactory sf = QuickConstructorGenerator.getInstance(String.class, StringFactory.class); * ... * String str = sf.newStringFromChars(new char[] {'h', 'e', 'l', 'l', 'o'}); * </pre> * * @param objectType type of object to construct * @param factory interface defining which objects can be constructed * @throws IllegalArgumentException if factory type is not an interface or * if it is malformed */ @SuppressWarnings("unchecked") public static synchronized <F> F getInstance(final Class<?> objectType, final Class<F> factory) { Map<Class<?>, Object> innerCache = cCache.get(factory); if (innerCache == null) { innerCache = new SoftValuedHashMap(); cCache.put(factory, innerCache); } F instance = (F) innerCache.get(objectType); if (instance != null) { return instance; } if (objectType == null) { throw new IllegalArgumentException("No object type"); } if (factory == null) { throw new IllegalArgumentException("No factory type"); } if (!factory.isInterface()) { throw new IllegalArgumentException("Factory must be an interface"); } final Map<Class<?>, Object> fInnerCache = innerCache; return AccessController.doPrivileged(new PrivilegedAction<F>() { public F run() { return getInstance(fInnerCache, objectType, factory); } }); } private static synchronized <F> F getInstance(Map<Class<?>, Object> innerCache, Class<?> objectType, Class<F> factory) { String prefix = objectType.getName(); if (prefix.startsWith("java.")) { // Defining classes in java packages is restricted. int index = prefix.lastIndexOf('.'); if (index > 0) { prefix = prefix.substring(index + 1); } } RuntimeClassFile cf = null; for (Method method : factory.getMethods()) { if (!Modifier.isAbstract(method.getModifiers())) { continue; } Constructor ctor; try { ctor = objectType.getConstructor((Class[]) method.getParameterTypes()); } catch (NoSuchMethodException e) { throw new IllegalArgumentException(e); } if (!method.getReturnType().isAssignableFrom(objectType)) { throw new IllegalArgumentException ("Method return type must be \"" + objectType.getName() + "\" or supertype: " + method); } Class<?>[] methodExTypes = method.getExceptionTypes(); for (Class<?> ctorExType : ctor.getExceptionTypes()) { if (RuntimeException.class.isAssignableFrom(ctorExType) || Error.class.isAssignableFrom(ctorExType)) { continue; } exCheck: { // Make sure method declares throwing it or a supertype. for (Class<?> methodExType : methodExTypes) { if (methodExType.isAssignableFrom(ctorExType)) { break exCheck; } } throw new IllegalArgumentException("Method must declare throwing \"" + ctorExType.getName() +"\": " + method); } } if (cf == null) { cf = new RuntimeClassFile(prefix, null, objectType.getClassLoader()); cf.setSourceFile(QuickConstructorGenerator.class.getName()); cf.setTarget("1.5"); cf.addInterface(factory); cf.markSynthetic(); cf.addDefaultConstructor(); } // Now define the method that constructs the object. CodeBuilder b = new CodeBuilder(cf.addMethod(method)); b.newObject(TypeDesc.forClass(objectType)); b.dup(); int count = b.getParameterCount(); for (int i=0; i<count; i++) { b.loadLocal(b.getParameter(i)); } b.invoke(ctor); b.returnValue(TypeDesc.OBJECT); } if (cf == null) { // No methods found to implement. throw new IllegalArgumentException("No methods in factory to implement"); } F instance; try { instance = (F) cf.defineClass().newInstance(); } catch (IllegalAccessException e) { throw new UndeclaredThrowableException(e); } catch (InstantiationException e) { throw new UndeclaredThrowableException(e); } innerCache.put(objectType, instance); return instance; } }