1373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver/*
2373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * Copyright 2013, Google Inc.
3373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * All rights reserved.
4373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver *
5373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * Redistribution and use in source and binary forms, with or without
6373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * modification, are permitted provided that the following conditions are
7373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * met:
8373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver *
9373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver *     * Redistributions of source code must retain the above copyright
10373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * notice, this list of conditions and the following disclaimer.
11373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver *     * Redistributions in binary form must reproduce the above
12373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * copyright notice, this list of conditions and the following disclaimer
13373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * in the documentation and/or other materials provided with the
14373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * distribution.
15373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver *     * Neither the name of Google Inc. nor the names of its
16373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * contributors may be used to endorse or promote products derived from
17373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * this software without specific prior written permission.
18373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver *
19373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver */
31373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
32373ff22ec69bb6e93646994347b6d80502be1588Ben Gruverpackage org.jf.dexlib2.util;
33373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
3431d87776c459972f311a3527694e0d630d92a84bBen Gruverimport com.google.common.base.Strings;
35373ff22ec69bb6e93646994347b6d80502be1588Ben Gruverimport com.google.common.collect.Lists;
3631d87776c459972f311a3527694e0d630d92a84bBen Gruverimport com.google.common.collect.Maps;
37dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver
38dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruverimport org.jf.util.ExceptionWithContext;
39373ff22ec69bb6e93646994347b6d80502be1588Ben Gruverimport org.jf.util.Hex;
40373ff22ec69bb6e93646994347b6d80502be1588Ben Gruverimport org.jf.util.TwoColumnOutput;
41373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
42373ff22ec69bb6e93646994347b6d80502be1588Ben Gruverimport javax.annotation.Nonnull;
4331d87776c459972f311a3527694e0d630d92a84bBen Gruverimport javax.annotation.Nullable;
44373ff22ec69bb6e93646994347b6d80502be1588Ben Gruverimport java.io.IOException;
45373ff22ec69bb6e93646994347b6d80502be1588Ben Gruverimport java.io.Writer;
46373ff22ec69bb6e93646994347b6d80502be1588Ben Gruverimport java.util.List;
4731d87776c459972f311a3527694e0d630d92a84bBen Gruverimport java.util.Map;
4831d87776c459972f311a3527694e0d630d92a84bBen Gruverimport java.util.TreeMap;
49373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
50373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver/**
5131d87776c459972f311a3527694e0d630d92a84bBen Gruver * Collects/presents a set of textual annotations, each associated with a range of bytes or a specific point
5231d87776c459972f311a3527694e0d630d92a84bBen Gruver * between bytes.
5331d87776c459972f311a3527694e0d630d92a84bBen Gruver *
5431d87776c459972f311a3527694e0d630d92a84bBen Gruver * Point annotations cannot occur within the middle of a range annotation, only at the endpoints, or some other area
5531d87776c459972f311a3527694e0d630d92a84bBen Gruver * with no range annotation.
5631d87776c459972f311a3527694e0d630d92a84bBen Gruver *
5731d87776c459972f311a3527694e0d630d92a84bBen Gruver * Multiple point annotations can be defined for a given point. They will be printed in insertion order.
5831d87776c459972f311a3527694e0d630d92a84bBen Gruver *
5931d87776c459972f311a3527694e0d630d92a84bBen Gruver * Only a single range annotation may exist for any given range of bytes. Range annotations may not overlap.
60373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver */
61373ff22ec69bb6e93646994347b6d80502be1588Ben Gruverpublic class AnnotatedBytes {
6231d87776c459972f311a3527694e0d630d92a84bBen Gruver    /**
6331d87776c459972f311a3527694e0d630d92a84bBen Gruver     * This defines the bytes ranges and their associated range and point annotations.
6431d87776c459972f311a3527694e0d630d92a84bBen Gruver     *
6531d87776c459972f311a3527694e0d630d92a84bBen Gruver     * A range is defined by 2 consecutive keys in the map. The first key is the inclusive start point, the second key
6631d87776c459972f311a3527694e0d630d92a84bBen Gruver     * is the exclusive end point. The range annotation for a range is associated with the first key for that range.
6731d87776c459972f311a3527694e0d630d92a84bBen Gruver     * The point annotations for a point are associated with the key at that point.
6831d87776c459972f311a3527694e0d630d92a84bBen Gruver     */
6931d87776c459972f311a3527694e0d630d92a84bBen Gruver    @Nonnull private TreeMap<Integer, AnnotationEndpoint> annotatations = Maps.newTreeMap();
7031d87776c459972f311a3527694e0d630d92a84bBen Gruver
71373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    private int cursor;
72373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    private int indentLevel;
73373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
74373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    /** &gt;= 40 (if used); the desired maximum output width */
75373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    private int outputWidth;
76373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
77373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    /**
78373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     * &gt;= 8 (if used); the number of bytes of hex output to use
79373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     * in annotations
80373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     */
81373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    private int hexCols = 8;
82373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
83dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver    private int startLimit = -1;
84dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver    private int endLimit = -1;
85dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver
86373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    public AnnotatedBytes(int width) {
87373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        this.outputWidth = width;
88373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    }
89373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
90373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    /**
9131d87776c459972f311a3527694e0d630d92a84bBen Gruver     * Moves the cursor to a new location
92658dfb08805b2344d214d8beebf8385027ee7ffaBen Gruver     *
9331d87776c459972f311a3527694e0d630d92a84bBen Gruver     * @param offset The offset to move to
94658dfb08805b2344d214d8beebf8385027ee7ffaBen Gruver     */
9531d87776c459972f311a3527694e0d630d92a84bBen Gruver    public void moveTo(int offset) {
9631d87776c459972f311a3527694e0d630d92a84bBen Gruver        cursor = offset;
9731d87776c459972f311a3527694e0d630d92a84bBen Gruver    }
9831d87776c459972f311a3527694e0d630d92a84bBen Gruver
9931d87776c459972f311a3527694e0d630d92a84bBen Gruver    /**
10031d87776c459972f311a3527694e0d630d92a84bBen Gruver     * Moves the cursor forward or backward by some amount
10131d87776c459972f311a3527694e0d630d92a84bBen Gruver     *
10231d87776c459972f311a3527694e0d630d92a84bBen Gruver     * @param offset The amount to move the cursor
10331d87776c459972f311a3527694e0d630d92a84bBen Gruver     */
10431d87776c459972f311a3527694e0d630d92a84bBen Gruver    public void moveBy(int offset) {
10531d87776c459972f311a3527694e0d630d92a84bBen Gruver        cursor += offset;
10631d87776c459972f311a3527694e0d630d92a84bBen Gruver    }
10731d87776c459972f311a3527694e0d630d92a84bBen Gruver
10831d87776c459972f311a3527694e0d630d92a84bBen Gruver    public void annotateTo(int offset, @Nonnull String msg, Object... formatArgs) {
10931d87776c459972f311a3527694e0d630d92a84bBen Gruver        annotate(offset - cursor, msg, formatArgs);
110658dfb08805b2344d214d8beebf8385027ee7ffaBen Gruver    }
111658dfb08805b2344d214d8beebf8385027ee7ffaBen Gruver
112658dfb08805b2344d214d8beebf8385027ee7ffaBen Gruver    /**
113373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     * Add an annotation of the given length at the current location.
114373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     *
11531d87776c459972f311a3527694e0d630d92a84bBen Gruver     * The location
11631d87776c459972f311a3527694e0d630d92a84bBen Gruver     *
11731d87776c459972f311a3527694e0d630d92a84bBen Gruver     *
118373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     * @param length the length of data being annotated
119373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     * @param msg the annotation message
120373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     * @param formatArgs format arguments to pass to String.format
121373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     */
122373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    public void annotate(int length, @Nonnull String msg, Object... formatArgs) {
123dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver        if (startLimit != -1 && endLimit != -1 && (cursor < startLimit || cursor >= endLimit)) {
124dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver            throw new ExceptionWithContext("Annotating outside the parent bounds");
125dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver        }
126dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver
127029ad25c66e37600f68a95015715d091543c7072Ben Gruver        String formattedMsg;
128029ad25c66e37600f68a95015715d091543c7072Ben Gruver        if (formatArgs != null && formatArgs.length > 0) {
129029ad25c66e37600f68a95015715d091543c7072Ben Gruver            formattedMsg = String.format(msg, formatArgs);
130029ad25c66e37600f68a95015715d091543c7072Ben Gruver        } else {
131029ad25c66e37600f68a95015715d091543c7072Ben Gruver            formattedMsg = msg;
132029ad25c66e37600f68a95015715d091543c7072Ben Gruver        }
13331d87776c459972f311a3527694e0d630d92a84bBen Gruver        int exclusiveEndOffset = cursor + length;
13431d87776c459972f311a3527694e0d630d92a84bBen Gruver
13531d87776c459972f311a3527694e0d630d92a84bBen Gruver        AnnotationEndpoint endPoint = null;
13631d87776c459972f311a3527694e0d630d92a84bBen Gruver
13731d87776c459972f311a3527694e0d630d92a84bBen Gruver        // Do we have an endpoint at the beginning of this annotation already?
13831d87776c459972f311a3527694e0d630d92a84bBen Gruver        AnnotationEndpoint startPoint = annotatations.get(cursor);
13931d87776c459972f311a3527694e0d630d92a84bBen Gruver        if (startPoint == null) {
14031d87776c459972f311a3527694e0d630d92a84bBen Gruver            // Nope. We need to check that we're not in the middle of an existing range annotation.
14131d87776c459972f311a3527694e0d630d92a84bBen Gruver            Map.Entry<Integer, AnnotationEndpoint> previousEntry = annotatations.lowerEntry(cursor);
14231d87776c459972f311a3527694e0d630d92a84bBen Gruver            if (previousEntry != null) {
14331d87776c459972f311a3527694e0d630d92a84bBen Gruver                AnnotationEndpoint previousAnnotations = previousEntry.getValue();
14431d87776c459972f311a3527694e0d630d92a84bBen Gruver                AnnotationItem previousRangeAnnotation = previousAnnotations.rangeAnnotation;
14531d87776c459972f311a3527694e0d630d92a84bBen Gruver                if (previousRangeAnnotation != null) {
146dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                    throw new ExceptionWithContext(
147dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                            "Cannot add annotation %s, due to existing annotation %s",
14831d87776c459972f311a3527694e0d630d92a84bBen Gruver                            formatAnnotation(cursor, cursor + length, formattedMsg),
149dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                            formatAnnotation(previousEntry.getKey(),
150dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                                previousRangeAnnotation.annotation));
15131d87776c459972f311a3527694e0d630d92a84bBen Gruver                }
15231d87776c459972f311a3527694e0d630d92a84bBen Gruver            }
15331d87776c459972f311a3527694e0d630d92a84bBen Gruver        } else if (length > 0) {
15431d87776c459972f311a3527694e0d630d92a84bBen Gruver            AnnotationItem existingRangeAnnotation = startPoint.rangeAnnotation;
15531d87776c459972f311a3527694e0d630d92a84bBen Gruver            if (existingRangeAnnotation != null) {
156dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                throw new ExceptionWithContext(
157dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                        "Cannot add annotation %s, due to existing annotation %s",
15831d87776c459972f311a3527694e0d630d92a84bBen Gruver                                formatAnnotation(cursor, cursor + length, formattedMsg),
159dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                                formatAnnotation(cursor, existingRangeAnnotation.annotation));
16031d87776c459972f311a3527694e0d630d92a84bBen Gruver            }
16131d87776c459972f311a3527694e0d630d92a84bBen Gruver        }
16231d87776c459972f311a3527694e0d630d92a84bBen Gruver
16331d87776c459972f311a3527694e0d630d92a84bBen Gruver        if (length > 0) {
16431d87776c459972f311a3527694e0d630d92a84bBen Gruver            // Ensure that there is no later annotation that would intersect with this one
16531d87776c459972f311a3527694e0d630d92a84bBen Gruver            Map.Entry<Integer, AnnotationEndpoint> nextEntry = annotatations.higherEntry(cursor);
16631d87776c459972f311a3527694e0d630d92a84bBen Gruver            if (nextEntry != null) {
16731d87776c459972f311a3527694e0d630d92a84bBen Gruver                int nextKey = nextEntry.getKey();
16831d87776c459972f311a3527694e0d630d92a84bBen Gruver                if (nextKey < exclusiveEndOffset) {
16931d87776c459972f311a3527694e0d630d92a84bBen Gruver                    // there is an endpoint that would intersect with this annotation. Find one of the annotations
17031d87776c459972f311a3527694e0d630d92a84bBen Gruver                    // associated with the endpoint, to print in the error message
17131d87776c459972f311a3527694e0d630d92a84bBen Gruver                    AnnotationEndpoint nextEndpoint = nextEntry.getValue();
17231d87776c459972f311a3527694e0d630d92a84bBen Gruver                    AnnotationItem nextRangeAnnotation = nextEndpoint.rangeAnnotation;
17331d87776c459972f311a3527694e0d630d92a84bBen Gruver                    if (nextRangeAnnotation != null) {
174dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                        throw new ExceptionWithContext(
175dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                                "Cannot add annotation %s, due to existing annotation %s",
17631d87776c459972f311a3527694e0d630d92a84bBen Gruver                                        formatAnnotation(cursor, cursor + length, formattedMsg),
177dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                                        formatAnnotation(nextKey, nextRangeAnnotation.annotation));
17831d87776c459972f311a3527694e0d630d92a84bBen Gruver                    }
17931d87776c459972f311a3527694e0d630d92a84bBen Gruver                    if (nextEndpoint.pointAnnotations.size() > 0) {
180dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                        throw new ExceptionWithContext(
181dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                                "Cannot add annotation %s, due to existing annotation %s",
18231d87776c459972f311a3527694e0d630d92a84bBen Gruver                                        formatAnnotation(cursor, cursor + length, formattedMsg),
18331d87776c459972f311a3527694e0d630d92a84bBen Gruver                                        formatAnnotation(nextKey, nextKey,
184dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                                            nextEndpoint.pointAnnotations.get(0).annotation));
18531d87776c459972f311a3527694e0d630d92a84bBen Gruver                    }
18631d87776c459972f311a3527694e0d630d92a84bBen Gruver                    // There are no annotations on this endpoint. This "shouldn't" happen. We can still throw an exception.
187dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                    throw new ExceptionWithContext(
188dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                            "Cannot add annotation %s, due to existing annotation endpoint at %d",
18931d87776c459972f311a3527694e0d630d92a84bBen Gruver                                    formatAnnotation(cursor, cursor + length, formattedMsg),
190dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver                                    nextKey);
19131d87776c459972f311a3527694e0d630d92a84bBen Gruver                }
19231d87776c459972f311a3527694e0d630d92a84bBen Gruver
19331d87776c459972f311a3527694e0d630d92a84bBen Gruver                if (nextKey == exclusiveEndOffset) {
19431d87776c459972f311a3527694e0d630d92a84bBen Gruver                    // the next endpoint matches the end of the annotation we are adding
19531d87776c459972f311a3527694e0d630d92a84bBen Gruver                    endPoint = nextEntry.getValue();
19631d87776c459972f311a3527694e0d630d92a84bBen Gruver                }
19731d87776c459972f311a3527694e0d630d92a84bBen Gruver            }
19831d87776c459972f311a3527694e0d630d92a84bBen Gruver        }
19931d87776c459972f311a3527694e0d630d92a84bBen Gruver
20031d87776c459972f311a3527694e0d630d92a84bBen Gruver        // Now, actually add the annotation
20131d87776c459972f311a3527694e0d630d92a84bBen Gruver        // If startPoint is null, we need to create a new one and add it to annotations. Otherwise, we just need to add
20231d87776c459972f311a3527694e0d630d92a84bBen Gruver        // the annotation to the existing AnnotationEndpoint
20331d87776c459972f311a3527694e0d630d92a84bBen Gruver        // the range annotation
20431d87776c459972f311a3527694e0d630d92a84bBen Gruver        if (startPoint == null) {
20531d87776c459972f311a3527694e0d630d92a84bBen Gruver            startPoint = new AnnotationEndpoint();
20631d87776c459972f311a3527694e0d630d92a84bBen Gruver            annotatations.put(cursor, startPoint);
20731d87776c459972f311a3527694e0d630d92a84bBen Gruver        }
20831d87776c459972f311a3527694e0d630d92a84bBen Gruver        if (length == 0) {
20931d87776c459972f311a3527694e0d630d92a84bBen Gruver            startPoint.pointAnnotations.add(new AnnotationItem(indentLevel, formattedMsg));
21031d87776c459972f311a3527694e0d630d92a84bBen Gruver        } else {
21131d87776c459972f311a3527694e0d630d92a84bBen Gruver            startPoint.rangeAnnotation = new AnnotationItem(indentLevel, formattedMsg);
21231d87776c459972f311a3527694e0d630d92a84bBen Gruver
21331d87776c459972f311a3527694e0d630d92a84bBen Gruver            // If endPoint is null, we need to create a new, empty one and add it to annotations
21431d87776c459972f311a3527694e0d630d92a84bBen Gruver            if (endPoint == null) {
21531d87776c459972f311a3527694e0d630d92a84bBen Gruver                endPoint = new AnnotationEndpoint();
21631d87776c459972f311a3527694e0d630d92a84bBen Gruver                annotatations.put(exclusiveEndOffset, endPoint);
21731d87776c459972f311a3527694e0d630d92a84bBen Gruver            }
21831d87776c459972f311a3527694e0d630d92a84bBen Gruver        }
21931d87776c459972f311a3527694e0d630d92a84bBen Gruver
220373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        cursor += length;
221373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    }
222373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
22331d87776c459972f311a3527694e0d630d92a84bBen Gruver    private String formatAnnotation(int offset, String annotationMsg) {
22431d87776c459972f311a3527694e0d630d92a84bBen Gruver        Integer endOffset = annotatations.higherKey(offset);
22531d87776c459972f311a3527694e0d630d92a84bBen Gruver        return formatAnnotation(offset, endOffset, annotationMsg);
22631d87776c459972f311a3527694e0d630d92a84bBen Gruver    }
22731d87776c459972f311a3527694e0d630d92a84bBen Gruver
22831d87776c459972f311a3527694e0d630d92a84bBen Gruver    private String formatAnnotation(int offset, Integer endOffset, String annotationMsg) {
22931d87776c459972f311a3527694e0d630d92a84bBen Gruver        if (endOffset != null) {
23031d87776c459972f311a3527694e0d630d92a84bBen Gruver            return String.format("[0x%x, 0x%x) \"%s\"", offset, endOffset, annotationMsg);
23131d87776c459972f311a3527694e0d630d92a84bBen Gruver        } else {
23231d87776c459972f311a3527694e0d630d92a84bBen Gruver            return String.format("[0x%x, ) \"%s\"", offset, annotationMsg);
23331d87776c459972f311a3527694e0d630d92a84bBen Gruver        }
23431d87776c459972f311a3527694e0d630d92a84bBen Gruver    }
23531d87776c459972f311a3527694e0d630d92a84bBen Gruver
236373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    public void indent() {
237373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        indentLevel++;
238373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    }
239373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
240373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    public void deindent() {
241373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        indentLevel--;
242373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        if (indentLevel < 0) {
243373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver            indentLevel = 0;
244373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        }
245373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    }
246373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
247373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    public int getCursor() {
248373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        return cursor;
249373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    }
250373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
25131d87776c459972f311a3527694e0d630d92a84bBen Gruver    private static class AnnotationEndpoint {
25231d87776c459972f311a3527694e0d630d92a84bBen Gruver        /** Annotations that are associated with a specific point between bytes */
25331d87776c459972f311a3527694e0d630d92a84bBen Gruver        @Nonnull
25431d87776c459972f311a3527694e0d630d92a84bBen Gruver        public final List<AnnotationItem> pointAnnotations = Lists.newArrayList();
25531d87776c459972f311a3527694e0d630d92a84bBen Gruver        /** Annotations that are associated with a range of bytes */
25631d87776c459972f311a3527694e0d630d92a84bBen Gruver        @Nullable
25731d87776c459972f311a3527694e0d630d92a84bBen Gruver        public AnnotationItem rangeAnnotation = null;
25831d87776c459972f311a3527694e0d630d92a84bBen Gruver    }
25931d87776c459972f311a3527694e0d630d92a84bBen Gruver
260373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    private static class AnnotationItem {
261373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        public final int indentLevel;
262373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        public final String annotation;
263373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
26431d87776c459972f311a3527694e0d630d92a84bBen Gruver        public AnnotationItem(int  indentLevel, String annotation) {
265373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver            this.indentLevel = indentLevel;
266373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver            this.annotation = annotation;
267373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        }
268373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    }
269373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
270373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    /**
271373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     * Gets the width of the right side containing the annotations
272373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     * @return
273373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     */
274373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    public int getAnnotationWidth() {
275373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        int leftWidth = 8 + (hexCols * 2) + (hexCols / 2);
276373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
277373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        return outputWidth - leftWidth;
278373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    }
279373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
280373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    /**
281373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     * Writes the annotated content of this instance to the given writer.
282373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     *
283373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     * @param out non-null; where to write to
284373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver     */
285373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    public void writeAnnotations(Writer out, byte[] data) throws IOException {
286373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        int rightWidth = getAnnotationWidth();
287373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        int leftWidth = outputWidth - rightWidth - 1;
288373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
28931d87776c459972f311a3527694e0d630d92a84bBen Gruver        String padding = Strings.repeat(" ", 1000);
290373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
291373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        TwoColumnOutput twoc = new TwoColumnOutput(out, leftWidth, rightWidth, "|");
29231d87776c459972f311a3527694e0d630d92a84bBen Gruver
29331d87776c459972f311a3527694e0d630d92a84bBen Gruver        Integer[] keys = new Integer[annotatations.size()];
29431d87776c459972f311a3527694e0d630d92a84bBen Gruver        keys = annotatations.keySet().toArray(keys);
29531d87776c459972f311a3527694e0d630d92a84bBen Gruver
29631d87776c459972f311a3527694e0d630d92a84bBen Gruver        AnnotationEndpoint[] values = new AnnotationEndpoint[annotatations.size()];
29731d87776c459972f311a3527694e0d630d92a84bBen Gruver        values = annotatations.values().toArray(values);
29831d87776c459972f311a3527694e0d630d92a84bBen Gruver
29931d87776c459972f311a3527694e0d630d92a84bBen Gruver        for (int i=0; i<keys.length-1; i++) {
30031d87776c459972f311a3527694e0d630d92a84bBen Gruver            int rangeStart = keys[i];
30131d87776c459972f311a3527694e0d630d92a84bBen Gruver            int rangeEnd = keys[i+1];
30231d87776c459972f311a3527694e0d630d92a84bBen Gruver
30331d87776c459972f311a3527694e0d630d92a84bBen Gruver            AnnotationEndpoint annotations = values[i];
30431d87776c459972f311a3527694e0d630d92a84bBen Gruver
30531d87776c459972f311a3527694e0d630d92a84bBen Gruver            for (AnnotationItem pointAnnotation: annotations.pointAnnotations) {
30631d87776c459972f311a3527694e0d630d92a84bBen Gruver                String paddingSub = padding.substring(0, pointAnnotation.indentLevel*2);
30731d87776c459972f311a3527694e0d630d92a84bBen Gruver                twoc.write("", paddingSub + pointAnnotation.annotation);
308373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver            }
309373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
31031d87776c459972f311a3527694e0d630d92a84bBen Gruver            String right;
31131d87776c459972f311a3527694e0d630d92a84bBen Gruver            AnnotationItem rangeAnnotation = annotations.rangeAnnotation;
31231d87776c459972f311a3527694e0d630d92a84bBen Gruver            if (rangeAnnotation != null) {
31331d87776c459972f311a3527694e0d630d92a84bBen Gruver                right = padding.substring(0, rangeAnnotation.indentLevel*2);
31431d87776c459972f311a3527694e0d630d92a84bBen Gruver                right += rangeAnnotation.annotation;
31531d87776c459972f311a3527694e0d630d92a84bBen Gruver            } else {
31631d87776c459972f311a3527694e0d630d92a84bBen Gruver                right = "";
31731d87776c459972f311a3527694e0d630d92a84bBen Gruver            }
318373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
31931d87776c459972f311a3527694e0d630d92a84bBen Gruver            String left = Hex.dump(data, rangeStart, rangeEnd - rangeStart, rangeStart, hexCols, 6);
320373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
32131d87776c459972f311a3527694e0d630d92a84bBen Gruver            twoc.write(left, right);
322373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        }
323373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver
32431d87776c459972f311a3527694e0d630d92a84bBen Gruver        int lastKey = keys[keys.length-1];
32531d87776c459972f311a3527694e0d630d92a84bBen Gruver        if (lastKey < data.length) {
32631d87776c459972f311a3527694e0d630d92a84bBen Gruver            String left = Hex.dump(data, lastKey, data.length - lastKey, lastKey, hexCols, 6);
32731d87776c459972f311a3527694e0d630d92a84bBen Gruver            twoc.write(left, "");
328373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver        }
329373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver    }
330dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver
331dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver    public void setLimit(int start, int end) {
332dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver        this.startLimit = start;
333dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver        this.endLimit = end;
334dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver    }
335dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver
336dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver    public void clearLimit() {
337dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver        this.startLimit = -1;
338dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver        this.endLimit = -1;
339dc802b06607cde3eadaaffeae888bfd6146000f1Ben Gruver    }
340373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver}