/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.debugging.information;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.teavm.common.IntegerArray;
import org.teavm.common.RecordArray;
import org.teavm.debugging.information.DebugInformation.ClassMetadata;
/**
*
* @author Alexey Andreev
*/
class DebugInformationWriter {
private DataOutput output;
private int lastNumber;
public DebugInformationWriter(DataOutput output) {
this.output = output;
}
public void write(DebugInformation debugInfo) throws IOException {
writeStringArray(debugInfo.fileNames);
writeStringArray(debugInfo.classNames);
writeStringArray(debugInfo.fields);
writeStringArray(debugInfo.methods);
writeStringArray(debugInfo.variableNames);
writeExactMethods(debugInfo.exactMethods);
writeMapping(debugInfo.fileMapping);
writeMapping(debugInfo.lineMapping);
writeMapping(debugInfo.classMapping);
writeMapping(debugInfo.methodMapping);
writeLinesAndColumns(debugInfo.statementStartMapping);
writeCallSiteMapping(debugInfo.callSiteMapping);
writeVariableMappings(debugInfo);
writeClassMetadata(debugInfo.classesMetadata);
writeCFGs(debugInfo);
}
private void writeVariableMappings(DebugInformation debugInfo) throws IOException {
int lastVar = 0;
writeUnsignedNumber(nonNullVariableMappings(debugInfo));
for (int i = 0; i < debugInfo.variableMappings.length; ++i) {
RecordArray mapping = debugInfo.variableMappings[i];
if (mapping == null) {
continue;
}
writeUnsignedNumber(i - lastVar);
lastVar = i;
writeMultiMapping(mapping);
}
}
private void writeClassMetadata(List<ClassMetadata> classes) throws IOException {
for (int i = 0; i < classes.size(); ++i) {
ClassMetadata cls = classes.get(i);
writeUnsignedNumber(cls.parentId != null ? cls.parentId + 1 : 0);
writeUnsignedNumber(cls.fieldMap.size());
List<Integer> keys = new ArrayList<>(cls.fieldMap.keySet());
Collections.sort(keys);
resetRelativeNumber();
for (int key : keys) {
writeRelativeNumber(key);
writeUnsignedNumber(cls.fieldMap.get(key));
}
}
}
private int nonNullVariableMappings(DebugInformation debugInfo) {
int count = 0;
for (int i = 0; i < debugInfo.variableMappings.length; ++i) {
if (debugInfo.variableMappings[i] != null) {
++count;
}
}
return count;
}
private void writeStringArray(String[] array) throws IOException {
writeUnsignedNumber(array.length);
for (int i = 0; i < array.length; ++i) {
writeString(array[i]);
}
}
private void writeExactMethods(long[] array) throws IOException {
int lastClass = 0;
int lastMethod = 0;
writeUnsignedNumber(array.length);
for (int i = 0; i < array.length; ++i) {
long item = array[i];
int classIndex = (int)(item >> 32);
int methodIndex = (int)item;
writeNumber(classIndex - lastClass);
lastClass = classIndex;
writeNumber(methodIndex - lastMethod);
lastMethod = methodIndex;
}
}
private void writeMultiMapping(RecordArray mapping) throws IOException {
writeLinesAndColumns(mapping);
for (int i = 0; i < mapping.size(); ++i) {
int[] array = mapping.get(i).getArray(0);
writeUnsignedNumber(array.length);
int lastNumber = 0;
for (int elem : array) {
writeNumber(elem - lastNumber);
lastNumber = elem;
}
}
}
private void writeMapping(RecordArray mapping) throws IOException {
writeLinesAndColumns(mapping);
writeRle(packValues(mapping));
}
private void writeCallSiteMapping(RecordArray mapping) throws IOException {
writeLinesAndColumns(mapping);
writeRle(packValues(mapping));
writeRle(packCallSites(mapping));
}
private void writeLinesAndColumns(RecordArray mapping) throws IOException {
writeUnsignedNumber(mapping.size());
writeRle(packLines(mapping));
writeRle(packColumns(mapping));
}
private int[] packLines(RecordArray mapping) {
int[] lines = mapping.cut(0);
int last = 0;
for (int i = 0; i < lines.length; ++i) {
int next = lines[i];
lines[i] -= last;
last = next;
}
return lines;
}
private int[] packColumns(RecordArray mapping) {
int[] columns = mapping.cut(1);
int lastLine = -1;
int lastColumn = 0;
for (int i = 0; i < columns.length; ++i) {
if (lastLine != mapping.get(i).get(0)) {
lastColumn = 0;
lastLine = mapping.get(i).get(0);
}
int column = columns[i];
columns[i] = column - lastColumn;
lastColumn = column;
}
return columns;
}
private int[] packValues(RecordArray mapping) {
int[] values = mapping.cut(2);
int last = 0;
for (int i = 0; i < values.length; ++i) {
int value = values[i];
if (value == -1) {
values[i] = 0;
} else {
values[i] = 1 + convertToSigned(value - last);
last = value;
}
}
return values;
}
private int[] packCallSites(RecordArray mapping) {
int[] callSites = mapping.cut(3);
int last = 0;
int j = 0;
for (int i = 0; i < callSites.length; ++i) {
int type = mapping.get(i).get(2);
if (type != 0) {
int callSite = callSites[i];
callSites[j++] = convertToSigned(callSite - last);
last = callSite;
}
}
return Arrays.copyOf(callSites, j);
}
private void writeCFGs(DebugInformation debugInfo) throws IOException {
for (int i = 0; i < debugInfo.controlFlowGraphs.length; ++i) {
writeCFG(debugInfo.controlFlowGraphs[i]);
}
}
private void writeCFG(RecordArray mapping) throws IOException {
writeUnsignedNumber(mapping.size());
writeRle(mapping.cut(0));
IntegerArray sizes = new IntegerArray(1);
IntegerArray files = new IntegerArray(1);
IntegerArray lines = new IntegerArray(1);
int lastFile = 0;
int lastLine = 0;
for (int i = 0; i < mapping.size(); ++i) {
int type = mapping.get(i).get(0);
if (type == 0) {
continue;
}
int[] data = mapping.get(i).getArray(0);
sizes.add(data.length / 2);
for (int j = 0; j < data.length; j += 2) {
int file = data[j];
int line = data[j + 1];
files.add(convertToSigned(file - lastFile));
lines.add(convertToSigned(line - lastLine));
lastFile = file;
lastLine = line;
}
}
writeRle(sizes.getAll());
writeRle(files.getAll());
writeRle(lines.getAll());
}
private void writeNumber(int number) throws IOException {
writeUnsignedNumber(convertToSigned(number));
}
private int convertToSigned(int number) {
return number < 0 ? (-number << 1) | 1 : number << 1;
}
private void writeUnsignedNumber(int number) throws IOException {
do {
byte b = (byte)(number & 0x7F);
if ((number & 0xFFFFFF80) != 0) {
b |= 0x80;
}
number >>>= 7;
output.writeByte(b);
} while (number != 0);
}
private void writeRle(int[] array) throws IOException {
int last = 0;
for (int i = 0; i < array.length;) {
int e = array[i];
int count = 1;
int current = i;
++i;
while (i < array.length && array[i] == e) {
++count;
++i;
}
if (count > 1) {
if (current > last) {
writeUnsignedNumber(((current - last) << 1) | 0);
while (last < current) {
writeUnsignedNumber(array[last++]);
}
}
writeUnsignedNumber((count << 1) | 1);
writeUnsignedNumber(e);
last = i;
}
}
if (array.length > last) {
writeUnsignedNumber(((array.length - last) << 1) | 0);
while (last < array.length) {
writeUnsignedNumber(array[last++]);
}
}
}
private void writeRelativeNumber(int number) throws IOException {
writeNumber(number - lastNumber);
lastNumber = number;
}
private void resetRelativeNumber() {
lastNumber = 0;
}
private void writeString(String str) throws IOException {
byte[] bytes = str.getBytes("UTF-8");
writeUnsignedNumber(bytes.length);
output.write(bytes);
}
}