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