/*
* $Id$
*
* Copyright (C) 2003-2009 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.ant.taskdefs;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Properties;
import org.apache.tools.ant.BuildException;
/**
* That ant task will add some annotations to some compiled classes
* mentioned in a property file.
* For now, it's only necessary to add annotations to some
* openjdk classes to avoid modifying the original source code.
*
* @author Fabien DUMINY (fduminy at jnode dot org)
*/
public class AnnotateTask extends FileSetTask {
private File annotationFile;
private String[] classesFiles;
private String buildStartTime = "";
private String pattern = "";
private long startTime = 0;
private String baseDir;
private Properties annotations = new Properties();
private final Annotator annotator = new Annotator();
protected int doExecute() throws BuildException {
try {
SimpleDateFormat format = new SimpleDateFormat(pattern);
startTime = format.parse(buildStartTime).getTime();
} catch (Exception e) {
throw new BuildException("invalid buildStartTime or pattern", e);
}
int nbModifiedFiles = 0;
try {
if (readProperties()) {
for (String file : classesFiles) {
File classFile = new File(baseDir, file);
boolean fileModified = processFile(classFile);
if (fileModified) {
nbModifiedFiles++;
}
}
}
} catch (IOException ioe) {
throw new BuildException(ioe);
}
return nbModifiedFiles;
}
/**
* Defines the annotation property file where are specified annotations to add.
*
* @param annotationFile
*/
public final void setAnnotationFile(File annotationFile) {
this.annotationFile = annotationFile;
}
/**
* Define the time at which build started.
*
* @param buildStartTime
*/
public final void setBuildStartTime(String buildStartTime) {
this.buildStartTime = buildStartTime;
}
/**
* Define the pattern with which buildStartTime is defined.
*
* @param pattern
*/
public final void setPattern(String pattern) {
this.pattern = pattern;
}
public void setBaseDir(String baseDir) {
this.baseDir = baseDir;
}
/**
* Read the properties file. For now, it simply contains a list of
* classes that need the SharedStatics annotation.
*
* @return
* @throws BuildException
*/
private boolean readProperties() throws BuildException {
readProperties("annotationFile", annotationFile, annotations);
if (annotations.isEmpty()) {
System.err.println("WARNING: annotationFile is empty (or doesn't exist)");
return false;
}
classesFiles = (String[]) annotations.keySet().toArray(new String[annotations.size()]);
// we must sort the classes in reverse order so that
// classes with longest package name will be used first
// (that is only necessary for classes whose name is the same
// but package is different ; typical such class name : "Constants")
Arrays.sort(classesFiles, Collections.reverseOrder());
return true;
}
/**
* Generic method that read properties from a given file.
*
* @param name
* @param file
* @param properties
* @throws BuildException
*/
private void readProperties(String name, File file, Properties properties) throws BuildException {
if (file == null) {
throw new BuildException(name + " is mandatory");
}
if (!file.exists()) {
return;
}
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
properties.load(fis);
} catch (IOException e) {
throw new BuildException(e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new BuildException(e);
}
}
}
}
/**
* Get the list of annotations for the given class file.
*
* @param classFile list of annotations with ',' as separator. null if no annotation for that class.
* @return
*/
private String getAnnotations(File classFile) {
String annotations = null;
String classFilePath = classFile.getAbsolutePath();
for (String f : classesFiles) {
if (classFilePath.endsWith(f)) {
annotations = this.annotations.getProperty(f);
break;
}
}
return annotations;
}
/**
* Actually process a class file (called from parent class).
*/
@Override
protected boolean processFile(File classFile) throws IOException {
if (classFile.lastModified() < startTime) {
return false;
}
String annotations = getAnnotations(classFile);
if (annotations == null) {
return false;
}
File tmpFile = new File(classFile.getParentFile(), classFile.getName() + ".tmp");
FileInputStream fis = null;
boolean classIsModified = false;
try {
fis = new FileInputStream(classFile);
classIsModified = addAnnotation(classFile, fis, tmpFile, annotations);
} finally {
if (fis != null) {
fis.close();
}
}
if (classIsModified) {
if (!classFile.delete()) {
throw new IOException("can't delete " + classFile.getAbsolutePath());
}
if (!tmpFile.renameTo(classFile)) {
throw new IOException("can't rename " + tmpFile.getAbsolutePath());
}
}
return classIsModified;
}
/**
* Add an annotation to a class file.
*
* @param classFile
* @param inputClass
* @param tmpFile
* @param annotations
* @return
* @throws BuildException
*/
private boolean addAnnotation(File classFile, InputStream inputClass, File tmpFile, String annotations)
throws BuildException {
boolean classIsModified = false;
try {
classIsModified = annotator.addAnnotations(inputClass, tmpFile, Arrays.asList(annotations.split(",")));
} catch (Exception ex) {
ex.printStackTrace();
throw new BuildException("Unable to add annotations to file " + classFile.getName(), ex);
} finally {
if (classIsModified) {
long timestamp = classFile.lastModified();
tmpFile.setLastModified(timestamp);
}
}
return classIsModified;
}
}