/* * 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.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.lang.reflect.Field; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.drools.core.base.AccessorKey.AccessorType; import org.drools.core.base.extractors.MVELDateClassFieldReader; import org.drools.core.base.extractors.MVELNumberClassFieldReader; import org.drools.core.base.extractors.MVELObjectClassFieldReader; import org.drools.core.rule.TypeDeclaration; import org.drools.core.spi.AcceptsClassObjectType; import org.drools.core.spi.AcceptsReadAccessor; import org.drools.core.spi.ClassWireable; import org.drools.core.spi.InternalReadAccessor; import org.drools.core.util.asm.ClassFieldInspector; import org.kie.api.definition.type.FactField; import org.kie.internal.builder.KnowledgeBuilderResult; public class ClassFieldAccessorStore implements Externalizable { private static final long serialVersionUID = 510l; private Map<AccessorKey, BaseLookupEntry> lookup; private ClassFieldAccessorCache cache; /** * This field is just there to assist in testing */ private boolean eagerWire = true; public ClassFieldAccessorStore() { lookup = new HashMap<AccessorKey, BaseLookupEntry>(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject( lookup ); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { lookup = (Map<AccessorKey, BaseLookupEntry>) in.readObject(); } public void setEagerWire(boolean eagerWire) { this.eagerWire = eagerWire; } public void setClassFieldAccessorCache(ClassFieldAccessorCache cache) { this.cache = cache; } public ClassFieldReader getReader(Class cls, String fieldName) { return getReader( cls.getName(), fieldName, null, AccessorKey.AccessorType.FieldAccessor ); } public ClassFieldReader getReader(final String className, final String fieldName, final AcceptsReadAccessor target) { return getReader( className, fieldName, target, AccessorKey.AccessorType.FieldAccessor ); } public synchronized ClassFieldReader getReader(final String className, String fieldName, final AcceptsReadAccessor target, final AccessorKey.AccessorType accessorType) { AccessorKey key = new AccessorKey( className, fieldName, accessorType ); FieldLookupEntry entry = (FieldLookupEntry) this.lookup.get( key ); boolean exists = entry != null; if ( !exists ) { entry = new FieldLookupEntry( new ClassFieldReader( className, fieldName ) ); } if ( this.eagerWire ) { wire( entry.getClassFieldReader() ); ClassFieldReader reader = (ClassFieldReader) entry.getClassFieldReader(); if ( ! reader.hasReadAccessor() ) { return null; } } if ( target != null ) { target.setReadAccessor( entry.getClassFieldReader() ); } if ( !exists ) { // we delay the key writing as we only want to do it if the wiring was successful this.lookup.put( key, entry ); } return ( ClassFieldReader ) entry.getClassFieldReader(); } public ClassFieldReader getReader(AccessorKey key) { FieldLookupEntry entry = (FieldLookupEntry) this.lookup.get( key ); return entry != null ? ( ClassFieldReader ) entry.getClassFieldReader() : null; } public InternalReadAccessor getMVELReader(final String pkgName, final String className, final String expr, final boolean typesafe, Class returnType) { AccessorKey key = new AccessorKey( pkgName + className, expr, AccessorKey.AccessorType.FieldAccessor ); FieldLookupEntry entry = (FieldLookupEntry) this.lookup.get( key ); if ( entry == null ) { InternalReadAccessor reader = getReadAcessor( className, expr, typesafe, returnType ); entry = new FieldLookupEntry( reader ); this.lookup.put( key, entry ); } return entry.getClassFieldReader(); } public static InternalReadAccessor getReadAcessor(String className, String expr, boolean typesafe, Class returnType) { if (Number.class.isAssignableFrom( returnType ) || ( returnType == byte.class || returnType == short.class || returnType == int.class || returnType == long.class || returnType == float.class || returnType == double.class ) ) { return new MVELNumberClassFieldReader( className, expr, typesafe ); } else if ( Date.class.isAssignableFrom( returnType ) ) { return new MVELDateClassFieldReader( className, expr, typesafe ); } else { return new MVELObjectClassFieldReader( className, expr, typesafe ); } } public ClassFieldAccessor getAccessor(Class cls, String fieldName) { return getAccessor( cls.getName(), fieldName ); } public ClassFieldAccessor getAccessor(final String className, final String fieldName) { AccessorKey key = new AccessorKey( className, fieldName, AccessorKey.AccessorType.FieldAccessor ); FieldLookupEntry entry = (FieldLookupEntry) this.lookup.get( key ); if ( entry == null ) { entry = new FieldLookupEntry( new ClassFieldReader( className, fieldName ), new ClassFieldWriter( className, fieldName ) ); this.lookup.put( key, entry ); } ClassFieldAccessor accessor = new ClassFieldAccessor( (ClassFieldReader) entry.getClassFieldReader(), entry.getClassFieldWriter() ); if ( this.eagerWire ) { wire( entry.getClassFieldReader() ); wire( entry.getClassFieldWriter() ); } return accessor; } public ClassObjectType getClassObjectType(final ClassObjectType objectType, final AcceptsClassObjectType target) { return getClassObjectType( objectType, objectType.isEvent(), target ); } public ClassObjectType getClassObjectType(final ClassObjectType objectType, final boolean isEvent, final AcceptsClassObjectType target) { AccessorKey key = new AccessorKey( objectType.getClassName(), isEvent ? "$$DROOLS__isEvent__" : null, AccessorKey.AccessorType.ClassObjectType ); ClassObjectTypeLookupEntry entry = (ClassObjectTypeLookupEntry) this.lookup.get( key ); if ( entry == null ) { entry = new ClassObjectTypeLookupEntry( cache.getClassObjectType( objectType, false ) ); this.lookup.put( key, entry ); } if ( target != null ) { target.setClassObjectType( entry.getClassObjectType() ); } return entry.getClassObjectType(); } public void removeType(TypeDeclaration type) { lookup.remove(new AccessorKey( type.getTypeClassName(), null, AccessorKey.AccessorType.ClassObjectType )); for (FactField field : type.getTypeClassDef().getFields()) { lookup.remove(new AccessorKey( type.getTypeClassName(), field.getName(), AccessorKey.AccessorType.FieldAccessor )); } } public void removeClass(Class<?> clazz) { lookup.remove(new AccessorKey( clazz.getName(), null, AccessorKey.AccessorType.ClassObjectType )); for (Field field : clazz.getDeclaredFields()) { lookup.remove(new AccessorKey( clazz.getName(), field.getName(), AccessorKey.AccessorType.FieldAccessor )); } } public void merge(ClassFieldAccessorStore other) { for ( Entry<AccessorKey, BaseLookupEntry> entry : other.lookup.entrySet() ) { switch ( entry.getValue().getAccessorType() ) { case FieldAccessor : { FieldLookupEntry lookupEntry = (FieldLookupEntry) this.lookup.get( entry.getKey() ); if ( lookupEntry == null ) { lookupEntry = (FieldLookupEntry) entry.getValue(); this.lookup.put( entry.getKey(), lookupEntry ); } // wire up ClassFieldReaders if (lookupEntry.getClassFieldReader() != null ) { InternalReadAccessor reader = ((FieldLookupEntry)entry.getValue()).getClassFieldReader(); BaseClassFieldReader accessor = wire(reader); if (other.cache != null && reader instanceof ClassFieldReader) { other.cache.setReadAcessor( (ClassFieldReader) reader, accessor ); } } if (lookupEntry.getClassFieldWriter() != null) { ClassFieldWriter writer = ((FieldLookupEntry)entry.getValue()).getClassFieldWriter(); BaseClassFieldWriter accessor = wire(writer); if (other.cache != null) { other.cache.setWriteAcessor( writer, accessor ); } } break; } case ClassObjectType : { ClassObjectTypeLookupEntry lookupEntry = (ClassObjectTypeLookupEntry) this.lookup.get( entry.getKey() ); if ( lookupEntry == null ) { // Create new entry with correct ClassObjectType and targets ClassObjectType oldObjectType = ((ClassObjectTypeLookupEntry) entry.getValue()).getClassObjectType(); ClassObjectType newObjectType = cache.getClassObjectType( oldObjectType, true ); this.lookup.put( entry.getKey(), new ClassObjectTypeLookupEntry( newObjectType ) ); // also rewire the class of the old ClassObjectType in case it is still in use oldObjectType.setClassType( newObjectType.getClassType() ); } } } } } public void wire() { for ( Entry<AccessorKey, BaseLookupEntry> entry : lookup.entrySet() ) { switch ( entry.getValue().getAccessorType() ) { case FieldAccessor : { InternalReadAccessor reader = ((FieldLookupEntry) entry.getValue()).getClassFieldReader(); if ( reader != null ) { wire( reader ); } ClassFieldWriter writer = ((FieldLookupEntry) entry.getValue()).getClassFieldWriter(); if ( writer != null ) { wire( writer ); } break; } case ClassObjectType : { ClassObjectType classObjectType = ((ClassObjectTypeLookupEntry) entry.getValue()).getClassObjectType(); wire( classObjectType ); break; } } } } public BaseClassFieldReader wire(InternalReadAccessor reader) { if ( reader instanceof ClassFieldReader ) { BaseClassFieldReader accessor = cache.getReadAcessor( (ClassFieldReader) reader ); ((ClassFieldReader)reader).setReadAccessor( accessor ); return accessor; } return null; } public Class<?> getFieldType(Class<?> clazz, String fieldName) { return ClassFieldAccessorFactory.getFieldType( clazz, fieldName, cache.getCacheEntry(clazz) ); } public BaseClassFieldWriter wire(ClassFieldWriter writer) { BaseClassFieldWriter accessor = cache.getWriteAcessor( writer ); writer.setWriteAccessor( accessor ); return accessor; } public void wire( ClassWireable wireable ) { try { if ( wireable.getClassType() == null || ! wireable.getClassType().isPrimitive() ) { Class cls = this.cache.getClassLoader().loadClass( wireable.getClassName() ); wireable.wire( cls ); } } catch ( ClassNotFoundException e ) { throw new RuntimeException( "Unable to load ClassObjectType class '" + wireable.getClassName() + "'" ); } } public Collection<KnowledgeBuilderResult> getWiringResults( Class klass, String fieldName ) { if ( cache == null ) { return Collections.EMPTY_LIST; } Map<Class<?>, ClassFieldInspector> inspectors = cache.getCacheEntry( klass ).getInspectors(); return inspectors.containsKey( klass ) ? inspectors.get( klass ).getInspectionResults( fieldName ) : Collections.EMPTY_LIST; } public interface BaseLookupEntry extends Externalizable { AccessorKey.AccessorType getAccessorType(); } public static class ClassObjectTypeLookupEntry implements BaseLookupEntry { ClassObjectType classObjectType; public ClassObjectTypeLookupEntry() { super(); } public ClassObjectTypeLookupEntry(ClassObjectType classObjectType) { super(); this.classObjectType = classObjectType; } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject( classObjectType ); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { classObjectType = (ClassObjectType) in.readObject(); } public ClassObjectType getClassObjectType() { return classObjectType; } public void setClassObjectType(ClassObjectType classObjectType) { this.classObjectType = classObjectType; } public AccessorType getAccessorType() { return AccessorKey.AccessorType.ClassObjectType; } } public static class FieldLookupEntry implements BaseLookupEntry { private InternalReadAccessor reader; private ClassFieldWriter writer; public FieldLookupEntry() { } public FieldLookupEntry(InternalReadAccessor reader) { this.reader = reader; } public FieldLookupEntry(ClassFieldWriter writer) { this.writer = writer; } public FieldLookupEntry(ClassFieldReader reader, ClassFieldWriter writer) { this.writer = writer; this.reader = reader; } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject( reader ); out.writeObject( writer ); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { reader = (InternalReadAccessor) in.readObject(); writer = (ClassFieldWriter) in.readObject(); } public InternalReadAccessor getClassFieldReader() { return reader; } public ClassFieldWriter getClassFieldWriter() { return this.writer; } public AccessorType getAccessorType() { return AccessorKey.AccessorType.FieldAccessor; } } }