/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.envers.internal.tools;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewConstructor;
import javassist.NotFoundException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.envers.internal.entities.PropertyData;
import static org.hibernate.envers.internal.tools.StringTools.capitalizeFirst;
import static org.hibernate.envers.internal.tools.StringTools.getLastComponent;
/**
* @author Lukasz Zuchowski (author at zuchos dot com)
*/
public final class MapProxyTool {
private MapProxyTool() {
}
/**
* Creates instance of map proxy class. This proxy class will be a java bean with properties from <code>propertyDatas</code>.
* Instance will proxy calls to instance of the map passed as parameter.
*
* @param className Name of the class to construct (should be unique within class loader)
* @param map instance that will be proxied by java bean
* @param propertyDatas properties that should java bean declare
* @param classLoaderService class loader service
*
* @return new instance of proxy
*/
public static Object newInstanceOfBeanProxyForMap(
String className,
Map<String, Object> map,
Set<PropertyData> propertyDatas,
ClassLoaderService classLoaderService) {
Map<String, Class<?>> properties = prepareProperties( propertyDatas );
return createNewInstance( map, classForName( className, properties, classLoaderService ) );
}
private static Object createNewInstance(Map<String, Object> map, Class aClass) {
try {
return aClass.getConstructor( Map.class ).newInstance( map );
}
catch (Exception e) {
throw new RuntimeException( e );
}
}
private static Map<String, Class<?>> prepareProperties(Set<PropertyData> propertyDatas) {
Map<String, Class<?>> properties = new HashMap<>();
for ( PropertyData propertyData : propertyDatas ) {
properties.put( propertyData.getBeanName(), Object.class );
}
return properties;
}
private static Class loadClass(String className, ClassLoaderService classLoaderService) {
try {
return ReflectionTools.loadClass( className, classLoaderService );
}
catch (ClassLoadingException e) {
return null;
}
}
/**
* Generates/loads proxy class for given name with properties for map.
*
* @param className name of the class that will be generated/loaded
* @param properties list of properties that should be exposed via java bean
* @param classLoaderService class loader service
*
* @return proxy class that wraps map into java bean
*/
public static Class classForName(
String className,
Map<String, Class<?>> properties,
ClassLoaderService classLoaderService) {
Class aClass = loadClass( className, classLoaderService );
if ( aClass == null ) {
aClass = generate( className, properties );
}
return aClass;
}
/**
* Protected for test only
*/
protected static Class generate(String className, Map<String, Class<?>> properties) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass( className );
cc.addInterface( resolveCtClass( Serializable.class ) );
cc.addField( new CtField( resolveCtClass( Map.class ), "theMap", cc ) );
cc.addConstructor( generateConstructor( className, cc ) );
for ( Entry<String, Class<?>> entry : properties.entrySet() ) {
// add getter
cc.addMethod( generateGetter( cc, entry.getKey(), entry.getValue() ) );
// add setter
cc.addMethod( generateSetter( cc, entry.getKey(), entry.getValue() ) );
}
return cc.toClass();
}
catch (Exception e) {
throw new RuntimeException( e );
}
}
private static CtConstructor generateConstructor(String className, CtClass cc)
throws NotFoundException, CannotCompileException {
StringBuffer sb = new StringBuffer();
sb.append( "public " )
.append( getLastComponent( className ) )
.append( "(" )
.append( Map.class.getName() )
.append( " map)" )
.append( "{" )
.append( "this.theMap = map;" )
.append( "}" );
return CtNewConstructor.make( sb.toString(), cc );
}
private static CtMethod generateGetter(CtClass declaringClass, String fieldName, Class fieldClass)
throws CannotCompileException {
String getterName = "get" + capitalizeFirst( fieldName );
StringBuilder sb = new StringBuilder();
sb.append( "public " ).append( fieldClass.getName() ).append( " " )
.append( getterName ).append( "(){" ).append( "return (" ).append( fieldClass.getName() ).append(
")this.theMap.get(\""
)
.append( fieldName ).append( "\")" ).append( ";" ).append( "}" );
return CtMethod.make( sb.toString(), declaringClass );
}
private static CtMethod generateSetter(CtClass declaringClass, String fieldName, Class fieldClass)
throws CannotCompileException {
String setterName = "set" + capitalizeFirst( fieldName );
StringBuilder sb = new StringBuilder();
sb.append( "public void " ).append( setterName ).append( "(" )
.append( fieldClass.getName() ).append( " " ).append( fieldName )
.append( ")" ).append( "{" ).append( "this.theMap.put(\"" ).append( fieldName )
.append( "\"," ).append( fieldName ).append( ")" ).append( ";" ).append( "}" );
return CtMethod.make( sb.toString(), declaringClass );
}
private static CtClass resolveCtClass(Class clazz) throws NotFoundException {
return resolveCtClass( clazz.getName() );
}
private static CtClass resolveCtClass(String clazz) throws NotFoundException {
try {
ClassPool pool = ClassPool.getDefault();
return pool.get( clazz );
}
catch (NotFoundException e) {
return null;
}
}
}