/*******************************************************************************
* Copyright (c) 2010 Eric Bodden.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eric Bodden - initial API and implementation
******************************************************************************/
package de.bodden.tamiflex.playout;
import static de.bodden.tamiflex.normalizer.Hasher.containsGeneratedClassName;
import static de.bodden.tamiflex.normalizer.Hasher.generateHashNumber;
import static de.bodden.tamiflex.normalizer.Hasher.hashedClassNameForGeneratedClassName;
import static de.bodden.tamiflex.normalizer.Hasher.replaceGeneratedClassNamesByHashedNames;
import static de.bodden.tamiflex.playout.rt.ShutdownStatus.hasShutDown;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
public class ClassDumper implements ClassFileTransformer {
protected final File outDir;
/**
* It is important that this be a <i>linked</i> has map because we need to generate hash numbers
* for the classes in the order in which they are loaded. This is because a generated class <i>a</i> may reference
* other generated classes, and when determining a hash code for <i>a</i>, the hash code for those
* referenced classes must already have been computed.
*/
protected final LinkedHashMap<String,byte[]> classNameToBytes = new LinkedHashMap<String, byte[]>();
private final boolean verbose;
private final boolean dontReallyDump;
public ClassDumper(File outDir, boolean dontReallyDump, boolean verbose) {
this.outDir = outDir;
this.dontReallyDump = dontReallyDump;
this.verbose = verbose;
}
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(hasShutDown) return null;
if(className.startsWith(Agent.PKGNAME)) return null;
byte[] oldBytes;
synchronized (this) {
oldBytes = classNameToBytes.put(className, classfileBuffer);
}
if(verbose && oldBytes!=null && !Arrays.equals(classfileBuffer, oldBytes)) {
System.err.println("WARNING: There exist two different classes with name "+className);
}
return null;
}
public void writeClassesToDisk() {
synchronized (this) {
Set<Entry<String, byte[]>> entrySet = classNameToBytes.entrySet();
for (Map.Entry<String, byte[]> entry: entrySet) {
String className = entry.getKey();
byte[] classfileBuffer = entry.getValue();
if(containsGeneratedClassName(className)) {
generateHashNumber(className, classfileBuffer);
className = hashedClassNameForGeneratedClassName(className);
classfileBuffer = replaceGeneratedClassNamesByHashedNames(classfileBuffer);
}
if(dontReallyDump) continue; //don't dump
File localOutDir = outDir;
localOutDir.mkdirs();
String simpleName = className;
if(className.contains("/")) {
String packageName = className.substring(0,className.lastIndexOf('/'));
simpleName = className.substring(className.lastIndexOf('/')+1);
localOutDir = new File(localOutDir,packageName);
localOutDir.mkdirs();
}
String fileName = simpleName+".class";
File outFile = new File(localOutDir, fileName);
if(outFile.exists()) {
outFile.delete();
}
FileOutputStream fos = null;
try {
outFile.createNewFile();
fos = new FileOutputStream(outFile);
fos.write(classfileBuffer);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos!=null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
}