package org.kie.dmn.feel.lang.impl;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import org.kie.dmn.feel.lang.CompositeType;
import org.kie.dmn.feel.lang.FEELProperty;
import org.kie.dmn.feel.lang.FEELType;
import org.kie.dmn.feel.lang.Type;
import org.kie.dmn.feel.lang.types.BuiltInType;
import org.kie.dmn.feel.parser.feel11.ParserHelper;
import org.kie.dmn.feel.util.EvalHelper;
public class JavaBackedType implements CompositeType {
private static Map<Class<?>, JavaBackedType> cache = new HashMap<>();
private static Set<Method> javaObjectMethods = new HashSet<>( Arrays.asList( Object.class.getMethods() ) );
private Class<?> wrapped;
private Map<String, Type> properties = new LinkedHashMap<>();
private JavaBackedType(Class<?> class1) {
this.wrapped = class1;
Stream.of( class1.getMethods() )
.filter( m -> Modifier.isPublic( m.getModifiers() ) || Modifier.isProtected( m.getModifiers() ) )
.filter( m -> ! javaObjectMethods.contains(m) )
.flatMap( m -> Stream.<Function<Method, Optional<String>>>of(JavaBackedType::methodToCustomProperty, EvalHelper::propertyFromAccessor)
.map(f -> f.apply( m ))
.filter(Optional::isPresent)
.map(p -> new Property( p.get(), ParserHelper.determineTypeFromClass( m.getReturnType() ) ) ) )
.forEach( p -> properties.put( p.name , p.type ) );
}
/**
* If method m is annotated with FEELProperty, will return FEELProperty.value, otherwise empty.
*/
private static Optional<String> methodToCustomProperty(Method m) {
return Optional.ofNullable(m.getAnnotation(FEELProperty.class)).map(a->a.value());
}
/**
* If clazz can be represented as a JavaBackedType, returns a JavaBackedType for representing clazz.
* If clazz can not be represented as a JavaBackedType, returns BuiltInType.UNKNOWN.
* This method performs memoization when necessary.
* @param clazz the class to be represented as JavaBackedType
* @return JavaBackedType representing clazz or BuiltInType.UNKNOWN
*/
public static Type of(Class<?> clazz) {
return Optional.ofNullable( (Type) cache.computeIfAbsent( clazz, JavaBackedType::createIfAnnotated ) ).orElse( BuiltInType.UNKNOWN );
}
/**
* For internal use, returns a new JavaBackedType if clazz can be represented as such, returns null otherwise.
*/
private static JavaBackedType createIfAnnotated(Class<?> clazz) {
if (clazz.isAnnotationPresent(FEELType.class) || Stream.of(clazz.getMethods()).anyMatch(m->m.getAnnotation(FEELProperty.class)!=null)) {
return new JavaBackedType(clazz) ;
}
return null;
}
@Override
public String getName() {
return wrapped.getName();
}
public Class<?> getWrapped() {
return wrapped;
}
@Override
public Map<String, Type> getFields() {
return this.properties;
}
private static class Property {
public final String name;
public final Type type;
public Property( String name, Type type ) {
this.name = name;
this.type = type;
}
}
@Override
public boolean isInstanceOf(Object o) {
return wrapped.getClass().isInstance(o);
}
@Override
public boolean isAssignableValue(Object value) {
return value == null || wrapped.getClass().isAssignableFrom(value.getClass());
}
}