1 /* 2 * Copyright (C) 2012 The Android Open Source Project 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 17 package com.squareup.okhttp.internal; 18 19 import com.squareup.okhttp.HttpUrl; 20 import java.io.Closeable; 21 import java.io.IOException; 22 import java.io.InterruptedIOException; 23 import java.io.UnsupportedEncodingException; 24 import java.lang.reflect.Array; 25 import java.net.ServerSocket; 26 import java.net.Socket; 27 import java.nio.charset.Charset; 28 import java.security.MessageDigest; 29 import java.security.NoSuchAlgorithmException; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.Collections; 33 import java.util.LinkedHashMap; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.concurrent.ThreadFactory; 37 import java.util.concurrent.TimeUnit; 38 import okio.Buffer; 39 import okio.ByteString; 40 import okio.Source; 41 42 /** Junk drawer of utility methods. */ 43 public final class Util { 44 public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 45 public static final String[] EMPTY_STRING_ARRAY = new String[0]; 46 47 /** A cheap and type-safe constant for the UTF-8 Charset. */ 48 public static final Charset UTF_8 = Charset.forName("UTF-8"); 49 Util()50 private Util() { 51 } 52 checkOffsetAndCount(long arrayLength, long offset, long count)53 public static void checkOffsetAndCount(long arrayLength, long offset, long count) { 54 if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { 55 throw new ArrayIndexOutOfBoundsException(); 56 } 57 } 58 59 /** Returns true if two possibly-null objects are equal. */ equal(Object a, Object b)60 public static boolean equal(Object a, Object b) { 61 return a == b || (a != null && a.equals(b)); 62 } 63 64 /** 65 * Closes {@code closeable}, ignoring any checked exceptions. Does nothing 66 * if {@code closeable} is null. 67 */ closeQuietly(Closeable closeable)68 public static void closeQuietly(Closeable closeable) { 69 if (closeable != null) { 70 try { 71 closeable.close(); 72 } catch (RuntimeException rethrown) { 73 throw rethrown; 74 } catch (Exception ignored) { 75 } 76 } 77 } 78 79 /** 80 * Closes {@code socket}, ignoring any checked exceptions. Does nothing if 81 * {@code socket} is null. 82 */ closeQuietly(Socket socket)83 public static void closeQuietly(Socket socket) { 84 if (socket != null) { 85 try { 86 socket.close(); 87 } catch (AssertionError e) { 88 if (!isAndroidGetsocknameError(e)) throw e; 89 } catch (RuntimeException rethrown) { 90 throw rethrown; 91 } catch (Exception ignored) { 92 } 93 } 94 } 95 96 /** 97 * Closes {@code serverSocket}, ignoring any checked exceptions. Does nothing if 98 * {@code serverSocket} is null. 99 */ closeQuietly(ServerSocket serverSocket)100 public static void closeQuietly(ServerSocket serverSocket) { 101 if (serverSocket != null) { 102 try { 103 serverSocket.close(); 104 } catch (RuntimeException rethrown) { 105 throw rethrown; 106 } catch (Exception ignored) { 107 } 108 } 109 } 110 111 /** 112 * Closes {@code a} and {@code b}. If either close fails, this completes 113 * the other close and rethrows the first encountered exception. 114 */ closeAll(Closeable a, Closeable b)115 public static void closeAll(Closeable a, Closeable b) throws IOException { 116 Throwable thrown = null; 117 try { 118 a.close(); 119 } catch (Throwable e) { 120 thrown = e; 121 } 122 try { 123 b.close(); 124 } catch (Throwable e) { 125 if (thrown == null) thrown = e; 126 } 127 if (thrown == null) return; 128 if (thrown instanceof IOException) throw (IOException) thrown; 129 if (thrown instanceof RuntimeException) throw (RuntimeException) thrown; 130 if (thrown instanceof Error) throw (Error) thrown; 131 throw new AssertionError(thrown); 132 } 133 134 /** 135 * Attempts to exhaust {@code source}, returning true if successful. This is useful when reading 136 * a complete source is helpful, such as when doing so completes a cache body or frees a socket 137 * connection for reuse. 138 */ discard(Source source, int timeout, TimeUnit timeUnit)139 public static boolean discard(Source source, int timeout, TimeUnit timeUnit) { 140 try { 141 return skipAll(source, timeout, timeUnit); 142 } catch (IOException e) { 143 return false; 144 } 145 } 146 147 /** 148 * Reads until {@code in} is exhausted or the deadline has been reached. This is careful to not 149 * extend the deadline if one exists already. 150 */ skipAll(Source source, int duration, TimeUnit timeUnit)151 public static boolean skipAll(Source source, int duration, TimeUnit timeUnit) throws IOException { 152 long now = System.nanoTime(); 153 long originalDuration = source.timeout().hasDeadline() 154 ? source.timeout().deadlineNanoTime() - now 155 : Long.MAX_VALUE; 156 source.timeout().deadlineNanoTime(now + Math.min(originalDuration, timeUnit.toNanos(duration))); 157 try { 158 Buffer skipBuffer = new Buffer(); 159 while (source.read(skipBuffer, 2048) != -1) { 160 skipBuffer.clear(); 161 } 162 return true; // Success! The source has been exhausted. 163 } catch (InterruptedIOException e) { 164 return false; // We ran out of time before exhausting the source. 165 } finally { 166 if (originalDuration == Long.MAX_VALUE) { 167 source.timeout().clearDeadline(); 168 } else { 169 source.timeout().deadlineNanoTime(now + originalDuration); 170 } 171 } 172 } 173 174 /** Returns a 32 character string containing an MD5 hash of {@code s}. */ md5Hex(String s)175 public static String md5Hex(String s) { 176 try { 177 MessageDigest messageDigest = MessageDigest.getInstance("MD5"); 178 byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8")); 179 return ByteString.of(md5bytes).hex(); 180 } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { 181 throw new AssertionError(e); 182 } 183 } 184 185 /** Returns a Base 64-encoded string containing a SHA-1 hash of {@code s}. */ shaBase64(String s)186 public static String shaBase64(String s) { 187 try { 188 MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); 189 byte[] sha1Bytes = messageDigest.digest(s.getBytes("UTF-8")); 190 return ByteString.of(sha1Bytes).base64(); 191 } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { 192 throw new AssertionError(e); 193 } 194 } 195 196 /** Returns a SHA-1 hash of {@code s}. */ sha1(ByteString s)197 public static ByteString sha1(ByteString s) { 198 try { 199 MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); 200 byte[] sha1Bytes = messageDigest.digest(s.toByteArray()); 201 return ByteString.of(sha1Bytes); 202 } catch (NoSuchAlgorithmException e) { 203 throw new AssertionError(e); 204 } 205 } 206 207 /** Returns an immutable copy of {@code list}. */ immutableList(List<T> list)208 public static <T> List<T> immutableList(List<T> list) { 209 return Collections.unmodifiableList(new ArrayList<>(list)); 210 } 211 212 /** Returns an immutable list containing {@code elements}. */ immutableList(T... elements)213 public static <T> List<T> immutableList(T... elements) { 214 return Collections.unmodifiableList(Arrays.asList(elements.clone())); 215 } 216 217 /** Returns an immutable copy of {@code map}. */ immutableMap(Map<K, V> map)218 public static <K, V> Map<K, V> immutableMap(Map<K, V> map) { 219 return Collections.unmodifiableMap(new LinkedHashMap<>(map)); 220 } 221 threadFactory(final String name, final boolean daemon)222 public static ThreadFactory threadFactory(final String name, final boolean daemon) { 223 return new ThreadFactory() { 224 @Override public Thread newThread(Runnable runnable) { 225 Thread result = new Thread(runnable, name); 226 result.setDaemon(daemon); 227 return result; 228 } 229 }; 230 } 231 232 /** 233 * Returns an array containing containing only elements found in {@code first} and also in 234 * {@code second}. The returned elements are in the same order as in {@code first}. 235 */ 236 @SuppressWarnings("unchecked") 237 public static <T> T[] intersect(Class<T> arrayType, T[] first, T[] second) { 238 List<T> result = intersect(first, second); 239 return result.toArray((T[]) Array.newInstance(arrayType, result.size())); 240 } 241 242 /** 243 * Returns a list containing containing only elements found in {@code first} and also in 244 * {@code second}. The returned elements are in the same order as in {@code first}. 245 */ 246 private static <T> List<T> intersect(T[] first, T[] second) { 247 List<T> result = new ArrayList<>(); 248 for (T a : first) { 249 for (T b : second) { 250 if (a.equals(b)) { 251 result.add(b); 252 break; 253 } 254 } 255 } 256 return result; 257 } 258 259 public static String hostHeader(HttpUrl url, boolean includeDefaultPort) { 260 String host = url.host().contains(":") 261 ? "[" + url.host() + "]" 262 : url.host(); 263 return includeDefaultPort || url.port() != HttpUrl.defaultPort(url.scheme()) 264 ? host + ":" + url.port() 265 : host; 266 } 267 268 /** Returns {@code s} with control characters and non-ASCII characters replaced with '?'. */ 269 public static String toHumanReadableAscii(String s) { 270 for (int i = 0, length = s.length(), c; i < length; i += Character.charCount(c)) { 271 c = s.codePointAt(i); 272 if (c > '\u001f' && c < '\u007f') continue; 273 274 Buffer buffer = new Buffer(); 275 buffer.writeUtf8(s, 0, i); 276 for (int j = i; j < length; j += Character.charCount(c)) { 277 c = s.codePointAt(j); 278 buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?'); 279 } 280 return buffer.readUtf8(); 281 } 282 return s; 283 } 284 285 /** 286 * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2. 287 * https://code.google.com/p/android/issues/detail?id=54072 288 */ 289 public static boolean isAndroidGetsocknameError(AssertionError e) { 290 return e.getCause() != null && e.getMessage() != null 291 && e.getMessage().contains("getsockname failed"); 292 } 293 294 public static boolean contains(String[] array, String value) { 295 return Arrays.asList(array).contains(value); 296 } 297 298 public static String[] concat(String[] array, String value) { 299 String[] result = new String[array.length + 1]; 300 System.arraycopy(array, 0, result, 0, array.length); 301 result[result.length - 1] = value; 302 return result; 303 } 304 } 305