package fr.inria.diversify.sosie.logger;
import java.io.*;
import java.nio.ByteBuffer;
import java.security.KeyPair;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Compact log in binary format to reduce the size of the log
* <p/>
* Created by marodrig on 25/06/2014.
*/
public class InstruBinaryLog extends InstruLogWriter {
///Magic number for method in log tuples
public static final byte LOG_METHOD = 1;
///Magic number for transplantation point in log tuples
public static final byte LOG_TRANSPLANT_CALL = 2;
///Magic number for tests in log tuples
public static final byte LOG_TEST = 3;
///Magic number for asserts in log tuples
public static final byte LOG_ASSERT = 4;
///Magic number for vars in log tuples
public static final byte LOG_VAR = 5;
///Magic number for exceptions in log tuples
public static final byte LOG_EXCEPTION = 6;
///Magic number for catchs in log tuples
public static final byte LOG_CATCH = 7;
///Magic number for the end of the file
public static final byte LOG_CLOSE = 8;
///DataInput for each thread. Each one saved in a different file
private Map<Thread, DataOutputStream> streamsPerThread;
///Class, test, exception has a signature that is assigned an integer value.
// This way we save lots of space
protected Map<String, Integer> idMap;
///Each method has a signature that is assigned an numeric value.
// This way we save space
protected Map<String, Integer> methodMap;
//Current ID signature
private int currentId;
//Current ID signature for methods
private int methodId;
//We write the id map as we go. That way we may store it in the same file
private int lastHashEntry = 0;
//List of new hash entries before the last call of a write method
ArrayList<Map.Entry<String, Integer>> lastSignatures;
public InstruBinaryLog(String logDir) {
super(logDir);
//ATTENTION!!!: Remember we are copying these files to another source file
//so they must be maintain in java 1.5
lastSignatures = new ArrayList<Map.Entry<String, Integer>>();
idMap = new HashMap<String, Integer>();
currentId = 0;
methodId = 0;
methodMap = new HashMap<String, Integer>();
streamsPerThread = new HashMap<Thread, DataOutputStream>();
}
protected void writeSignatures(DataOutputStream os) {
try {
os.writeInt(lastSignatures.size());
for (Map.Entry<String, Integer> e : lastSignatures) {
os.writeUTF(e.getKey());
os.writeInt(e.getValue());
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
lastSignatures.clear();
}
/**
* Calculate the size in bytes a number takes
* @param value
* @return
*/
private byte valueSize(int value) {
byte size = 3;
if ( value < 256 ) { size = 0; }
else if ( value < 65536 ) { size = 1; }
else if ( value < 16777216 ) { size = 2; }
return size;
}
/**
* Converts a integer into a byte array
* @return A byte array
*/
private byte[] fromIntToByte(int value, int valueSize) {
byte[] result = new byte[valueSize];
for (int i = 0; i < valueSize; i++) {
result[i] = (byte) (value >> (i * 8));
}
return result;
}
/**
* Log a call to a method
*
* @param thread Thread where the call is invoked
* @param methodSignatureId Signature of the method
*/
public void methodCall(Thread thread, String methodSignatureId) {
try {
if (getLogMethod(thread)) {
int id;
boolean mustWriteSignature = !methodMap.containsKey(methodSignatureId);
if (mustWriteSignature) {
methodId++;
methodMap.put(methodSignatureId, methodId);
id = methodId;
} else {
id = methodMap.get(methodSignatureId);
}
DataOutputStream os = getStream(thread);
int depth = incCallDepth(thread);
//Write options byte for this method
byte valueSizeId = valueSize(id);
byte valueSizeDepth = valueSize(depth);
//Write the options byte to file
byte optByte = 0;
optByte = (byte) (optByte | valueSizeId << 3);
optByte = (byte) (optByte | valueSizeDepth << 5);
optByte = (byte) (optByte | (mustWriteSignature ? (byte)1 : (byte)0) << 7);
os.write(optByte);
//Write the signature in case we need it
if ( mustWriteSignature ) {
os.writeUTF(methodSignatureId);
}
//Write the id
os.write(fromIntToByte(id, valueSizeId + 1));
///Write the depth
os.write(fromIntToByte(depth, valueSizeDepth + 1));
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void writeTestStart(Thread thread, String testSignature) {
try {
//Each test runs in a 0 depth for what we care
resetCallDepth(thread);
DataOutputStream os = getStream(thread);
os.writeByte(LOG_TEST);
//int id = getSignatureId(testSignature);
//Does not seem probable that the same test is called several times
writeSignatures(os);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void writeSourcePositionCall(String transplantationPoint) {
}
@Override
public void countAssert(String assertSignature) {
}
@Override
public void writeAssert(int id, Thread thread, String className, String methodSignature, String assertName, Object... var) {
try {
//Each test runs in a 0 depth for what we care
DataOutputStream os = getStream(thread);
os.writeByte(LOG_ASSERT);
int classID = getSignatureId(className);
int methdID = getSignatureId(methodSignature);
int assrtID = getSignatureId(assertName);
writeSignatures(os);
os.writeInt(classID);
os.writeInt(methdID);
os.writeInt(assrtID);
os.writeInt(var.length);
for (int i = 0; i < var.length; i++) {
os.writeUTF(printString(var[i]));
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void writeVar(int id, Thread thread, String methodSignatureId, Object... var) {
String separator = ":;:";
String simpleSeparator = ";";
// if ( previousVarLog == null ) { previousVarLog = new HashMap<Thread, String>(); }
if (getLogMethod(thread)) {
try {
DataOutputStream os = getStream(thread);
os.writeByte(LOG_VAR);
int methdID = getSignatureId(methodSignatureId);
writeSignatures(os);
os.writeInt(id);
os.writeInt(methdID);
os.writeInt(getCallDeep(thread));
String vars = buildVars(thread, separator, simpleSeparator, var);
os.writeInt(vars.length());
if ( previousVarLog.containsKey(thread) &&
!vars.equals(previousVarLog.get(thread)) ) {
os.writeUTF(vars);
} else {
// previousVarLog.putDataToJSON(thread, vars);
os.writeUTF(separator+"P");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void writeException(int id, Thread thread, Object exception) {
try {
DataOutputStream os = getStream(thread);
os.writeByte(LOG_EXCEPTION);
// int classId = getSignatureId(className);
// int methdId = getSignatureId(methodSignature);
int excepId = getSignatureId(exception.toString());
writeSignatures(os);
os.writeInt(id);
os.writeInt(getCallDeep(thread));
// os.writeInt(classId);
// os.writeInt(methdId);
os.writeInt(excepId);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void writeCatch(int id, Thread thread, Object exception) {
try {
DataOutputStream os = getStream(thread);
os.writeByte(LOG_CATCH);
os.writeInt(getCallDeep(thread));
os.writeInt(id);
// int classId = getSignatureId(className);
// int methdId = getSignatureId(methodSignature);
int excepId = getSignatureId(exception.toString());
writeSignatures(os);
// os.writeInt(classId);
// os.writeInt(methdId);
os.writeInt(excepId);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void close() {
for (Thread thread : streamsPerThread.keySet()) {
//String semaphore = "";
try {
DataOutputStream os = getStream(thread);
os.writeByte(LOG_CLOSE);
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void writeStartLogging(Thread thread, String id) {
}
@Override
public void writeTestFinish() {
}
@Override
public void testCount(String signature) {
}
@Override
public void assertCount(String signature) {
}
/**
* Gets the stream for a thread each one saved in a different file
*
* @param thread
* @return
*/
protected DataOutputStream getStream(Thread thread) throws InterruptedException {
if (!streamsPerThread.containsKey(thread)) {
String fileName = getThreadLogFilePath(thread);
try {
BufferedOutputStream bf = new BufferedOutputStream(new FileOutputStream(fileName, true), 16 * 1024);
DataOutputStream s = new DataOutputStream(bf);
streamsPerThread.put(thread, s);
//semaphores.putDataToJSON(s.toString() + s.hashCode(), new Semaphore(1));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
DataOutputStream s = streamsPerThread.get(thread);
//semaphores.get(s.toString() + s.hashCode()).tryAcquire(50, TimeUnit.MILLISECONDS);
return s;
}
/**
* Gets the id Map id for a signature
*
* @param signature
* @return
*/
protected int getSignatureId(String signature) {
if (!idMap.containsKey(signature)) {
//Add the entry to log it now
currentId++;
Map.Entry<String, Integer> e = new HashMap.SimpleImmutableEntry<String, Integer>(signature, currentId);
lastSignatures.add(e);
//The map to know all the entries
idMap.put(signature, currentId);
return currentId;
}
return idMap.get(signature);
}
/**
* This method hash the values of var using their hash values
*
* @param var
* @param os
*/
private void printVars(Object[] var, DataOutputStream os) {
}
/*
///Each class name is assigned an integer value. This way we save lots of space
protected Map<String, Integer> classID;
///Each class name is assigned an integer value. This way we save lots of space
private Map<String, Integer> testID;
///Each class name is assigned an integer value. This way we save lots of space
private Map<String, Integer> exceptionsID;
*/
//Number of found method signatures so far. ID of the method signatures as they come
/*
//Number of class names so far. ID of the method signatures as they come
private int foundClasses;
//Number of tests names so far. ID of the method signatures as they come
private int foundTest;
//Number of exceptions names so far. ID of the method signatures as they come
private int foundExceptions;
*/
}