/* * Copyright 2016 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.kie.workbench.common.services.datamodeller.util; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.drools.core.base.ClassTypeResolver; import org.jboss.forge.roaster.model.Member; import org.jboss.forge.roaster.model.Type; import org.jboss.forge.roaster.model.VisibilityScoped; import org.jboss.forge.roaster.model.source.Import; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.JavaSource; import org.kie.workbench.common.services.datamodeller.core.AnnotationDefinition; import org.kie.workbench.common.services.datamodeller.core.AnnotationRetention; import org.kie.workbench.common.services.datamodeller.core.AnnotationValuePairDefinition; import org.kie.workbench.common.services.datamodeller.core.ElementType; import org.kie.workbench.common.services.datamodeller.core.Visibility; import org.kie.workbench.common.services.datamodeller.core.impl.AnnotationDefinitionImpl; import org.kie.workbench.common.services.datamodeller.core.impl.AnnotationValuePairDefinitionImpl; import org.kie.workbench.common.services.datamodeller.driver.ModelDriverException; public class DriverUtils { public static ClassTypeResolver createClassTypeResolver( JavaSource javaSource, ClassLoader classLoader ) { String packageName; Set<String> classImports = new HashSet<String>(); // Importer.getImports() returns both normal and static imports // You can see if an Import is static by calling hte // Import.isStatic() method List<Import> imports = javaSource.getImports(); if ( imports != null ) { for ( Import currentImport : imports ) { String importName = currentImport.getQualifiedName(); if ( currentImport.isWildcard() ) { importName = importName + ".*"; } classImports.add( importName ); } } packageName = javaSource.getPackage(); //add current package too, if not added, the class type resolver don't resolve current package classes. if ( packageName != null && !"".equals( packageName ) ) { classImports.add( packageName + ".*" ); } if( javaSource instanceof JavaClassSource ) { JavaClassSource javaClassSource = (JavaClassSource) javaSource; //add current file inner types as import clauses to help the ClassTypeResolver to find variables of inner types //It was detected that current ClassTypeResolver don't resolve inner classes well. //workaround for BZ https://bugzilla.redhat.com/show_bug.cgi?id=1172711 List<JavaSource<?>> innerTypes = javaClassSource.getNestedTypes(); if ( innerTypes != null ) { for ( JavaSource<?> type : innerTypes ) { classImports.add( packageName + "." + javaClassSource.getName() + "." + type.getName() ); } } } return new ClassTypeResolver( classImports, classLoader ); } public static ClassTypeResolver createClassTypeResolver( ClassLoader classLoader ) { return new ClassTypeResolver( new HashSet<String>( ), classLoader); } public static Object[] isSimpleGeneric( Type type, ClassTypeResolver classTypeResolver ) throws ModelDriverException { Object[] result = new Object[ 3 ]; result[ 0 ] = false; result[ 1 ] = null; result[ 2 ] = null; if ( type.isArray() || type.isPrimitive() || !type.isParameterized() || ( type.isParameterized() && type.getTypeArguments().size() != 1 ) ) { return result; } Type<?> argument = ( ( List<Type> ) type.getTypeArguments() ).get( 0 ); if ( !isSimpleClass( argument ) ) { return result; } try { String outerClass = classTypeResolver.getFullTypeName( type.getName() ); String argumentClass = classTypeResolver.getFullTypeName( argument.getName() ); result[ 0 ] = true; result[ 1 ] = outerClass; result[ 2 ] = argumentClass; return result; } catch ( ClassNotFoundException e ) { throw new ModelDriverException( "Class could not be resolved for name: " + type.getName() + ". " + e.getMessage(), e ); } } /** * @return Return true if the given type can be managed by the driver, and subsequently by the UI. * <p/> * E.g. of managed types are: * int, Integer, java.lang.Integer, org.kie.SomeClass, List<Integer>, java.util.List<org.kie.SomeClass> * <p/> * e.g. of not manged types are: * int[], java.util.List<List<String>>, List<Map<String, org.kie.SomeClass>> */ public static boolean isManagedType( Type type, ClassTypeResolver classTypeResolver ) throws ModelDriverException { //quickest checks first. if ( type.isPrimitive() ) { return true; } if ( type.isArray() ) { return false; } if ( type.isParameterized() && type.getTypeArguments().size() > 1 ) { return false; } try { Class<?> clazz = classTypeResolver.resolveType( type.getName() ); if ( clazz.isAnonymousClass() || clazz.isLocalClass() || clazz.isMemberClass() ) { return false; } if ( type.isParameterized() ) { Class<?> bag = classTypeResolver.resolveType( type.getName() ); if ( !Collection.class.isAssignableFrom( bag ) ) { return false; } return isSimpleClass( ( ( List<Type> ) type.getTypeArguments() ).get( 0 ) ); } return true; } catch ( ClassNotFoundException e ) { throw new ModelDriverException( "Class could not be resolved for name: " + type.getName() + ". " + e.getMessage(), e ); } } public static boolean isSimpleClass( Type<?> type ) { return !type.isArray() && !type.isPrimitive() && !type.isParameterized(); } public static boolean equalsType( Type type, String fullClassName, boolean multiple, String fullBagClassName, ClassTypeResolver classTypeResolver ) throws Exception { String currentClassName; String currentBag; if ( type.isArray() ) { return false; } if ( type.isPrimitive() ) { return !multiple && fullClassName.equals( type.getName() ); } if ( isSimpleClass( type ) ) { currentClassName = classTypeResolver.getFullTypeName( type.getName() ); return !multiple && fullClassName.equals( currentClassName ); } Object[] simpleGenerics = isSimpleGeneric( type, classTypeResolver ); if ( multiple && Boolean.TRUE.equals( simpleGenerics[ 0 ] ) && isManagedType( type, classTypeResolver ) ) { currentBag = ( String ) simpleGenerics[ 1 ]; currentBag = classTypeResolver.getFullTypeName( currentBag ); currentClassName = ( String ) simpleGenerics[ 2 ]; currentClassName = classTypeResolver.getFullTypeName( currentClassName ); return fullBagClassName.equals( currentBag ) && fullClassName.equals( currentClassName ); } return false; } public static int buildModifierRepresentation( Member<?> member ) { int result = 0x0; result = addModifierRepresentation( result, member ); result = addModifierRepresentation( result, ( VisibilityScoped ) member ); return result; } public static Visibility buildVisibility( int javaSpecModifiers ) { if ( Modifier.isPublic( javaSpecModifiers ) ) { return Visibility.PUBLIC; } if ( Modifier.isProtected( javaSpecModifiers ) ) { return Visibility.PROTECTED; } if ( Modifier.isPrivate( javaSpecModifiers ) ) { return Visibility.PRIVATE; } return Visibility.PACKAGE_PRIVATE; } public static Visibility buildVisibility( org.jboss.forge.roaster.model.Visibility visibility ) { switch ( visibility ) { case PUBLIC: return Visibility.PUBLIC; case PROTECTED: return Visibility.PROTECTED; case PRIVATE: return Visibility.PRIVATE; default: return Visibility.PACKAGE_PRIVATE; } } public static org.jboss.forge.roaster.model.Visibility buildVisibility( Visibility visibility ) { switch ( visibility ) { case PUBLIC: return org.jboss.forge.roaster.model.Visibility.PUBLIC; case PROTECTED: return org.jboss.forge.roaster.model.Visibility.PROTECTED; case PRIVATE: return org.jboss.forge.roaster.model.Visibility.PRIVATE; default: return org.jboss.forge.roaster.model.Visibility.PACKAGE_PRIVATE; } } public static AnnotationRetention buildRetention( RetentionPolicy retention ) { switch ( retention ) { case RUNTIME: return AnnotationRetention.RUNTIME; case SOURCE: return AnnotationRetention.SOURCE; default: return AnnotationRetention.CLASS; } } public static ElementType buildElementType( java.lang.annotation.ElementType elementType ) { switch ( elementType ) { case TYPE: return ElementType.TYPE; case FIELD: return ElementType.FIELD; case METHOD: return ElementType.METHOD; case PARAMETER: return ElementType.PARAMETER; case CONSTRUCTOR: return ElementType.CONSTRUCTOR; case LOCAL_VARIABLE: return ElementType.LOCAL_VARIABLE; case ANNOTATION_TYPE: return ElementType.ANNOTATION_TYPE; case PACKAGE: return ElementType.PACKAGE; } return null; } public static AnnotationValuePairDefinition.ValuePairType buildValuePairType( Class cls ) { if ( cls.isEnum() ) { return AnnotationValuePairDefinition.ValuePairType.ENUM; } else if ( cls.isAnnotation() ) { return AnnotationValuePairDefinition.ValuePairType.ANNOTATION; } else if ( cls.getName().equals( String.class.getName() ) ) { return AnnotationValuePairDefinition.ValuePairType.STRING; } else if ( NamingUtils.isPrimitiveTypeId( cls.getName() ) ) { return AnnotationValuePairDefinition.ValuePairType.PRIMITIVE; } else { return AnnotationValuePairDefinition.ValuePairType.CLASS; } } public static boolean isAnnotationMember( Class cls, Method method ) { //TODO review this calculation return cls.equals( method.getDeclaringClass() ) && Modifier.isPublic( method.getModifiers() ) && ( method.getParameterTypes() == null || method.getParameterTypes().length == 0 ) && isAnnotationReturnType( method.getReturnType() ); } public static boolean isAnnotationReturnType( Class cls ) { //TODO review this calculation Class targetType = cls; if ( cls.isArray() && ( targetType = cls.getComponentType() ).isArray() ) { return false; } return ( targetType.isAnnotation() || targetType.isEnum() || targetType.isPrimitive() ) || ( !targetType.isAnonymousClass() && !targetType.isLocalClass() ); } public static boolean isValidAnnotationBaseReturnType( Class cls ) { //TODO review this calculation return ( cls.isAnnotation() || cls.isEnum() || NamingUtils.isPrimitiveTypeId( cls.getName() ) ) || ( !cls.isAnonymousClass() && !cls.isLocalClass() ); } public static void copyAnnotationRetention( Class annotationClass, AnnotationDefinition annotationDefinition ) { if ( annotationClass.isAnnotationPresent( Retention.class ) ) { Retention retentionAnnotation = ( Retention ) annotationClass.getAnnotation( Retention.class ); annotationDefinition.setRetention( DriverUtils.buildRetention( retentionAnnotation.value() ) ); } } public static void copyAnnotationTarget( Class annotationClass, AnnotationDefinition annotationDefinition ) { if ( annotationClass.isAnnotationPresent( Target.class ) ) { Target targetAnnotation = ( Target ) annotationClass.getAnnotation( Target.class ); java.lang.annotation.ElementType[] targets = targetAnnotation.value(); if ( targets != null && targets.length > 0 ) { for ( int i = 0; i < targets.length; i++ ) { annotationDefinition.addTarget( buildElementType( targets[ i ] ) ); } } else { //added to avoid an errai unmarshalling error in broser side, when an annotation has no targets, e.g. //javax.persistence.UniqueConstraint annotationDefinition.addTarget( ElementType.UNDEFINED ); } } } public static int buildModifierRepresentation( JavaClassSource classSource ) { return addModifierRepresentation( 0x0, classSource ); } public static int addModifierRepresentation( int modifiers, Member<?> member ) { if ( member != null ) { if ( member.isStatic() ) { modifiers = modifiers | Modifier.STATIC; } if ( member.isFinal() ) { modifiers = modifiers | Modifier.FINAL; } } return modifiers; } public static int addModifierRepresentation( int modifiers, VisibilityScoped visibilityScoped ) { if ( visibilityScoped != null ) { if ( visibilityScoped.isPublic() ) { modifiers = modifiers | Modifier.PUBLIC; } if ( visibilityScoped.isProtected() ) { modifiers = modifiers | Modifier.PROTECTED; } if ( visibilityScoped.isPrivate() ) { modifiers = modifiers | Modifier.PRIVATE; } } return modifiers; } public static AnnotationDefinition buildAnnotationDefinition( Class cls ) { if ( !cls.isAnnotation() ) return null; AnnotationDefinitionImpl annotationDefinition = new AnnotationDefinitionImpl( NamingUtils.normalizeClassName( cls.getName() ) ); //set retention and target. DriverUtils.copyAnnotationRetention( cls, annotationDefinition ); DriverUtils.copyAnnotationTarget( cls, annotationDefinition ); Method[] methods = cls.getMethods(); Method method; AnnotationValuePairDefinitionImpl valuePairDefinition; Class returnType; boolean isArray = false; for ( int i = 0; methods != null && i < methods.length; i++ ) { method = methods[i]; if ( DriverUtils.isAnnotationMember( cls, method ) ) { returnType = method.getReturnType(); if ( ( isArray = returnType.isArray() ) ) returnType = returnType.getComponentType(); valuePairDefinition = new AnnotationValuePairDefinitionImpl( method.getName(), NamingUtils.normalizeClassName( returnType.getName() ), DriverUtils.buildValuePairType( returnType ), isArray, //TODO, review this default value assignment, when we have annotations the default value should be an AnnotationInstance method.getDefaultValue() != null ? method.getDefaultValue().toString() : null ); if ( valuePairDefinition.isAnnotation() ) { valuePairDefinition.setAnnotationDefinition( buildAnnotationDefinition( returnType ) ); } if ( valuePairDefinition.isEnum() ) { Object[] enumConstants = returnType.getEnumConstants(); if ( enumConstants != null ) { String[] strEnumConstants = new String[ enumConstants.length ]; for ( int j = 0; j < enumConstants.length; j++ ) { strEnumConstants[j] = enumConstants[j].toString(); } valuePairDefinition.setEnumValues( strEnumConstants ); } } annotationDefinition.addValuePair( valuePairDefinition ); } } return annotationDefinition; } public static String encodePrimitiveArrayValue( AnnotationValuePairDefinition valuePairDefinition, Object value ) { if ( value == null ) return null; List<Object> encodedValues = new ArrayList<Object>( ); String encodedItem; if ( value instanceof List ) { for ( Object item : (List)value ) { if ( item != null && (encodedItem = encodePrimitiveValue( valuePairDefinition, item ) ) != null ) { encodedValues.add( encodedItem ); } } } else { if ( ( encodedItem = encodePrimitiveValue( valuePairDefinition, value ) ) != null ) { encodedValues.add( encodedItem ); } } return toEncodedArray( encodedValues ); } public static String encodePrimitiveValue( AnnotationValuePairDefinition valuePairDefinition, Object value ) { if ( value == null ) return null; StringBuilder encodedValue = new StringBuilder(); if ( NamingUtils.isCharId( valuePairDefinition.getClassName() ) || Character.class.getName().equals( valuePairDefinition.getClassName() ) ) { String strValue = value.toString(); if ( StringEscapeUtils.isSingleQuoted( strValue ) ) { encodedValue.append( strValue ); } else { encodedValue.append( "'" ); encodedValue.append( value.toString() ); encodedValue.append( "'" ); } } else if ( NamingUtils.isLongId( valuePairDefinition.getClassName() ) || Long.class.getName().equals( valuePairDefinition.getClassName() ) ) { encodedValue.append( value.toString() ); encodedValue.append( "L" ); } else if ( NamingUtils.isFloatId( valuePairDefinition.getClassName() ) || Float.class.getName().equals( valuePairDefinition.getClassName() ) ) { encodedValue.append( value.toString() ); encodedValue.append( "f" ); } else if ( NamingUtils.isDoubleId( valuePairDefinition.getClassName()) || Double.class.getName().equals( valuePairDefinition.getClassName() ) ) { encodedValue.append( value.toString() ); encodedValue.append( "d" ); } else if ( NamingUtils.isByteId( valuePairDefinition.getClassName() ) || Byte.class.getName().equals( valuePairDefinition.getClassName() ) ) { encodedValue.append( "(byte)" ); encodedValue.append( value.toString() ); } else { encodedValue.append( value.toString() ); } return encodedValue.toString(); } public static String encodeClassValue( String value ) { if ( value == null ) return value; if ( value.endsWith( ".class" ) ) return value; return value + ".class"; } public static String encodeClassArrayValue( Object value ) { if ( value == null ) return null; List<Object> encodedValues = new ArrayList<Object>( ); String encodedItem; if ( value instanceof List ) { for ( Object item : (List)value ) { if ( item != null && ( encodedItem = encodeClassValue( item.toString() ) ) != null ) { encodedValues.add( encodedItem ); } } } else if ( ( encodedItem = encodeClassValue( value.toString() ) ) != null ) { encodedValues.add( encodedItem ); } return toEncodedArray( encodedValues ); } public static String encodeStringArrayValue( Object value, boolean escapeJavaNonUTFChars ) { if ( value == null ) return null; List<Object> encodedValues = new ArrayList<Object>( ); String encodedItem; if ( value instanceof List ) { for ( Object item : (List)value ) { if ( item != null && ( encodedItem = encodeStringValue( item, escapeJavaNonUTFChars ) ) != null ) { encodedValues.add( encodedItem ); } } } else if ( ( encodedItem = encodeStringValue( value.toString(), escapeJavaNonUTFChars ) ) != null ) { encodedValues.add( encodedItem ); } return toEncodedArray( encodedValues ); } public static String encodeStringValue( Object value, boolean escapeJavaNonUTFChars ) { if ( value == null ) { return null; } else { StringBuilder encodedValue = new StringBuilder( ); String escapedValue = escapeJavaNonUTFChars ? StringEscapeUtils.escapeJavaNonUTFChars( value.toString() ) : value.toString(); encodedValue.append( "\"" ); encodedValue.append( escapedValue ); encodedValue.append( "\"" ); return encodedValue.toString(); } } public static String[] encodeStringArrayValueToArray( Object value ) { if ( value == null ) return null; List<String> notNulls = new ArrayList<String>( ); if ( value instanceof List ) { for ( Object currentValue : (List)value ) { if ( currentValue != null ) { notNulls.add( currentValue.toString() ); } } } else { notNulls.add( value.toString() ); } return notNulls.size() > 0 ? notNulls.toArray( new String[ notNulls.size() ] ) : new String[]{}; } public static String encodeEnumValue( AnnotationValuePairDefinition valuePairDefinition, Object value ) { if ( value == null ) return null; StringBuilder encodedValue = new StringBuilder( ); encodedValue.append( NamingUtils.normalizeClassName( valuePairDefinition.getClassName() ) ); encodedValue.append( "." ); encodedValue.append( value.toString() ); return encodedValue.toString(); } public static String encodeEnumArrayValue( AnnotationValuePairDefinition valuePairDefinition, Object value ) { if ( value == null ) return null; List<Object> encodedValues = new ArrayList<Object>( ); String encodedItem; if ( value instanceof List ) { for ( Object item : (List)value ) { if ( item != null && ( encodedItem = encodeEnumValue( valuePairDefinition, item ) ) != null ) { encodedValues.add( encodedItem ); } } } else if ( ( encodedItem = encodeEnumValue( valuePairDefinition, value ) ) != null ) { encodedValues.add( encodedItem ); } return toEncodedArray( encodedValues ); } public static boolean isEmptyArray( String value ) { if ( value == null || ( value = value.trim() ).equals( "" ) || !value.startsWith( "{" ) || !value.endsWith( "}" ) ) { return false; } value = PortableStringUtils.removeLastChar( PortableStringUtils.removeFirstChar( value, '{' ), '}' ); return "".equals( value != null ? value.trim() : null ); } private static String toEncodedArray( List<Object> values ) { StringBuilder encodedValue = new StringBuilder( ); boolean hasItems = false; encodedValue.append( "{" ); for ( Object value : values ) { if ( hasItems ) encodedValue.append( ", " ); encodedValue.append( value != null ? value.toString() : "null" ); hasItems = true; } encodedValue.append( "}" ); return encodedValue.toString(); } }