package com.telerik.metadata;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.ArrayDeque;
import java.util.HashMap;
import com.telerik.metadata.TreeNode.FieldInfo;
import com.telerik.metadata.TreeNode.MethodInfo;
public class Writer {
private final StreamWriter outNodeStream;
private final StreamWriter outValueStream;
private final StreamWriter outStringsStream;
private int commonInterfacePrefixPosition;
public Writer(StreamWriter outNodeStream, StreamWriter outValueStream,
StreamWriter outStringsStream) {
this.outNodeStream = outNodeStream;
this.outValueStream = outValueStream;
this.outStringsStream = outStringsStream;
}
private final static byte[] writeUniqueName_lenBuff = new byte[2];
private final static byte[] writeInt_buff = new byte[4];
private final static byte[] writeTreeNodeId_buff = new byte[2];
private final static byte[] writeLength_lenBuff = new byte[2];
private final static byte[] writeModifierFinal_buff = new byte[1];
private static int writeUniqueName(String name,
HashMap<String, Integer> uniqueStrings,
StreamWriter outStringsStream) throws Exception {
int position = outStringsStream.getPosition();
int len = name.length();
writeUniqueName_lenBuff[0] = (byte) (len & 0xFF);
writeUniqueName_lenBuff[1] = (byte) ((len >> 8) & 0xFF);
outStringsStream.write(writeUniqueName_lenBuff);
outStringsStream.write(name.getBytes("UTF-8"));
uniqueStrings.put(name, position);
return position;
}
private static void writeInt(int value, StreamWriter out) throws Exception {
writeInt_buff[0] = (byte) (value & 0xFF);
writeInt_buff[1] = (byte) ((value >> 8) & 0xFF);
writeInt_buff[2] = (byte) ((value >> 16) & 0xFF);
writeInt_buff[3] = (byte) ((value >> 24) & 0xFF);
out.write(writeInt_buff);
}
private static void writeMethodInfo(MethodInfo mi,
HashMap<String, Integer> uniqueStrings, StreamWriter outValueStream)
throws Exception {
int pos = uniqueStrings.get(mi.name).intValue();
writeInt(pos, outValueStream);
byte isResolved = (byte) (mi.isResolved ? 1 : 0);
outValueStream.write(isResolved);
int sigLen = writeLength(mi.signature.size(), outValueStream);
for (int i = 0; i < sigLen; i++) {
TreeNode arg = mi.signature.get(i);
writeTreeNodeId(arg, outValueStream);
}
}
private static void writeTreeNodeId(TreeNode node, StreamWriter out)
throws Exception {
if (node == null) {
writeTreeNodeId_buff[0] = writeTreeNodeId_buff[1] = 0;
} else {
writeTreeNodeId_buff[0] = (byte) (node.id & 0xFF);
writeTreeNodeId_buff[1] = (byte) ((node.id >> 8) & 0xFF);
}
out.write(writeTreeNodeId_buff);
}
private static void writeFinalModifier(FieldInfo fi, StreamWriter out)
throws Exception {
writeModifierFinal_buff[0] = fi.isFinalType ? TreeNode.Final : 0;
out.write(writeModifierFinal_buff);
}
private static int writeLength(int length, StreamWriter out)
throws Exception {
writeLength_lenBuff[0] = (byte) (length & 0xFF);
writeLength_lenBuff[1] = (byte) ((length >> 8) & 0xFF);
out.write(writeLength_lenBuff);
return length;
}
public void writeClassValue(StreamWriter writer,
HashMap<String, Integer> stringsMap, TreeNode n) throws Exception {
writer.write(n.nodeType);
//
writeTreeNodeId(n.baseClassNode, writer);
//
if ((n.nodeType & TreeNode.Interface) == TreeNode.Interface) {
writer.write((byte) 1);
writeInt(commonInterfacePrefixPosition, writer);
}
int len = writeLength(n.instanceMethods.size(), writer);
for (int i = 0; i < len; i++) {
writeMethodInfo(n.instanceMethods.get(i), stringsMap, writer);
}
len = writeLength(n.instanceFields.size(), writer);
for (int i = 0; i < len; i++) {
FieldInfo fi = n.instanceFields.get(i);
int pos = stringsMap.get(fi.name).intValue(); // get start position
// of the name
writeInt(pos, writer); // write start position of the name of the
// variable
writeTreeNodeId(fi.valueType, writer); // pointer to the value type
// of the variable
writeFinalModifier(fi, writer);
}
len = writeLength(n.staticMethods.size(), writer);
for (int i = 0; i < len; i++) {
MethodInfo mi = n.staticMethods.get(i);
writeMethodInfo(mi, stringsMap, writer);
writeTreeNodeId(mi.declaringType, writer);
}
len = writeLength(n.staticFields.size(), writer);
for (int i = 0; i < len; i++) {
FieldInfo fi = n.staticFields.get(i);
int pos = stringsMap.get(fi.name).intValue();
writeInt(pos, writer);
writeTreeNodeId(fi.valueType, writer);
writeFinalModifier(fi, writer);
writeTreeNodeId(fi.declaringType, writer);
}
}
public void writeTree(TreeNode root) throws Exception {
short curId = 0;
ArrayDeque<TreeNode> d = new ArrayDeque<TreeNode>();
HashMap<String, Integer> uniqueStrings = new HashMap<String, Integer>();
commonInterfacePrefixPosition = writeUniqueName("com/tns/gen/",
uniqueStrings, outStringsStream);
// this while loop fils the treeStringsStream.dat file with a sequence
// of the
// length and name of all the nodes in the built tree + the primitive
// types used by method signatures
// the "n" variable holds all the nodes -> n.offsetName is the initial
// position where you can read the (length/name) pair
// n.offsetName is used to later find the node names in the
// treeStringsStream.dat file
d.push(root);
while (!d.isEmpty()) {
TreeNode n = d.pollFirst();
n.id = n.firstChildId = n.nextSiblingId = curId++;
String name = n.getName();
if (uniqueStrings.containsKey(name)) {
n.offsetName = uniqueStrings.get(name).intValue();
} else {
n.offsetName = writeUniqueName(name, uniqueStrings,
outStringsStream);
}
if (((n.nodeType & TreeNode.Interface) == TreeNode.Interface)
|| ((n.nodeType & TreeNode.Class) == TreeNode.Class)) {
for (int i = 0; i < n.instanceMethods.size(); i++) {
name = n.instanceMethods.get(i).name;
if (!uniqueStrings.containsKey(name)) {
writeUniqueName(name, uniqueStrings, outStringsStream);
}
}
for (int i = 0; i < n.staticMethods.size(); i++) {
name = n.staticMethods.get(i).name;
if (!uniqueStrings.containsKey(name)) {
writeUniqueName(name, uniqueStrings, outStringsStream);
}
}
for (int i = 0; i < n.instanceFields.size(); i++) {
name = n.instanceFields.get(i).name;
if (!uniqueStrings.containsKey(name)) {
writeUniqueName(name, uniqueStrings, outStringsStream);
}
}
for (int i = 0; i < n.staticFields.size(); i++) {
name = n.staticFields.get(i).name;
if (!uniqueStrings.containsKey(name)) {
writeUniqueName(name, uniqueStrings, outStringsStream);
}
}
}
for (TreeNode child : n.children) {
d.add(child);
}
}
outStringsStream.flush();
outStringsStream.close();
writeInt(0, outValueStream);
final int array_offset = 1000 * 1000 * 1000;
d.push(root);
while (!d.isEmpty()) {
TreeNode n = d.pollFirst();
if (n.nodeType == TreeNode.Package) {
n.offsetValue = 0;
} else if ((n.nodeType & TreeNode.Primitive) == TreeNode.Primitive) {
n.offsetValue = (int) outValueStream.getPosition();
outValueStream.write(n.nodeType);
} else if (((n.nodeType & TreeNode.Class) == TreeNode.Class)
|| ((n.nodeType & TreeNode.Interface) == TreeNode.Interface)) {
n.offsetValue = (int) outValueStream.getPosition();
writeClassValue(outValueStream, uniqueStrings, n);
} else if ((n.nodeType & TreeNode.Array) == TreeNode.Array) {
n.offsetValue = array_offset;
} else {
throw new Exception("should not happen");
}
for (TreeNode child : n.children) {
d.add(child);
}
}
outValueStream.flush();
outValueStream.close();
d.push(root);
while (!d.isEmpty()) {
TreeNode n = d.pollFirst();
if (n.arrayElement != null) {
n.offsetValue = array_offset + n.arrayElement.id;
}
if (!n.children.isEmpty()) {
n.firstChildId = n.children.get(0).id;
}
for (int i = 0; i < n.children.size(); i++) {
if (i > 0) {
n.children.get(i - 1).nextSiblingId = n.children.get(i).id;
}
d.add(n.children.get(i));
}
}
int[] nodeData = new int[3];
ByteBuffer byteBuffer = ByteBuffer.allocate(nodeData.length * 4);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
IntBuffer intBuffer = byteBuffer.asIntBuffer();
d.push(root);
while (!d.isEmpty()) {
TreeNode n = d.pollFirst();
nodeData[0] = n.firstChildId + (n.nextSiblingId << 16);
nodeData[1] = n.offsetName;
nodeData[2] = n.offsetValue;
intBuffer.clear();
intBuffer.put(nodeData);
outNodeStream.write(byteBuffer.array());
for (TreeNode child : n.children) {
d.add(child);
}
}
outNodeStream.flush();
outNodeStream.close();
}
}