/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.serial.classmetamodel;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamField;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.log4j.Logger;
import org.jboss.serial.util.ClassMetaConsts;
import org.jboss.serial.util.FastHashMap;
import org.jboss.serial.util.PartitionedWeakHashMap;
/**
* $Id: ClassMetamodelFactory.java 326 2006-07-28 04:57:13Z csuconic $
* @author clebert suconic
*/
public class ClassMetamodelFactory implements ClassMetaConsts
{
private static final Logger log = Logger.getLogger(ClassMetamodelFactory.class);
private static final boolean isDebug = log.isDebugEnabled();
/** table mapping primitive type names to corresponding class objects.
* This code was created by Carlos de Wolf on a fix of EJBTHREE-440.
* By coincidence I needed to parse primitive values, and instead of reinventing the wheel I got the same code. */
private static final HashMap primClasses = new HashMap(8, 1.0F);
static {
primClasses.put("boolean", boolean.class);
primClasses.put("byte", byte.class);
primClasses.put("char", char.class);
primClasses.put("short", short.class);
primClasses.put("int", int.class);
primClasses.put("long", long.class);
primClasses.put("float", float.class);
primClasses.put("double", double.class);
primClasses.put("void", void.class);
primClasses.put("[Z", boolean[].class);
primClasses.put("[B", byte[].class);
primClasses.put("[C", char[].class);
primClasses.put("[S", short[].class);
primClasses.put("[I", int[].class);
primClasses.put("[J", long[].class);
primClasses.put("[F", float[].class);
primClasses.put("[D", double[].class);
}
/** We are caching the getClassLoader operation, to avoid locks on the WeakHashMap */
private static class CacheLoaderReference
{
WeakReference currentClassLoader;
Map currentHashMap;
public ClassLoader getCurrentClassLoader()
{
if (currentClassLoader==null)
return null;
else
return (ClassLoader)currentClassLoader.get();
}
public void setCurrentClassLoader(ClassLoader loader)
{
currentClassLoader = new WeakReference(loader);
}
public Map getCurrentMap()
{
return currentHashMap;
}
public void setCurrentMap(Map currentMap)
{
this.currentHashMap = currentMap;
}
}
/**
* Method for ObjectStreamClass.lookup
* */
static Method methodLookup;
/**
* Method for ObjectStreamClass.getField
*/
static Method methodGetField;
/** The HashMap for SystemclassLoader */
private static Map systemClassLoaderMap = new FastHashMap();
private static ObjectStreamClass lookup(Class clazz) throws IllegalAccessException, InvocationTargetException
{
return (ObjectStreamClass) methodLookup.invoke(null,new Object[]{clazz,Boolean.TRUE});
}
private static Field getField(Object source) throws IllegalAccessException, InvocationTargetException
{
return (Field) methodGetField.invoke(source,EMPTY_OBJECT_ARRAY);
}
static ThreadLocal cacheLoader = new ThreadLocal();
static
{
try
{
methodLookup = ObjectStreamClass.class.getDeclaredMethod("lookup",new Class[]{Class.class,Boolean.TYPE});
methodLookup.setAccessible(true);
}
catch (Throwable e)
{
e.printStackTrace();
}
try
{
methodGetField = ObjectStreamField.class.getDeclaredMethod("getField",new Class[]{});
methodGetField.setAccessible(true);
}
catch (Throwable e)
{
e.printStackTrace();
}
}
public static void clear(boolean elimiteClassLoadersCached)
{
Iterator iter = cache.values().iterator();
while (iter.hasNext())
{
Map map = (Map)iter.next();
map.clear();
}
if (elimiteClassLoadersCached) cache.clear();
systemClassLoaderMap.clear();
}
public static void clear()
{
clear(true);
}
public static Map getCache()
{
return cache;
}
public static void printCacheDebug()
{
java.util.Iterator iter = getCache().keySet().iterator();
while (iter.hasNext())
{
Object loader = iter.next();
log.debug("ClassLoader = " + loader);
Map classesMap = (Map)getCache().get(loader);
java.util.Iterator classesIterator = classesMap.keySet().iterator();
while (classesIterator.hasNext())
{
log.debug("Class = " + classesIterator.next());
}
}
}
/** PartitionedWeakHashMap<ClassLoader,ConcurrentHashMap<String,ClassMetaData>>
* (The current implementation is using FastHashMap instead of ConcurrentHashMap)
* **/
static PartitionedWeakHashMap cache = new PartitionedWeakHashMap();
static ClassMetaData proxyMetaData = null;
static
{
try
{
proxyMetaData=getClassMetaData("java.lang.reflect.Proxy",Thread.currentThread().getContextClassLoader(),true);
proxyMetaData.setProxy(true);
}
catch (Exception e)
{
e.printStackTrace();;
}
}
private static Map getLoaderMap(ClassLoader loader)
{
if (loader==null)
{
return systemClassLoaderMap;
}
CacheLoaderReference loaderReference = (CacheLoaderReference)cacheLoader.get();
if (loaderReference==null)
{
loaderReference = new CacheLoaderReference();
cacheLoader.set(loaderReference);
}
if (loaderReference.getCurrentClassLoader()==loader)
{
return loaderReference.getCurrentMap();
}
Map hashMap = (Map)cache.get(loader);
ClassLoader returnLoader = null;
if (hashMap==null)
{
hashMap = new FastHashMap();
cache.put(loader,hashMap);
hashMap= (Map)cache.get(loader);
}
loaderReference.setCurrentClassLoader(loader);
loaderReference.setCurrentMap(hashMap);
return hashMap;
}
private static ClassMetaData getClassMetaData(String clazzName, ClassLoader loader, boolean checkSerializable) throws IOException
{
return getClassMetaData(clazzName,null,loader,checkSerializable);
}
private static Class resolveClassByName(String clazzName, ClassResolver resolver, ClassLoader loader) throws ClassNotFoundException
{
Class clazz = (Class)primClasses.get(clazzName);
if (clazz==null)
{
if (resolver!=null)
{
clazz = resolver.resolveClass(clazzName);
if (clazz==null)
{
clazz = Class.forName(clazzName,false,loader);
}
}
else
{
clazz = Class.forName(clazzName,false,loader);
}
}
return clazz;
}
public static ClassMetaData getClassMetaData(String clazzName, ClassResolver resolver, ClassLoader loader, boolean checkSerializable) throws IOException
{
try
{
Map loaderMap = getLoaderMap(loader);
ClassMetaData classMetadata = (ClassMetaData)loaderMap.get(clazzName);
if (classMetadata==null)
{
Class clazz = resolveClassByName(clazzName, resolver, loader);
if (checkSerializable && !Serializable.class.isAssignableFrom(clazz))
{
throw new NotSerializableException(clazz.getName());
}
classMetadata = new ClassMetaData(clazz);
loaderMap = getLoaderMap(loader);
loaderMap.put(clazzName,classMetadata);
classMetadata = (ClassMetaData)loaderMap.get(clazzName);
}
if (classMetadata.getClazz()==null)
{
// this never happens, so, this is just in case
// The only possibility for that would be crossed classLoader operations.
// I tried to replicate this scenario on a testcase and it was not possible.
// So, I decided to have this verification here to be safe
if (isDebug)
{
log.debug("Rebuilding clazz due to cross Loader GC (getClassMetaData(String class, ClassLoader loader)");
}
Class clazz = resolveClassByName(clazzName,resolver,loader);
loaderMap.remove(clazzName);
classMetadata = new ClassMetaData(clazz);
loaderMap.put(clazz.getName(),classMetadata);
classMetadata = (ClassMetaData)loaderMap.get(clazzName);
}
// It looks dummy but doing Class.isProxy is a very expensive operation, so we just do it on a cached basis
if (classMetadata.isProxy())
{
return proxyMetaData;
} else
{
return classMetadata;
}
}
catch (ClassNotFoundException e)
{
IOException ex = new IOException (e.getMessage());
ex.initCause(e);
throw ex;
}
}
public static ClassMetaData getClassMetaData(Class clazz, boolean checkSerializable) throws IOException
{
if (checkSerializable && !Serializable.class.isAssignableFrom(clazz) && !clazz.isPrimitive())
{
throw new NotSerializableException(clazz.getName());
}
Map loaderMap = getLoaderMap(clazz.getClassLoader());
ClassMetaData classMetadata = (ClassMetaData)loaderMap.get(clazz.getName());
if (classMetadata==null)
{
classMetadata = new ClassMetaData(clazz);
loaderMap.put(clazz.getName(),classMetadata);
classMetadata = (ClassMetaData)loaderMap.get(clazz.getName());
}
if (classMetadata.getClazz()==null)
{
// this never happens, so, this is just in case
// The only possibility for that would be crossed classLoader operations.
// I tried to replicate this scenario on a testcase and it was not possible.
// So, I decided to have this verification here to be safe
if (isDebug)
{
log.debug("Rebuilding clazz due to cross Loader GC (getClassMetaData(Class clazz, ClassLoader loader)");
}
loaderMap.remove(clazz.getName());
classMetadata = new ClassMetaData(clazz);
loaderMap.put(clazz.getName(),classMetadata);
classMetadata = (ClassMetaData)loaderMap.get(clazz.getName());
}
// It looks dummy but doing Class.isProxy is a very expensive operation, so we just do it on a cached basis
if (classMetadata.isProxy())
{
return proxyMetaData;
} else
{
return classMetadata;
}
}
public static boolean isImmutable(Class clazz)
{
return (clazz==Character.class || clazz==String.class ||
clazz == Long.class ||
clazz == Byte.class ||
clazz == Double.class ||
clazz == Float.class ||
clazz == Integer.class ||
clazz == Short.class ||
clazz == Boolean.class ||
clazz.isPrimitive());
}
}