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.nio.file.Files;
29 import java.nio.file.OpenOption;
30 import java.nio.file.Path;
31 import java.util.logging.Level;
32 import java.util.logging.Logger;
33 import javax.annotation.Nullable;
34 import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
35
36 import static okio.Util.checkOffsetAndCount;
37
38 /** Essential APIs for working with Okio. */
39 public final class Okio {
40   static final Logger logger = Logger.getLogger(Okio.class.getName());
41
42   private Okio() {
43   }
44
45   /**
46    * Returns a new source that buffers reads from {@code source}. The returned
47    * source will perform bulk reads into its in-memory buffer. Use this wherever
48    * you read a source to get an ergonomic and efficient access to data.
49    */

50   public static BufferedSource buffer(Source source) {
51     return new RealBufferedSource(source);
52   }
53
54   /**
55    * Returns a new sink that buffers writes to {@code sink}. The returned sink
56    * will batch writes to {@code sink}. Use this wherever you write to a sink to
57    * get an ergonomic and efficient access to data.
58    */

59   public static BufferedSink buffer(Sink sink) {
60     return new RealBufferedSink(sink);
61   }
62
63   /** Returns a sink that writes to {@code out}. */
64   public static Sink sink(OutputStream out) {
65     return sink(out, new Timeout());
66   }
67
68   private static Sink sink(final OutputStream out, final Timeout timeout) {
69     if (out == nullthrow new IllegalArgumentException("out == null");
70     if (timeout == nullthrow new IllegalArgumentException("timeout == null");
71
72     return new Sink() {
73       @Override public void write(Buffer source, long byteCount) throws IOException {
74         checkOffsetAndCount(source.size, 0, byteCount);
75         while (byteCount > 0) {
76           timeout.throwIfReached();
77           Segment head = source.head;
78           int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
79           out.write(head.data, head.pos, toCopy);
80
81           head.pos += toCopy;
82           byteCount -= toCopy;
83           source.size -= toCopy;
84
85           if (head.pos == head.limit) {
86             source.head = head.pop();
87             SegmentPool.recycle(head);
88           }
89         }
90       }
91
92       @Override public void flush() throws IOException {
93         out.flush();
94       }
95
96       @Override public void close() throws IOException {
97         out.close();
98       }
99
100       @Override public Timeout timeout() {
101         return timeout;
102       }
103
104       @Override public String toString() {
105         return "sink(" + out + ")";
106       }
107     };
108   }
109
110   /**
111    * Returns a sink that writes to {@code socket}. Prefer this over {@link
112    * #sink(OutputStream)} because this method honors timeouts. When the socket
113    * write times out, the socket is asynchronously closed by a watchdog thread.
114    */

115   public static Sink sink(Socket socket) throws IOException {
116     if (socket == nullthrow new IllegalArgumentException("socket == null");
117     if (socket.getOutputStream() == nullthrow new IOException("socket's output stream == null");
118     AsyncTimeout timeout = timeout(socket);
119     Sink sink = sink(socket.getOutputStream(), timeout);
120     return timeout.sink(sink);
121   }
122
123   /** Returns a source that reads from {@code in}. */
124   public static Source source(InputStream in) {
125     return source(in, new Timeout());
126   }
127
128   private static Source source(final InputStream in, final Timeout timeout) {
129     if (in == nullthrow new IllegalArgumentException("in == null");
130     if (timeout == nullthrow new IllegalArgumentException("timeout == null");
131
132     return new Source() {
133       @Override public long read(Buffer sink, long byteCount) throws IOException {
134         if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
135         if (byteCount == 0) return 0;
136         try {
137           timeout.throwIfReached();
138           Segment tail = sink.writableSegment(1);
139           int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
140           int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
141           if (bytesRead == -1) return -1;
142           tail.limit += bytesRead;
143           sink.size += bytesRead;
144           return bytesRead;
145         } catch (AssertionError e) {
146           if (isAndroidGetsocknameError(e)) throw new IOException(e);
147           throw e;
148         }
149       }
150
151       @Override public void close() throws IOException {
152         in.close();
153       }
154
155       @Override public Timeout timeout() {
156         return timeout;
157       }
158
159       @Override public String toString() {
160         return "source(" + in + ")";
161       }
162     };
163   }
164
165   /** Returns a source that reads from {@code file}. */
166   public static Source source(File file) throws FileNotFoundException {
167     if (file == nullthrow new IllegalArgumentException("file == null");
168     return source(new FileInputStream(file));
169   }
170
171   /** Returns a source that reads from {@code path}. */
172   @IgnoreJRERequirement // Should only be invoked on Java 7+.
173   public static Source source(Path path, OpenOption... options) throws IOException {
174     if (path == nullthrow new IllegalArgumentException("path == null");
175     return source(Files.newInputStream(path, options));
176   }
177
178   /** Returns a sink that writes to {@code file}. */
179   public static Sink sink(File file) throws FileNotFoundException {
180     if (file == nullthrow new IllegalArgumentException("file == null");
181     return sink(new FileOutputStream(file));
182   }
183
184   /** Returns a sink that appends to {@code file}. */
185   public static Sink appendingSink(File file) throws FileNotFoundException {
186     if (file == nullthrow new IllegalArgumentException("file == null");
187     return sink(new FileOutputStream(file, true));
188   }
189
190   /** Returns a sink that writes to {@code path}. */
191   @IgnoreJRERequirement // Should only be invoked on Java 7+.
192   public static Sink sink(Path path, OpenOption... options) throws IOException {
193     if (path == nullthrow new IllegalArgumentException("path == null");
194     return sink(Files.newOutputStream(path, options));
195   }
196
197   /** Returns a sink that writes nowhere. */
198   public static Sink blackhole() {
199     return new Sink() {
200       @Override public void write(Buffer source, long byteCount) throws IOException {
201         source.skip(byteCount);
202       }
203
204       @Override public void flush() throws IOException {
205       }
206
207       @Override public Timeout timeout() {
208         return Timeout.NONE;
209       }
210
211       @Override public void close() throws IOException {
212       }
213     };
214   }
215
216   /**
217    * Returns a source that reads from {@code socket}. Prefer this over {@link
218    * #source(InputStream)} because this method honors timeouts. When the socket
219    * read times out, the socket is asynchronously closed by a watchdog thread.
220    */

221   public static Source source(Socket socket) throws IOException {
222     if (socket == nullthrow new IllegalArgumentException("socket == null");
223     if (socket.getInputStream() == nullthrow new IOException("socket's input stream == null");
224     AsyncTimeout timeout = timeout(socket);
225     Source source = source(socket.getInputStream(), timeout);
226     return timeout.source(source);
227   }
228
229   private static AsyncTimeout timeout(final Socket socket) {
230     return new AsyncTimeout() {
231       @Override protected IOException newTimeoutException(@Nullable IOException cause) {
232         InterruptedIOException ioe = new SocketTimeoutException("timeout");
233         if (cause != null) {
234           ioe.initCause(cause);
235         }
236         return ioe;
237       }
238
239       @Override protected void timedOut() {
240         try {
241           socket.close();
242         } catch (Exception e) {
243           logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
244         } catch (AssertionError e) {
245           if (isAndroidGetsocknameError(e)) {
246             // Catch this exception due to a Firmware issue up to android 4.2.2
247             // https://code.google.com/p/android/issues/detail?id=54072
248             logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
249           } else {
250             throw e;
251           }
252         }
253       }
254     };
255   }
256
257   /**
258    * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2.
259    * https://code.google.com/p/android/issues/detail?id=54072
260    */

261   static boolean isAndroidGetsocknameError(AssertionError e) {
262     return e.getCause() != null && e.getMessage() != null
263         && e.getMessage().contains("getsockname failed");
264   }
265 }
266