/* * 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; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import javax.annotation.Generated; import javax.annotation.processing.FilerException; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import javax.tools.FileObject; import org.hibernate.jpamodelgen.model.MetaAttribute; import org.hibernate.jpamodelgen.model.MetaEntity; import org.hibernate.jpamodelgen.util.Constants; import org.hibernate.jpamodelgen.util.TypeUtils; /** * Helper class to write the actual meta model class using the {@link javax.annotation.processing.Filer} API. * * @author Emmanuel Bernard * @author Hardy Ferentschik */ public final class ClassWriter { private static final String META_MODEL_CLASS_NAME_SUFFIX = "_"; private static final ThreadLocal<SimpleDateFormat> SIMPLE_DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() { @Override public SimpleDateFormat initialValue() { return new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" ); } }; private ClassWriter() { } public static void writeFile(MetaEntity entity, Context context) { try { String metaModelPackage = entity.getPackageName(); // need to generate the body first, since this will also update the required imports which need to // be written out first String body = generateBody( entity, context ).toString(); FileObject fo = context.getProcessingEnvironment().getFiler().createSourceFile( getFullyQualifiedClassName( entity, metaModelPackage ) ); OutputStream os = fo.openOutputStream(); PrintWriter pw = new PrintWriter( os ); if ( !metaModelPackage.isEmpty() ) { pw.println( "package " + metaModelPackage + ";" ); pw.println(); } pw.println( entity.generateImports() ); pw.println( body ); pw.flush(); pw.close(); } catch ( FilerException filerEx ) { context.logMessage( Diagnostic.Kind.ERROR, "Problem with Filer: " + filerEx.getMessage() ); } catch ( IOException ioEx ) { context.logMessage( Diagnostic.Kind.ERROR, "Problem opening file to write MetaModel for " + entity.getSimpleName() + ioEx.getMessage() ); } } /** * Generate everything after import statements. * * @param entity The meta entity for which to write the body * @param context The processing context * * @return body content */ private static StringBuffer generateBody(MetaEntity entity, Context context) { StringWriter sw = new StringWriter(); PrintWriter pw = null; try { pw = new PrintWriter( sw ); if ( context.addGeneratedAnnotation() ) { pw.println( writeGeneratedAnnotation( entity, context ) ); } if ( context.isAddSuppressWarningsAnnotation() ) { pw.println( writeSuppressWarnings() ); } pw.println( writeStaticMetaModelAnnotation( entity ) ); printClassDeclaration( entity, pw, context ); pw.println(); List<MetaAttribute> members = entity.getMembers(); for ( MetaAttribute metaMember : members ) { pw.println( " " + metaMember.getDeclarationString() ); } pw.println(); pw.println( "}" ); return sw.getBuffer(); } finally { if ( pw != null ) { pw.close(); } } } private static void printClassDeclaration(MetaEntity entity, PrintWriter pw, Context context) { pw.print( "public abstract class " + entity.getSimpleName() + META_MODEL_CLASS_NAME_SUFFIX ); String superClassName = findMappedSuperClass( entity, context ); if ( superClassName != null ) { pw.print( " extends " + superClassName + META_MODEL_CLASS_NAME_SUFFIX ); } pw.println( " {" ); } private static String findMappedSuperClass(MetaEntity entity, Context context) { TypeMirror superClass = entity.getTypeElement().getSuperclass(); //superclass of Object is of NoType which returns some other kind while ( superClass.getKind() == TypeKind.DECLARED ) { //F..king Ch...t Have those people used their horrible APIs even once? final Element superClassElement = ( (DeclaredType) superClass ).asElement(); String superClassName = ( (TypeElement) superClassElement ).getQualifiedName().toString(); if ( extendsSuperMetaModel( superClassElement, entity.isMetaComplete(), context ) ) { return superClassName; } superClass = ( (TypeElement) superClassElement ).getSuperclass(); } return null; } /** * Checks whether this metamodel class needs to extend another metamodel class. * This methods checks whether the processor has generated a metamodel class for the super class, but it also * allows for the possibility that the metamodel class was generated in a previous compilation (eg it could be * part of a separate jar. See also METAGEN-35). * * @param superClassElement the super class element * @param entityMetaComplete flag indicating if the entity for which the metamodel should be generarted is metamodel * complete. If so we cannot use reflection to decide whether we have to add the extend clause * @param context the execution context * * @return {@code true} in case there is super class meta model to extend from {@code false} otherwise. */ private static boolean extendsSuperMetaModel(Element superClassElement, boolean entityMetaComplete, Context context) { // if we processed the superclass in the same run we definitely need to extend String superClassName = ( (TypeElement) superClassElement ).getQualifiedName().toString(); if ( context.containsMetaEntity( superClassName ) || context.containsMetaEmbeddable( superClassName ) ) { return true; } // to allow for the case that the metamodel class for the super entity is for example contained in another // jar file we use reflection. However, we need to consider the fact that there is xml configuration // and annotations should be ignored if ( !entityMetaComplete && ( TypeUtils.containsAnnotation( superClassElement, Constants.ENTITY ) || TypeUtils.containsAnnotation( superClassElement, Constants.MAPPED_SUPERCLASS ) ) ) { return true; } return false; } private static String getFullyQualifiedClassName(MetaEntity entity, String metaModelPackage) { String fullyQualifiedClassName = ""; if ( !metaModelPackage.isEmpty() ) { fullyQualifiedClassName = fullyQualifiedClassName + metaModelPackage + "."; } fullyQualifiedClassName = fullyQualifiedClassName + entity.getSimpleName() + META_MODEL_CLASS_NAME_SUFFIX; return fullyQualifiedClassName; } private static String writeGeneratedAnnotation(MetaEntity entity, Context context) { StringBuilder generatedAnnotation = new StringBuilder(); generatedAnnotation.append( "@" ) .append( entity.importType( Generated.class.getName() ) ) .append( "(value = \"" ) .append( JPAMetaModelEntityProcessor.class.getName() ); if ( context.addGeneratedDate() ) { generatedAnnotation.append( "\", date = \"" ) .append( SIMPLE_DATE_FORMAT.get().format( new Date() ) ) .append( "\")" ); } else { generatedAnnotation.append( "\")" ); } return generatedAnnotation.toString(); } private static String writeSuppressWarnings() { return "@SuppressWarnings(\"all\")"; } private static String writeStaticMetaModelAnnotation(MetaEntity entity) { return "@" + entity.importType( "javax.persistence.metamodel.StaticMetamodel" ) + "(" + entity.getSimpleName() + ".class)"; } }