com.android.dx.util.ByteArrayAnnotatedOutput Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of builder Show documentation
Show all versions of builder Show documentation
Library to build Android applications.
/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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 com.android.dx.util;
import com.android.dex.util.ByteOutput;
import com.android.dex.util.ExceptionWithContext;
import com.android.dex.Leb128;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
/**
* Implementation of {@link AnnotatedOutput} which stores the written data
* into a {@code byte[]}.
*
* Note: As per the {@link Output} interface, multi-byte
* writes all use little-endian order.
*/
public final class ByteArrayAnnotatedOutput
implements AnnotatedOutput, ByteOutput {
/** default size for stretchy instances */
private static final int DEFAULT_SIZE = 1000;
/**
* whether the instance is stretchy, that is, whether its array
* may be resized to increase capacity
*/
private final boolean stretchy;
/** {@code non-null;} the data itself */
private byte[] data;
/** {@code >= 0;} current output cursor */
private int cursor;
/** whether annotations are to be verbose */
private boolean verbose;
/**
* {@code null-ok;} list of annotations, or {@code null} if this instance
* isn't keeping them
*/
private ArrayList annotations;
/** {@code >= 40 (if used);} the desired maximum annotation width */
private int annotationWidth;
/**
* {@code >= 8 (if used);} the number of bytes of hex output to use
* in annotations
*/
private int hexCols;
/**
* Constructs an instance with a fixed maximum size. Note that the
* given array is the only one that will be used to store data. In
* particular, no reallocation will occur in order to expand the
* capacity of the resulting instance. Also, the constructed
* instance does not keep annotations by default.
*
* @param data {@code non-null;} data array to use for output
*/
public ByteArrayAnnotatedOutput(byte[] data) {
this(data, false);
}
/**
* Constructs a "stretchy" instance. The underlying array may be
* reallocated. The constructed instance does not keep annotations
* by default.
*/
public ByteArrayAnnotatedOutput() {
this(DEFAULT_SIZE);
}
/**
* Constructs a "stretchy" instance with initial size {@code size}. The
* underlying array may be reallocated. The constructed instance does not
* keep annotations by default.
*/
public ByteArrayAnnotatedOutput(int size) {
this(new byte[size], true);
}
/**
* Internal constructor.
*
* @param data {@code non-null;} data array to use for output
* @param stretchy whether the instance is to be stretchy
*/
private ByteArrayAnnotatedOutput(byte[] data, boolean stretchy) {
if (data == null) {
throw new NullPointerException("data == null");
}
this.stretchy = stretchy;
this.data = data;
this.cursor = 0;
this.verbose = false;
this.annotations = null;
this.annotationWidth = 0;
this.hexCols = 0;
}
/**
* Gets the underlying {@code byte[]} of this instance, which
* may be larger than the number of bytes written
*
* @see #toByteArray
*
* @return {@code non-null;} the {@code byte[]}
*/
public byte[] getArray() {
return data;
}
/**
* Constructs and returns a new {@code byte[]} that contains
* the written contents exactly (that is, with no extra unwritten
* bytes at the end).
*
* @see #getArray
*
* @return {@code non-null;} an appropriately-constructed array
*/
public byte[] toByteArray() {
byte[] result = new byte[cursor];
System.arraycopy(data, 0, result, 0, cursor);
return result;
}
/** {@inheritDoc} */
public int getCursor() {
return cursor;
}
/** {@inheritDoc} */
public void assertCursor(int expectedCursor) {
if (cursor != expectedCursor) {
throw new ExceptionWithContext("expected cursor " +
expectedCursor + "; actual value: " + cursor);
}
}
/** {@inheritDoc} */
public void writeByte(int value) {
int writeAt = cursor;
int end = writeAt + 1;
if (stretchy) {
ensureCapacity(end);
} else if (end > data.length) {
throwBounds();
return;
}
data[writeAt] = (byte) value;
cursor = end;
}
/** {@inheritDoc} */
public void writeShort(int value) {
int writeAt = cursor;
int end = writeAt + 2;
if (stretchy) {
ensureCapacity(end);
} else if (end > data.length) {
throwBounds();
return;
}
data[writeAt] = (byte) value;
data[writeAt + 1] = (byte) (value >> 8);
cursor = end;
}
/** {@inheritDoc} */
public void writeInt(int value) {
int writeAt = cursor;
int end = writeAt + 4;
if (stretchy) {
ensureCapacity(end);
} else if (end > data.length) {
throwBounds();
return;
}
data[writeAt] = (byte) value;
data[writeAt + 1] = (byte) (value >> 8);
data[writeAt + 2] = (byte) (value >> 16);
data[writeAt + 3] = (byte) (value >> 24);
cursor = end;
}
/** {@inheritDoc} */
public void writeLong(long value) {
int writeAt = cursor;
int end = writeAt + 8;
if (stretchy) {
ensureCapacity(end);
} else if (end > data.length) {
throwBounds();
return;
}
int half = (int) value;
data[writeAt] = (byte) half;
data[writeAt + 1] = (byte) (half >> 8);
data[writeAt + 2] = (byte) (half >> 16);
data[writeAt + 3] = (byte) (half >> 24);
half = (int) (value >> 32);
data[writeAt + 4] = (byte) half;
data[writeAt + 5] = (byte) (half >> 8);
data[writeAt + 6] = (byte) (half >> 16);
data[writeAt + 7] = (byte) (half >> 24);
cursor = end;
}
/** {@inheritDoc} */
public int writeUleb128(int value) {
if (stretchy) {
ensureCapacity(cursor + 5); // pessimistic
}
int cursorBefore = cursor;
Leb128.writeUnsignedLeb128(this, value);
return (cursor - cursorBefore);
}
/** {@inheritDoc} */
public int writeSleb128(int value) {
if (stretchy) {
ensureCapacity(cursor + 5); // pessimistic
}
int cursorBefore = cursor;
Leb128.writeSignedLeb128(this, value);
return (cursor - cursorBefore);
}
/** {@inheritDoc} */
public void write(ByteArray bytes) {
int blen = bytes.size();
int writeAt = cursor;
int end = writeAt + blen;
if (stretchy) {
ensureCapacity(end);
} else if (end > data.length) {
throwBounds();
return;
}
bytes.getBytes(data, writeAt);
cursor = end;
}
/** {@inheritDoc} */
public void write(byte[] bytes, int offset, int length) {
int writeAt = cursor;
int end = writeAt + length;
int bytesEnd = offset + length;
// twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0)
if (((offset | length | end) < 0) || (bytesEnd > bytes.length)) {
throw new IndexOutOfBoundsException("bytes.length " +
bytes.length + "; " +
offset + "..!" + end);
}
if (stretchy) {
ensureCapacity(end);
} else if (end > data.length) {
throwBounds();
return;
}
System.arraycopy(bytes, offset, data, writeAt, length);
cursor = end;
}
/** {@inheritDoc} */
public void write(byte[] bytes) {
write(bytes, 0, bytes.length);
}
/** {@inheritDoc} */
public void writeZeroes(int count) {
if (count < 0) {
throw new IllegalArgumentException("count < 0");
}
int end = cursor + count;
if (stretchy) {
ensureCapacity(end);
} else if (end > data.length) {
throwBounds();
return;
}
/*
* There is no need to actually write zeroes, since the array is
* already preinitialized with zeroes.
*/
cursor = end;
}
/** {@inheritDoc} */
public void alignTo(int alignment) {
int mask = alignment - 1;
if ((alignment < 0) || ((mask & alignment) != 0)) {
throw new IllegalArgumentException("bogus alignment");
}
int end = (cursor + mask) & ~mask;
if (stretchy) {
ensureCapacity(end);
} else if (end > data.length) {
throwBounds();
return;
}
/*
* There is no need to actually write zeroes, since the array is
* already preinitialized with zeroes.
*/
cursor = end;
}
/** {@inheritDoc} */
public boolean annotates() {
return (annotations != null);
}
/** {@inheritDoc} */
public boolean isVerbose() {
return verbose;
}
/** {@inheritDoc} */
public void annotate(String msg) {
if (annotations == null) {
return;
}
endAnnotation();
annotations.add(new Annotation(cursor, msg));
}
/** {@inheritDoc} */
public void annotate(int amt, String msg) {
if (annotations == null) {
return;
}
endAnnotation();
int asz = annotations.size();
int lastEnd = (asz == 0) ? 0 : annotations.get(asz - 1).getEnd();
int startAt;
if (lastEnd <= cursor) {
startAt = cursor;
} else {
startAt = lastEnd;
}
annotations.add(new Annotation(startAt, startAt + amt, msg));
}
/** {@inheritDoc} */
public void endAnnotation() {
if (annotations == null) {
return;
}
int sz = annotations.size();
if (sz != 0) {
annotations.get(sz - 1).setEndIfUnset(cursor);
}
}
/** {@inheritDoc} */
public int getAnnotationWidth() {
int leftWidth = 8 + (hexCols * 2) + (hexCols / 2);
return annotationWidth - leftWidth;
}
/**
* Indicates that this instance should keep annotations. This method may
* be called only once per instance, and only before any data has been
* written to the it.
*
* @param annotationWidth {@code >= 40;} the desired maximum annotation width
* @param verbose whether or not to indicate verbose annotations
*/
public void enableAnnotations(int annotationWidth, boolean verbose) {
if ((annotations != null) || (cursor != 0)) {
throw new RuntimeException("cannot enable annotations");
}
if (annotationWidth < 40) {
throw new IllegalArgumentException("annotationWidth < 40");
}
int hexCols = (((annotationWidth - 7) / 15) + 1) & ~1;
if (hexCols < 6) {
hexCols = 6;
} else if (hexCols > 10) {
hexCols = 10;
}
this.annotations = new ArrayList(1000);
this.annotationWidth = annotationWidth;
this.hexCols = hexCols;
this.verbose = verbose;
}
/**
* Finishes up annotation processing. This closes off any open
* annotations and removes annotations that don't refer to written
* data.
*/
public void finishAnnotating() {
// Close off the final annotation, if any.
endAnnotation();
// Remove annotations that refer to unwritten data.
if (annotations != null) {
int asz = annotations.size();
while (asz > 0) {
Annotation last = annotations.get(asz - 1);
if (last.getStart() > cursor) {
annotations.remove(asz - 1);
asz--;
} else if (last.getEnd() > cursor) {
last.setEnd(cursor);
break;
} else {
break;
}
}
}
}
/**
* Writes the annotated content of this instance to the given writer.
*
* @param out {@code non-null;} where to write to
*/
public void writeAnnotationsTo(Writer out) throws IOException {
int width2 = getAnnotationWidth();
int width1 = annotationWidth - width2 - 1;
TwoColumnOutput twoc = new TwoColumnOutput(out, width1, width2, "|");
Writer left = twoc.getLeft();
Writer right = twoc.getRight();
int leftAt = 0; // left-hand byte output cursor
int rightAt = 0; // right-hand annotation index
int rightSz = annotations.size();
while ((leftAt < cursor) && (rightAt < rightSz)) {
Annotation a = annotations.get(rightAt);
int start = a.getStart();
int end;
String text;
if (leftAt < start) {
// This is an area with no annotation.
end = start;
start = leftAt;
text = "";
} else {
// This is an area with an annotation.
end = a.getEnd();
text = a.getText();
rightAt++;
}
left.write(Hex.dump(data, start, end - start, start, hexCols, 6));
right.write(text);
twoc.flush();
leftAt = end;
}
if (leftAt < cursor) {
// There is unannotated output at the end.
left.write(Hex.dump(data, leftAt, cursor - leftAt, leftAt,
hexCols, 6));
}
while (rightAt < rightSz) {
// There are zero-byte annotations at the end.
right.write(annotations.get(rightAt).getText());
rightAt++;
}
twoc.flush();
}
/**
* Throws the excpetion for when an attempt is made to write past the
* end of the instance.
*/
private static void throwBounds() {
throw new IndexOutOfBoundsException("attempt to write past the end");
}
/**
* Reallocates the underlying array if necessary. Calls to this method
* should be guarded by a test of {@link #stretchy}.
*
* @param desiredSize {@code >= 0;} the desired minimum total size of the array
*/
private void ensureCapacity(int desiredSize) {
if (data.length < desiredSize) {
byte[] newData = new byte[desiredSize * 2 + 1000];
System.arraycopy(data, 0, newData, 0, cursor);
data = newData;
}
}
/**
* Annotation on output.
*/
private static class Annotation {
/** {@code >= 0;} start of annotated range (inclusive) */
private final int start;
/**
* {@code >= 0;} end of annotated range (exclusive);
* {@code Integer.MAX_VALUE} if unclosed
*/
private int end;
/** {@code non-null;} annotation text */
private final String text;
/**
* Constructs an instance.
*
* @param start {@code >= 0;} start of annotated range
* @param end {@code >= start;} end of annotated range (exclusive) or
* {@code Integer.MAX_VALUE} if unclosed
* @param text {@code non-null;} annotation text
*/
public Annotation(int start, int end, String text) {
this.start = start;
this.end = end;
this.text = text;
}
/**
* Constructs an instance. It is initally unclosed.
*
* @param start {@code >= 0;} start of annotated range
* @param text {@code non-null;} annotation text
*/
public Annotation(int start, String text) {
this(start, Integer.MAX_VALUE, text);
}
/**
* Sets the end as given, but only if the instance is unclosed;
* otherwise, do nothing.
*
* @param end {@code >= start;} the end
*/
public void setEndIfUnset(int end) {
if (this.end == Integer.MAX_VALUE) {
this.end = end;
}
}
/**
* Sets the end as given.
*
* @param end {@code >= start;} the end
*/
public void setEnd(int end) {
this.end = end;
}
/**
* Gets the start.
*
* @return the start
*/
public int getStart() {
return start;
}
/**
* Gets the end.
*
* @return the end
*/
public int getEnd() {
return end;
}
/**
* Gets the text.
*
* @return {@code non-null;} the text
*/
public String getText() {
return text;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy