Home | History | Annotate | Download | only in okio
      1 /*
      2  * Copyright (C) 2014 Square, Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package okio;
     17 
     18 import java.io.File;
     19 import java.io.FileInputStream;
     20 import java.io.FileNotFoundException;
     21 import java.io.FileOutputStream;
     22 import java.io.IOException;
     23 import java.io.InputStream;
     24 import java.io.InterruptedIOException;
     25 import java.io.OutputStream;
     26 import java.net.Socket;
     27 import java.net.SocketTimeoutException;
     28 import java.util.logging.Level;
     29 import java.util.logging.Logger;
     30 
     31 import static okio.Util.checkOffsetAndCount;
     32 
     33 /** Essential APIs for working with Okio. */
     34 public final class Okio {
     35   private static final Logger logger = Logger.getLogger(Okio.class.getName());
     36 
     37   private Okio() {
     38   }
     39 
     40   /**
     41    * Returns a new source that buffers reads from {@code source}. The returned
     42    * source will perform bulk reads into its in-memory buffer. Use this wherever
     43    * you read a source to get an ergonomic and efficient access to data.
     44    */
     45   public static BufferedSource buffer(Source source) {
     46     if (source == null) throw new IllegalArgumentException("source == null");
     47     return new RealBufferedSource(source);
     48   }
     49 
     50   /**
     51    * Returns a new sink that buffers writes to {@code sink}. The returned sink
     52    * will batch writes to {@code sink}. Use this wherever you write to a sink to
     53    * get an ergonomic and efficient access to data.
     54    */
     55   public static BufferedSink buffer(Sink sink) {
     56     if (sink == null) throw new IllegalArgumentException("sink == null");
     57     return new RealBufferedSink(sink);
     58   }
     59 
     60   /** Returns a sink that writes to {@code out}. */
     61   public static Sink sink(OutputStream out) {
     62     return sink(out, new Timeout());
     63   }
     64 
     65   private static Sink sink(final OutputStream out, final Timeout timeout) {
     66     if (out == null) throw new IllegalArgumentException("out == null");
     67     if (timeout == null) throw new IllegalArgumentException("timeout == null");
     68 
     69     return new Sink() {
     70       @Override public void write(Buffer source, long byteCount) throws IOException {
     71         checkOffsetAndCount(source.size, 0, byteCount);
     72         while (byteCount > 0) {
     73           timeout.throwIfReached();
     74           Segment head = source.head;
     75           int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
     76           out.write(head.data, head.pos, toCopy);
     77 
     78           head.pos += toCopy;
     79           byteCount -= toCopy;
     80           source.size -= toCopy;
     81 
     82           if (head.pos == head.limit) {
     83             source.head = head.pop();
     84             SegmentPool.recycle(head);
     85           }
     86         }
     87       }
     88 
     89       @Override public void flush() throws IOException {
     90         out.flush();
     91       }
     92 
     93       @Override public void close() throws IOException {
     94         out.close();
     95       }
     96 
     97       @Override public Timeout timeout() {
     98         return timeout;
     99       }
    100 
    101       @Override public String toString() {
    102         return "sink(" + out + ")";
    103       }
    104     };
    105   }
    106 
    107   /**
    108    * Returns a sink that writes to {@code socket}. Prefer this over {@link
    109    * #sink(OutputStream)} because this method honors timeouts. When the socket
    110    * write times out, the socket is asynchronously closed by a watchdog thread.
    111    */
    112   public static Sink sink(Socket socket) throws IOException {
    113     if (socket == null) throw new IllegalArgumentException("socket == null");
    114     AsyncTimeout timeout = timeout(socket);
    115     Sink sink = sink(socket.getOutputStream(), timeout);
    116     return timeout.sink(sink);
    117   }
    118 
    119   /** Returns a source that reads from {@code in}. */
    120   public static Source source(InputStream in) {
    121     return source(in, new Timeout());
    122   }
    123 
    124   private static Source source(final InputStream in, final Timeout timeout) {
    125     if (in == null) throw new IllegalArgumentException("in == null");
    126     if (timeout == null) throw new IllegalArgumentException("timeout == null");
    127 
    128     return new Source() {
    129       @Override public long read(Buffer sink, long byteCount) throws IOException {
    130         if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
    131         if (byteCount == 0) return 0;
    132         try {
    133           timeout.throwIfReached();
    134           Segment tail = sink.writableSegment(1);
    135           int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
    136           int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
    137           if (bytesRead == -1) return -1;
    138           tail.limit += bytesRead;
    139           sink.size += bytesRead;
    140           return bytesRead;
    141         } catch (AssertionError e) {
    142           if (isAndroidGetsocknameError(e)) throw new IOException(e);
    143           throw e;
    144         }
    145       }
    146 
    147       @Override public void close() throws IOException {
    148         in.close();
    149       }
    150 
    151       @Override public Timeout timeout() {
    152         return timeout;
    153       }
    154 
    155       @Override public String toString() {
    156         return "source(" + in + ")";
    157       }
    158     };
    159   }
    160 
    161   /** Returns a source that reads from {@code file}. */
    162   public static Source source(File file) throws FileNotFoundException {
    163     if (file == null) throw new IllegalArgumentException("file == null");
    164     return source(new FileInputStream(file));
    165   }
    166 
    167   // ANDROID-BEGIN
    168   //  /** Returns a source that reads from {@code path}. */
    169   //  @IgnoreJRERequirement // Should only be invoked on Java 7+.
    170   //  public static Source source(Path path, OpenOption... options) throws IOException {
    171   //    if (path == null) throw new IllegalArgumentException("path == null");
    172   //    return source(Files.newInputStream(path, options));
    173   //  }
    174   // ANDROID-END
    175 
    176   /** Returns a sink that writes to {@code file}. */
    177   public static Sink sink(File file) throws FileNotFoundException {
    178     if (file == null) throw new IllegalArgumentException("file == null");
    179     return sink(new FileOutputStream(file));
    180   }
    181 
    182   /** Returns a sink that appends to {@code file}. */
    183   public static Sink appendingSink(File file) throws FileNotFoundException {
    184     if (file == null) throw new IllegalArgumentException("file == null");
    185     return sink(new FileOutputStream(file, true));
    186   }
    187 
    188   // ANDROID-BEGIN
    189   //  /** Returns a sink that writes to {@code path}. */
    190   //  @IgnoreJRERequirement // Should only be invoked on Java 7+.
    191   //  public static Sink sink(Path path, OpenOption... options) throws IOException {
    192   //    if (path == null) throw new IllegalArgumentException("path == null");
    193   //    return sink(Files.newOutputStream(path, options));
    194   //  }
    195   // ANDROID-END
    196 
    197   /**
    198    * Returns a source that reads from {@code socket}. Prefer this over {@link
    199    * #source(InputStream)} because this method honors timeouts. When the socket
    200    * read times out, the socket is asynchronously closed by a watchdog thread.
    201    */
    202   public static Source source(Socket socket) throws IOException {
    203     if (socket == null) throw new IllegalArgumentException("socket == null");
    204     AsyncTimeout timeout = timeout(socket);
    205     Source source = source(socket.getInputStream(), timeout);
    206     return timeout.source(source);
    207   }
    208 
    209   private static AsyncTimeout timeout(final Socket socket) {
    210     return new AsyncTimeout() {
    211       @Override protected IOException newTimeoutException(IOException cause) {
    212         InterruptedIOException ioe = new SocketTimeoutException("timeout");
    213         if (cause != null) {
    214           ioe.initCause(cause);
    215         }
    216         return ioe;
    217       }
    218 
    219       @Override protected void timedOut() {
    220         try {
    221           socket.close();
    222         } catch (Exception e) {
    223           logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
    224         } catch (AssertionError e) {
    225           if (isAndroidGetsocknameError(e)) {
    226             // Catch this exception due to a Firmware issue up to android 4.2.2
    227             // https://code.google.com/p/android/issues/detail?id=54072
    228             logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
    229           } else {
    230             throw e;
    231           }
    232         }
    233       }
    234     };
    235   }
    236 
    237   /**
    238    * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2.
    239    * https://code.google.com/p/android/issues/detail?id=54072
    240    */
    241   private static boolean isAndroidGetsocknameError(AssertionError e) {
    242     return e.getCause() != null && e.getMessage() != null
    243         && e.getMessage().contains("getsockname failed");
    244   }
    245 }
    246