package org.codehaus.modello.plugin.converters;
/*
* Copyright (c) 2006, Codehaus.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import org.codehaus.modello.ModelloException;
import org.codehaus.modello.ModelloParameterConstants;
import org.codehaus.modello.ModelloRuntimeException;
import org.codehaus.modello.model.Model;
import org.codehaus.modello.model.ModelAssociation;
import org.codehaus.modello.model.ModelClass;
import org.codehaus.modello.model.ModelDefault;
import org.codehaus.modello.model.ModelField;
import org.codehaus.modello.model.Version;
import org.codehaus.modello.model.VersionDefinition;
import org.codehaus.modello.plugin.java.AbstractJavaModelloGenerator;
import org.codehaus.modello.plugin.java.javasource.JClass;
import org.codehaus.modello.plugin.java.javasource.JInterface;
import org.codehaus.modello.plugin.java.javasource.JMethod;
import org.codehaus.modello.plugin.java.javasource.JMethodSignature;
import org.codehaus.modello.plugin.java.javasource.JParameter;
import org.codehaus.modello.plugin.java.javasource.JSourceCode;
import org.codehaus.modello.plugin.java.javasource.JSourceWriter;
import org.codehaus.modello.plugin.java.javasource.JType;
import org.codehaus.modello.plugin.java.metadata.JavaClassMetadata;
import org.codehaus.modello.plugin.java.metadata.JavaFieldMetadata;
import org.codehaus.plexus.util.IOUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
/**
* Generate a basic conversion class between two versions of a model.
*/
public class ConverterGenerator
extends AbstractJavaModelloGenerator
{
public void generate( Model model, Properties parameters )
throws ModelloException
{
initialize( model, parameters );
String[] versions = parameters.getProperty( ModelloParameterConstants.ALL_VERSIONS ).split( "," );
List<Version> allVersions = new ArrayList<Version>( versions.length );
for ( String version : versions )
{
allVersions.add( new Version( version ) );
}
Collections.sort( allVersions );
Version nextVersion = null;
for ( Version v : allVersions )
{
if ( v.greaterThan( getGeneratedVersion() ) )
{
nextVersion = v;
break;
}
}
try
{
// if nextVersion remains null, there is none greater so we are converting back to the unpackaged version
generateConverters( nextVersion );
if ( nextVersion == null )
{
generateConverterTool( allVersions );
}
}
catch ( IOException ex )
{
throw new ModelloException( "Exception while generating model converters.", ex );
}
}
private void generateConverters( Version toVersion )
throws ModelloException, IOException
{
Model objectModel = getModel();
Version fromVersion = getGeneratedVersion();
String packageName = objectModel.getDefaultPackageName( true, fromVersion ) + ".convert";
Version effectiveToVersion = ( toVersion == null ) ? fromVersion : toVersion;
String jDoc = "Converts from version " + fromVersion + " (with version in package name) to version "
+ effectiveToVersion + " (with" + ( toVersion != null ? "" : "out" )
+ " version in package name) of the model.";
JInterface conversionInterface = new JInterface( packageName + ".VersionConverter" );
initHeader( conversionInterface );
suppressAllWarnings( objectModel, conversionInterface );
conversionInterface.getJDocComment().setComment( jDoc );
JClass basicConverterClass = new JClass( packageName + ".BasicVersionConverter" );
initHeader( basicConverterClass );
suppressAllWarnings( objectModel, basicConverterClass );
basicConverterClass.getJDocComment().setComment( jDoc );
basicConverterClass.addInterface( conversionInterface );
VersionDefinition versionDefinition = objectModel.getVersionDefinition();
for ( ModelClass modelClass : objectModel.getClasses( fromVersion ) )
{
JavaClassMetadata javaClassMetadata = (JavaClassMetadata) modelClass.getMetadata( JavaClassMetadata.ID );
if ( !javaClassMetadata.isEnabled() )
{
// Skip generation of those classes that are not enabled for the java plugin.
continue;
}
// check if it's present in the next version
if ( toVersion != null && !toVersion.inside( modelClass.getVersionRange() ) )
{
// Don't convert - it's not there in the next one
continue;
}
String methodName = "convert" + modelClass.getName();
String parameterName = uncapitalise( modelClass.getName() );
String sourceClass = getSourceClassName( modelClass, fromVersion );
String targetClass =
modelClass.getPackageName( toVersion != null, toVersion ) + "." + modelClass.getName();
if ( !javaClassMetadata.isAbstract() )
{
// Don't generate converter for abstract classes.
JMethodSignature methodSig = new JMethodSignature( methodName, new JType( targetClass ) );
methodSig.addParameter( new JParameter( new JType( sourceClass ), parameterName ) );
conversionInterface.addMethod( methodSig );
// Method from interface, delegates to converter with the given implementation of the target class
JMethod jMethod = new JMethod( methodName, new JType( targetClass ), null );
jMethod.addParameter( new JParameter( new JType( sourceClass ), parameterName ) );
basicConverterClass.addMethod( jMethod );
JSourceCode sc = jMethod.getSourceCode();
sc.add( "return " + methodName + "( " + parameterName + ", new " + targetClass + "() );" );
}
// Actual conversion method, takes implementation as a parameter to facilitate being called as a superclass
JMethod jMethod = new JMethod( methodName, new JType( targetClass ), null );
jMethod.addParameter( new JParameter( new JType( sourceClass ), parameterName ) );
jMethod.addParameter( new JParameter( new JType( targetClass ), "value" ) );
basicConverterClass.addMethod( jMethod );
JSourceCode sc = jMethod.getSourceCode();
sc.add( "if ( " + parameterName + " == null )" );
sc.add( "{" );
sc.indent();
sc.add( "return null;" );
sc.unindent();
sc.add( "}" );
if ( modelClass.getSuperClass() != null )
{
sc.add( "// Convert super class" );
sc.add( "value = (" + targetClass + ") convert" + modelClass.getSuperClass() + "( " + parameterName
+ ", value );" );
sc.add( "" );
}
for ( ModelField modelField : modelClass.getFields( fromVersion ) )
{
String name = capitalise( modelField.getName() );
if ( toVersion != null )
{
if ( versionDefinition != null && versionDefinition.isFieldType() )
{
if ( versionDefinition.getValue().equals( modelField.getName() )
|| versionDefinition.getValue().equals( modelField.getAlias() ) )
{
sc.add( "value.set" + name + "( \"" + toVersion + "\" );" );
continue;
}
}
}
// check if it's present in the next version
if ( toVersion != null && !toVersion.inside( modelField.getVersionRange() ) )
{
// check if it is present in a new definition instead
ModelField newField = null;
try
{
newField = modelClass.getField( modelField.getName(), toVersion );
}
catch ( ModelloRuntimeException e )
{
// Don't convert - it's not there in the next one
continue;
}
if ( !newField.getType().equals( modelField.getType() ) )
{
// Don't convert - it's a different type in the next one
continue;
}
}
if ( modelField instanceof ModelAssociation )
{
ModelAssociation assoc = (ModelAssociation) modelField;
if ( assoc.isManyMultiplicity() )
{
String type = assoc.getType();
if ( ModelDefault.LIST.equals( type ) || ModelDefault.SET.equals( type ) )
{
sc.add( "{" );
sc.indent();
sc.add( assoc.getType() + " list = " + assoc.getDefaultValue() + ";" );
sc.add( "for ( java.util.Iterator i = " + parameterName + ".get" + name
+ "().iterator(); i.hasNext(); )" );
sc.add( "{" );
sc.indent();
if ( isClassInModel( assoc.getTo(), modelClass.getModel() ) )
{
String className = getSourceClassName( assoc.getToClass(), fromVersion );
sc.add( className + " v = (" + className + ") i.next();" );
}
else
{
sc.add( assoc.getTo() + " v = (" + assoc.getTo() + ") i.next();" );
}
if ( isClassInModel( assoc.getTo(), objectModel ) )
{
sc.add( "list.add( convert" + assoc.getTo() + "( v ) );" );
}
else
{
sc.add( "list.add( v );" );
}
sc.unindent();
sc.add( "}" );
sc.add( "value.set" + name + "( list );" );
sc.unindent();
sc.add( "}" );
}
else
{
sc.add( "{" );
sc.indent();
// Map or Properties
sc.add( assoc.getType() + " map = " + assoc.getDefaultValue() + ";" );
sc.add( "for ( java.util.Iterator i = " + parameterName + ".get" + name
+ "().entrySet().iterator(); i.hasNext(); )" );
sc.add( "{" );
sc.indent();
sc.add( "java.util.Map.Entry entry = (java.util.Map.Entry) i.next();" );
if ( isClassInModel( assoc.getTo(), modelClass.getModel() ) )
{
String className = getSourceClassName( assoc.getToClass(), fromVersion );
sc.add( className + " v = (" + className + ") entry.getValue();" );
}
else
{
sc.add( assoc.getTo() + " v = (" + assoc.getTo() + ") entry.getValue();" );
}
if ( isClassInModel( assoc.getTo(), objectModel ) )
{
sc.add( "map.put( entry.getKey(), convert" + assoc.getTo() + "( v ) );" );
}
else
{
sc.add( "map.put( entry.getKey(), v );" );
}
sc.unindent();
sc.add( "}" );
sc.add( "value.set" + name + "( map );" );
sc.unindent();
sc.add( "}" );
}
}
else
{
sc.add( "value.set" + name + "( convert" + assoc.getTo() + "( " + parameterName + ".get" + name
+ "() ) );" );
}
}
else
{
sc.add( "// Convert field " + modelField.getName() );
JavaFieldMetadata javaFieldMetadata =
(JavaFieldMetadata) modelField.getMetadata( JavaFieldMetadata.ID );
String value = parameterName + "." + getPrefix( javaFieldMetadata ) + name + "()";
sc.add( "value.set" + name + "( " + value + " );" );
}
}
sc.add( "" );
sc.add( "return value;" );
}
JSourceWriter interfaceWriter = null;
JSourceWriter classWriter = null;
try
{
interfaceWriter = newJSourceWriter( packageName, conversionInterface.getName( true ) );
classWriter = newJSourceWriter( packageName, basicConverterClass.getName( true ) );
conversionInterface.print( interfaceWriter );
basicConverterClass.print( classWriter );
}
finally
{
IOUtil.close( classWriter );
IOUtil.close( interfaceWriter );
}
}
private void generateConverterTool( List<Version> allVersions )
throws ModelloException, IOException
{
Model objectModel = getModel();
String root = objectModel.getRoot( getGeneratedVersion() );
ModelClass rootClass = objectModel.getClass( root, getGeneratedVersion() );
String basePackage = objectModel.getDefaultPackageName( false, null );
String packageName = basePackage + ".convert";
String jDoc = "Converts between the available versions of the model.";
JClass converterClass = new JClass( packageName + ".ConverterTool" );
initHeader( converterClass );
suppressAllWarnings( objectModel, converterClass );
converterClass.getJDocComment().setComment( jDoc );
converterClass.addImport( "java.io.File" );
converterClass.addImport( "java.io.IOException" );
converterClass.addImport( "javax.xml.stream.*" );
for ( Version v : allVersions )
{
writeConvertMethod( converterClass, objectModel, basePackage, allVersions, v, rootClass );
}
writeConvertMethod( converterClass, objectModel, basePackage, allVersions, null, rootClass );
JSourceWriter classWriter = null;
try
{
classWriter = newJSourceWriter( packageName, converterClass.getName( true ) );
converterClass.print( new JSourceWriter( classWriter ) );
}
finally
{
IOUtil.close( classWriter );
}
}
private static void writeConvertMethod( JClass converterClass, Model objectModel, String basePackage,
List<Version> allVersions, Version v, ModelClass rootClass )
{
String modelName = objectModel.getName();
String rootClassName = rootClass.getName();
String targetPackage = objectModel.getDefaultPackageName( v != null, v );
String targetClass = targetPackage + "." + rootClassName;
String methodName = "convertFromFile";
if ( v != null )
{
methodName += "_" + v.toString( "v", "_" );
}
JMethod method = new JMethod( methodName, new JType( targetClass ), null );
method.addParameter( new JParameter( new JType( "File" ), "f" ) );
method.addException( new JClass( "IOException" ) );
method.addException( new JClass( "XMLStreamException" ) );
converterClass.addMethod( method );
JSourceCode sc = method.getSourceCode();
sc.add( basePackage + ".io.stax." + modelName + "StaxReaderDelegate reader = new " + basePackage + ".io.stax."
+ modelName + "StaxReaderDelegate();" );
sc.add( "Object value = reader.read( f );" );
String prefix = "";
for ( Version sourceVersion : allVersions )
{
String sourcePackage = objectModel.getDefaultPackageName( true, sourceVersion );
String sourceClass = sourcePackage + "." + rootClassName;
sc.add( prefix + "if ( value instanceof " + sourceClass + " )" );
sc.add( "{" );
sc.indent();
boolean foundFirst = false;
for ( Version targetVersion : allVersions )
{
if ( !foundFirst )
{
if ( targetVersion.equals( sourceVersion ) )
{
foundFirst = true;
}
else
{
continue;
}
}
if ( targetVersion.equals( v ) )
{
break;
}
// TODO: need to be able to specify converter class implementation
String p = objectModel.getDefaultPackageName( true, targetVersion );
String c = p + "." + rootClassName;
sc.add( "value = new " + p + ".convert.BasicVersionConverter().convert" + rootClassName
+ "( (" + c + ") value );" );
}
sc.unindent();
sc.add( "}" );
prefix = "else ";
if ( sourceVersion.equals( v ) )
{
break;
}
}
sc.add( "else" );
sc.add( "{" );
sc.indent();
sc.add( "throw new IllegalStateException( \"Can't find converter for class '\" + value.getClass() + \"'\" );" );
sc.unindent();
sc.add( "}" );
sc.add( "return (" + targetClass + ") value;" );
}
private static String getSourceClassName( ModelClass modelClass, Version generatedVersion )
{
return modelClass.getPackageName( true, generatedVersion ) + "." + modelClass.getName();
}
}