| /* |
| * 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); |
| } |