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 /** >= 40 (if used); the desired maximum output width */ 75373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver private int outputWidth; 76373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver 77373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver /** 78373ff22ec69bb6e93646994347b6d80502be1588Ben Gruver * >= 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}