/* * Copyright (c) 2002 Cunningham & Cunningham, Inc. * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence * * This file is part of FitGoodies. * * FitGoodies is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * FitGoodies is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with FitGoodies. If not, see <http://www.gnu.org/licenses/>. */ package de.cologneintelligence.fitgoodies.database.dynamic; import org.apache.bcel.Constants; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.FieldGen; import org.apache.bcel.generic.Type; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashMap; /** * Factory that allows to generate java classes on the fly. * The class will have a public constructor and only public variables. * <p> * This class is primary written for internal use. */ public class DynamicObjectFactory { /** * Class loader which is able to load the dynamic generated class. * * @author jwierum */ public static class JavaClassClassloader extends java.lang.ClassLoader { /** * Default constructor. Creates a new {@code ClassLoader}. */ public JavaClassClassloader() { super(JavaClassClassloader.class.getClassLoader()); } @Override public final Class<?> loadClass(final String name) throws ClassNotFoundException { if (cache.containsKey(name)) { return cache.get(name); } else { return super.loadClass(name); } } private final HashMap<String, Class<?>> cache = new HashMap<>(); /** * Loads the byte code of {@code javaClass}, defines a class * and resolves it. The loaded class is returned. * * @param name class name * @param javaClass {@code JavaClass} object which holds the * dynamically generated class. * @return the loaded class */ public final Class<?> loadJavaClass(final String name, final JavaClass javaClass) { byte[] binClass = javaClass.getBytes(); Class<?> c = defineClass(javaClass.getClassName(), binClass, 0, binClass.length); resolveClass(c); cache.put(name, c); return c; } } private final ClassGen cg; private Class<?> result; private static int classCount = 1; private static JavaClassClassloader loader; static { loader = AccessController.doPrivileged( new PrivilegedAction<JavaClassClassloader>() { @Override public JavaClassClassloader run() { return new JavaClassClassloader(); } }); } /** * Default constructor. Prepares a new class. */ public DynamicObjectFactory() { cg = new ClassGen( "$DynamicGeneratedObject$" + classCount, "java.lang.Object", "<generated>", Constants.ACC_PUBLIC | Constants.ACC_SUPER, null); ++classCount; cg.addEmptyConstructor(Constants.ACC_PUBLIC); } /** * Adds a public field to the constructed class. * * @param type type of the field * @param name name of the field * @throws ClassNotFoundException indicates a problem with {@code type} */ public void add(final Class<?> type, final String name) throws ClassNotFoundException { FieldGen fg; if (result != null) { throw new IllegalStateException("Class already generated"); } fg = new FieldGen(Constants.ACC_PUBLIC | Constants.ACC_SUPER, Type.getType(type), name, cg.getConstantPool()); cg.addField(fg.getField()); } /** * Compiles the class and returns a class object which contains all * added fields. * * @return dynamic generated class with all added fields. */ public final Class<?> compile() { if (result == null) { loader.loadJavaClass(cg.getClassName(), cg.getJavaClass()); try { result = loader.loadClass(cg.getClassName()); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } return result; } }