/* * JBoss, Home of Professional Open Source * Copyright 2010, Red Hat Middleware LLC, and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * 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.hibernate.jpamodelgen.test.util; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.URL; import java.net.URLClassLoader; import java.util.List; import javax.tools.Diagnostic; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * @author Hardy Ferentschik */ public class TestUtil { private static final Logger log = LoggerFactory.getLogger( TestUtil.class ); private static final String PATH_SEPARATOR = System.getProperty( "file.separator" ); private static final String PACKAGE_SEPARATOR = "."; private static final String META_MODEL_CLASS_POSTFIX = "_"; private static final File OUT_BASE_DIR; static { File targetDir = getTargetDir(); OUT_BASE_DIR = new File( targetDir, "processor-generated-test-classes" ); if ( !OUT_BASE_DIR.exists() ) { if ( !OUT_BASE_DIR.mkdirs() ) { fail( "Unable to create test output directory " + OUT_BASE_DIR.toString() ); } } } private TestUtil() { } public static void assertNoSourceFileGeneratedFor(Class<?> clazz) { assertNotNull( "Class parameter cannot be null", clazz ); File sourceFile = getMetaModelSourceFileFor( clazz ); assertFalse( "There should be no source file: " + sourceFile.getName(), sourceFile.exists() ); } public static void assertAbsenceOfFieldInMetamodelFor(Class<?> clazz, String fieldName) { assertAbsenceOfFieldInMetamodelFor( clazz, fieldName, "'" + fieldName + "' should not appear in metamodel class" ); } public static void assertAbsenceOfFieldInMetamodelFor(Class<?> clazz, String fieldName, String errorString) { assertFalse( buildErrorString( errorString, clazz ), hasFieldInMetamodelFor( clazz, fieldName ) ); } public static void assertPresenceOfFieldInMetamodelFor(Class<?> clazz, String fieldName) { assertPresenceOfFieldInMetamodelFor( clazz, fieldName, "'" + fieldName + "' should appear in metamodel class" ); } public static void assertPresenceOfFieldInMetamodelFor(Class<?> clazz, String fieldName, String errorString) { assertTrue( buildErrorString( errorString, clazz ), hasFieldInMetamodelFor( clazz, fieldName ) ); } public static void assertAttributeTypeInMetaModelFor(Class<?> clazz, String fieldName, Class<?> expectedType, String errorString) { Field field = getFieldFromMetamodelFor( clazz, fieldName ); assertNotNull( "Cannot find field '" + fieldName + "' in " + clazz.getName(), field ); ParameterizedType type = (ParameterizedType) field.getGenericType(); Type actualType = type.getActualTypeArguments()[1]; if ( expectedType.isArray() ) { expectedType = expectedType.getComponentType(); actualType = getComponentType( actualType ); } assertEquals( "Types do not match: " + buildErrorString( errorString, clazz ), expectedType, actualType ); } public static void assertMapAttributesInMetaModelFor(Class<?> clazz, String fieldName, Class<?> expectedMapKey, Class<?> expectedMapValue, String errorString) { Field field = getFieldFromMetamodelFor( clazz, fieldName ); assertNotNull( field ); ParameterizedType type = (ParameterizedType) field.getGenericType(); Type actualMapKeyType = type.getActualTypeArguments()[1]; assertEquals( buildErrorString( errorString, clazz ), expectedMapKey, actualMapKeyType ); Type actualMapKeyValue = type.getActualTypeArguments()[2]; assertEquals( buildErrorString( errorString, clazz ), expectedMapValue, actualMapKeyValue ); } public static void assertSuperClassRelationShipInMetamodel(Class<?> entityClass, Class<?> superEntityClass) { Class<?> clazz = getMetamodelClassFor( entityClass ); Class<?> superClazz = getMetamodelClassFor( superEntityClass ); assertEquals( "Entity " + superClazz.getName() + " should be the superclass of " + clazz.getName(), superClazz.getName(), clazz.getSuperclass().getName() ); } public static void assertNoCompilationError(List<Diagnostic<?>> diagnostics) { for ( Diagnostic<?> diagnostic : diagnostics ) { if ( diagnostic.getKind().equals( Diagnostic.Kind.ERROR ) ) { fail( "There was a compilation error during annotation processing:\n" + diagnostic.getMessage( null ) ); } } } /** * Asserts that a metamodel class for the specified class got generated. * * @param clazz the class for which a metamodel class should have been generated. */ public static void assertMetamodelClassGeneratedFor(Class<?> clazz) { assertNotNull( getMetamodelClassFor( clazz ) ); } /** * Deletes recursively all files found in the output directory for the annotation processor. */ public static void deleteProcessorGeneratedFiles() { for ( File file : OUT_BASE_DIR.listFiles() ) { deleteFilesRecursive( file ); } } /** * @return the output directory for the generated source and class files. */ public static File getOutBaseDir() { return OUT_BASE_DIR; } /** * Returns the static metamodel class for the specified entity. * * @param entityClass the entity for which to retrieve the metamodel class. Cannot be {@code null}. * * @return the static metamodel class for the specified entity. */ public static Class<?> getMetamodelClassFor(Class<?> entityClass) { assertNotNull( "Class parameter cannot be null", entityClass ); String metaModelClassName = entityClass.getName() + META_MODEL_CLASS_POSTFIX; try { URL outDirUrl = OUT_BASE_DIR.toURI().toURL(); URL[] urls = new URL[1]; urls[0] = outDirUrl; URLClassLoader classLoader = new URLClassLoader( urls, TestUtil.class.getClassLoader() ); return classLoader.loadClass( metaModelClassName ); } catch ( Exception e ) { fail( metaModelClassName + " was not generated." ); } // keep the compiler happy return null; } public static File getMetaModelSourceFileFor(Class<?> clazz) { String metaModelClassName = clazz.getName() + META_MODEL_CLASS_POSTFIX; // generate the file name String fileName = metaModelClassName.replace( PACKAGE_SEPARATOR, PATH_SEPARATOR ); fileName = fileName.concat( ".java" ); return new File( OUT_BASE_DIR + PATH_SEPARATOR + fileName ); } public static String getMetaModelSourceAsString(Class<?> clazz) { File sourceFile = getMetaModelSourceFileFor( clazz ); StringBuilder contents = new StringBuilder(); try { BufferedReader input = new BufferedReader( new FileReader( sourceFile ) ); try { String line; /* * readLine is a bit quirky : * it returns the content of a line MINUS the newline. * it returns null only for the END of the stream. * it returns an empty String if two newlines appear in a row. */ while ( ( line = input.readLine() ) != null ) { contents.append( line ); contents.append( System.getProperty( "line.separator" ) ); } } finally { input.close(); } } catch ( IOException ex ) { ex.printStackTrace(); } return contents.toString(); } public static void dumpMetaModelSourceFor(Class<?> clazz) { log.info( "Dumping meta model source for " + clazz.getName() + ":" ); log.info( getMetaModelSourceAsString( clazz ) ); } public static Field getFieldFromMetamodelFor(Class<?> entityClass, String fieldName) { Class<?> metaModelClass = getMetamodelClassFor( entityClass ); Field field; try { field = metaModelClass.getDeclaredField( fieldName ); } catch ( NoSuchFieldException e ) { field = null; } return field; } public static String fcnToPath(String fcn) { return fcn.replace( PACKAGE_SEPARATOR, PATH_SEPARATOR ); } private static boolean hasFieldInMetamodelFor(Class<?> clazz, String fieldName) { return getFieldFromMetamodelFor( clazz, fieldName ) != null; } private static String buildErrorString(String baseError, Class<?> clazz) { StringBuilder builder = new StringBuilder(); builder.append( baseError ); builder.append( ".\n\n" ); builder.append( "Source code for " ); builder.append( clazz.getName() ); builder.append( "_.java:" ); builder.append( "\n" ); builder.append( getMetaModelSourceAsString( clazz ) ); return builder.toString(); } private static Type getComponentType(Type actualType) { if ( actualType instanceof Class ) { Class<?> clazz = (Class<?>) actualType; if ( clazz.isArray() ) { return clazz.getComponentType(); } else { fail( "Unexpected component type" ); } } if ( actualType instanceof GenericArrayType ) { return ( (GenericArrayType) actualType ).getGenericComponentType(); } else { fail( "Unexpected component type" ); return null; } } private static class MetaModelFilenameFilter implements FileFilter { @Override public boolean accept(File pathName) { if ( pathName.isDirectory() ) { return true; } else { return pathName.getAbsolutePath().endsWith( "_.java" ) || pathName.getAbsolutePath().endsWith( "_.class" ); } } } private static void deleteFilesRecursive(File file) { if ( file.isDirectory() ) { for ( File c : file.listFiles() ) { deleteFilesRecursive( c ); } } if ( !file.delete() ) { fail( "Unable to delete file: " + file ); } } /** * Returns the target directory of the build. * * @return the target directory of the build */ public static File getTargetDir() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); // get a URL reference to something we now is part of the classpath (our own classes) String currentTestClass = TestUtil.class.getName(); int hopsToCompileDirectory = currentTestClass.split( "\\." ).length; int hopsToTargetDirectory = hopsToCompileDirectory + 2; URL classURL = contextClassLoader.getResource( currentTestClass.replace( '.', '/' ) + ".class" ); // navigate back to '/target' File targetDir = new File( classURL.getFile() ); // navigate back to '/target' for ( int i = 0; i < hopsToTargetDirectory; i++ ) { targetDir = targetDir.getParentFile(); } return targetDir; } }