aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/ziputils/View.java
blob: e6762bed7e4ad0d5a5ac513af16b52ade366c1b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.android.ziputils;

import static java.nio.ByteOrder.LITTLE_ENDIAN;
import static java.nio.charset.StandardCharsets.UTF_8;

import java.nio.ByteBuffer;

/**
 * A {@code View} represents a range of a larger sequence of bytes (e.g. part of a file).
 * It consist of an internal byte buffer providing access to the data range, and an
 * offset of the first byte in the buffer within the larger sequence.
 * Subclasses will typically assign a specific interpretations of the data in a view 
 * (e.g. records of a specific type).
 *
 * <p>Instances of subclasses may generally be "allocated", or created "of" or "over" a
 * byte buffer provided at creation time.
 *
 * <p>An "allocated" view gets a new heap allocated byte buffer. Allocation methods
 * typically defines parameters for variable sized part of the object they create a
 * view of. Once allocated, the variable size parts (e.g. filename) cannot be changed.
 *
 * <p>A view created "of" an existing byte buffer, expects the buffer to contain an
 * appropriate record at its current position. The buffers limit is set at the
 * end of the object being viewed.
 *
 * <p>A view created "over" an existing byte buffer, reserves space in the buffer for the
 * object being viewed. The variable sized parts of the object are initialized, but
 * otherwise the existing buffer data are left as-is, and can be manipulated  through the
 * view.
 *
 * <p> An view can also be copied into an existing byte buffer. This is like creating a
 * view "over" the buffer, but initializing the new view as an exact copy of the one
 * being copied.
 *
 * @param <SUB> to be type-safe, a subclass {@code S} must extend {@code View<S>}. It must not
 * extend {@code View<S2>}, where {@code S2} is another subclass, which is not also a superclass
 * of {@code S}. To maintain this guarantee, this class is declared abstract and package private.
 * Unchecked warnings are suppressed as per this specification constraint.
 */
abstract class View<SUB extends View<?>> {

  /** Zero length byte array */
  protected static final byte[] EMPTY = {};

  /** {@code ByteBuffer} backing this view. */
  protected final ByteBuffer buffer;

  /**
   * Offset of first byte covered by this view. For input views, this is the file offset where the
   * item occur. For output, it's the offset at which we expect to write the item (may be -1, for
   * unknown).
   */
  protected long fileOffset;

  /**
   * Creates a view backed by the given {@code ByteBuffer}. Sets the buffer's byte order to
   * little endian, and sets the file offset to -1 (unknown).
   *
   * @param buffer backing byte buffer.
   */
  protected View(ByteBuffer buffer) {
    buffer.order(LITTLE_ENDIAN);
    this.buffer = buffer;
    this.fileOffset = -1;
  }

  /**
   * Sets the file offset of the data item covered by this view,
   *
   * @param fileOffset
   * @return this object.
   */
  @SuppressWarnings("unchecked") // safe by specification
  public SUB at(long fileOffset) {
    this.fileOffset = fileOffset;
    return (SUB) this;
  }

  /**
   * Gets the fileOffset of this view.
   *
   * @return the location of the viewed object within the underlying file.
   */
  public long fileOffset() {
    return fileOffset;
  }

  /**
   * Returns an array with data copied from the backing byte buffer, at the given offset, relative
   * to the beginning of this view. This method does not perform boundary checks of offset or
   * length. This method may temporarily changes the position of the backing buffer. However, after
   * the call, the position will be unchanged.
   *
   * @param off offset relative to this view.
   * @param len number of bytes to return.
   * @return Newly allocated array with copy of requested data.
   * @throws IndexOutOfBoundsException in case of illegal arguments.
   */
  protected byte[] getBytes(int off, int len) {
    if (len == 0) {
      return EMPTY;
    }
    byte[] bytes;
    try {
      bytes = new byte[len];
      int currPos = buffer.position();
      buffer.position(off);
      buffer.get(bytes);
      buffer.position(currPos);
    } catch (Exception ex) {
      throw new IndexOutOfBoundsException();
    }
    return bytes;
  }

  /**
   * Returns a String representation of {@code len} bytes starting at offset {@code off} in this
   * view. This method may temporarily changes the position of the backing buffer. However, after
   * the call, the position will be unchanged.
   *
   * @param off offset relative to backing buffer.
   * @param len number of bytes to return. This method may throw an
   * @return Newly allocated String created by interpreting the specified bytes as UTF-8 data.
   * @throws IndexOutOfBoundsException in case of illegal arguments.
   */
  protected String getString(int off, int len) {
    if (len == 0) {
      return "";
    }
    if (buffer.hasArray()) {
      return new String(buffer.array(), buffer.arrayOffset() + off, len, UTF_8);
    } else {
      return new String(getBytes(off, len), UTF_8);
    }
  }

  /**
   * Gets the value of an identified integer field.
   *
   * @param id field identifier
   * @return the value of the field identified by {@code id}.
   */
  public int get(IntFieldId<? extends SUB> id) {
    return buffer.getInt(id.address());
  }

  /**
   * Gets the value of an identified short field.
   *
   * @param id field identifier
   * @return the value of the field identified by {@code id}.
   */
  public short get(ShortFieldId<? extends SUB> id) {
    return buffer.getShort(id.address());
  }

  /**
   * Sets the value of an identified integer field.
   *
   * @param id field identifier
   * @param value value to set for the field identified by {@code id}.
   * @return this object.
   */
  @SuppressWarnings("unchecked") // safe by specification
  public SUB set(IntFieldId<? extends SUB> id, int value) {
    buffer.putInt(id.address(), value);
    return (SUB) this;
  }

  /**
   * Sets the value of an identified short field.
   *
   * @param id field identifier
   * @param value value to set for the field identified by {@code id}.
   * @return this object.
   */
  @SuppressWarnings("unchecked") // safe by specification
  public SUB set(ShortFieldId<? extends SUB> id, short value) {
    buffer.putShort(id.address(), value);
    return (SUB) this;
  }

  /**
   * Copies the values of one or more identified fields from another view to this view.
   *
   * @param from The view from which to copy field values.
   * @param ids field identifiers for fields to copy.
   * @return this object.
   */
  @SuppressWarnings("unchecked") // safe by specification
  public SUB copy(View<SUB> from, FieldId<? extends SUB, ?>... ids) {
    for (FieldId<? extends SUB, ?> id : ids) {
      int address = id.address;
      buffer.put(address, from.buffer.get(address++));
      buffer.put(address, from.buffer.get(address++));
      if (id.type() == Integer.TYPE) {
        buffer.put(address, from.buffer.get(address++));
        buffer.put(address, from.buffer.get(address));
      }
    }
    return (SUB) this;
  }

  /**
   * Base class for data field descriptors. Describes a data field's type and address in a view.
   * This base class allows the
   * {@link #copy(View, com.google.devtools.build.android.ziputils.View.FieldId[])} method
   * to operate of fields of mixed types.
   *
   * @param <T> {@code Integer.TYPE} or {@code Short.TYPE}.
   * @param <V> subclass of {@code View} for which a field id is defined.
   */
  protected abstract static class FieldId<V extends View<?>, T> {
    private final int address;
    private final Class<T> type;

    protected FieldId(int address, Class<T> type) {
      this.address = address;
      this.type = type;
    }

    /**
     * Returns the class of the field type, {@code Class<T>}.
     */
    public Class<T> type() {
      return type;
    }

    /**
     * Returns the field address, within a record of type {@code V}
     */
    public int address() {
      return address;
    }
  }

  /**
   * Describes an integer fields for a view.
   *
   * @param <V> subclass of {@code View} for which a field id is defined.
   */
  protected static class IntFieldId<V extends View<?>> extends FieldId<V, Integer> {
    protected IntFieldId(int address) {
      super(address, Integer.TYPE);
    }
  }

  /**
   * Describes a short field for a view.
   *
   * @param <V> subclass of {@code View} for which a field id is defined.
   */
  protected static class ShortFieldId<V extends View<?>> extends FieldId<V, Short> {
    protected ShortFieldId(int address) {
      super(address, Short.TYPE);
    }
  }
}