/*
* Copyright 2010 JBoss Inc
*
* 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.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.drools.core.RuntimeDroolsException;
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.Acceptor;
import org.drools.core.spi.AcceptsClassObjectType;
import org.drools.core.spi.AcceptsReadAccessor;
import org.drools.core.spi.AcceptsWriteAccessor;
import org.drools.core.spi.ClassWireable;
import org.drools.core.spi.InternalReadAccessor;
import org.kie.api.definition.type.FactField;
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 ClassFieldWriter getWriter(Class cls,
String fieldName,
ClassLoader classLoader) {
return getWriter( cls.getName(),
fieldName,
null );
}
public ClassFieldReader getReader(Class cls,
String fieldName,
ClassLoader classLoader) {
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 = true;
if ( entry == null ) {
exists = false;
entry = new FieldLookupEntry( new ClassFieldReader( className,
fieldName ) );
}
if ( this.eagerWire ) {
wire( entry.getClassFieldReader() );
}
if ( target != null ) {
target.setReadAccessor( entry.getClassFieldReader() );
entry.addAccessorTarget( target );
}
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 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 synchronized ClassFieldWriter getWriter(final String className,
final String fieldName,
final AcceptsWriteAccessor target) {
AccessorKey key = new AccessorKey( className,
fieldName,
AccessorKey.AccessorType.FieldAccessor );
FieldLookupEntry entry = (FieldLookupEntry) this.lookup.get( key );
boolean exists = true;
if ( entry == null ) {
exists = false;
entry = new FieldLookupEntry( new ClassFieldWriter( className,
fieldName ) );
}
if ( this.eagerWire ) {
wire( entry.getClassFieldReader() );
}
if ( target != null ) {
target.setWriteAccessor( entry.getClassFieldWriter() );
entry.addAccessorTarget( target );
}
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 entry.getClassFieldWriter();
}
public ClassFieldAccessor getAccessor(Class cls,
String fieldName,
ClassLoader classLoader) {
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() );
entry.addAccessorTarget( accessor );
if ( this.eagerWire ) {
wire( entry.getClassFieldReader() );
wire( entry.getClassFieldWriter() );
}
return accessor;
}
// public PatternExtractor getObjectAccessor(final Class cls,
// final String identifier,
// final Declaration declaration) {
// AccessorKey key = new AccessorKey( cls.getName(),
// identifier, // we are re-using the fieldName as a global identifier
// AccessorKey.AccessorType.GlobalAccessor );
//
// ObjectExtractorLookupEntry entry = (ObjectExtractorLookupEntry) this.lookup.get( key );
// if ( entry == null ) {
// PatternExtractor extractor = (PatternExtractor) declaration.getExtractor();
// entry = new ObjectExtractorLookupEntry( extractor );
// this.lookup.put( key,
// entry );
// }
//
// entry.addAccessorTarget( declaration );
//
// // there is no wiring here as the GlobalExtractor already references the class,
// // although we will need to re-wire on serialisation
//
// return entry.getObjectExtractor();
// }
//
// public GlobalExtractor getGlobalAccessor(final ClassObjectType classObjectType,
// final String identifier,
// final Declaration declaration) {
// AccessorKey key = new AccessorKey( classObjectType.getClassType().getName(),
// identifier, // we are re-using the fieldName as a global identifier
// AccessorKey.AccessorType.GlobalAccessor );
//
// GlobalExtractorLookupEntry entry = (GlobalExtractorLookupEntry) this.lookup.get( key );
// if ( entry == null ) {
// entry = new GlobalExtractorLookupEntry( new GlobalExtractor( identifier,
// classObjectType ) );
// this.lookup.put( key,
// entry );
// }
//
// entry.addAccessorTarget( declaration );
// declaration.setReadAccessor( entry.getGlobalExtractor() );
//
// // there is no wiring here as the GlobalExtractor already references the class,
// // although we will need to re-wire on serialisation
//
// return entry.getGlobalExtractor();
// }
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 ) );
this.lookup.put( key,
entry );
}
if ( target != null ) {
entry.addAccessorTarget( target );
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 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 );
for ( Acceptor target : lookupEntry.getAccessorTargets() ) {
if ( target instanceof ClassWireable ) {
wire( (ClassWireable) target );
}
}
} else {
// iterate through new targets adding them and wiring them up
// to the existing ClassFieldReader, no need to wire generated accessor
// as we know it already exists
for ( Acceptor target : entry.getValue().getAccessorTargets() ) {
if ( target instanceof AcceptsReadAccessor ) {
((AcceptsReadAccessor) target).setReadAccessor( lookupEntry.getClassFieldReader() );
} else if ( target instanceof AcceptsWriteAccessor ) {
((AcceptsWriteAccessor) target).setWriteAccessor( lookupEntry.getClassFieldWriter() );
}
lookupEntry.addAccessorTarget( target );
}
}
// wire up ClassFieldReaders
if (lookupEntry.getClassFieldReader() != null ) {
wire(((FieldLookupEntry)entry.getValue()).getClassFieldReader());
}
if (lookupEntry.getClassFieldWriter() != null) {
wire(((FieldLookupEntry)entry.getValue()).getClassFieldWriter());
}
break;
}
case ClassObjectType : {
ClassObjectTypeLookupEntry lookupEntry = (ClassObjectTypeLookupEntry) this.lookup.get( entry.getKey() );
if ( lookupEntry == null ) {
// Create new entry with correct ClassObjectType and targets
lookupEntry = new ClassObjectTypeLookupEntry( cache.getClassObjectType( ((ClassObjectTypeLookupEntry) entry.getValue()).getClassObjectType() ) );
this.lookup.put( entry.getKey(),
lookupEntry );
}
for ( Acceptor target : entry.getValue().getAccessorTargets() ) {
((AcceptsClassObjectType) target).setClassObjectType( lookupEntry.getClassObjectType() );
lookupEntry.addAccessorTarget( target );
}
}
// case ObjectAccessor : {
// ObjectExtractorLookupEntry lookupEntry = ( ObjectExtractorLookupEntry ) this.lookup.get( entry.getKey() );
// if ( lookupEntry == null ) {
// lookupEntry = ( ObjectExtractorLookupEntry ) entry.getValue();
// this.lookup.put( entry.getKey(),
// lookupEntry );
// wire( lookupEntry.getObjectExtractor() );
// } else {
// for ( Acceptor target : entry.getValue().getAccessorTargets() ) {
// ((Declaration)target).setReadAccessor( lookupEntry.getObjectExtractor() );
// lookupEntry.addAccessorTarget( target );
// }
// }
// break;
// }
// case GlobalAccessor : {
// GlobalExtractorLookupEntry lookupEntry = ( GlobalExtractorLookupEntry ) this.lookup.get( entry.getKey() );
// if ( lookupEntry == null ) {
// lookupEntry = ( GlobalExtractorLookupEntry ) entry.getValue();
// this.lookup.put( entry.getKey(),
// lookupEntry );
// wire( lookupEntry.getGlobalExtractor() );
// } else {
// for ( Acceptor target : entry.getValue().getAccessorTargets() ) {
// ((Declaration)target).setReadAccessor( lookupEntry.getGlobalExtractor() );
// lookupEntry.addAccessorTarget( target );
// }
// }
// break;
// }
}
}
}
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;
}
// case ObjectAccessor : {
// PatternExtractor reader = ((ObjectExtractorLookupEntry) entry.getValue()).getObjectExtractor();
// wire( reader );
// break;
// }
// case GlobalAccessor : {
// GlobalExtractor reader = ((GlobalExtractorLookupEntry) entry.getValue()).getGlobalExtractor();
// wire( reader );
// break;
// }
}
}
}
public void wire(InternalReadAccessor reader) {
if ( reader instanceof ClassFieldReader ) {
((ClassFieldReader)reader).setReadAccessor( cache.getReadAcessor( (ClassFieldReader) reader ) );
}
}
public void wire(ClassFieldWriter writer) {
writer.setWriteAccessor( cache.getWriteAcessor( writer ) );
}
// public void wire(PatternExtractor reader) {
// ObjectType objectType = reader.getObjectType();
//
// if ( objectType instanceof ClassObjectType ) {
// ClassObjectType cot = (ClassObjectType) objectType;
// try {
// Class cls = this.cache.getClassLoader().loadClass( cot.getClassName() );
// cot.setClassType( cls );
// } catch ( ClassNotFoundException e ) {
// throw new RuntimeDroolsException( "Unable to load ClassObjectType class '" + cot.getClassName() + "'" );
// }
// }
// }
// public void wire(ClassObjectType objectType) {
// try {
// Class cls = this.cache.getClassLoader().loadClass( objectType.getClassName() );
// objectType.setClassType( cls );
// } catch ( ClassNotFoundException e ) {
// throw new RuntimeDroolsException( "Unable to load ClassObjectType class '" + objectType.getClassName() + "'" );
// }
// }
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 RuntimeDroolsException( "Unable to load ClassObjectType class '" + wireable.getClassName() + "'" );
}
}
public static abstract class BaseLookupEntry
implements
Externalizable {
// we use an identity hashmap to avoid hashcode/equals being called on stored targets
private Map<Acceptor, Object> accessorTargets = Collections.<Acceptor, Object> emptyMap();
public BaseLookupEntry() {
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject( accessorTargets );
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
accessorTargets = (Map<Acceptor, Object>) in.readObject();
}
public Set<Acceptor> getAccessorTargets() {
return accessorTargets.keySet();
}
public void addAccessorTarget(Acceptor target) {
if ( this.accessorTargets == Collections.EMPTY_MAP ) {
this.accessorTargets = new IdentityHashMap<Acceptor, Object>();
}
this.accessorTargets.put( target, null );
}
public void addAccessorTargets(Set<Acceptor> targets) {
if ( this.accessorTargets == Collections.EMPTY_MAP ) {
this.accessorTargets = new IdentityHashMap<Acceptor, Object>( );
}
for ( Acceptor target : targets ) {
this.accessorTargets.put( target, null );
}
}
public void removeTarget(Acceptor target) {
this.accessorTargets.remove( target );
}
public abstract AccessorKey.AccessorType getAccessorType();
}
public static class ClassObjectTypeLookupEntry extends BaseLookupEntry {
ClassObjectType classObjectType;
public ClassObjectTypeLookupEntry() {
super();
}
public ClassObjectTypeLookupEntry(ClassObjectType classObjectType) {
super();
this.classObjectType = classObjectType;
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal( out );
out.writeObject( classObjectType );
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
super.readExternal( in );
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 GlobalExtractorLookupEntry extends BaseLookupEntry {
// GlobalExtractor globalExtractor;
//
// public GlobalExtractorLookupEntry() {
// super();
// }
//
// public GlobalExtractorLookupEntry(GlobalExtractor globalExtractor) {
// super();
// this.globalExtractor = globalExtractor;
// }
//
// public void writeExternal(ObjectOutput out) throws IOException {
// super.writeExternal( out );
// out.writeObject( globalExtractor );
// }
//
// public void readExternal(ObjectInput in) throws IOException,
// ClassNotFoundException {
// super.readExternal( in );
// globalExtractor = (GlobalExtractor) in.readObject();
// }
//
// public GlobalExtractor getGlobalExtractor() {
// return globalExtractor;
// }
//
// public AccessorType getAccessorType() {
// return AccessorKey.AccessorType.GlobalAccessor;
// }
//
// }
//
// public static class ObjectExtractorLookupEntry extends BaseLookupEntry {
// PatternExtractor patternExtractor;
//
// public ObjectExtractorLookupEntry() {
// super();
// }
//
// public ObjectExtractorLookupEntry(PatternExtractor patternExtractor) {
// super();
// this.patternExtractor = patternExtractor;
// }
//
// public void writeExternal(ObjectOutput out) throws IOException {
// super.writeExternal( out );
// out.writeObject( patternExtractor );
// }
//
// public void readExternal(ObjectInput in) throws IOException,
// ClassNotFoundException {
// super.readExternal( in );
// patternExtractor = (PatternExtractor) in.readObject();
// }
//
// public PatternExtractor getObjectExtractor() {
// return patternExtractor;
// }
//
// public AccessorType getAccessorType() {
// return AccessorKey.AccessorType.ObjectAccessor;
// }
// }
//
public static class FieldLookupEntry extends 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 {
super.writeExternal( out );
out.writeObject( reader );
out.writeObject( writer );
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
super.readExternal( in );
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;
}
}
// public static class LookupEntry
// implements
// Externalizable {
// private ClassFieldReader reader;
// private ClassFieldWriter writer;
// private Set<AcceptsAccessor> accessorTargets = Collections.<AcceptsAccessor> emptySet();
//
// public LookupEntry() {
//
// }
//
// public void writeExternal(ObjectOutput out) throws IOException {
// out.writeObject( reader );
// out.writeObject( writer );
// out.writeObject( accessorTargets );
// }
//
// public void readExternal(ObjectInput in) throws IOException,
// ClassNotFoundException {
// reader = (ClassFieldReader) in.readObject();
// writer = (ClassFieldWriter) in.readObject();
// accessorTargets = (Set<AcceptsAccessor>) in.readObject();
//
// }
//
// public LookupEntry(ClassFieldReader reader) {
// this.reader = reader;
// }
//
// public LookupEntry(ClassFieldWriter writer) {
// this.writer = writer;
// }
//
// public LookupEntry(ClassFieldReader reader,
// ClassFieldWriter writer) {
// this.writer = writer;
// this.reader = reader;
// }
//
// public ClassFieldReader getClassFieldReader() {
// return reader;
// }
//
// public ClassFieldWriter getClassFieldWriter() {
// return this.writer;
// }
//
// public Set<AcceptsAccessor> getAccessorTargets() {
// return accessorTargets;
// }
//
// public void addAccessorTarget(AcceptsAccessor target) {
// if ( this.accessorTargets == Collections.EMPTY_SET ) {
// this.accessorTargets = new HashSet<AcceptsAccessor>();
// }
//
// this.accessorTargets.add( target );
// }
//
// public void removeConstraint(Constraint constraint) {
// this.accessorTargets.remove( constraint );
// }
// }
}