/*
* Copyright 2015 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.
*
* 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.compiler.builder.impl;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.drools.compiler.compiler.PackageRegistry;
import org.drools.compiler.compiler.TypeDeclarationError;
import org.drools.compiler.lang.descr.Annotated;
import org.drools.compiler.lang.descr.PackageDescr;
import org.drools.core.factmodel.ClassDefinition;
import org.drools.core.factmodel.FieldDefinition;
import org.drools.core.factmodel.traits.Thing;
import org.drools.core.factmodel.traits.Trait;
import org.drools.core.rule.TypeDeclaration;
import org.drools.core.util.ClassUtils;
import org.kie.api.definition.type.Modifies;
import org.kie.api.definition.type.Position;
import org.kie.api.io.Resource;
import org.kie.api.runtime.rule.Match;
import static org.drools.compiler.builder.impl.TypeDeclarationConfigurator.processTypeAnnotations;
import static org.drools.core.util.BitMaskUtil.isSet;
public class TypeDeclarationCache {
private KnowledgeBuilderImpl kbuilder;
private Map<String, TypeDeclaration> cacheTypes = new HashMap<String, TypeDeclaration>();
TypeDeclarationCache( KnowledgeBuilderImpl kbuilder ) {
this.kbuilder = kbuilder;
initBuiltinTypeDeclarations();
}
private void initBuiltinTypeDeclarations() {
initBuiltinTypeDeclaration( Collection.class );
initBuiltinTypeDeclaration( Map.class );
initBuiltinTypeDeclaration( Match.class );
initBuiltinTypeDeclaration(Thing.class).setKind( TypeDeclaration.Kind.TRAIT );
}
private TypeDeclaration initBuiltinTypeDeclaration(Class<?> cls) {
TypeDeclaration type = new TypeDeclaration( cls.getSimpleName() );
type.setTypesafe( false );
type.setTypeClass( cls );
setClassDefinitionOnTypeDeclaration( cls, type );
cacheTypes.put( cls.getCanonicalName(), type );
return type;
}
public TypeDeclaration getAndRegisterTypeDeclaration( Class<?> cls, String packageName ) {
if (cls.isPrimitive() || cls.isArray()) {
return null;
}
TypeDeclaration typeDeclaration = getCachedTypeDeclaration(cls);
if (typeDeclaration != null) {
registerTypeDeclaration(packageName, typeDeclaration);
return typeDeclaration;
}
typeDeclaration = getExistingTypeDeclaration(cls);
if (typeDeclaration != null) {
initTypeDeclaration(cls, typeDeclaration);
return typeDeclaration;
}
typeDeclaration = createTypeDeclarationForBean(cls);
initTypeDeclaration( cls, typeDeclaration );
registerTypeDeclaration( packageName, typeDeclaration );
return typeDeclaration;
}
TypeDeclaration getTypeDeclaration( Class<?> cls ) {
if (cls.isPrimitive() || cls.isArray())
return null;
// If this class has already been accessed, it'll be in the cache
TypeDeclaration tdecl = getCachedTypeDeclaration(cls);
return tdecl != null ? tdecl : createTypeDeclaration(cls);
}
private void registerTypeDeclaration( String packageName,
TypeDeclaration typeDeclaration ) {
if (typeDeclaration.getNature() == TypeDeclaration.Nature.DECLARATION || packageName.equals( typeDeclaration.getTypeClass().getPackage().getName() )) {
PackageRegistry packageRegistry = kbuilder.getPackageRegistry(packageName);
if (packageRegistry != null) {
packageRegistry.getPackage().addTypeDeclaration(typeDeclaration);
} else {
kbuilder.newPackage(new PackageDescr(packageName, ""));
kbuilder.getPackageRegistry(packageName).getPackage().addTypeDeclaration(typeDeclaration);
}
}
}
private TypeDeclaration createTypeDeclaration(Class<?> cls) {
TypeDeclaration typeDeclaration = getExistingTypeDeclaration(cls);
if (typeDeclaration == null) {
typeDeclaration = createTypeDeclarationForBean(cls);
}
initTypeDeclaration(cls, typeDeclaration);
return typeDeclaration;
}
public TypeDeclaration getCachedTypeDeclaration(Class<?> cls) {
return getCachedTypeDeclaration( cls.getName() );
}
public TypeDeclaration getCachedTypeDeclaration(String className) {
return cacheTypes.get(className);
}
private TypeDeclaration getExistingTypeDeclaration(Class<?> cls) {
TypeDeclaration typeDeclaration = null;
PackageRegistry pkgReg = kbuilder.getPackageRegistry( ClassUtils.getPackage( cls ) );
if (pkgReg != null) {
String className = cls.getName();
String typeName = className.substring(className.lastIndexOf( "." ) + 1 );
typeDeclaration = pkgReg.getPackage().getTypeDeclaration(typeName);
}
return typeDeclaration;
}
private void initTypeDeclaration(Class<?> cls,
TypeDeclaration typeDeclaration) {
ClassDefinition clsDef = typeDeclaration.getTypeClassDef();
if (clsDef == null) {
clsDef = setClassDefinitionOnTypeDeclaration( cls, typeDeclaration );
} else {
processFieldsPosition( cls, clsDef, typeDeclaration );
}
if (typeDeclaration.isPropertyReactive()) {
processModifiedProps(cls, clsDef);
}
// build up a set of all the super classes and interfaces
Set<TypeDeclaration> tdecls = new LinkedHashSet<TypeDeclaration>();
tdecls.add(typeDeclaration);
buildTypeDeclarations(cls,
tdecls);
// Iterate and for each typedeclr assign it's value if it's not already set
// We start from the rear as those are the furthest away classes and interfaces
TypeDeclaration[] tarray = tdecls.toArray(new TypeDeclaration[tdecls.size()]);
for (int i = tarray.length - 1; i >= 0; i--) {
TypeDeclaration currentTDecl = tarray[i];
if (!isSet(typeDeclaration.getSetMask(),
TypeDeclaration.ROLE_BIT) && isSet(currentTDecl.getSetMask(),
TypeDeclaration.ROLE_BIT)) {
typeDeclaration.setRole(currentTDecl.getRole());
}
if (!isSet(typeDeclaration.getSetMask(),
TypeDeclaration.FORMAT_BIT) && isSet(currentTDecl.getSetMask(),
TypeDeclaration.FORMAT_BIT)) {
typeDeclaration.setFormat(currentTDecl.getFormat());
}
if (!isSet(typeDeclaration.getSetMask(),
TypeDeclaration.TYPESAFE_BIT) && isSet(currentTDecl.getSetMask(),
TypeDeclaration.TYPESAFE_BIT)) {
typeDeclaration.setTypesafe(currentTDecl.isTypesafe());
}
}
this.cacheTypes.put(cls.getName(),
typeDeclaration);
}
private ClassDefinition setClassDefinitionOnTypeDeclaration( Class<?> cls, TypeDeclaration typeDeclaration ) {
ClassDefinition clsDef = new ClassDefinition();
ClassDefinitionFactory.populateDefinitionFromClass( clsDef, typeDeclaration.getResource(), cls, cls.getAnnotation( Trait.class ) != null );
typeDeclaration.setTypeClassDef(clsDef);
return clsDef;
}
private void processFieldsPosition( Class<?> cls,
ClassDefinition clsDef,
TypeDeclaration typeDeclaration ) {
// it's a new type declaration, so generate the @Position for it
Collection<Field> fields = new LinkedList<Field>();
Class<?> tempKlass = cls;
while (tempKlass != null && tempKlass != Object.class) {
Collections.addAll( fields, tempKlass.getDeclaredFields() );
tempKlass = tempKlass.getSuperclass();
}
FieldDefinition[] orderedFields = new FieldDefinition[ fields.size() ];
for (Field fld : fields) {
Position pos = fld.getAnnotation(Position.class);
if (pos != null) {
if (pos.value() < 0 || pos.value() >= fields.size()) {
kbuilder.addBuilderResult(new TypeDeclarationError(typeDeclaration,
"Out of range position " + pos.value() + " for field '" + fld.getName() + "' on class " + cls.getName()));
continue;
}
if (orderedFields[pos.value()] != null) {
kbuilder.addBuilderResult(new TypeDeclarationError(typeDeclaration,
"Duplicated position " + pos.value() + " for field '" + fld.getName() + "' on class " + cls.getName()));
continue;
}
FieldDefinition fldDef = clsDef.getField(fld.getName());
if (fldDef == null) {
fldDef = new FieldDefinition(fld.getName(), fld.getType().getName());
}
fldDef.setIndex(pos.value());
orderedFields[ pos.value() ] = fldDef;
}
}
for (FieldDefinition fld : orderedFields) {
if (fld != null) {
// it's null if there is no @Position
clsDef.addField(fld);
}
}
}
private void processModifiedProps(Class<?> cls,
ClassDefinition clsDef) {
for (Method method : cls.getDeclaredMethods()) {
Modifies modifies = method.getAnnotation(Modifies.class);
if (modifies != null) {
String[] props = modifies.value();
List<String> properties = new ArrayList<String>(props.length);
for (String prop : props) {
properties.add(prop.trim());
}
clsDef.addModifiedPropsByMethod(method,
properties);
}
}
}
private TypeDeclaration createTypeDeclarationForBean(Class<?> cls) {
TypeDeclaration typeDeclaration = new TypeDeclaration(cls);
String namespace = ClassUtils.getPackage( cls );
PackageRegistry pkgRegistry = kbuilder.getPackageRegistry( namespace );
if (pkgRegistry == null) {
pkgRegistry = kbuilder.createPackageRegistry( new PackageDescr(namespace) );
}
processTypeAnnotations( kbuilder, pkgRegistry, new Annotated.ClassAdapter(cls), typeDeclaration );
return typeDeclaration;
}
private void buildTypeDeclarations(Class<?> cls,
Set<TypeDeclaration> tdecls) {
// Process current interfaces
Class<?>[] intfs = cls.getInterfaces();
for (Class<?> intf : intfs) {
buildTypeDeclarationInterfaces(intf,
tdecls);
}
// Process super classes and their interfaces
cls = cls.getSuperclass();
while (cls != null && cls != Object.class) {
if (!buildTypeDeclarationInterfaces(cls,
tdecls)) {
break;
}
cls = cls.getSuperclass();
}
}
private boolean buildTypeDeclarationInterfaces(Class cls,
Set<TypeDeclaration> tdecls) {
PackageRegistry pkgReg;
TypeDeclaration tdecl = this.cacheTypes.get((cls.getName()));
if (tdecl == null) {
pkgReg = kbuilder.getPackageRegistry(ClassUtils.getPackage(cls));
if (pkgReg != null) {
tdecl = pkgReg.getPackage().getTypeDeclaration(cls.getSimpleName());
}
}
if (tdecl != null) {
if (!tdecls.add(tdecl)) {
return false; // the interface already exists, return to stop recursion
}
}
Class<?>[] intfs = cls.getInterfaces();
for (Class<?> intf : intfs) {
pkgReg = kbuilder.getPackageRegistry(ClassUtils.getPackage(intf));
if (pkgReg != null) {
tdecl = pkgReg.getPackage().getTypeDeclaration(intf.getSimpleName());
}
if (tdecl != null) {
tdecls.add(tdecl);
}
}
for (Class<?> intf : intfs) {
if (!buildTypeDeclarationInterfaces(intf,
tdecls)) {
return false;
}
}
return true;
}
void removeTypesGeneratedFromResource(Resource resource) {
if (cacheTypes != null) {
List<String> typesToBeRemoved = new ArrayList<String>();
for (Map.Entry<String, TypeDeclaration> type : cacheTypes.entrySet()) {
if (resource.equals(type.getValue().getResource())) {
typesToBeRemoved.add(type.getKey());
}
}
for (String type : typesToBeRemoved) {
cacheTypes.remove(type);
}
}
}
}