Subversion Repositories display

Rev

Rev 606 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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;
        }
}