/* * 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.kie.workbench.common.services.datamodel.backend.server.builder.projects; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.drools.workbench.models.commons.backend.oracle.ProjectDataModelOracleImpl; import org.drools.workbench.models.datamodel.oracle.Annotation; import org.drools.workbench.models.datamodel.oracle.MethodInfo; import org.drools.workbench.models.datamodel.oracle.ModelField; import org.drools.workbench.models.datamodel.oracle.TypeSource; import org.kie.workbench.common.services.datamodel.backend.server.builder.util.AnnotationUtils; import org.kie.workbench.common.services.datamodel.backend.server.builder.util.BlackLists; /** * Builder for Fact Types originating from a .class */ public class ClassFactBuilder extends BaseFactBuilder { private final ClassToGenericClassConverter typeSystemConverter = new JavaTypeSystemTranslator(); private final Map<String, List<MethodInfo>> methodInformation = new HashMap<String, List<MethodInfo>>(); private final Map<String, String> fieldParametersType = new HashMap<String, String>(); private final List<String> superTypes; private final Set<Annotation> annotations = new LinkedHashSet<Annotation>(); private final Map<String, Set<Annotation>> fieldAnnotations = new HashMap<String, Set<Annotation>>(); private final Map<String, FactBuilder> fieldFactBuilders = new HashMap<String, FactBuilder>(); public ClassFactBuilder( final ProjectDataModelOracleBuilder builder, final Class<?> clazz, final boolean isEvent, final TypeSource typeSource ) throws IOException { this( builder, new HashMap<String, FactBuilder>(), clazz, isEvent, typeSource ); } public ClassFactBuilder( final ProjectDataModelOracleBuilder builder, final Map<String, FactBuilder> discoveredFieldFactBuilders, final Class<?> clazz, final boolean isEvent, final TypeSource typeSource ) throws IOException { super( builder, clazz, isEvent, typeSource ); this.superTypes = getSuperTypes( clazz ); this.annotations.addAll( AnnotationUtils.getClassAnnotations( clazz ) ); loadClassFields( clazz, discoveredFieldFactBuilders ); } @Override public void build( final ProjectDataModelOracleImpl oracle ) { super.build( oracle ); oracle.addProjectMethodInformation( methodInformation ); oracle.addProjectFieldParametersType( fieldParametersType ); oracle.addProjectSuperTypes( buildSuperTypes() ); oracle.addProjectTypeAnnotations( buildTypeAnnotations() ); oracle.addProjectTypeFieldsAnnotations( buildTypeFieldsAnnotations() ); } private List<String> getSuperTypes( final Class<?> clazz ) { ArrayList<String> strings = new ArrayList<String>(); Class<?> superType = clazz.getSuperclass(); while ( superType != null ) { strings.add( superType.getName() ); superType = superType.getSuperclass(); } return strings; } private void loadClassFields( final Class<?> clazz, final Map<String, FactBuilder> discoveredFieldFactBuilders ) throws IOException { if ( clazz == null ) { return; } final String factType = getType(); //Get all getters and setters for the class. This does not handle delegated properties //- FIELDS need a getter ("getXXX", "isXXX") or setter ("setXXX") or are public properties //- METHODS are any accessor that does not have a getter or setter final ClassFieldInspector inspector = new ClassFieldInspector( clazz ); final Set<String> fieldNames = inspector.getFieldNames(); for ( final String fieldName : fieldNames ) { final ClassFieldInspector.FieldInfo f = inspector.getFieldTypesFieldInfo().get( fieldName ); addParametricTypeForField( factType, fieldName, f.getGenericType() ); final Class<?> returnType = f.getReturnType(); final String genericReturnType = typeSystemConverter.translateClassToGenericType( returnType ); addField( new ModelField( fieldName, returnType.getName(), ModelField.FIELD_CLASS_TYPE.REGULAR_CLASS, f.getOrigin(), f.getAccessorAndMutator(), genericReturnType ) ); addEnumsForField( factType, fieldName, returnType ); //To prevent recursion we keep track of all ClassFactBuilder's created and re-use where applicable if ( BlackLists.isReturnTypeBlackListed( returnType ) ) { continue; } discoverFieldFactBuilder( genericReturnType, returnType, discoveredFieldFactBuilders ); // Check types on generic arguments if ( f.getGenericType() instanceof ParameterizedType ) { ParameterizedType parameterizedType = (ParameterizedType) f.getGenericType(); for ( Type parameterType : parameterizedType.getActualTypeArguments() ) { if ( discoveredFieldFactBuilders.containsKey( parameterType.getTypeName() ) ) { continue; } if ( parameterType instanceof Class ) { Class<?> parameterClazz = (Class<?>) parameterType; discoverFieldFactBuilder( parameterClazz.getName(), parameterClazz, discoveredFieldFactBuilders ); } } } Set<Annotation> fieldAnnotations = f.getAnnotations(); if ( fieldAnnotations != null && !fieldAnnotations.isEmpty() ) { this.fieldAnnotations.put( fieldName, f.getAnnotations() ); } } //Methods for use in Expressions and ActionCallMethod's ClassMethodInspector methodInspector = new ClassMethodInspector( clazz, typeSystemConverter ); final List<MethodInfo> methodInformation = methodInspector.getMethodInfos(); for ( final MethodInfo mi : methodInformation ) { final String genericType = mi.getParametricReturnType(); if ( genericType != null ) { final String qualifiedFactFieldName = factType + "#" + mi.getNameWithParameters(); this.fieldParametersType.put( qualifiedFactFieldName, genericType ); } } this.methodInformation.put( factType, methodInformation ); } protected void discoverFieldFactBuilder( final String genericTypeName, final Class<?> genericType, final Map<String, FactBuilder> discoveredFieldFactBuilders ) throws IOException { if ( !discoveredFieldFactBuilders.containsKey( genericTypeName ) ) { discoveredFieldFactBuilders.put( genericTypeName, null ); discoveredFieldFactBuilders.put( genericTypeName, new ClassFactBuilder( builder, discoveredFieldFactBuilders, genericType, false, typeSource ) ); } if ( discoveredFieldFactBuilders.get( genericTypeName ) != null ) { fieldFactBuilders.put( genericTypeName, discoveredFieldFactBuilders.get( genericTypeName ) ); } } private void addEnumsForField( final String className, final String fieldName, final Class<?> fieldClazz ) { if ( fieldClazz.isEnum() ) { final Field[] enumFields = fieldClazz.getDeclaredFields(); final List<String> enumValues = new ArrayList<String>(); for ( final Field enumField : enumFields ) { if ( enumField.isEnumConstant() ) { String shortName = fieldClazz.getName().substring( fieldClazz.getName().lastIndexOf( "." ) + 1 ) + "." + enumField.getName(); if ( shortName.contains( "$" ) ) { shortName = shortName.replaceAll( "\\$", "." ); } enumValues.add( shortName + "=" + shortName ); } } final String a[] = new String[enumValues.size()]; enumValues.toArray( a ); getDataModelBuilder().addEnum( className, fieldName, a ); } } private void addParametricTypeForField( final String className, final String fieldName, final Type type ) { final String qualifiedFactFieldName = className + "#" + fieldName; final String parametricType = getParametricType( type ); if ( parametricType != null ) { fieldParametersType.put( qualifiedFactFieldName, parametricType ); } } private String getParametricType( final Type type ) { if ( type instanceof ParameterizedType ) { final ParameterizedType pt = (ParameterizedType) type; Type parameter = null; for ( final Type t : pt.getActualTypeArguments() ) { parameter = t; } if ( parameter != null ) { if ( parameter instanceof Class<?> ) { return ( (Class<?>) parameter ).getName(); } return null; } else { return null; } } return null; } private Map<String, List<String>> buildSuperTypes() { final Map<String, List<String>> loadableSuperTypes = new HashMap<String, List<String>>(); loadableSuperTypes.put( getType(), superTypes ); return loadableSuperTypes; } private Map<String, Set<Annotation>> buildTypeAnnotations() { final Map<String, Set<Annotation>> loadableTypeAnnotations = new HashMap<String, Set<Annotation>>(); loadableTypeAnnotations.put( getType(), annotations ); return loadableTypeAnnotations; } private Map<String, Map<String, Set<Annotation>>> buildTypeFieldsAnnotations() { final Map<String, Map<String, Set<Annotation>>> loadableTypeFieldsAnnotations = new HashMap<String, Map<String, Set<Annotation>>>(); loadableTypeFieldsAnnotations.put( getType(), fieldAnnotations ); return loadableTypeFieldsAnnotations; } @Override public Map<String, FactBuilder> getInternalBuilders() { for ( final FactBuilder factBuilder : new ArrayList<FactBuilder>( this.fieldFactBuilders.values() ) ) { this.fieldFactBuilders.putAll( factBuilder.getInternalBuilders() ); } return fieldFactBuilders; } }