/* * 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.driver.impl; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.drools.core.base.ClassTypeResolver; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.JavaClass; import org.jboss.forge.roaster.model.JavaType; import org.jboss.forge.roaster.model.Method; import org.jboss.forge.roaster.model.Parameter; import org.jboss.forge.roaster.model.SyntaxError; import org.jboss.forge.roaster.model.Type; import org.jboss.forge.roaster.model.source.AnnotationSource; import org.jboss.forge.roaster.model.source.AnnotationTargetSource; import org.jboss.forge.roaster.model.source.FieldSource; 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.jboss.forge.roaster.model.source.JavaEnumSource; import org.jboss.forge.roaster.model.source.MethodSource; import org.jboss.forge.roaster.model.source.ParameterSource; import org.kie.workbench.common.services.datamodeller.codegen.GenerationContext; import org.kie.workbench.common.services.datamodeller.codegen.GenerationEngine; import org.kie.workbench.common.services.datamodeller.codegen.GenerationTools; import org.kie.workbench.common.services.datamodeller.core.Annotation; import org.kie.workbench.common.services.datamodeller.core.AnnotationDefinition; import org.kie.workbench.common.services.datamodeller.core.AnnotationValuePairDefinition; import org.kie.workbench.common.services.datamodeller.core.DataModel; import org.kie.workbench.common.services.datamodeller.core.DataObject; import org.kie.workbench.common.services.datamodeller.core.ElementType; import org.kie.workbench.common.services.datamodeller.core.ObjectProperty; import org.kie.workbench.common.services.datamodeller.core.Visibility; import org.kie.workbench.common.services.datamodeller.core.impl.DataObjectImpl; import org.kie.workbench.common.services.datamodeller.core.impl.ImportImpl; import org.kie.workbench.common.services.datamodeller.core.impl.JavaClassImpl; import org.kie.workbench.common.services.datamodeller.core.impl.JavaEnumImpl; import org.kie.workbench.common.services.datamodeller.core.impl.JavaTypeInfoImpl; import org.kie.workbench.common.services.datamodeller.core.impl.MethodImpl; import org.kie.workbench.common.services.datamodeller.core.impl.ModelFactoryImpl; import org.kie.workbench.common.services.datamodeller.core.impl.ObjectPropertyImpl; import org.kie.workbench.common.services.datamodeller.driver.AnnotationDriver; import org.kie.workbench.common.services.datamodeller.driver.FilterHolder; import org.kie.workbench.common.services.datamodeller.driver.ModelDriver; import org.kie.workbench.common.services.datamodeller.driver.ModelDriverException; import org.kie.workbench.common.services.datamodeller.driver.ModelDriverListener; import org.kie.workbench.common.services.datamodeller.driver.TypeInfoResult; import org.kie.workbench.common.services.datamodeller.driver.impl.annotations.CommonAnnotations; import org.kie.workbench.common.services.datamodeller.driver.model.AnnotationSourceRequest; import org.kie.workbench.common.services.datamodeller.driver.model.AnnotationSourceResponse; import org.kie.workbench.common.services.datamodeller.driver.model.DriverError; import org.kie.workbench.common.services.datamodeller.driver.model.DriverResult; import org.kie.workbench.common.services.datamodeller.driver.model.ModelDriverResult; import org.kie.workbench.common.services.datamodeller.util.DataModelUtils; import org.kie.workbench.common.services.datamodeller.util.DriverUtils; import org.kie.workbench.common.services.datamodeller.util.FileUtils; import org.kie.workbench.common.services.datamodeller.util.NamingUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.uberfire.backend.server.util.Paths; import org.uberfire.commons.data.Pair; import org.uberfire.io.IOService; import org.uberfire.java.nio.file.Path; public class JavaRoasterModelDriver implements ModelDriver { private static final Logger logger = LoggerFactory.getLogger( JavaRoasterModelDriver.class ); private IOService ioService; private Path javaRootPath; private ClassLoader classLoader; private List<AnnotationDefinition> configuredAnnotations = new ArrayList<AnnotationDefinition>(); private Map<String, AnnotationDefinition> configuredAnnotationsIndex = new HashMap<String, AnnotationDefinition>(); private Map<String, AnnotationDriver> annotationDrivers = new HashMap<String, AnnotationDriver>(); private FilterHolder filterHolder; private static final String DATA_OBJECT_LOAD_ERROR = "It was not possible to create or load DataObject: \"{0}\" ."; private static final String ANNOTATION_LOAD_ERROR = "It was not possible to create or load a DataObject or Field annotation for annotation class name: \"{0}\" ."; private static final String DATA_OBJECT_FIELD_LOAD_ERROR = "It was not possible to create or load field: \"{0}\" for DataObject: \"{1}\" ."; private static final String MODEL_LOAD_GENERIC_ERROR = "Unexpected error was produced when a DataModel was being loaded from the following path: \"{0}\" ."; private static final String GENERIC_ERROR = "Unexpected error was produced."; public JavaRoasterModelDriver() { configuredAnnotations.addAll( CommonAnnotations.getCommonAnnotations() ); for ( AnnotationDefinition annotationDefinition : configuredAnnotations ) { annotationDrivers.put( annotationDefinition.getClassName(), new DefaultJavaRoasterModelAnnotationDriver() ); configuredAnnotationsIndex.put( annotationDefinition.getClassName(), annotationDefinition ); } } public JavaRoasterModelDriver( FilterHolder filterHolder ) { this(); this.filterHolder = filterHolder; } public JavaRoasterModelDriver( IOService ioService, Path javaRootPath, ClassLoader classLoader, FilterHolder filterHolder ) { this(); this.ioService = ioService; this.javaRootPath = javaRootPath; this.classLoader = classLoader; this.filterHolder = filterHolder; } @Override public List<AnnotationDefinition> getConfiguredAnnotations() { return configuredAnnotations; } @Override public AnnotationDefinition getConfiguredAnnotation( String annotationClassName ) { return configuredAnnotationsIndex.get( annotationClassName ); } @Override public AnnotationDriver getAnnotationDriver( String annotationClassName ) { return annotationDrivers.get( annotationClassName ); } @Override public void generateModel( DataModel dataModel, ModelDriverListener generationListener ) throws Exception { //This driver type do not generate the model. } @Override public ModelDriverResult loadModel() throws ModelDriverException { ModelDriverResult result = new ModelDriverResult(); DataModel dataModel; String fileContent; dataModel = createModel(); result.setDataModel( dataModel ); List<Path> rootPaths = new ArrayList<Path>(); rootPaths.add( javaRootPath ); Collection<FileUtils.ScanResult> scanResults = FileUtils.getInstance().scan( ioService, rootPaths, ".java", true ); if ( scanResults != null ) { for ( FileUtils.ScanResult scanResult : scanResults ) { logger.debug( "Starting file loading into model, file: " + scanResult.getFile() ); fileContent = ioService.readAllString( scanResult.getFile() ); if ( fileContent == null || "".equals( fileContent ) ) { logger.debug( "file: " + scanResult.getFile() + " is empty." ); continue; } try { JavaType<?> javaType = Roaster.parse( fileContent ); final boolean isManaged = isManagedJavaType( javaType ); final boolean vetoed = ( isManaged ? isVetoed( javaType ) : false ); if ( isManaged && !vetoed ) { if ( javaType.getSyntaxErrors() != null && !javaType.getSyntaxErrors().isEmpty() ) { //if a file has parsing errors it will be skipped. addSyntaxErrors( result, scanResult.getFile(), javaType.getSyntaxErrors() ); } else if ( javaType.isEnum() ) { loadFromJavaEnum( ( JavaEnumSource ) javaType, scanResult.getFile(), dataModel, result); } else { loadFromJavaClass( ( JavaClassSource ) javaType, scanResult.getFile(), dataModel, result ); } } else if ( vetoed ) { logger.debug( "The class, {}, in the file, {}, was vetoed and will be skipped.", javaType.getQualifiedName(), scanResult.getFile() ); } else { logger.debug( "File: " + scanResult.getFile() + " do not contain a managed java type, it will be skipped." ); } } catch ( Exception e ) { //Unexpected parsing or model loading exception. logger.error( errorMessage( MODEL_LOAD_GENERIC_ERROR, javaRootPath.toUri() ), e ); throw new ModelDriverException( errorMessage( MODEL_LOAD_GENERIC_ERROR, javaRootPath.toUri() ), e ); } } } return result; } private boolean isVetoed( final JavaType<?> javaType ) { return filterHolder.getSourceFilters().stream().anyMatch( filter -> filter.veto( javaType ) ); } private boolean isAccepted( final JavaType<?> nestedClass ) { return filterHolder.getNestedClassFilters().stream().anyMatch( filter -> filter.accept( nestedClass ) ); } private boolean isAccepted( final Method<?, ?> method) { return filterHolder.getMethodFilters( ).stream( ).anyMatch( filter -> filter.accept( method ) ); } private boolean isManagedJavaType( final JavaType<?> javaType ) { return javaType.isClass() || javaType.isEnum(); } private void loadFromJavaClass( JavaClassSource javaClassSource, Path file, DataModel dataModel, ModelDriverResult result ) { try { Pair<DataObject, List<ObjectProperty>> pair = parseDataObject( javaClassSource ); if ( pair.getK1() != null ) { dataModel.addDataObject( pair.getK1() ); result.setClassPath( pair.getK1().getClassName(), Paths.convert( file ) ); result.setUnmanagedProperties( pair.getK1().getClassName(), pair.getK2() ); } } catch ( ModelDriverException e ) { logger.error( "An error was produced when file: " + file + " was being loaded into a DataObject.", e ); addModelDriverError( result, file, e ); } } private void loadFromJavaEnum( JavaEnumSource javaEnumSource, Path file, DataModel dataModel, ModelDriverResult result ) { String className = javaEnumSource.getName(); String packageName = javaEnumSource.getPackage(); Visibility visibility = DriverUtils.buildVisibility( javaEnumSource.getVisibility() ); JavaEnumImpl javaEnum = new JavaEnumImpl( packageName, className, visibility ); dataModel.addJavaEnum( javaEnum ); result.setClassPath( javaEnum.getClassName(), Paths.convert( file ) ); } public ModelDriverResult loadDataObject( final String source, final Path path ) throws ModelDriverException { ModelDriverResult result = new ModelDriverResult(); DataModel dataModel = createModel(); result.setDataModel( dataModel ); if ( source == null || "".equals( source ) ) { logger.debug( "source: " + source + " is empty." ); return result; } try { JavaType<?> javaType = Roaster.parse( source ); if ( javaType.isClass() ) { if ( javaType.getSyntaxErrors() != null && !javaType.getSyntaxErrors().isEmpty() ) { //if a file has parsing errors it will be skipped. addSyntaxErrors( result, path, javaType.getSyntaxErrors() ); } else { try { //try to load the data object. Pair<DataObject, List<ObjectProperty>> pair = parseDataObject( ( JavaClassSource ) javaType ); dataModel.addDataObject( pair.getK1() ); result.setClassPath( pair.getK1().getClassName(), Paths.convert( path ) ); result.setUnmanagedProperties( pair.getK1().getClassName(), pair.getK2() ); } catch ( ModelDriverException e ) { logger.error( "An error was produced when source: " + source + " was being loaded into a DataObject.", e ); addModelDriverError( result, path, e ); } } } else { logger.debug( "No Class definition was found for source: " + source + ", it will be skipped." ); } } catch ( Exception e ) { //Unexpected parsing o model loading exception. logger.error( errorMessage( MODEL_LOAD_GENERIC_ERROR, javaRootPath.toUri() ), e ); throw new ModelDriverException( errorMessage( MODEL_LOAD_GENERIC_ERROR, javaRootPath.toUri() ), e ); } return result; } public TypeInfoResult loadJavaTypeInfo( final String source ) throws ModelDriverException { TypeInfoResult result = new TypeInfoResult(); if ( source == null || "".equals( source ) ) { logger.debug( "source: " + source + " is empty." ); return result; } try { JavaType<?> javaType = Roaster.parse( source ); if ( javaType.getSyntaxErrors() != null && !javaType.getSyntaxErrors().isEmpty() ) { addSyntaxErrors( result, null, javaType.getSyntaxErrors() ); } else { JavaTypeInfoImpl typeInfo = new JavaTypeInfoImpl(); result.setTypeInfo( typeInfo ); typeInfo.setName( javaType.getName() ); typeInfo.setPackageName( javaType.getPackage() ); typeInfo.setAnnotation( javaType.isAnnotation() ); typeInfo.setClass( javaType.isClass() ); typeInfo.setEnum( javaType.isEnum() ); typeInfo.setInterface( javaType.isInterface() ); typeInfo.setPackagePrivate( javaType.isPackagePrivate() ); typeInfo.setPrivate( javaType.isPrivate() ); typeInfo.setProtected( javaType.isProtected() ); typeInfo.setInterface( javaType.isInterface() ); } } catch ( Exception e ) { //Unexpected parsing o model loading exception. logger.error( errorMessage( GENERIC_ERROR, e ) ); throw new ModelDriverException( errorMessage( MODEL_LOAD_GENERIC_ERROR ), e ); } return result; } public AnnotationSourceResponse resolveSourceRequest( AnnotationSourceRequest sourceRequest ) { AnnotationSourceResponse sourceResponse = new AnnotationSourceResponse(); for ( Annotation annotation : sourceRequest.getAnnotations() ) { sourceResponse.withAnnotationSource( annotation.getClassName(), resolveAnnotationSource( annotation ) ); } return sourceResponse; } public org.kie.workbench.common.services.datamodeller.driver.model.AnnotationSource resolveAnnotationSource( Annotation annotation ) { org.kie.workbench.common.services.datamodeller.driver.model.AnnotationSource annotationSource = new org.kie.workbench.common.services.datamodeller.driver.model.AnnotationSource(); //TODO this method can be optimized and likely migrated to Roaster. Should be reviewed when we evaluate //the removal of Velocity. GenerationTools generationTools = new GenerationTools(); AnnotationDefinition annotationDefinition; StringBuilder annotationCode = new StringBuilder( ); annotationCode.append( "@" ); annotationCode.append( annotation.getClassName() ); if ( ( annotationDefinition = annotation.getAnnotationDefinition() ) != null ) { if ( !annotationDefinition.isMarker() ) { annotationCode.append( generationTools.resolveAnnotationType( annotation ) ); } if ( annotationDefinition.getValuePairs() != null ) { Object value; String valuePairCode; for ( AnnotationValuePairDefinition valuePairDefinition : annotationDefinition.getValuePairs() ) { if ( ( value = annotation.getValue( valuePairDefinition.getName() ) ) != null ) { valuePairCode = generationTools.resolveMemberTypeExpression( valuePairDefinition, value ); } else { valuePairCode = null; } annotationSource.withValuePairSource( valuePairDefinition.getName(), valuePairCode ); } } } annotationSource.withSource( annotationCode.toString() ); return annotationSource; } private void addModelDriverError( ModelDriverResult result, Path file, ModelDriverException e ) { DriverError error; StringBuilder message = new StringBuilder(); message.append( e.getMessage() ); Throwable cause = e.getCause(); while ( cause != null ) { message.append( " : " ); message.append( cause.getMessage() ); if ( cause instanceof ModelDriverException ) { cause = cause.getCause(); } else { cause = null; } } error = new DriverError( message.toString(), Paths.convert( file ) ); result.addError( error ); } private void addSyntaxErrors( DriverResult result, Path file, List<SyntaxError> syntaxErrors ) { DriverError error; for ( SyntaxError syntaxError : syntaxErrors ) { error = new DriverError( syntaxError.getDescription(), Paths.convert( file ) ); error.setLine( syntaxError.getLine() ); error.setColumn( syntaxError.getColumn() ); result.addError( error ); } } @Override public DataModel createModel() { return ModelFactoryImpl.getInstance().newModel(); } private Pair<DataObject, List<ObjectProperty>> parseDataObject( JavaClassSource javaClassSource ) throws ModelDriverException { String className; String packageName; String superClass; String qualifiedName; ClassTypeResolver classTypeResolver; className = javaClassSource.getName(); packageName = javaClassSource.getPackage(); qualifiedName = NamingUtils.createQualifiedName( packageName, className ); if ( logger.isDebugEnabled() ) { logger.debug( "Building DataObject for, packageName: " + packageName + ", className: " + className ); } classTypeResolver = DriverUtils.createClassTypeResolver( javaClassSource, classLoader ); Visibility visibility = DriverUtils.buildVisibility( javaClassSource.getVisibility() ); DataObject dataObject = new DataObjectImpl( packageName, className, visibility, javaClassSource.isAbstract(), javaClassSource.isFinal() ); List<ObjectProperty> unmanagedProperties = new ArrayList<ObjectProperty>(); try { if ( javaClassSource.getSuperType() != null ) { superClass = resolveTypeName( classTypeResolver, javaClassSource.getSuperType() ); dataObject.setSuperClassName( superClass ); } List<AnnotationSource<JavaClassSource>> annotations = javaClassSource.getAnnotations(); if ( annotations != null ) { for ( AnnotationSource annotation : annotations ) { addJavaClassAnnotation( dataObject, annotation, classTypeResolver ); } } List<MethodSource<JavaClassSource>> classMethods = javaClassSource.getMethods(); if ( classMethods != null ) { for ( MethodSource<JavaClassSource> classMethod : classMethods ) { if ( isAccepted( classMethod ) ) { addMethod( dataObject, classMethod, classTypeResolver ); } } } List<JavaSource<?>> nestedTypes = javaClassSource.getNestedTypes(); if ( nestedTypes != null ) { for ( JavaSource nestedType : nestedTypes ) { if ( isAccepted( nestedType ) ) { if ( nestedType instanceof JavaClassSource ) { JavaClassImpl nestedJavaClass = new JavaClassImpl( "", nestedType.getName(), DriverUtils.buildVisibility( nestedType.getVisibility() ) ); dataObject.addNestedClass( nestedJavaClass ); if ( javaClassSource.getInterfaces() != null ) { for ( String interfaceDefinition : ( (JavaClassSource) nestedType ).getInterfaces() ) { nestedJavaClass.addInterface( interfaceDefinition ); } } List<AnnotationSource<JavaClassSource>> nestedClassAnnotations = nestedType.getAnnotations(); if ( nestedClassAnnotations != null ) { for ( AnnotationSource annotation : nestedClassAnnotations ) { addJavaClassAnnotation( nestedJavaClass, annotation, classTypeResolver ); } } List<MethodSource<JavaClassSource>> nestedClassMethods = ( (JavaClassSource) nestedType ).getMethods(); if ( nestedClassMethods != null ) { for ( Method nestedClassMethod : nestedClassMethods ) { if ( isAccepted( nestedClassMethod ) ) { addMethod( nestedJavaClass, nestedClassMethod, classTypeResolver ); } } } } } } } List<FieldSource<JavaClassSource>> fields = javaClassSource.getFields(); if ( fields != null ) { for ( FieldSource<JavaClassSource> field : fields ) { if ( DriverUtils.isManagedType( field.getType(), classTypeResolver ) ) { addProperty( dataObject, field, classTypeResolver ); } else { logger.debug( "field: " + field + "with fieldName: " + field.getName() + " won't be loaded by the diver because type: " + field.getType().getName() + " isn't a managed type." ); unmanagedProperties.add( new ObjectPropertyImpl( field.getName(), field.getType().toString(), false, DriverUtils.buildVisibility( field.getVisibility() ), field.isStatic(), field.isFinal() ) ); } } } List<Import> imports = javaClassSource.getImports(); if ( imports != null ) { for ( Import _import : imports ) { dataObject.addImport( new ImportImpl( _import.getQualifiedName() ) ); } } return new Pair<DataObject, List<ObjectProperty>>( dataObject, unmanagedProperties ); } catch ( ClassNotFoundException e ) { logger.error( errorMessage( DATA_OBJECT_LOAD_ERROR, qualifiedName ), e ); throw new ModelDriverException( errorMessage( DATA_OBJECT_LOAD_ERROR, qualifiedName ), e ); } catch ( ModelDriverException e ) { logger.error( errorMessage( DATA_OBJECT_LOAD_ERROR, qualifiedName ), e ); throw new ModelDriverException( errorMessage( DATA_OBJECT_LOAD_ERROR, qualifiedName ), e ); } } private ObjectProperty addProperty( DataObject dataObject, FieldSource<JavaClassSource> field, ClassTypeResolver classTypeResolver ) throws ModelDriverException { ObjectProperty property = parseProperty( field, classTypeResolver ); dataObject.addProperty( property ); return property; } private List<org.kie.workbench.common.services.datamodeller.core.Type> resolveTypeArguments( List<Type> typeArguments ) { List<org.kie.workbench.common.services.datamodeller.core.Type> result = new ArrayList<>(); if ( typeArguments != null ) { resolveTypeArguments( typeArguments, result ); } return result; } private void resolveTypeArguments( List<Type> typeArguments, List<org.kie.workbench.common.services.datamodeller.core.Type> resultTypeArguments ) { if ( typeArguments != null ) { for ( Type typeArgument : typeArguments ) { org.kie.workbench.common.services.datamodeller.core.impl.TypeImpl resultType = new org.kie.workbench.common.services.datamodeller.core.impl.TypeImpl( typeArgument.getQualifiedName(), new ArrayList<>() ); resultTypeArguments.add( resultType ); resolveTypeArguments( typeArgument.getTypeArguments(), resultType.getTypeArguments() ); } } } private void addMethod( org.kie.workbench.common.services.datamodeller.core.JavaClass javaClass, Method method, ClassTypeResolver classTypeResolver ) throws ClassNotFoundException, ModelDriverException { List<Parameter> parameters = method.getParameters(); List<org.kie.workbench.common.services.datamodeller.core.Parameter> modelParameters = new ArrayList<>(); if ( parameters != null ) { for ( Parameter parameter : parameters ) { modelParameters.add( new org.kie.workbench.common.services.datamodeller.core.impl.ParameterImpl( new org.kie.workbench.common.services.datamodeller.core.impl.TypeImpl( resolveTypeName( classTypeResolver, parameter.getType().getName() ), resolveTypeArguments( parameter.getType().getTypeArguments() ) ), parameter.getName() ) ); } } org.kie.workbench.common.services.datamodeller.core.Type returnType = null; if ( method.getReturnType() != null ) { returnType = new org.kie.workbench.common.services.datamodeller.core.impl.TypeImpl( resolveTypeName( classTypeResolver, method.getReturnType().getName() ), resolveTypeArguments( method.getReturnType().getTypeArguments() ) ); } Visibility visibility = Visibility.PACKAGE_PRIVATE; if ( method.getVisibility() != null ) { visibility = DriverUtils.buildVisibility( method.getVisibility() ); } MethodImpl dataObjectMethod = new MethodImpl( method.getName(), modelParameters, method.getBody(), returnType, visibility ); List<AnnotationSource<JavaClassSource>> annotations = method.getAnnotations(); if ( annotations != null ) { for ( AnnotationSource annotation : annotations ) { dataObjectMethod.addAnnotation( createAnnotation( annotation, classTypeResolver ) ); } } javaClass.addMethod( dataObjectMethod ); } public ObjectProperty parseProperty( FieldSource<JavaClassSource> field, ClassTypeResolver classTypeResolver ) throws ModelDriverException { Type type; boolean multiple = false; String className; String bag = null; ObjectProperty property; Visibility visibility = DriverUtils.buildVisibility( field.getVisibility() ); try { type = field.getType(); if ( type.isPrimitive() ) { className = type.getName(); } else { if ( DriverUtils.isSimpleClass( type ) ) { className = resolveTypeName( classTypeResolver, type.getName() ); } else { //if this point was reached, we know it's a Collection. Managed type check was done previous to adding the property. multiple = true; @SuppressWarnings( "unchecked" ) Type elementsType = ( ( List<Type> ) type.getTypeArguments() ).get( 0 ); className = resolveTypeName( classTypeResolver, elementsType.getName() ); bag = resolveTypeName( classTypeResolver, type.getName() ); } } property = new ObjectPropertyImpl( field.getName(), className, multiple, bag, visibility, field.isStatic(), field.isFinal() ); List<AnnotationSource<JavaClassSource>> annotations = field.getAnnotations(); if ( annotations != null ) { for ( AnnotationSource annotation : annotations ) { addPropertyAnnotation( property, annotation, classTypeResolver ); } } } catch ( ClassNotFoundException e ) { logger.error( errorMessage( DATA_OBJECT_FIELD_LOAD_ERROR, field.getName(), field.getOrigin().getName() ), e ); throw new ModelDriverException( errorMessage( DATA_OBJECT_FIELD_LOAD_ERROR, field.getName(), field.getOrigin().getName() ), e ); } return property; } public List<ObjectProperty> parseManagedTypesProperties( JavaClassSource javaClassSource, ClassTypeResolver classTypeResolver ) throws ModelDriverException { List<FieldSource<JavaClassSource>> fields = javaClassSource.getFields(); List<ObjectProperty> properties = new ArrayList<ObjectProperty>(); ObjectProperty property; for ( FieldSource<JavaClassSource> field : fields ) { if ( DriverUtils.isManagedType( field.getType(), classTypeResolver ) ) { property = parseProperty( field, classTypeResolver ); properties.add( property ); } else { logger.debug( "field: " + field + "with fieldName: " + field.getName() + " won't be loaded by the diver because type: " + field.getType().getName() + " isn't a managed type." ); } } return properties; } private void addJavaClassAnnotation( org.kie.workbench.common.services.datamodeller.core.JavaClass javaClass, AnnotationSource annotationSource, ClassTypeResolver classTypeResolver ) throws ModelDriverException { Annotation annotation = createAnnotation( annotationSource, classTypeResolver ); if ( annotation != null ) { javaClass.addAnnotation( annotation ); } } private void addPropertyAnnotation( ObjectProperty property, AnnotationSource annotationSource, ClassTypeResolver classTypeResolver ) throws ModelDriverException { Annotation annotation = createAnnotation( annotationSource, classTypeResolver ); if ( annotation != null ) { property.addAnnotation( annotation ); } } private Annotation createAnnotation( AnnotationSource annotationToken, ClassTypeResolver classTypeResolver ) throws ModelDriverException { AnnotationDefinition annotationDefinition; Annotation annotation = null; annotationDefinition = buildAnnotationDefinition( annotationToken, classTypeResolver ); if ( annotationDefinition != null ) { AnnotationDriver annotationDriver = new DefaultJavaRoasterModelAnnotationDriver(); annotation = annotationDriver.buildAnnotation( annotationDefinition, annotationToken ); } else { logger.warn( "Annotation: " + annotationToken.getName() + " is not configured for this ModelDriver driver." ); } return annotation; } public AnnotationDefinition buildAnnotationDefinition( AnnotationSource annotationSource, ClassTypeResolver classTypeResolver ) throws ModelDriverException { return buildAnnotationDefinition( annotationSource.getQualifiedName(), classTypeResolver ); } public AnnotationDefinition buildAnnotationDefinition( String annotationClassName, ClassTypeResolver classTypeResolver ) throws ModelDriverException { try { String resolvedClassName = resolveTypeName( classTypeResolver, annotationClassName ); Class annotationClass = classTypeResolver.resolveType( resolvedClassName ); return DriverUtils.buildAnnotationDefinition( annotationClass ); } catch ( ClassNotFoundException e ) { logger.error( errorMessage( ANNOTATION_LOAD_ERROR, annotationClassName ), e ); throw new ModelDriverException( errorMessage( ANNOTATION_LOAD_ERROR, annotationClassName ), e ); } } private String resolveTypeName( ClassTypeResolver classTypeResolver, String name ) throws ClassNotFoundException { try { if ( name == null ) { return null; } else if ( NamingUtils.isQualifiedName( name ) ) { return name; } else if ( "void".equals( name ) ) { return name; } else { return classTypeResolver.getFullTypeName( name ); } } catch ( ClassNotFoundException e ) { logger.error( "Class could not be resolved for name: " + name, e ); throw e; } } public void updateImports( JavaClassSource javaClassSource, List<org.kie.workbench.common.services.datamodeller.core.Import> dataObjectImports, UpdateInfo updateInfo ) { List<Import> imports = javaClassSource.getImports(); if ( imports != null ) { for ( Import _import : imports ) { javaClassSource.removeImport( _import ); } } if ( dataObjectImports != null ) { for ( org.kie.workbench.common.services.datamodeller.core.Import _import : dataObjectImports ) { javaClassSource.addImport( _import.getName() ); } } String newClassName; String currentPackage = javaClassSource.isDefaultPackage() ? null : javaClassSource.getPackage(); if ( imports != null ) { for ( Import currentImport : imports ) { if ( !currentImport.isWildcard() && !currentImport.isStatic() ) { if ( ( newClassName = updateInfo.getRenamedClasses().get( currentImport.getQualifiedName() ) ) != null ) { javaClassSource.removeImport( currentImport ); if ( !StringUtils.equals( currentPackage, NamingUtils.extractPackageName( newClassName ) ) ) { javaClassSource.addImport( newClassName ); } } else if ( updateInfo.getDeletedClasses().contains( currentImport.getQualifiedName() ) ) { javaClassSource.removeImport( currentImport ); } } } } } public void updateImports( JavaClassSource javaClassSource, Map<String, String> renamedClasses, List<String> deletedClasses ) { List<Import> imports = javaClassSource.getImports(); String newClassName; String currentPackage = javaClassSource.isDefaultPackage() ? null : javaClassSource.getPackage(); if ( imports != null ) { for ( Import currentImport : imports ) { if ( !currentImport.isWildcard() && !currentImport.isStatic() ) { if ( ( newClassName = renamedClasses.get( currentImport.getQualifiedName() ) ) != null ) { javaClassSource.removeImport( currentImport ); if ( !StringUtils.equals( currentPackage, NamingUtils.extractPackageName( newClassName ) ) ) { javaClassSource.addImport( newClassName ); } } else if ( deletedClasses.contains( currentImport.getQualifiedName() ) ) { javaClassSource.removeImport( currentImport ); } } } } } public boolean updatePackage( JavaClassSource javaClassSource, String packageName ) { String oldPackageName = javaClassSource.getPackage(); if ( packageName == null ) { javaClassSource.setDefaultPackage(); } else { javaClassSource.setPackage( packageName ); } return StringUtils.equals( oldPackageName, packageName ); } public void updateSource( JavaClassSource javaClassSource, DataObject dataObject, UpdateInfo updateInfo, ClassTypeResolver classTypeResolver ) throws Exception { if ( javaClassSource == null || !javaClassSource.isClass() ) { logger.warn( "A null javaClassSource or javaClassSouce is not a Class, no processing will be done. javaClassSource: " + javaClassSource + " className: " + ( javaClassSource != null ? javaClassSource.getName() : null ) ); return; } Map<String, FieldSource<JavaClassSource>> currentClassFields = new HashMap<String, FieldSource<JavaClassSource>>(); List<FieldSource<JavaClassSource>> classFields = javaClassSource.getFields(); Map<String, String> preservedFields = new HashMap<String, String>(); //update package, class name, and super class name if needed. updatePackage( javaClassSource, dataObject.getPackageName() ); updateImports( javaClassSource, dataObject.getImports(), updateInfo ); updateAnnotations( javaClassSource, dataObject.getAnnotations(), classTypeResolver ); updateMethods( javaClassSource, dataObject.getMethods(), classTypeResolver ); updateClassName( javaClassSource, dataObject.getName() ); updateSuperClassName( javaClassSource, dataObject.getSuperClassName(), classTypeResolver ); if ( classFields != null ) { for ( FieldSource<JavaClassSource> field : classFields ) { currentClassFields.put( field.getName(), field ); } } List<ObjectProperty> currentManagedProperties = parseManagedTypesProperties( javaClassSource, classTypeResolver ); currentManagedProperties = DataModelUtils.filterAssignableFields( currentManagedProperties ); //prior to touch the class fields get the constructors candidates to be the current all fields, position annotated fields, and key annotated fields constructors. List<MethodSource<JavaClassSource>> allFieldsConstructorCandidates = findAllFieldsConstructorCandidates( javaClassSource, currentManagedProperties, classTypeResolver ); List<MethodSource<JavaClassSource>> keyFieldsConstructorCandidates = findKeyFieldsConstructorCandidates( javaClassSource, currentManagedProperties, classTypeResolver ); List<MethodSource<JavaClassSource>> positionFieldsConstructorCandidates = findPositionFieldsConstructorCandidates( javaClassSource, currentManagedProperties, classTypeResolver ); //create new fields and update existing. for ( ObjectProperty property : dataObject.getProperties() ) { if ( property.isFinal() || property.isStatic() ) { preservedFields.put( property.getName(), property.getName() ); continue; } if ( currentClassFields.containsKey( property.getName() ) ) { updateField( javaClassSource, property.getName(), property, classTypeResolver ); } else { createField( javaClassSource, property, classTypeResolver ); } preservedFields.put( property.getName(), property.getName() ); } //update constructors, equals and hashCode methods. updateConstructors( javaClassSource, dataObject, allFieldsConstructorCandidates, keyFieldsConstructorCandidates, positionFieldsConstructorCandidates, classTypeResolver ); //delete fields from .java file that not exists in the DataObject. List<String> removableFields = new ArrayList<String>(); for ( FieldSource<JavaClassSource> field : currentClassFields.values() ) { if ( !preservedFields.containsKey( field.getName() ) && isManagedField( field, classTypeResolver ) ) { removableFields.add( field.getName() ); } } for ( String fieldName : removableFields ) { removeField( javaClassSource, fieldName, classTypeResolver ); } // update nested classes List<JavaSource<?>> nestedTypes = javaClassSource.getNestedTypes(); if ( nestedTypes != null ) { for ( JavaSource nestedJavaSource : nestedTypes ) { if ( isAccepted( nestedJavaSource ) ) { javaClassSource.removeNestedType( nestedJavaSource ); } } } GenerationEngine engine = GenerationEngine.getInstance(); GenerationContext context = new GenerationContext( null ); for ( org.kie.workbench.common.services.datamodeller.core.JavaClass nestedJavaClass : dataObject.getNestedClasses()) { javaClassSource.addNestedType( engine.generateNestedClassString( context, nestedJavaClass, "" )); } } public boolean updateClassName( JavaClassSource javaClassSource, String name ) throws Exception { String oldName = javaClassSource.getName(); javaClassSource.setName( name ); return StringUtils.equals( oldName, name ); } public boolean updateSuperClassName( JavaClassSource javaClassSource, String superClassName, ClassTypeResolver classTypeResolver ) throws Exception { String oldSuperClassName = javaClassSource.getSuperType() != null ? resolveTypeName( classTypeResolver, javaClassSource.getSuperType() ) : null; if ( !StringUtils.equals( oldSuperClassName, superClassName ) ) { //TODO remove the extra "import packageName.SuperClassName" added by Roaster when a class name is set as superclass. javaClassSource.setSuperType( superClassName ); return true; } return false; } public void updateAnnotations( AnnotationTargetSource annotationTargetSource, List<Annotation> annotations, ClassTypeResolver classTypeResolver ) throws Exception { List<AnnotationSource<?>> currentAnnotations = annotationTargetSource.getAnnotations(); if ( currentAnnotations != null ) { for ( AnnotationSource<?> currentAnnotation : currentAnnotations ) { annotationTargetSource.removeAnnotation( currentAnnotation ); } } if ( annotations != null ) { for ( Annotation annotation : annotations ) { addAnnotation( annotationTargetSource, annotation ); } } } public AnnotationSource<?> addAnnotation( AnnotationTargetSource annotationTargetSource, Annotation annotation ) { AnnotationSource<?> newAnnotationSource = annotationTargetSource.addAnnotation(); newAnnotationSource.setName( annotation.getClassName() ); AnnotationDefinition annotationDefinition = annotation.getAnnotationDefinition(); if ( !annotationDefinition.isMarker() ) { for ( AnnotationValuePairDefinition memberDefinition : annotationDefinition.getValuePairs() ) { Object value = annotation.getValue( memberDefinition.getName() ); if ( value != null ) { addMemberValue( newAnnotationSource, memberDefinition, value ); } } } return newAnnotationSource; } public void addMemberValue( AnnotationSource<?> annotationSource, AnnotationValuePairDefinition valuePairDefinition, Object value ) { if ( value == null ) return; String encodedValue; if ( valuePairDefinition.isEnum() ) { if ( valuePairDefinition.isArray() ) { encodedValue = DriverUtils.encodeEnumArrayValue( valuePairDefinition, value ); } else { encodedValue = DriverUtils.encodeEnumValue( valuePairDefinition, value ); } if ( encodedValue != null ) { annotationSource.setLiteralValue( valuePairDefinition.getName(), encodedValue ); } } else if ( valuePairDefinition.isString() ) { //characters like '\r\t', \n, and " needs to be escaped due to Roaster internal implementation. if ( valuePairDefinition.isArray() ) { encodedValue = DriverUtils.encodeStringArrayValue( value, true ); } else { encodedValue = DriverUtils.encodeStringValue( value, true ); } if ( encodedValue != null ) { annotationSource.setLiteralValue( valuePairDefinition.getName(), encodedValue ); } } else if ( valuePairDefinition.isPrimitiveType() ) { //primitive types are wrapped by the java.lang.type. if ( valuePairDefinition.isArray() ) { encodedValue = DriverUtils.encodePrimitiveArrayValue( valuePairDefinition, value ); } else { encodedValue = DriverUtils.encodePrimitiveValue( valuePairDefinition, value ); } if ( encodedValue != null ) { annotationSource.setLiteralValue( valuePairDefinition.getName(), encodedValue ); } } else if ( valuePairDefinition.isAnnotation() ) { if ( valuePairDefinition.isArray() ) { List<Annotation> annotations = new ArrayList<Annotation>( ); if ( value instanceof List ) { for ( Object item : (List)value ) { if ( item instanceof Annotation ) { annotations.add( (Annotation) item ); } } } else if ( value instanceof Annotation ) { annotations.add( (Annotation) value ); } addAnnotationArrayMemberValue( annotationSource, valuePairDefinition, annotations ); } else if ( value instanceof Annotation ) { addAnnotationMemberValue( annotationSource, valuePairDefinition, (Annotation) value ); } } else if ( valuePairDefinition.isClass() ) { if ( valuePairDefinition.isArray() ) { encodedValue = DriverUtils.encodeClassArrayValue( value ); } else { encodedValue = DriverUtils.encodeClassValue( value.toString() ); } if ( encodedValue != null ) { annotationSource.setLiteralValue( valuePairDefinition.getName(), encodedValue ); } } } private void addAnnotationMemberValue( AnnotationSource annotationSource, AnnotationValuePairDefinition valuePairDefinition, Annotation annotation) { AnnotationSource targetAnnotation = annotationSource.addAnnotationValue( valuePairDefinition.getName() ); targetAnnotation.setName( annotation.getClassName() ); if ( !annotation.getAnnotationDefinition().isMarker() ) { for ( AnnotationValuePairDefinition memberDefinition : annotation.getAnnotationDefinition().getValuePairs() ) { Object value = annotation.getValue( memberDefinition.getName() ); if ( value != null ) { addMemberValue( targetAnnotation, memberDefinition, value ); } } } } private void addAnnotationArrayMemberValue( AnnotationSource annotationSource, AnnotationValuePairDefinition valuePairDefinition, List<Annotation> annotations) { if ( annotations != null ) { for ( Annotation annotation : annotations ) { addAnnotationMemberValue( annotationSource, valuePairDefinition, annotation ); } } } public void updateMethods( JavaClassSource javaClassSource, List<org.kie.workbench.common.services.datamodeller.core.Method> methods, ClassTypeResolver classTypeResolver ) throws Exception { List<MethodSource<JavaClassSource>> currentMethods = javaClassSource.getMethods(); if ( currentMethods != null ) { for ( MethodSource<JavaClassSource> currentMethod : currentMethods ) { if ( isAccepted( currentMethod ) ) { javaClassSource.removeMethod( currentMethod ); } } } if ( methods != null ) { for ( org.kie.workbench.common.services.datamodeller.core.Method method : methods ) { addMethod( javaClassSource, method, classTypeResolver ); } } } private void addMethod( JavaClassSource javaClassSource, org.kie.workbench.common.services.datamodeller.core.Method method, ClassTypeResolver classTypeResolver ) throws ClassNotFoundException { MethodSource<JavaClassSource> methodSource = javaClassSource.addMethod(); methodSource.setName( method.getName() ); methodSource.setReturnType( buildMethodReturnTypeString( method.getReturnType(), classTypeResolver ) ); methodSource.setParameters( buildMethodParameterString( method.getParameters(), classTypeResolver ) ); methodSource.setBody( method.getBody() ); methodSource.setVisibility( buildVisibility( method.getVisibilty() ) ); for ( Annotation annotation : method.getAnnotations() ) { addAnnotation( methodSource, annotation ); } } private String buildMethodReturnTypeString( org.kie.workbench.common.services.datamodeller.core.Type methodReturnType, ClassTypeResolver classTypeResolver ) throws ClassNotFoundException { if ( methodReturnType == null ) { return null; } StringBuilder builder = new StringBuilder(); builder.append( resolveTypeName( classTypeResolver, methodReturnType.getName() ) ); buildTypeArgumentsString( methodReturnType.getTypeArguments(), classTypeResolver, builder ); return builder.toString(); } private String buildMethodParameterString( List<org.kie.workbench.common.services.datamodeller.core.Parameter> methodParameters, ClassTypeResolver classTypeResolver ) throws ClassNotFoundException { if ( methodParameters == null || methodParameters.isEmpty() ) { return null; } StringBuilder builder = new StringBuilder(); java.util.Iterator<org.kie.workbench.common.services.datamodeller.core.Parameter> iterator = methodParameters.iterator(); while ( iterator.hasNext() ) { org.kie.workbench.common.services.datamodeller.core.Parameter parameter = iterator.next(); org.kie.workbench.common.services.datamodeller.core.Type parameterType = parameter.getType(); builder.append( resolveTypeName( classTypeResolver, parameterType.getName() ) ); buildTypeArgumentsString( parameter.getType().getTypeArguments(), classTypeResolver, builder ); builder.append( " " ); builder.append( parameter.getName() ); if ( iterator.hasNext() ) { builder.append( "," ); } } return builder.toString(); } private org.jboss.forge.roaster.model.Visibility buildVisibility( Visibility visibility ) { if ( visibility == null ) { return org.jboss.forge.roaster.model.Visibility.PACKAGE_PRIVATE; } return DriverUtils.buildVisibility( visibility ); } private void buildTypeArgumentsString( List<org.kie.workbench.common.services.datamodeller.core.Type> typeArguments, ClassTypeResolver classTypeResolver, StringBuilder builder ) throws ClassNotFoundException { if ( typeArguments == null || typeArguments.isEmpty() ) { return; } builder.append( "<" ); java.util.Iterator<org.kie.workbench.common.services.datamodeller.core.Type> iterator = typeArguments.iterator(); while ( iterator.hasNext() ) { org.kie.workbench.common.services.datamodeller.core.Type argument = iterator.next(); builder.append( resolveTypeName( classTypeResolver, argument.getName() ) ); buildTypeArgumentsString( argument.getTypeArguments(), classTypeResolver, builder ); if ( iterator.hasNext() ) { builder.append( "," ); } } builder.append( ">" ); } public void createField( JavaClassSource javaClassSource, ObjectProperty property, ClassTypeResolver classTypeResolver ) throws Exception { String fieldSource; String methodSource; String methodName; GenerationContext generationContext = new GenerationContext( null ); GenerationEngine engine; GenerationTools genTools = new GenerationTools(); try { engine = GenerationEngine.getInstance(); fieldSource = genTools.indent( engine.generateCompleteFieldString( generationContext, property ) ); javaClassSource.addField( fieldSource ); //create getter methodSource = genTools.indent( engine.generateFieldGetterString( generationContext, property ) ); methodName = genTools.toJavaGetter( property.getName(), property.getClassName() ); //remove old getter if exists removeMethodByParamsClassName( javaClassSource, methodName ); //add the new getter javaClassSource.addMethod( methodSource ); //create setter methodSource = genTools.indent( engine.generateFieldSetterString( generationContext, property ) ); methodName = genTools.toJavaSetter( property.getName() ); //remove old setter if exists //TODO check collections //TODO aca tengo un problema cuando creo un Pojo en memoria y a su vez un field de ese tipo. //Porque intento resolver la clase con el classTypeResolver y el Pojo aun no ha sido creado con lo cual //tengo Class Not found exception. //Tengo que implementar el remove de otra forma para este caso, posiblemente iterando todos los metodos. //Cuando le cambio el tipo a un field de un pojo existente hacia un tipo de una clase creada en memoria //Crei que podria darse tambien esta exception pero parece que no. //Tengo que ver a ver porque no se da el error en este caso. //Class<?> fieldClass = classTypeResolver.resolveType( property.getClassName() ); removeMethodByParamsClassName( javaClassSource, methodName, property.getClassName() ); //add the new setter javaClassSource.addMethod( methodSource ); } catch ( Exception e ) { logger.error( "Field: " + property.getName() + " couldn't be created.", e ); throw e; } } public void updateField( JavaClassSource javaClassSource, String fieldName, ObjectProperty property, ClassTypeResolver classTypeResolver ) throws Exception { GenerationTools genTools = new GenerationTools(); GenerationEngine engine = GenerationEngine.getInstance(); GenerationContext context = new GenerationContext( null ); boolean updateAccessors = false; FieldSource<JavaClassSource> field; field = javaClassSource.getField( fieldName ); Type oldType = field.getType(); if ( hasChangedToCollectionType( field, property, classTypeResolver ) ) { //fields that changed to a collection like java.util.List<SomeEntity> //needs to be removed and created again due to Roaster. Ideally it shouldn't be so. updateCollectionField( javaClassSource, fieldName, property, classTypeResolver ); } else { //for the rest of changes is better to manage the field update without removing the field. if ( !fieldName.equals( property.getName() ) ) { field.setName( property.getName() ); //the field was renamed, accessors must be updated. updateAccessors = true; } if ( DriverUtils.isManagedType( field.getType(), classTypeResolver ) && !DriverUtils.equalsType( field.getType(), property.getClassName(), property.isMultiple(), property.getBag(), classTypeResolver ) ) { //the has type changed, and not to a collection type. String newClassName = property.getClassName(); field.setType( newClassName ); if ( field.getLiteralInitializer() != null ) { //current field has an initializer, but the field type changed so we are not sure old initializer is //valid for the new type. if ( NamingUtils.isPrimitiveTypeId( newClassName ) ) { setPrimitiveTypeDefaultInitializer( field, newClassName ); } else { field.setLiteralInitializer( null ); } } updateAccessors = true; } updateAnnotations( field, property.getAnnotations(), classTypeResolver ); if ( updateAccessors ) { String accessorName; String methodSource; String oldClassName; //remove old accessors //TODO check primitive types Class<?> oldClass = classTypeResolver.resolveType( oldType.getName() ); oldClassName = oldClass.getName(); accessorName = genTools.toJavaGetter( fieldName, oldClassName ); removeMethodByParamsClass( javaClassSource, accessorName ); accessorName = genTools.toJavaSetter( fieldName ); removeMethodByParamsClass( javaClassSource, accessorName, oldClass ); //and generate the new ones methodSource = genTools.indent( engine.generateFieldGetterString( context, property ) ); javaClassSource.addMethod( methodSource ); methodSource = genTools.indent( engine.generateFieldSetterString( context, property ) ); javaClassSource.addMethod( methodSource ); } } } private void updateCollectionField( JavaClassSource javaClassSource, String fieldName, ObjectProperty property, ClassTypeResolver classTypeResolver ) throws Exception { GenerationTools genTools = new GenerationTools(); GenerationEngine engine = GenerationEngine.getInstance(); GenerationContext context = new GenerationContext( null ); boolean updateAccessors = true; FieldSource<JavaClassSource> currentField; currentField = javaClassSource.getField( fieldName ); Type currentType = currentField.getType(); StringBuilder fieldSource = new StringBuilder(); fieldSource.append( engine.generateCompleteFieldString( context, property ) ); javaClassSource.removeField( currentField ); javaClassSource.addField( fieldSource.toString() ); if ( updateAccessors ) { String accessorName; String methodSource; String oldClassName; //remove old accessors //TODO check primitive types Class<?> oldClass = classTypeResolver.resolveType( currentType.getName() ); oldClassName = oldClass.getName(); accessorName = genTools.toJavaGetter( fieldName, oldClassName ); removeMethodByParamsClass( javaClassSource, accessorName ); accessorName = genTools.toJavaSetter( fieldName ); removeMethodByParamsClass( javaClassSource, accessorName, oldClass ); //and generate the new ones methodSource = genTools.indent( engine.generateFieldGetterString( context, property ) ); javaClassSource.addMethod( methodSource ); methodSource = genTools.indent( engine.generateFieldSetterString( context, property ) ); javaClassSource.addMethod( methodSource ); } } private boolean hasChangedToCollectionType( FieldSource<JavaClassSource> field, ObjectProperty property, ClassTypeResolver classTypeResolver ) throws Exception { return DriverUtils.isManagedType( field.getType(), classTypeResolver ) && !DriverUtils.equalsType( field.getType(), property.getClassName(), property.isMultiple(), property.getBag(), classTypeResolver ) && property.isMultiple(); } public void updateConstructors( JavaClassSource javaClassSource, DataObject dataObject, List<MethodSource<JavaClassSource>> allFieldsConstructorCandidates, List<MethodSource<JavaClassSource>> keyFieldsConstructorCandidates, List<MethodSource<JavaClassSource>> positionFieldsConstructorCandidates, ClassTypeResolver classTypeResolver ) throws Exception { GenerationContext generationContext = new GenerationContext( null ); GenerationEngine engine = GenerationEngine.getInstance(); GenerationTools genTools = new GenerationTools(); JavaRoasterModelDriver modelDriver = new JavaRoasterModelDriver(); boolean needsAllFieldsConstructor; boolean needsKeyFieldsConstructor; boolean needsPositionFieldsConstructor; boolean needsEmptyConstructor; String defaultConstructorSource; String allFieldsConstructorSource; String keyFieldsConstructorSource; String positionFieldsConstructorSource; String equalsMethodSource; String hashCodeMethodSource; //check if the candidate methods has exactly the same body of the generated by the data modeller. List<MethodSource<JavaClassSource>> currentAllFieldsConstructors = modelDriver.filterGeneratedConstructors( allFieldsConstructorCandidates ); List<MethodSource<JavaClassSource>> currentKeyFieldsConstructors = modelDriver.filterGeneratedConstructors( keyFieldsConstructorCandidates ); List<MethodSource<JavaClassSource>> currentPositionFieldsConstructors = modelDriver.filterGeneratedConstructors( positionFieldsConstructorCandidates ); if ( logger.isDebugEnabled() ) { logger.debug( "allFieldsConstructorCandidates candidates: " + allFieldsConstructorCandidates.size() ); logger.debug( allFieldsConstructorCandidates.size() > 0 ? allFieldsConstructorCandidates.get( 0 ).toString() : "" ); logger.debug( "\n\n" ); logger.debug( "currentAllFieldsConstructors: " + currentAllFieldsConstructors.size() ); logger.debug( currentAllFieldsConstructors.size() > 0 ? currentAllFieldsConstructors.get( 0 ).toString() : "" ); logger.debug( "\n\n" ); logger.debug( "KeyFieldsConstructorCandidates: " + keyFieldsConstructorCandidates.size() ); logger.debug( keyFieldsConstructorCandidates.size() > 0 ? keyFieldsConstructorCandidates.get( 0 ).toString() : "" ); logger.debug( "\n\n" ); logger.debug( "currentKeyFieldsConstructors: " + currentKeyFieldsConstructors.size() ); logger.debug( currentKeyFieldsConstructors.size() > 0 ? currentKeyFieldsConstructors.get( 0 ).toString() : "" ); logger.debug( "\n\n" ); logger.debug( "positionFieldsConstructorCandidates: " + positionFieldsConstructorCandidates.size() ); logger.debug( positionFieldsConstructorCandidates.size() > 0 ? positionFieldsConstructorCandidates.get( 0 ).toString() : "" ); logger.debug( "\n\n" ); logger.debug( "currentPositionFieldsConstructors: " + currentPositionFieldsConstructors.size() ); logger.debug( currentPositionFieldsConstructors.size() > 0 ? currentPositionFieldsConstructors.get( 0 ).toString() : "" ); logger.debug( "\n\n" ); } //delete current data modeller generated all fields, key fields, and position fields constructors if there are any. for ( MethodSource<JavaClassSource> constructor : currentAllFieldsConstructors ) { javaClassSource.removeMethod( constructor ); } for ( MethodSource<JavaClassSource> constructor : currentKeyFieldsConstructors ) { javaClassSource.removeMethod( constructor ); } for ( MethodSource<JavaClassSource> constructor : currentPositionFieldsConstructors ) { javaClassSource.removeMethod( constructor ); } //calculate the file order for the fields. List<FieldSource<JavaClassSource>> fields = javaClassSource.getFields(); if ( fields != null && fields.size() > 0 ) { int fileOrder = 0; for ( FieldSource<JavaClassSource> field : fields ) { ObjectPropertyImpl objectProperty = ( ObjectPropertyImpl ) dataObject.getProperty( field.getName() ); if ( objectProperty != null ) { objectProperty.setFileOrder( fileOrder ); } fileOrder++; } } //get the sorted list of all fields, position annotated and key annotated fields. These lists will be used //to identify collisions with client provided constructors. List<ObjectProperty> allFields = DataModelUtils.sortByFileOrder( DataModelUtils.filterAssignableFields( dataObject ) ); List<ObjectProperty> positionFields = DataModelUtils.sortByPosition( DataModelUtils.filterPositionFields( dataObject ) ); List<ObjectProperty> keyFields = DataModelUtils.sortByFileOrder( DataModelUtils.filterKeyFields( dataObject ) ); needsEmptyConstructor = true; //we always wants to generate the default constructor. needsAllFieldsConstructor = allFields.size() > 0; needsPositionFieldsConstructor = positionFields.size() > 0 && !DataModelUtils.equalsByFieldName( allFields, positionFields ) && !DataModelUtils.equalsByFieldType( allFields, positionFields ); needsKeyFieldsConstructor = keyFields.size() > 0 && !DataModelUtils.equalsByFieldName( allFields, keyFields ) && !DataModelUtils.equalsByFieldType( allFields, keyFields ) && !DataModelUtils.equalsByFieldName( positionFields, keyFields ) && !DataModelUtils.equalsByFieldType( positionFields, keyFields ); List<MethodSource<JavaClassSource>> currentConstructors = new ArrayList<MethodSource<JavaClassSource>>(); MethodSource<JavaClassSource> currentEquals = null; MethodSource<JavaClassSource> currentHashCode = null; MethodSource<JavaClassSource> newConstructor; //Iterate remaining methods looking for client provided constructors, hashCode and equals methods. List<MethodSource<JavaClassSource>> methods = javaClassSource.getMethods(); if ( methods != null ) { for ( MethodSource<JavaClassSource> method : methods ) { if ( method.isConstructor() ) { currentConstructors.add( method ); if ( method.getParameters() == null || method.getParameters().size() == 0 ) { needsEmptyConstructor = false; } } else if ( isEquals( method ) ) { currentEquals = method; } else if ( isHashCode( method ) ) { currentHashCode = method; } } } //check collisions with remaining constructors first. needsAllFieldsConstructor = needsAllFieldsConstructor && ( findMatchingConstructorsByTypes( javaClassSource, allFields, classTypeResolver ).size() == 0 ); needsPositionFieldsConstructor = needsPositionFieldsConstructor && ( findMatchingConstructorsByTypes( javaClassSource, positionFields, classTypeResolver ).size() == 0 ); needsKeyFieldsConstructor = needsKeyFieldsConstructor && ( findMatchingConstructorsByTypes( javaClassSource, keyFields, classTypeResolver ).size() == 0 ); //remove current equals and hashCode methods if ( currentEquals != null ) { javaClassSource.removeMethod( currentEquals ); } if ( currentHashCode != null ) { javaClassSource.removeMethod( currentHashCode ); } //finally create the needed constructors if ( needsEmptyConstructor ) { defaultConstructorSource = genTools.indent( engine.generateDefaultConstructorString( generationContext, dataObject ) ); newConstructor = javaClassSource.addMethod( defaultConstructorSource ); newConstructor.setConstructor( true ); } if ( needsAllFieldsConstructor ) { allFieldsConstructorSource = genTools.indent( engine.generateAllFieldsConstructorString( generationContext, dataObject ) ); if ( allFieldsConstructorSource != null && !allFieldsConstructorSource.trim().isEmpty() ) { newConstructor = javaClassSource.addMethod( allFieldsConstructorSource ); newConstructor.setConstructor( true ); } } if ( needsPositionFieldsConstructor ) { positionFieldsConstructorSource = genTools.indent( engine.generatePositionFieldsConstructorString( generationContext, dataObject ) ); if ( positionFieldsConstructorSource != null && !positionFieldsConstructorSource.trim().isEmpty() ) { newConstructor = javaClassSource.addMethod( positionFieldsConstructorSource ); newConstructor.setConstructor( true ); } } if ( needsKeyFieldsConstructor ) { keyFieldsConstructorSource = genTools.indent( engine.generateKeyFieldsConstructorString( generationContext, dataObject ) ); if ( keyFieldsConstructorSource != null && !keyFieldsConstructorSource.trim().isEmpty() ) { newConstructor = javaClassSource.addMethod( keyFieldsConstructorSource ); newConstructor.setConstructor( true ); } } if ( keyFields.size() > 0 ) { equalsMethodSource = genTools.indent( engine.generateEqualsString( generationContext, dataObject ) ); javaClassSource.addMethod( equalsMethodSource ); hashCodeMethodSource = genTools.indent( engine.generateHashCodeString( generationContext, dataObject ) ); javaClassSource.addMethod( hashCodeMethodSource ); } } /** * Takes care of field and the corresponding setter/getter removal. */ public void removeField( JavaClassSource javaClassSource, String fieldName, ClassTypeResolver classTypeResolver ) throws Exception { logger.debug( "Removing field: " + fieldName + ", from class: " + javaClassSource.getName() ); FieldSource<JavaClassSource> field; GenerationTools genTools = new GenerationTools(); String methodName; field = javaClassSource.getField( fieldName ); if ( field != null ) { //check if the class has a setter/getter for the given field. Class<?> fieldClass = classTypeResolver.resolveType( field.getType().getName() ); methodName = genTools.toJavaGetter( fieldName, fieldClass.getName() ); removeMethodByParamsClass( javaClassSource, methodName ); methodName = genTools.toJavaSetter( fieldName ); removeMethodByParamsClass( javaClassSource, methodName, fieldClass ); //finally remove the field. javaClassSource.removeField( field ); } else { logger.debug( "Field field: " + fieldName + " was not found in class: " + javaClassSource.getName() ); } } public void removeMethodByParamsClass( JavaClassSource javaClassSource, String methodName, Class<?>... paramTypes ) { logger.debug( "Removing method: " + methodName + ", form class: " + javaClassSource.getName() ); MethodSource<JavaClassSource> method = javaClassSource.getMethod( methodName, paramTypes ); if ( method != null ) { javaClassSource.removeMethod( method ); logger.debug( "Method method: " + methodName + ", was removed from class: " + javaClassSource.getName() ); } else { logger.debug( "Method method: " + methodName + " not exists for class: " + javaClassSource.getName() ); } } public void removeMethodByParamsClassName( JavaClassSource javaClassSource, String methodName, String... paramTypes ) { logger.debug( "Removing method: " + methodName + ", form class: " + javaClassSource.getName() ); MethodSource<JavaClassSource> method = javaClassSource.getMethod( methodName, paramTypes ); if ( method != null ) { javaClassSource.removeMethod( method ); logger.debug( "Method method: " + methodName + ", was removed from class: " + javaClassSource.getName() ); } else { logger.debug( "Method method: " + methodName + " not exists for class: " + javaClassSource.getName() ); } } public List<MethodSource<JavaClassSource>> findAllFieldsConstructorCandidates( JavaClassSource javaClassSource, List<ObjectProperty> properties, ClassTypeResolver classTypeResolver ) { return findMatchingConstructorsByParameters( javaClassSource, properties, classTypeResolver ); } public List<MethodSource<JavaClassSource>> findKeyFieldsConstructorCandidates( JavaClassSource javaClassSource, List<ObjectProperty> properties, ClassTypeResolver classTypeResolver ) { List<ObjectProperty> keyFields = DataModelUtils.filterKeyFields( properties ); return findMatchingConstructorsByParameters( javaClassSource, keyFields, classTypeResolver ); } public List<MethodSource<JavaClassSource>> findPositionFieldsConstructorCandidates( JavaClassSource javaClassSource, List<ObjectProperty> properties, ClassTypeResolver classTypeResolver ) { List<ObjectProperty> positionalFields = DataModelUtils.filterPositionFields( properties ); return findMatchingConstructorsByParameters( javaClassSource, DataModelUtils.sortByPosition( positionalFields ), classTypeResolver ); } public List<MethodSource<JavaClassSource>> findMatchingConstructorsByParameters( JavaClassSource javaClassSource, List<ObjectProperty> properties, ClassTypeResolver classTypeResolver ) { List<MethodSource<JavaClassSource>> result = new ArrayList<MethodSource<JavaClassSource>>(); List<MethodSource<JavaClassSource>> constructors = getConstructors( javaClassSource ); for ( MethodSource<JavaClassSource> constructor : constructors ) { List<ParameterSource<JavaClassSource>> parameters = constructor.getParameters(); if ( parameters == null || parameters.size() == 0 || parameters.size() != properties.size() ) { continue; } int unmatchedParams = parameters.size(); int paramIndex = 0; for ( ParameterSource<JavaClassSource> param : parameters ) { if ( paramMatchesWithProperty( param, properties.get( paramIndex ), classTypeResolver ) ) { unmatchedParams--; //TODO optimize to not visit all parameters, now I want to visit them all by intention } paramIndex++; } if ( unmatchedParams == 0 ) { result.add( constructor ); } } return result; } public List<MethodSource<JavaClassSource>> findMatchingConstructorsByTypes( JavaClassSource javaClassSource, List<ObjectProperty> properties, ClassTypeResolver classTypeResolver ) { List<MethodSource<JavaClassSource>> result = new ArrayList<MethodSource<JavaClassSource>>(); List<MethodSource<JavaClassSource>> constructors = getConstructors( javaClassSource ); for ( MethodSource<JavaClassSource> constructor : constructors ) { List<ParameterSource<JavaClassSource>> parameters = constructor.getParameters(); if ( parameters == null || parameters.size() == 0 || parameters.size() != properties.size() ) { continue; } int unmatchedParams = parameters.size(); int paramIndex = 0; for ( ParameterSource<JavaClassSource> param : parameters ) { if ( paramMatchesWithPropertyType( param, properties.get( paramIndex ), classTypeResolver ) ) { unmatchedParams--; } else { break; } paramIndex++; } if ( unmatchedParams == 0 ) { result.add( constructor ); } } return result; } public boolean paramMatchesWithProperty( ParameterSource<JavaClassSource> param, ObjectProperty property, ClassTypeResolver classTypeResolver ) { if ( !param.getName().equals( property.getName() ) ) { return false; } try { return DriverUtils.equalsType( param.getType(), property.getClassName(), property.isMultiple(), property.getBag(), classTypeResolver ); } catch ( Exception e ) { //TODO check if we need to propagate this exception. logger.error( "An error was produced on parameter matching test with param: " + param.getName() + " and field: " + property.getName(), e ); return false; } } public boolean paramMatchesWithPropertyType( ParameterSource<JavaClassSource> param, ObjectProperty property, ClassTypeResolver classTypeResolver ) { try { return DriverUtils.equalsType( param.getType(), property.getClassName(), property.isMultiple(), property.getBag(), classTypeResolver ); } catch ( Exception e ) { //TODO check if we need to propagate this exception. logger.error( "An error was produced on parameter matching test with param: " + param.getName() + " and field: " + property.getName(), e ); return false; } } public List<MethodSource<JavaClassSource>> filterGeneratedConstructors( List<MethodSource<JavaClassSource>> constructors ) { List<MethodSource<JavaClassSource>> result = new ArrayList<MethodSource<JavaClassSource>>(); if ( constructors != null ) { for ( MethodSource<JavaClassSource> constructor : constructors ) { if ( isGeneratedConstructor( constructor ) ) { result.add( constructor ); } } } return result; } /** * @param constructor a Constructor method to check. * @return true, if the given constructor was generated by the data modeler. */ public boolean isGeneratedConstructor( MethodSource<JavaClassSource> constructor ) { if ( constructor.isAbstract() || constructor.isStatic() || constructor.isFinal() ) { return false; } if ( !constructor.isPublic() ) { return false; //we only generate public constructors. } if ( constructor.getAnnotations() != null && constructor.getAnnotations().size() > 0 ) { return false; //we never add annotations to constructors } List<ParameterSource<JavaClassSource>> parameters = constructor.getParameters(); List<String> expectedLines = new ArrayList<String>(); String expectedLine; if ( parameters != null ) { for ( ParameterSource<JavaClassSource> param : parameters ) { if ( param.getAnnotations() != null && param.getAnnotations().size() > 0 ) { return false; //we never add annotations to parameters } //ideally we should know if the parameter is final, but Roaster don't provide that info. expectedLine = "this." + param.getName() + "=" + param.getName() + ";"; expectedLines.add( expectedLine ); } } String body = constructor.getBody(); if ( body == null || ( body = body.trim() ).isEmpty() ) { return false; } try { BufferedReader reader = new BufferedReader( new StringReader( body ) ); String line = null; int lineNumber = 0; while ( ( line = reader.readLine() ) != null ) { lineNumber++; if ( lineNumber > expectedLines.size() ) { return false; } if ( !line.trim().equals( expectedLines.get( lineNumber - 1 ) ) ) { return false; } } return lineNumber == expectedLines.size(); } catch ( IOException e ) { return false; } } public List<MethodSource<JavaClassSource>> getConstructors( JavaClassSource javaClassSource ) { List<MethodSource<JavaClassSource>> constructors = new ArrayList<MethodSource<JavaClassSource>>(); List<MethodSource<JavaClassSource>> methods = javaClassSource.getMethods(); if ( methods != null ) { for ( MethodSource<JavaClassSource> method : methods ) { if ( method.isConstructor() ) { constructors.add( method ); } } } return constructors; } public boolean isManagedField( FieldSource<JavaClassSource> field, ClassTypeResolver classTypeResolver ) throws Exception { if ( !field.isFinal() && !field.isStatic() ) { //finally we can check if the field type is a managed type. //if not, the field should remain untouched return DriverUtils.isManagedType( field.getType(), classTypeResolver ); } return false; } public boolean isEquals( MethodSource<?> method ) { return method.getName().equals( "equals" ) && ( method.getParameters() == null || method.getParameters().size() == 1 ) && method.getReturnType() != null && method.getReturnType().isPrimitive() && "boolean".equals( method.getReturnType().getName() ); } public boolean isHashCode( MethodSource<?> method ) { return method.getName().equals( "hashCode" ) && ( method.getParameters() == null || method.getParameters().size() == 0 ) && method.getReturnType() != null && method.getReturnType().isPrimitive() && "int".equals( method.getReturnType().getName() ); } public void setPrimitiveTypeDefaultInitializer( FieldSource<?> field, String primitiveType ) { if ( NamingUtils.BYTE.equals( primitiveType ) ) { field.setLiteralInitializer( "0" ); } if ( NamingUtils.SHORT.equals( primitiveType ) ) { field.setLiteralInitializer( "0" ); } if ( NamingUtils.INT.equals( primitiveType ) ) { field.setLiteralInitializer( "0" ); } if ( NamingUtils.LONG.equals( primitiveType ) ) { field.setLiteralInitializer( "0L" ); } if ( NamingUtils.FLOAT.equals( primitiveType ) ) { field.setLiteralInitializer( "0.0f" ); } if ( NamingUtils.DOUBLE.equals( primitiveType ) ) { field.setLiteralInitializer( "0.0d" ); } if ( NamingUtils.CHAR.equals( primitiveType ) ) { field.setLiteralInitializer( "\'\\u0000\'" ); } if ( NamingUtils.BOOLEAN.equals( primitiveType ) ) { field.setLiteralInitializer( "false" ); } } public Pair<Annotation, List<DriverError>> parseAnnotationWithValuePair( String annotationClassName, ElementType target, String valuePairName, String literalValue, ClassLoader classLoader ) { List<DriverError> driverErrors = new ArrayList<DriverError>(); Annotation annotation = null; Pair<AnnotationSource<JavaClassSource>, List<DriverError>> parseResult = parseAnnotationWithValuePair( annotationClassName, target, valuePairName, literalValue ); driverErrors.addAll( parseResult.getK2() ); if ( driverErrors.size() == 0 ) { //TODO review this, we should use DriverUtils.createClassTypeResolver( javaClassSource, classLoader ); instead ClassTypeResolver classTypeResolver = new ClassTypeResolver( Collections.emptySet(), classLoader ); try { annotation = createAnnotation( parseResult.getK1(), classTypeResolver ); } catch ( Exception e ) { driverErrors.add( new DriverError( e.getMessage() ) ); } } return new Pair<>( annotation, driverErrors ); } public Pair<AnnotationSource<JavaClassSource>, List<DriverError>> parseAnnotationWithValuePair( String annotationClassName, ElementType target, String valuePairName, String literalValue ) { List<DriverError> syntaxErrors = new ArrayList<DriverError>(); String annotationStr = "@" + annotationClassName + "(" + valuePairName + "=" + literalValue + " )"; String stub; AnnotationSource<JavaClassSource> annotation = null; if ( ElementType.TYPE.equals( target ) ) { stub = annotationStr + " public class Stub { }"; } else { stub = "public class Stub { " + annotationStr + " int dummy; }"; } JavaClassSource temp = ( JavaClassSource ) Roaster.parse( JavaClass.class, stub ); if ( temp.getSyntaxErrors() != null && temp.getSyntaxErrors().size() > 0 ) { for ( org.jboss.forge.roaster.model.SyntaxError syntaxError : temp.getSyntaxErrors() ) { syntaxErrors.add( new DriverError( syntaxError.getDescription(), syntaxError.getLine(), syntaxError.getColumn() ) ); } } else if ( ElementType.TYPE.equals( target ) ) { annotation = temp.getAnnotation( annotationClassName ); } else { annotation = temp.getField( "dummy" ).getAnnotation( annotationClassName ); } if ( annotation == null ) { syntaxErrors.add( new DriverError( "Annotation value pair could not be parsed." ) ); } return new Pair<>( annotation, syntaxErrors ); } public boolean isManagedAnnotation( AnnotationSource<?> annotation, ClassTypeResolver classTypeResolver ) throws Exception { String annotationClassName = resolveTypeName( classTypeResolver, annotation.getName() ); return getConfiguredAnnotation( annotationClassName ) != null; } private String errorMessage( String message, Object... params ) { return MessageFormat.format( message, params ); } }