blob: 246f90339dd8cd960f26e7ed864dd07deb54880d [file] [log] [blame]
/*
* 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.dexgen.dex.file;
import com.android.dexgen.util.AnnotatedOutput;
import com.android.dexgen.util.ExceptionWithContext;
/**
* An item in a Dalvik file which is referenced by absolute offset.
*/
public abstract class OffsettedItem extends Item
implements Comparable<OffsettedItem> {
/** {@code > 0;} alignment requirement */
private final int alignment;
/** {@code >= -1;} the size of this instance when written, in bytes, or
* {@code -1} if not yet known */
private int writeSize;
/**
* {@code null-ok;} section the item was added to, or {@code null} if
* not yet added
*/
private Section addedTo;
/**
* {@code >= -1;} assigned offset of the item from the start of its section,
* or {@code -1} if not yet assigned
*/
private int offset;
/**
* Gets the absolute offset of the given item, returning {@code 0}
* if handed {@code null}.
*
* @param item {@code null-ok;} the item in question
* @return {@code >= 0;} the item's absolute offset, or {@code 0}
* if {@code item == null}
*/
public static int getAbsoluteOffsetOr0(OffsettedItem item) {
if (item == null) {
return 0;
}
return item.getAbsoluteOffset();
}
/**
* Constructs an instance. The offset is initially unassigned.
*
* @param alignment {@code > 0;} output alignment requirement; must be a
* power of 2
* @param writeSize {@code >= -1;} the size of this instance when written,
* in bytes, or {@code -1} if not immediately known
*/
public OffsettedItem(int alignment, int writeSize) {
Section.validateAlignment(alignment);
if (writeSize < -1) {
throw new IllegalArgumentException("writeSize < -1");
}
this.alignment = alignment;
this.writeSize = writeSize;
this.addedTo = null;
this.offset = -1;
}
/**
* {@inheritDoc}
*
* Comparisons for this class are defined to be type-major (if the
* types don't match then the objects are not equal), with
* {@link #compareTo0} deciding same-type comparisons.
*/
@Override
public final boolean equals(Object other) {
if (this == other) {
return true;
}
OffsettedItem otherItem = (OffsettedItem) other;
ItemType thisType = itemType();
ItemType otherType = otherItem.itemType();
if (thisType != otherType) {
return false;
}
return (compareTo0(otherItem) == 0);
}
/**
* {@inheritDoc}
*
* Comparisons for this class are defined to be class-major (if the
* classes don't match then the objects are not equal), with
* {@link #compareTo0} deciding same-class comparisons.
*/
public final int compareTo(OffsettedItem other) {
if (this == other) {
return 0;
}
ItemType thisType = itemType();
ItemType otherType = other.itemType();
if (thisType != otherType) {
return thisType.compareTo(otherType);
}
return compareTo0(other);
}
/**
* Sets the write size of this item. This may only be called once
* per instance, and only if the size was unknown upon instance
* creation.
*
* @param writeSize {@code > 0;} the write size, in bytes
*/
public final void setWriteSize(int writeSize) {
if (writeSize < 0) {
throw new IllegalArgumentException("writeSize < 0");
}
if (this.writeSize >= 0) {
throw new UnsupportedOperationException("writeSize already set");
}
this.writeSize = writeSize;
}
/** {@inheritDoc}
*
* @throws UnsupportedOperationException thrown if the write size
* is not yet known
*/
@Override
public final int writeSize() {
if (writeSize < 0) {
throw new UnsupportedOperationException("writeSize is unknown");
}
return writeSize;
}
/** {@inheritDoc} */
@Override
public final void writeTo(DexFile file, AnnotatedOutput out) {
out.alignTo(alignment);
try {
if (writeSize < 0) {
throw new UnsupportedOperationException(
"writeSize is unknown");
}
out.assertCursor(getAbsoluteOffset());
} catch (RuntimeException ex) {
throw ExceptionWithContext.withContext(ex,
"...while writing " + this);
}
writeTo0(file, out);
}
/**
* Gets the relative item offset. The offset is from the start of
* the section which the instance was written to.
*
* @return {@code >= 0;} the offset
* @throws RuntimeException thrown if the offset is not yet known
*/
public final int getRelativeOffset() {
if (offset < 0) {
throw new RuntimeException("offset not yet known");
}
return offset;
}
/**
* Gets the absolute item offset. The offset is from the start of
* the file which the instance was written to.
*
* @return {@code >= 0;} the offset
* @throws RuntimeException thrown if the offset is not yet known
*/
public final int getAbsoluteOffset() {
if (offset < 0) {
throw new RuntimeException("offset not yet known");
}
return addedTo.getAbsoluteOffset(offset);
}
/**
* Indicates that this item has been added to the given section at
* the given offset. It is only valid to call this method once per
* instance.
*
* @param addedTo {@code non-null;} the section this instance has
* been added to
* @param offset {@code >= 0;} the desired offset from the start of the
* section where this instance was placed
* @return {@code >= 0;} the offset that this instance should be placed at
* in order to meet its alignment constraint
*/
public final int place(Section addedTo, int offset) {
if (addedTo == null) {
throw new NullPointerException("addedTo == null");
}
if (offset < 0) {
throw new IllegalArgumentException("offset < 0");
}
if (this.addedTo != null) {
throw new RuntimeException("already written");
}
int mask = alignment - 1;
offset = (offset + mask) & ~mask;
this.addedTo = addedTo;
this.offset = offset;
place0(addedTo, offset);
return offset;
}
/**
* Gets the alignment requirement of this instance. An instance should
* only be written when so aligned.
*
* @return {@code > 0;} the alignment requirement; must be a power of 2
*/
public final int getAlignment() {
return alignment;
}
/**
* Gets the absolute offset of this item as a string, suitable for
* including in annotations.
*
* @return {@code non-null;} the offset string
*/
public final String offsetString() {
return '[' + Integer.toHexString(getAbsoluteOffset()) + ']';
}
/**
* Gets a short human-readable string representing this instance.
*
* @return {@code non-null;} the human form
*/
public abstract String toHuman();
/**
* Compares this instance to another which is guaranteed to be of
* the same class. The default implementation of this method is to
* throw an exception (unsupported operation). If a particular
* class needs to actually sort, then it should override this
* method.
*
* @param other {@code non-null;} instance to compare to
* @return {@code -1}, {@code 0}, or {@code 1}, depending
* on the sort order of this instance and the other
*/
protected int compareTo0(OffsettedItem other) {
throw new UnsupportedOperationException("unsupported");
}
/**
* Does additional work required when placing an instance. The
* default implementation of this method is a no-op. If a
* particular class needs to do something special, then it should
* override this method. In particular, if this instance did not
* know its write size up-front, then this method is responsible
* for setting it.
*
* @param addedTo {@code non-null;} the section this instance has been added to
* @param offset {@code >= 0;} the offset from the start of the
* section where this instance was placed
*/
protected void place0(Section addedTo, int offset) {
// This space intentionally left blank.
}
/**
* Performs the actual write of the contents of this instance to
* the given data section. This is called by {@link #writeTo},
* which will have taken care of ensuring alignment.
*
* @param file {@code non-null;} the file to use for reference
* @param out {@code non-null;} where to write to
*/
protected abstract void writeTo0(DexFile file, AnnotatedOutput out);
}