/*
* Copyright (C) 2007 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: Dec 16, 2007
*/
package test.display;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import uk.me.parabola.imgfmt.app.ImgFileReader;
/**
* Displays data in a manner similar to imgdecode written by John Mechalas.
*
* So we have an address on the left, un-decoded bytes in the middle and
* the decoded text and explanation on the right.
*
* @author Steve Ratcliffe
*/
public class Displayer
{
private static final String SEPARATOR =
"---------------------------------"
+
"---------------------------------------------------------------"
+
"---------------------------------------------------------------"
;
private static final int TABLE_WIDTH =
80;
private String title
;
private final List<DisplayItem
> items =
new ArrayList<>();
private final ImgFileReader reader
;
// References relative to the section start are much more useful for
// some purposes.
private long sectStart
;
public Displayer
(ImgFileReader reader
) {
this.
reader = reader
;
}
/**
* Prints this displayer, and causes all the contained display items to
* be printed out.
* @param writer The stream to write to.
*/
public void print
(PrintStream writer
) {
if (writer
!=
null) {
printTitle
(writer
);
for (DisplayItem item : items
) {
item.
print(writer
);
}
// writer.flush(); // slows down processing, maybe uncomment while debugging
}
items.
clear();
}
private void printTitle
(PrintStream writer
) {
if (title ==
null)
return;
int leadin =
9;
writer.
printf("%s ", SEPARATOR.
substring(0, leadin
));
writer.
print(title
);
writer.
printf(" %s", SEPARATOR.
substring(0, TABLE_WIDTH - leadin - title.
length() -
2));
writer.
println();
}
public void setTitle
(String title
) {
this.
title = title
;
}
/**
* Create a display item for the current position. You can add data and
* lines of text to it. If you can its easier to use the convenience
* routines below.
*
* This must be called *before* getting any data from the reader as it
* records the file position.
*
* @return A display item.
*/
public DisplayItem item
() {
DisplayItem item =
new DisplayItem
();
item.
setStartPos(reader.
position());
item.
setSectStart(sectStart
);
items.
add(item
);
return item
;
}
/**
* Draw a line across the display.
*/
public void line
() {
item
().
addText("------");
}
/**
* Make a gap in the display, nothing will be printed apart from the
* separators.
*/
public void gap
() {
item
().
addText(" ");
}
// TODO: would be much safer/clearer for byteValue, sByteValue, charValue etc to return appropriately signed int
// then more masking could be removed
/**
* Display a single byte as unsigned value 0 .. 255
* @param text
* @return the value as (signed) byte (-128 .. 127)
*/
public byte byteValue
(String text
) {
DisplayItem item = byteItem
();
int val = item.
getValue();
if (text
!=
null)
item.
addText(text, val
);
return (byte) val
;
}
/**
* Interpret a single byte as signed value -128 .. 127
* @param text
* @return the value as (signed) byte (-128 .. 127)
*/
public byte sByteValue
(String text
) {
DisplayItem item = item
();
item.
setBytes1(reader.
get1s());
int val = item.
getValue();
if (text
!=
null)
item.
addText(text, val
);
return (byte) val
;
}
public DisplayItem byteItem
() {
DisplayItem item = item
();
item.
setBytes1(reader.
get1u());
return item
;
}
public char charValue
(String text
) {
DisplayItem item = charItem
();
int val = item.
getValue();
if (text
!=
null)
item.
addText(text, val
);
return (char) val
;
}
public DisplayItem charItem
() {
DisplayItem item = item
();
item.
setBytes2(reader.
get2u());
return item
;
}
public short shortValue
(String text
) {
DisplayItem item = item
();
item.
setBytes2(reader.
get2s());
int val = item.
getValue();
if (text
!=
null)
item.
addText(text, val
);
return (short) val
;
}
public int intValue
(String text
) {
DisplayItem item = intItem
();
int val = item.
getValue();
if (text
!=
null)
item.
addText(text, val
);
return val
;
}
public DisplayItem intItem
() {
DisplayItem item = item
();
item.
setBytes4(reader.
get4());
return item
;
}
public DisplayItem intItem
(int n
) {
DisplayItem item = item
();
switch (n
) {
case 1:
item.
setBytes1(reader.
get1u());
break;
case 2:
item.
setBytes2(reader.
get2u());
break;
case 3:
item.
setBytes3(reader.
get3u());
break;
case 4:
item.
setBytes4(reader.
get4());
break;
default:
throw new IllegalArgumentException("intItem(): n must be inside 1..4: " + n
);
}
return item
;
}
public int intValue
(int n,
String text
) {
switch (n
) {
case 1:
return byteValue
(text
) & 0xff
;
case 2:
return charValue
(text
);
case 3:
return int3Value
(text
);
case 4:
return intValue
(text
);
default:
throw new IllegalArgumentException("intValue(): n must be inside 1..4: " + n
);
}
}
/**
* Display an unsigned 3 byte quantity.
*/
public int int3Value
(String text
) {
DisplayItem item = int3Item
();
int val = item.
getValue();
if (text
!=
null)
item.
addText(text, val
);
return val
;
}
public DisplayItem int3Item
() {
DisplayItem item = item
();
item.
setBytes3(reader.
get3u());
return item
;
}
public int int3sValue
(String text
) {
DisplayItem item = int3Item
();
int val = item.
getValue();
if ((val
& 0x800000
) !=
0)
val |= 0xff000000
;
if (text
!=
null)
item.
addText(text, val
);
return val
;
}
public DisplayItem rawItem
(int n
) {
DisplayItem item = item
();
byte[] buf = reader.
get(n
);
item.
setBytes(buf
);
return item
;
}
public byte[] rawValue
(int n,
String text
) {
if (n
<=
0) {
if (n
< 0)
item
().
addText("overshoot %d: %s", n, text
);
return null;
}
DisplayItem item = item
();
byte[] buf = reader.
get(n
);
item.
setBytes(buf
);
if (text
!=
null)
item.
addText(text +
" (len: %d, %#x)", n, n
);
return buf
;
}
public void stringValue
(int n,
String text
) {
DisplayItem item = item
();
byte[] b = item.
setBytes(reader.
get(n
));
String val =
new String(b
);
if (text
!=
null)
item.
addText(text, val
);
}
/**
* Display a zero terminated string.
* @param text Description of the value.
* @return The value as a string, remember that the nul byte is not included
* so that the string will be one less in length than in the file.
*/
public String zstringValue
(String text
) {
return zstringValue
(text,
"latin1");
}
public String zstringValue
(String text,
String charsetName
) {
DisplayItem item = item
();
ByteArrayOutputStream os =
new ByteArrayOutputStream();
byte b
;
while ((b = reader.
get()) !=
'\0')
os.
write(b
);
String val
;
try {
val = os.
toString(charsetName
);
} catch (UnsupportedEncodingException e
) {
val = os.
toString();
}
os.
write('\0');
item.
setBytes(os.
toByteArray());
if (text
!=
null)
item.
addText(text, val
);
return val
;
}
public void rawValue
(int n
) {
rawValueAsChars
(n,
"Unknown %d bytes:");
}
public void rawValueAsChars
(int n,
String formatStr
) {
if (n
<=
0) {
if (n
< 0) {
item
().
addText("overshoot %d", n
);
reader.
position(reader.
position() + n
);
}
return;
}
item
().
addText(formatStr, n
);
DisplayItem item = item
();
byte[] bytes = item.
setBytes(reader.
get(n
));
StringBuilder sb =
new StringBuilder();
for (int count =
0; count
< bytes.
length; count++
) {
char c =
(char) (bytes
[count
] & 0xff
);
sb.
append(/*Character.isLetterOrDigit(c)*/ c
>= 0x20
? c :
'.');
if ((count
& 0x7
) ==
7) {
item.
addText(sb.
toString());
sb =
new StringBuilder();
}
}
if (sb.
length() > 0)
item.
addText(sb.
toString());
}
public void setSectStart
(long sectStart
) {
this.
sectStart = sectStart
;
}
/**
* Read varying length integer where first byte also gives number of following
* bytes.
*
* @param msg the message format string
* @param valLength is set to the number of bytes that were read
* @return the length
*/
protected int varLength
(String msg,
AtomicInteger varLength
) {
long pos = reader.
position();
int len = reader.
readVarLength(varLength
);
reader.
position(pos
);
DisplayItem item = rawItem
(varLength.
get());
item.
addText(String.
format(msg, len
));
return len
;
}
protected int varUInt32
(String msg,
AtomicInteger varLength
) {
long pos = reader.
position();
int len = readVarUInt32
(varLength
);
reader.
position(pos
);
DisplayItem item = rawItem
(varLength.
get());
item.
addText(String.
format(msg, len
));
return len
;
}
protected int readVarUInt32
(AtomicInteger varLength
) {
int bytes
;
int shift
;
int b = reader.
get1u();
if ((b
& 1) ==
0) {
if ((b
& 2) ==
0) {
bytes =
((b
>> 2) & 1) ^
3;
shift =
3;
} else {
shift =
2;
bytes =
1;
}
} else {
shift =
1;
bytes =
0;
}
int val = b
>> shift
;
for (int i =
1; i
<= bytes
; i++
) {
b = reader.
get1u();
val |=
((b
) << (i
* 8)) >> shift
;
}
varLength.
set(bytes +
1);
return val
;
}
}