/**
* Copyright (C) 2001-2004 France Telecom R&D
*
* 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.objectweb.speedo.genclass.merger;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.jorm.util.lib.StringReplace;
import org.objectweb.speedo.api.ExceptionHelper;
import org.objectweb.speedo.api.SpeedoException;
import org.objectweb.speedo.api.SpeedoProperties;
import org.objectweb.speedo.genclass.GenClass;
import org.objectweb.speedo.generation.enhancer.common.DuplicatedMethodVerifier;
import org.objectweb.speedo.generation.enhancer.common.InterfaceAgregatorVisitor;
import org.objectweb.speedo.lib.Personality;
import org.objectweb.util.monolog.Monolog;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
*
* @author S.Chassande-Barrioz
*/
public class GenClassMerger extends MatchingTask {
public final static String LOGGER_NAME =
SpeedoProperties.LOGGER_NAME + ".genclassmerger";
public final static String GEN_CLASS_NAME =
GenClass.class.getName().replace('.', '/');
private File src = null;
protected Logger logger = null;
protected Personality personality;
public GenClassMerger() {
personality = null;
}
public GenClassMerger(Personality p) {
personality = p;
}
public File getSrc() {
return src;
}
public void setSrc(File src) {
this.src = src;
}
protected String getLoggerName() {
return LOGGER_NAME;
}
public void execute() throws BuildException {
if (logger == null) {
logger = Monolog.initialize().getLogger(getLoggerName());
}
String[] pdFiles = super.getDirectoryScanner(src).getIncludedFiles();
for(int i=0; i<pdFiles.length; i++) {
try {
mergeGenClass(newGCInfo(pdFiles[i]));
} catch (SpeedoException e) {
String msg = "Error while merging the file " + pdFiles[i]
+ " merged" ;
Exception ie = ExceptionHelper.getNested(e);
throw new BuildException(msg, ie);
} catch (RuntimeException e) {
logger.log(BasicLevel.ERROR, e.getMessage(), e);
throw new BuildException("Error", e);
}
}
}
public void mergeGenClass(GCInfo gc) throws SpeedoException {
logger.log(BasicLevel.DEBUG, "Treat the class " + gc.classToWrite);
ClassReader cr = loadJavaClass(gc.classToWrite, false);
if (cr != null) {
//The file exists ==> analyze it.
logger.log(BasicLevel.DEBUG, "Analyze the class " + gc.classToWrite);
GenClassAnalyzer gcInfo = new GenClassAnalyzer(logger, gc);
cr.accept(gcInfo, false);
if (!requireEnhancement(gc)) {
return;
}
}
logger.log(BasicLevel.INFO, "Enhance the generic class " + gc.classToWrite);
ClassWriter cw = new ClassWriter(false);
ClassVisitor current = cw;
current = new InterfaceAgregatorVisitor(current, logger, gc.classToWrite, personality);
current = new DuplicatedMethodVerifier(current, logger, personality);
writeFirstClass(gc, current);
writeSecondClass(gc, current);
writeJavaClass(gc.classToWrite, cw);
checkAfter(gc);
}
protected void writeFirstClass(final GCInfo gc, ClassVisitor current)
throws SpeedoException {
//Write the code of the first class
GenClassAdapter gca = new GenClassAdapter(current, logger, gc, personality);
loadJavaClass(gc.firstClass, false).accept(gca, false);
}
protected void writeSecondClass(final GCInfo gc, ClassVisitor current)
throws SpeedoException {
//Write the code of the second class
GenClassAdapter gca = new GenClassAdapter(current, logger, gc, personality);
gca.setVisitConstructor(false);
loadJavaClass(gc.secondClass, false).accept(gca, false);
}
protected String getClassToWrite(String gcn) {
return StringReplace.replaceChar('\\', '/',
gcn.substring(0, gcn.length()-6));
}
protected String getFirstClass(String gcn) {
return getClassToWrite(gcn);
}
protected String getSecondClass(String gcn) {
return GEN_CLASS_NAME;
}
protected boolean requireEnhancement(GCInfo gc) {
if (!gc.isAbstract) {
//do nothing
logger.log(BasicLevel.DEBUG, "Class " + gc.gcn + " already complete.");
return false;
}
return true;
}
protected void checkAfter(GCInfo gc) throws SpeedoException {
//Test the class loading
logger.log(BasicLevel.DEBUG, "check after the class " + gc.classToWrite);
String cn = StringReplace.replaceChar('/','.', gc.classToWrite);
cn = StringReplace.replaceChar('\\','.', cn);
try {
Class.forName(cn);
} catch (Throwable e) {
String msg = "Merged class '" + gc.classToWrite
+ "' cannot be loaded: " + e.getMessage();
logger.log(BasicLevel.ERROR, msg, e);
if (e instanceof Exception) {
throw new SpeedoException(msg, (Exception) e);
} else {
throw new SpeedoException(msg);
}
}
}
/**
* Loads a specified class.
* @param fn is the file name of the .class to load. the file name is
* a relative patht to the 'src' directory.
* @param remove indicates if the .class must be removed
* @return the JavaClass loaded
* @exception SpeedoException if the file cannot be loaded
*/
protected ClassReader loadJavaClass(final String fn,
final boolean remove) throws SpeedoException {
String filename = fn;
if (!filename.endsWith(".class")) {
filename = filename + ".class";
}
logger.log(BasicLevel.DEBUG, "Load the class " + filename);
try {
File f = new File(src, filename);
if (!f.exists()) {
return null;
}
FileInputStream fis = new FileInputStream(f);
ClassReader jclass = new ClassReader(fis);
fis.close();
if (remove) {
f.delete();
}
return jclass;
} catch (IOException e) {
throw new SpeedoException("Error during loading " + filename, e);
}
}
public GCInfo newGCInfo(String gcn) {
return new GCInfo(gcn,
getFirstClass(gcn),
getSecondClass(gcn),
getClassToWrite(gcn));
}
/**
* Saves the new bytecode of the specified Java class under a specified base
* directory.
*
* @param jclass the Java class that has to be saved
* @exception SpeedoException if the file cannot be written
*/
protected void writeJavaClass(final String fn,
final ClassWriter jclass)
throws SpeedoException {
String filename = fn;
if (!filename.endsWith(".class")) {
filename = filename + ".class";
}
logger.log(BasicLevel.DEBUG, "Write the class " + filename);
try {
File outputFile = new File(src, filename);
if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}
outputFile.createNewFile();
FileOutputStream fos = new FileOutputStream(outputFile);
fos.write(jclass.toByteArray());
fos.close();
} catch (IOException e) {
throw new SpeedoException("Cannot write " + filename, e);
}
}
}