/* * 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.dex.file; import com.android.dx.util.AnnotatedOutput; import com.android.dx.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); }