/* * Copyright 2010 Red Hat, Inc. and/or its affiliates. * * 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.drools.core.base; import java.security.ProtectionDomain; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.drools.core.util.ByteArrayClassLoader; import org.drools.core.util.ClassUtils; import org.drools.core.util.asm.ClassFieldInspector; import static org.drools.core.util.ClassUtils.convertPrimitiveNameToType; public class ClassFieldAccessorCache { private Map<ClassLoader, CacheEntry> cacheByClassLoader; private ClassLoader classLoader; public ClassFieldAccessorCache(ClassLoader classLoader) { // lookup = new HashMap<AccessorKey, LookupEntry>(); cacheByClassLoader = new WeakHashMap<ClassLoader, CacheEntry>(); this.classLoader = classLoader; } public ClassLoader getClassLoader() { return this.classLoader; } public ClassObjectType getClassObjectType(ClassObjectType objectType, boolean lookupClass) { // lookup the class when the ClassObjectType might refer to the class from another ClassLoader Class cls = lookupClass ? getClass( objectType.getClassName() ) : objectType.getClassType(); CacheEntry cache = getCacheEntry( cls ); return cache.getClassObjectType( cls, objectType ); } public static class ClassObjectTypeKey { private Class cls; private boolean event; public ClassObjectTypeKey(Class cls, boolean event) { this.cls = cls; this.event = event; } public Class getCls() { return cls; } public boolean isEvent() { return event; } public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((cls == null) ? 0 : cls.hashCode()); result = prime * result + (event ? 1231 : 1237); return result; } public boolean equals(Object obj) { if ( this == obj ) return true; if ( obj == null ) return false; if ( !(obj instanceof ClassObjectTypeKey) ) return false; ClassObjectTypeKey other = (ClassObjectTypeKey) obj; if ( cls == null ) { if ( other.cls != null ) return false; } else if ( !cls.equals( other.cls ) ) return false; return event == other.event; } } public BaseClassFieldReader getReadAcessor(ClassFieldReader reader) { // get the ReaderAccessor for this key Class cls = getClass( reader.getClassName() ); return getCacheEntry( cls ).getReadAccessor( getAccessorKey( reader ), cls ); } public void setReadAcessor(ClassFieldReader reader, BaseClassFieldReader readAccessor) { // get the ReaderAccessor for this key Class cls = getClass( reader.getClassName() ); getCacheEntry( cls ).setReadAccessor( getAccessorKey( reader ), readAccessor ); } private AccessorKey getAccessorKey( ClassFieldReader reader ) { String className = reader.getClassName(); String fieldName = reader.getFieldName(); return new AccessorKey( className, fieldName, AccessorKey.AccessorType.FieldAccessor ); } public BaseClassFieldWriter getWriteAcessor(ClassFieldWriter writer) { // get the ReaderAccessor for this key Class cls = getClass( writer.getClassName() ); return getCacheEntry( cls ).getWriteAccessor( getAccessorKey( writer ), cls ); } public void setWriteAcessor(ClassFieldWriter writer, BaseClassFieldWriter writeAccessor ) { // get the ReaderAccessor for this key Class cls = getClass( writer.getClassName() ); getCacheEntry( cls ).setWriteAccessor( getAccessorKey( writer ), writeAccessor ); } private AccessorKey getAccessorKey( ClassFieldWriter writer ) { String className = writer.getClassName(); String fieldName = writer.getFieldName(); return new AccessorKey( className, fieldName, AccessorKey.AccessorType.FieldAccessor ); } public Class getClass(String className) { try { Class<?> primitiveType = convertPrimitiveNameToType( className ); return primitiveType != null ? primitiveType : this.classLoader.loadClass( className ); } catch ( ClassNotFoundException e ) { throw new RuntimeException( "Unable to resolve class '" + className + "'" ); } } public CacheEntry getCacheEntry(Class cls) { // System classloader classes return null on some JVMs ClassLoader cl = cls.getClassLoader() != null ? cls.getClassLoader() : this.classLoader; CacheEntry cache = this.cacheByClassLoader.get( cl ); if ( cache == null ) { // setup a cache for this ClassLoader cache = new CacheEntry( this.classLoader ); this.cacheByClassLoader.put( cl, cache ); } return cache; } public static class CacheEntry { private final ByteArrayClassLoader byteArrayClassLoader; private final ConcurrentMap<AccessorKey, BaseClassFieldReader> readCache = new ConcurrentHashMap<AccessorKey, BaseClassFieldReader>(); private final ConcurrentMap<AccessorKey, BaseClassFieldWriter> writeCache = new ConcurrentHashMap<AccessorKey, BaseClassFieldWriter>(); private final ConcurrentMap<Class< ? >, ClassFieldInspector> inspectors = new ConcurrentHashMap<Class< ? >, ClassFieldInspector>(); private final ConcurrentMap<ClassObjectTypeKey, ClassObjectType> objectTypes = new ConcurrentHashMap<ClassObjectTypeKey, ClassObjectType>(); public CacheEntry(ClassLoader parentClassLoader) { if ( parentClassLoader == null ) { throw new RuntimeException( "ClassFieldAccessorFactory cannot have a null parent ClassLoader" ); } this.byteArrayClassLoader = ClassUtils.isAndroid() ? (ByteArrayClassLoader) ClassUtils.instantiateObject( "org.drools.android.MultiDexClassLoader", null, parentClassLoader) : new DefaultByteArrayClassLoader( parentClassLoader ); } public ByteArrayClassLoader getByteArrayClassLoader() { return byteArrayClassLoader; } public BaseClassFieldReader getReadAccessor(AccessorKey key, Class cls) { BaseClassFieldReader reader = this.readCache.get( key ); if ( reader == null ) { reader = ClassFieldAccessorFactory.getClassFieldReader( cls, key.getFieldName(), this ); if ( reader != null ) { BaseClassFieldReader existingReader = this.readCache.putIfAbsent( key, reader ); if ( existingReader != null ) { // Raced, use the (now) existing entry reader = existingReader; } } } return reader; } public void setReadAccessor(AccessorKey key, BaseClassFieldReader reader) { this.readCache.put( key, reader ); } public BaseClassFieldWriter getWriteAccessor(AccessorKey key, Class cls) { BaseClassFieldWriter writer = this.writeCache.get( key ); if ( writer == null ) { writer = ClassFieldAccessorFactory.getClassFieldWriter( cls, key.getFieldName(), this ); if ( writer != null ) { BaseClassFieldWriter existingWriter = this.writeCache.putIfAbsent( key, writer ); if ( existingWriter != null ) { // Raced, use the (now) existing entry writer = existingWriter; } } } return writer; } public void setWriteAccessor(AccessorKey key, BaseClassFieldWriter writer) { this.writeCache.put( key, writer ); } public Map<Class< ? >, ClassFieldInspector> getInspectors() { return inspectors; } public ClassObjectType getClassObjectType(Class<?> cls, ClassObjectType objectType) { ClassObjectTypeKey key = new ClassObjectTypeKey( cls, objectType.isEvent() ); ClassObjectType existing = objectTypes.get( key ); if ( existing == null ) { objectType.setClassType( cls ); // most likely set, but set anyway. existing = objectTypes.putIfAbsent( key, objectType ); if ( existing == null ) { // Not raced, use the one we created. existing = objectType; } } return existing; } } public static class DefaultByteArrayClassLoader extends ClassLoader implements ByteArrayClassLoader { public DefaultByteArrayClassLoader(final ClassLoader parent) { super( parent ); } public Class< ? > defineClass(final String name, final byte[] bytes, final ProtectionDomain domain) { return defineClass( name, bytes, 0, bytes.length, domain ); } } }