001/**
002 * 
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.fs;
020
021import java.io.*;
022import java.nio.ByteBuffer;
023import java.util.EnumSet;
024
025import org.apache.hadoop.classification.InterfaceAudience;
026import org.apache.hadoop.classification.InterfaceStability;
027import org.apache.hadoop.classification.MapRModified;
028import org.apache.hadoop.io.ByteBufferPool;
029import org.apache.hadoop.fs.ByteBufferUtil;
030import org.apache.hadoop.util.IdentityHashStore;
031
032/** Utility that wraps a {@link FSInputStream} in a {@link DataInputStream}
033 * and buffers input through a {@link BufferedInputStream}. */
034@InterfaceAudience.Public
035@InterfaceStability.Stable
036public class FSDataInputStream extends DataInputStream
037    implements Seekable, PositionedReadable, 
038      ByteBufferReadable, HasFileDescriptor, CanSetDropBehind, CanSetReadahead,
039      HasEnhancedByteBufferAccess, CanUnbuffer {
040  /**
041   * Map ByteBuffers that we have handed out to readers to ByteBufferPool 
042   * objects
043   */
044  private final IdentityHashStore<ByteBuffer, ByteBufferPool>
045    extendedReadBuffers
046      = new IdentityHashStore<ByteBuffer, ByteBufferPool>(0);
047
048  /**
049   * Type of file advise to be passed on to the underlying file system. This
050   * information can be used to make optimizations such as reclaiming buffers
051   * for files that are no longer needed by the application, etc.
052   */
053  @MapRModified
054  public static enum FadviseType {
055    FILE_DONTNEED,
056    FILE_RANDOM,
057    FILE_SEQUENTIAL;
058  }
059
060  public FSDataInputStream(InputStream in) {
061    super(in);
062    if( !(in instanceof Seekable) || !(in instanceof PositionedReadable) ) {
063      throw new IllegalArgumentException(
064          "In is not an instance of Seekable or PositionedReadable");
065    }
066  }
067  
068  /**
069   * Seek to the given offset.
070   *
071   * @param desired offset to seek to
072   */
073  @Override
074  public void seek(long desired) throws IOException {
075    ((Seekable)in).seek(desired);
076  }
077
078  /**
079   * Get the current position in the input stream.
080   *
081   * @return current position in the input stream
082   */
083  @Override
084  public long getPos() throws IOException {
085    return ((Seekable)in).getPos();
086  }
087  
088  /**
089   * Read bytes from the given position in the stream to the given buffer.
090   *
091   * @param position  position in the input stream to seek
092   * @param buffer    buffer into which data is read
093   * @param offset    offset into the buffer in which data is written
094   * @param length    maximum number of bytes to read
095   * @return total number of bytes read into the buffer, or <code>-1</code>
096   *         if there is no more data because the end of the stream has been
097   *         reached
098   */
099  @Override
100  public int read(long position, byte[] buffer, int offset, int length)
101    throws IOException {
102    return ((PositionedReadable)in).read(position, buffer, offset, length);
103  }
104
105  /**
106   * Read bytes from the given position in the stream to the given buffer.
107   * Continues to read until <code>length</code> bytes have been read.
108   *
109   * @param position  position in the input stream to seek
110   * @param buffer    buffer into which data is read
111   * @param offset    offset into the buffer in which data is written
112   * @param length    the number of bytes to read
113   * @throws EOFException If the end of stream is reached while reading.
114   *                      If an exception is thrown an undetermined number
115   *                      of bytes in the buffer may have been written. 
116   */
117  @Override
118  public void readFully(long position, byte[] buffer, int offset, int length)
119    throws IOException {
120    ((PositionedReadable)in).readFully(position, buffer, offset, length);
121  }
122  
123  /**
124   * See {@link #readFully(long, byte[], int, int)}.
125   */
126  @Override
127  public void readFully(long position, byte[] buffer)
128    throws IOException {
129    ((PositionedReadable)in).readFully(position, buffer, 0, buffer.length);
130  }
131  
132  /**
133   * Seek to the given position on an alternate copy of the data.
134   *
135   * @param  targetPos  position to seek to
136   * @return true if a new source is found, false otherwise
137   */
138  @Override
139  public boolean seekToNewSource(long targetPos) throws IOException {
140    return ((Seekable)in).seekToNewSource(targetPos); 
141  }
142  
143  /**
144   * Get a reference to the wrapped input stream. Used by unit tests.
145   *
146   * @return the underlying input stream
147   */
148  @InterfaceAudience.LimitedPrivate({"HDFS"})
149  public InputStream getWrappedStream() {
150    return in;
151  }
152
153  @Override
154  public int read(ByteBuffer buf) throws IOException {
155    if (in instanceof ByteBufferReadable) {
156      return ((ByteBufferReadable)in).read(buf);
157    }
158
159    throw new UnsupportedOperationException("Byte-buffer read unsupported by input stream");
160  }
161
162  @Override
163  public FileDescriptor getFileDescriptor() throws IOException {
164    if (in instanceof HasFileDescriptor) {
165      return ((HasFileDescriptor) in).getFileDescriptor();
166    } else if (in instanceof FileInputStream) {
167      return ((FileInputStream) in).getFD();
168    } else {
169      return null;
170    }
171  }
172
173  @Override
174  public void setReadahead(Long readahead)
175      throws IOException, UnsupportedOperationException {
176    try {
177      ((CanSetReadahead)in).setReadahead(readahead);
178    } catch (ClassCastException e) {
179      throw new UnsupportedOperationException(
180          "this stream does not support setting the readahead " +
181          "caching strategy.");
182    }
183  }
184
185  @Override
186  public void setDropBehind(Boolean dropBehind)
187      throws IOException, UnsupportedOperationException {
188    try {
189      ((CanSetDropBehind)in).setDropBehind(dropBehind);
190    } catch (ClassCastException e) {
191      throw new UnsupportedOperationException("this stream does not " +
192          "support setting the drop-behind caching setting.");
193    }
194  }
195
196  @Override
197  public ByteBuffer read(ByteBufferPool bufferPool, int maxLength,
198      EnumSet<ReadOption> opts) 
199          throws IOException, UnsupportedOperationException {
200    try {
201      return ((HasEnhancedByteBufferAccess)in).read(bufferPool,
202          maxLength, opts);
203    }
204    catch (ClassCastException e) {
205      ByteBuffer buffer = ByteBufferUtil.
206          fallbackRead(this, bufferPool, maxLength);
207      if (buffer != null) {
208        extendedReadBuffers.put(buffer, bufferPool);
209      }
210      return buffer;
211    }
212  }
213
214  private static final EnumSet<ReadOption> EMPTY_READ_OPTIONS_SET =
215      EnumSet.noneOf(ReadOption.class);
216
217  final public ByteBuffer read(ByteBufferPool bufferPool, int maxLength)
218          throws IOException, UnsupportedOperationException {
219    return read(bufferPool, maxLength, EMPTY_READ_OPTIONS_SET);
220  }
221  
222  @Override
223  public void releaseBuffer(ByteBuffer buffer) {
224    try {
225      ((HasEnhancedByteBufferAccess)in).releaseBuffer(buffer);
226    }
227    catch (ClassCastException e) {
228      ByteBufferPool bufferPool = extendedReadBuffers.remove( buffer);
229      if (bufferPool == null) {
230        throw new IllegalArgumentException("tried to release a buffer " +
231            "that was not created by this stream.");
232      }
233      bufferPool.putBuffer(buffer);
234    }
235  }
236
237  @Override
238  public void unbuffer() {
239    try {
240      ((CanUnbuffer)in).unbuffer();
241    } catch (ClassCastException e) {
242      throw new UnsupportedOperationException("this stream does not " +
243          "support unbuffering.");
244    }
245  }
246
247  /**
248   * Specifies the kind of advise to provide for this stream and the file
249   * offsets to which they apply.
250   *
251   * The default implementation does nothing. Sub classes can override this
252   * behavior.
253   *
254   * @param type advise type
255   * @param offset starting file offset
256   * @param count number of bytes starting from the offset
257   */
258  @MapRModified
259  public void adviseFile(FadviseType type, long offset, long count)
260    throws IOException {
261  }
262
263  /**
264   * Returns the file length.
265   *
266   * @return file length
267   */
268  @MapRModified
269  public long getFileLength() throws IOException {
270    throw new UnsupportedOperationException();
271  }
272
273  /**
274   * Returns the file id as string.
275   *
276   * @return file id as string
277   */
278  @MapRModified
279  public String getFidStr() {
280    throw new UnsupportedOperationException();
281  }
282
283  /**
284   * Returns the server IPs in which the file is stored. Each IP is stored in a
285   * long. For e.g., the first 4 bytes can be used to store the IP in
286   * hexadecimal format and the last 4 bytes to store the port number.
287   *
288   * @return array of server IPs in which the file is stored
289   */
290  @MapRModified
291  public long[] getFidServers() {
292    throw new UnsupportedOperationException();
293  }
294
295  /**
296   * Returns the file chunk size.
297   *
298   * @return file chunk size
299   */
300  @MapRModified
301  public long getChunkSize() {
302    throw new UnsupportedOperationException();
303  }
304}