import java.io.File;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileList;
/**
* Ant task to compose a sequence of source files or directories to a given
* target file or directory. The composition is done by parsing each
* source file in order and composing the parse trees.
*
* @layer<antCompose>
*/
public class ComposeAntTask extends Task {
public class SourceFile {
/**
* Returns the {@link File} representing this operand.
*
* @layer<antCompose>
*/
public File getFile() {
if ( file == null )
throw new BuildException( "no name specified for file operand",
getLocation() ) ;
return getProject().resolveFile( file.getPath() ) ;
}
/**
* Specifies the file for this operand.
*
* @layer<antCompose>
*/
public void setName( File file ) {
this.file = file ;
}
private File file = null ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //
/**
* Specifies a single operand file to the composition.
*
* @layer<antCompose>
*/
public void addFile( SourceFile file ) {
operands.add( file ) ;
}
/**
* Specifies a list of operand files to the composition.
*
* @layer<antCompose>
*/
public void addFilelist( FileList files ) {
operands.add( files ) ;
}
/**
* Invoked when this task is next to be executed.
*
* @layer<antCompose>
*/
public void execute() throws BuildException {
if ( outputFile == null )
throw new BuildException( "no output file given", getLocation() ) ;
File[] inputFiles = getFiles() ;
if ( inputFiles.length < 1 )
throw new BuildException( "no input files given", getLocation() ) ;
if ( upToDate( inputFiles, outputFile ) ) {
log( "output file is up-to-date", Project.MSG_INFO ) ;
return ;
}
try {
compose( inputFiles, outputFile ) ;
}
catch ( BuildException exception ) {
throw exception ;
}
catch ( Exception exception ) {
throw new BuildException( exception, getLocation() ) ;
}
}
/**
* Returns an array of {@link File} objects listing all operands.
*
* @layer<antCompose>
*/
public File[] getFiles() {
if ( operands.size() < 1 )
return new File [0] ;
List files = new ArrayList() ;
for ( Iterator p = operands.iterator() ; p.hasNext() ; ) {
Object operand = p.next() ;
if ( operand instanceof SourceFile )
files.add( ( ( SourceFile ) operand ) . getFile() ) ;
else
if ( operand instanceof FileList )
files.addAll( asList( ( FileList ) operand ) ) ;
else
throw new BuildException( "unexpected operand type",
getLocation() ) ;
}
return ( File[] ) files.toArray( new File [files.size()] ) ;
}
/**
* Specifies the aspect name for the generated composition.
*
* @layer<antCompose>
*/
public void setAspect( String aspectName ) {
this.aspectName = aspectName ;
}
/**
* Specifies whether the enclosing build should abort if the
* composition fails.
*
* @layer<antCompose>
*/
public void setFailonerror( boolean failOnError ) {
this.failOnError = failOnError ;
}
/**
* Specifies the output file (or directory) for the generated
* composition.
*
* @layer<antCompose>
*/
public void setOutput( File outputFile ) {
this.outputFile = outputFile ;
}
/**
* Specifies the output file (or directory) for the generated
* composition; this is an alias for <code>output</code>.
*
* @layer<antCompose>
*/
public void setTarget( File outputFile ) {
this.outputFile = outputFile ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //
/**
* Returns the files listed in <code>fileList</code> as a {@link List}
* of fully resolved {@link File} objects.
*
* @layer<antCompose>
*/
final private List asList( FileList fileList ) {
String[] names = fileList.getFiles( getProject() ) ;
File[] files = new File [names.length] ;
for ( int n = names.length ; --n >= 0 ; )
files [n] = getProject().resolveFile( names [n] ) ;
return Arrays.asList( files ) ;
}
/**
* Composes a sequence of source files, writing the result into a
* target file.
*
* @layer<antCompose>
*/
final private void compose( File[] sources, File target )
throws Exception {
int errors = AstNode.errorCount() ;
log( "reading " + sources [0], Project.MSG_VERBOSE ) ;
JTSParseTree composition =
new JTSParseTree( sources[0].getPath() ) ;
for ( int n = 1 ; n < sources.length ; ++n ) {
JTSParseTree extension =
new JTSParseTree( sources[n].getPath() ) ;
log( "reading " + sources [n], Project.MSG_VERBOSE ) ;
composition.compose( extension ) ;
}
if ( aspectName != null )
composition.setAspectName( aspectName ) ;
if ( errors == AstNode.errorCount() ) {
log( "writing " + target, Project.MSG_VERBOSE ) ;
composition.print2file( target ) ;
}
if ( failOnError && errors > AstNode.errorCount() )
throw new BuildException( "composition failed with "
+ ( AstNode.errorCount() - errors )
+ " errors",
getLocation() ) ;
}
/**
* Returns <code>true</code> if the given target file is more recent
* than the specified source files.
*
* @layer<antCompose>
*/
final private static boolean upToDate( File[] sources, File target ) {
if ( ! target.exists() )
return false ;
long targetTime = target.lastModified() ;
for ( int n = sources.length ; --n >= 0 ; )
if ( sources[n].lastModified() >= targetTime )
return false ;
return true ;
}
private String aspectName = null ;
private boolean failOnError = false ;
private List operands = new ArrayList() ;
private File outputFile = null ;
}