All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.facebook.presto.jdbc.internal.okio.ByteString Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 Square Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.facebook.presto.jdbc.internal.okio;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import com.facebook.presto.jdbc.internal.javax.annotation.Nullable;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import static com.facebook.presto.jdbc.internal.okio.Util.arrayRangeEquals;
import static com.facebook.presto.jdbc.internal.okio.Util.checkOffsetAndCount;

/**
 * An immutable sequence of bytes.
 *
 * 

Byte strings compare lexicographically as a sequence of unsigned bytes. That * is, the byte string {@code ff} sorts after {@code 00}. This is counter to the sort order of the * corresponding bytes, where {@code -1} sorts before {@code 0}. * *

Full disclosure: this class provides untrusted input and output streams with * raw access to the underlying byte array. A hostile stream implementation could keep a reference * to the mutable byte string, violating the immutable guarantee of this class. For this reason a * byte string's immutability guarantee cannot be relied upon for security in applets and other * environments that run both trusted and untrusted code in the same process. */ public class ByteString implements Serializable, Comparable { static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private static final long serialVersionUID = 1L; /** A singleton empty {@code ByteString}. */ public static final ByteString EMPTY = ByteString.of(); final byte[] data; transient int hashCode; // Lazily computed; 0 if unknown. transient String utf8; // Lazily computed. ByteString(byte[] data) { this.data = data; // Trusted internal constructor doesn't clone data. } /** * Returns a new byte string containing a clone of the bytes of {@code data}. */ public static ByteString of(byte... data) { if (data == null) throw new IllegalArgumentException("data == null"); return new ByteString(data.clone()); } /** * Returns a new byte string containing a copy of {@code byteCount} bytes of {@code data} starting * at {@code offset}. */ public static ByteString of(byte[] data, int offset, int byteCount) { if (data == null) throw new IllegalArgumentException("data == null"); checkOffsetAndCount(data.length, offset, byteCount); byte[] copy = new byte[byteCount]; System.arraycopy(data, offset, copy, 0, byteCount); return new ByteString(copy); } public static ByteString of(ByteBuffer data) { if (data == null) throw new IllegalArgumentException("data == null"); byte[] copy = new byte[data.remaining()]; data.get(copy); return new ByteString(copy); } /** Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. */ public static ByteString encodeUtf8(String s) { if (s == null) throw new IllegalArgumentException("s == null"); ByteString byteString = new ByteString(s.getBytes(Util.UTF_8)); byteString.utf8 = s; return byteString; } /** Returns a new byte string containing the {@code charset}-encoded bytes of {@code s}. */ public static ByteString encodeString(String s, Charset charset) { if (s == null) throw new IllegalArgumentException("s == null"); if (charset == null) throw new IllegalArgumentException("charset == null"); return new ByteString(s.getBytes(charset)); } /** Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. */ public String utf8() { String result = utf8; // We don't care if we double-allocate in racy code. return result != null ? result : (utf8 = new String(data, Util.UTF_8)); } /** Constructs a new {@code String} by decoding the bytes using {@code charset}. */ public String string(Charset charset) { if (charset == null) throw new IllegalArgumentException("charset == null"); return new String(data, charset); } /** * Returns this byte string encoded as Base64. In violation of the * RFC, the returned string does not wrap lines at 76 columns. */ public String base64() { return Base64.encode(data); } /** Returns the 128-bit MD5 hash of this byte string. */ public ByteString md5() { return digest("MD5"); } /** Returns the 160-bit SHA-1 hash of this byte string. */ public ByteString sha1() { return digest("SHA-1"); } /** Returns the 256-bit SHA-256 hash of this byte string. */ public ByteString sha256() { return digest("SHA-256"); } /** Returns the 512-bit SHA-512 hash of this byte string. */ public ByteString sha512() { return digest("SHA-512"); } private ByteString digest(String algorithm) { try { return ByteString.of(MessageDigest.getInstance(algorithm).digest(data)); } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } } /** Returns the 160-bit SHA-1 HMAC of this byte string. */ public ByteString hmacSha1(ByteString key) { return hmac("HmacSHA1", key); } /** Returns the 256-bit SHA-256 HMAC of this byte string. */ public ByteString hmacSha256(ByteString key) { return hmac("HmacSHA256", key); } /** Returns the 512-bit SHA-512 HMAC of this byte string. */ public ByteString hmacSha512(ByteString key) { return hmac("HmacSHA512", key); } private ByteString hmac(String algorithm, ByteString key) { try { Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(key.toByteArray(), algorithm)); return ByteString.of(mac.doFinal(data)); } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } catch (InvalidKeyException e) { throw new IllegalArgumentException(e); } } /** * Returns this byte string encoded as URL-safe * Base64. */ public String base64Url() { return Base64.encodeUrl(data); } /** * Decodes the Base64-encoded bytes and returns their value as a byte string. * Returns null if {@code base64} is not a Base64-encoded sequence of bytes. */ public static @Nullable ByteString decodeBase64(String base64) { if (base64 == null) throw new IllegalArgumentException("base64 == null"); byte[] decoded = Base64.decode(base64); return decoded != null ? new ByteString(decoded) : null; } /** Returns this byte string encoded in hexadecimal. */ public String hex() { char[] result = new char[data.length * 2]; int c = 0; for (byte b : data) { result[c++] = HEX_DIGITS[(b >> 4) & 0xf]; result[c++] = HEX_DIGITS[b & 0xf]; } return new String(result); } /** Decodes the hex-encoded bytes and returns their value a byte string. */ public static ByteString decodeHex(String hex) { if (hex == null) throw new IllegalArgumentException("hex == null"); if (hex.length() % 2 != 0) throw new IllegalArgumentException("Unexpected hex string: " + hex); byte[] result = new byte[hex.length() / 2]; for (int i = 0; i < result.length; i++) { int d1 = decodeHexDigit(hex.charAt(i * 2)) << 4; int d2 = decodeHexDigit(hex.charAt(i * 2 + 1)); result[i] = (byte) (d1 + d2); } return of(result); } private static int decodeHexDigit(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; throw new IllegalArgumentException("Unexpected hex digit: " + c); } /** * Reads {@code count} bytes from {@code in} and returns the result. * * @throws java.io.EOFException if {@code in} has fewer than {@code count} * bytes to read. */ public static ByteString read(InputStream in, int byteCount) throws IOException { if (in == null) throw new IllegalArgumentException("in == null"); if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); byte[] result = new byte[byteCount]; for (int offset = 0, read; offset < byteCount; offset += read) { read = in.read(result, offset, byteCount - offset); if (read == -1) throw new EOFException(); } return new ByteString(result); } /** * Returns a byte string equal to this byte string, but with the bytes 'A' * through 'Z' replaced with the corresponding byte in 'a' through 'z'. * Returns this byte string if it contains no bytes in 'A' through 'Z'. */ public ByteString toAsciiLowercase() { // Search for an uppercase character. If we don't find one, return this. for (int i = 0; i < data.length; i++) { byte c = data[i]; if (c < 'A' || c > 'Z') continue; // If we reach this point, this string is not not lowercase. Create and // return a new byte string. byte[] lowercase = data.clone(); lowercase[i++] = (byte) (c - ('A' - 'a')); for (; i < lowercase.length; i++) { c = lowercase[i]; if (c < 'A' || c > 'Z') continue; lowercase[i] = (byte) (c - ('A' - 'a')); } return new ByteString(lowercase); } return this; } /** * Returns a byte string equal to this byte string, but with the bytes 'a' * through 'z' replaced with the corresponding byte in 'A' through 'Z'. * Returns this byte string if it contains no bytes in 'a' through 'z'. */ public ByteString toAsciiUppercase() { // Search for an lowercase character. If we don't find one, return this. for (int i = 0; i < data.length; i++) { byte c = data[i]; if (c < 'a' || c > 'z') continue; // If we reach this point, this string is not not uppercase. Create and // return a new byte string. byte[] lowercase = data.clone(); lowercase[i++] = (byte) (c - ('a' - 'A')); for (; i < lowercase.length; i++) { c = lowercase[i]; if (c < 'a' || c > 'z') continue; lowercase[i] = (byte) (c - ('a' - 'A')); } return new ByteString(lowercase); } return this; } /** * Returns a byte string that is a substring of this byte string, beginning at the specified * index until the end of this string. Returns this byte string if {@code beginIndex} is 0. */ public ByteString substring(int beginIndex) { return substring(beginIndex, data.length); } /** * Returns a byte string that is a substring of this byte string, beginning at the specified * {@code beginIndex} and ends at the specified {@code endIndex}. Returns this byte string if * {@code beginIndex} is 0 and {@code endIndex} is the length of this byte string. */ public ByteString substring(int beginIndex, int endIndex) { if (beginIndex < 0) throw new IllegalArgumentException("beginIndex < 0"); if (endIndex > data.length) { throw new IllegalArgumentException("endIndex > length(" + data.length + ")"); } int subLen = endIndex - beginIndex; if (subLen < 0) throw new IllegalArgumentException("endIndex < beginIndex"); if ((beginIndex == 0) && (endIndex == data.length)) { return this; } byte[] copy = new byte[subLen]; System.arraycopy(data, beginIndex, copy, 0, subLen); return new ByteString(copy); } /** Returns the byte at {@code pos}. */ public byte getByte(int pos) { return data[pos]; } /** * Returns the number of bytes in this ByteString. */ public int size() { return data.length; } /** * Returns a byte array containing a copy of the bytes in this {@code ByteString}. */ public byte[] toByteArray() { return data.clone(); } /** Returns the bytes of this string without a defensive copy. Do not mutate! */ byte[] internalArray() { return data; } /** * Returns a {@code ByteBuffer} view of the bytes in this {@code ByteString}. */ public ByteBuffer asByteBuffer() { return ByteBuffer.wrap(data).asReadOnlyBuffer(); } /** Writes the contents of this byte string to {@code out}. */ public void write(OutputStream out) throws IOException { if (out == null) throw new IllegalArgumentException("out == null"); out.write(data); } /** Writes the contents of this byte string to {@code buffer}. */ void write(Buffer buffer) { buffer.write(data, 0, data.length); } /** * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is * out of bounds. */ public boolean rangeEquals(int offset, ByteString other, int otherOffset, int byteCount) { return other.rangeEquals(otherOffset, this.data, offset, byteCount); } /** * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is * out of bounds. */ public boolean rangeEquals(int offset, byte[] other, int otherOffset, int byteCount) { return offset >= 0 && offset <= data.length - byteCount && otherOffset >= 0 && otherOffset <= other.length - byteCount && arrayRangeEquals(data, offset, other, otherOffset, byteCount); } public final boolean startsWith(ByteString prefix) { return rangeEquals(0, prefix, 0, prefix.size()); } public final boolean startsWith(byte[] prefix) { return rangeEquals(0, prefix, 0, prefix.length); } public final boolean endsWith(ByteString suffix) { return rangeEquals(size() - suffix.size(), suffix, 0, suffix.size()); } public final boolean endsWith(byte[] suffix) { return rangeEquals(size() - suffix.length, suffix, 0, suffix.length); } public final int indexOf(ByteString other) { return indexOf(other.internalArray(), 0); } public final int indexOf(ByteString other, int fromIndex) { return indexOf(other.internalArray(), fromIndex); } public final int indexOf(byte[] other) { return indexOf(other, 0); } public int indexOf(byte[] other, int fromIndex) { fromIndex = Math.max(fromIndex, 0); for (int i = fromIndex, limit = data.length - other.length; i <= limit; i++) { if (arrayRangeEquals(data, i, other, 0, other.length)) { return i; } } return -1; } public final int lastIndexOf(ByteString other) { return lastIndexOf(other.internalArray(), size()); } public final int lastIndexOf(ByteString other, int fromIndex) { return lastIndexOf(other.internalArray(), fromIndex); } public final int lastIndexOf(byte[] other) { return lastIndexOf(other, size()); } public int lastIndexOf(byte[] other, int fromIndex) { fromIndex = Math.min(fromIndex, data.length - other.length); for (int i = fromIndex; i >= 0; i--) { if (arrayRangeEquals(data, i, other, 0, other.length)) { return i; } } return -1; } @Override public boolean equals(Object o) { if (o == this) return true; return o instanceof ByteString && ((ByteString) o).size() == data.length && ((ByteString) o).rangeEquals(0, data, 0, data.length); } @Override public int hashCode() { int result = hashCode; return result != 0 ? result : (hashCode = Arrays.hashCode(data)); } @Override public int compareTo(ByteString byteString) { int sizeA = size(); int sizeB = byteString.size(); for (int i = 0, size = Math.min(sizeA, sizeB); i < size; i++) { int byteA = getByte(i) & 0xff; int byteB = byteString.getByte(i) & 0xff; if (byteA == byteB) continue; return byteA < byteB ? -1 : 1; } if (sizeA == sizeB) return 0; return sizeA < sizeB ? -1 : 1; } /** * Returns a human-readable string that describes the contents of this byte string. Typically this * is a string like {@code [text=Hello]} or {@code [hex=0000ffff]}. */ @Override public String toString() { if (data.length == 0) { return "[size=0]"; } String text = utf8(); int i = codePointIndexToCharIndex(text, 64); if (i == -1) { return data.length <= 64 ? "[hex=" + hex() + "]" : "[size=" + data.length + " hex=" + substring(0, 64).hex() + "…]"; } String safeText = text.substring(0, i) .replace("\\", "\\\\") .replace("\n", "\\n") .replace("\r", "\\r"); return i < text.length() ? "[size=" + data.length + " text=" + safeText + "…]" : "[text=" + safeText + "]"; } static int codePointIndexToCharIndex(String s, int codePointCount) { for (int i = 0, j = 0, length = s.length(), c; i < length; i += Character.charCount(c)) { if (j == codePointCount) { return i; } c = s.codePointAt(i); if ((Character.isISOControl(c) && c != '\n' && c != '\r') || c == Buffer.REPLACEMENT_CHARACTER) { return -1; } j++; } return s.length(); } private void readObject(ObjectInputStream in) throws IOException { int dataLength = in.readInt(); ByteString byteString = ByteString.read(in, dataLength); try { Field field = ByteString.class.getDeclaredField("data"); field.setAccessible(true); field.set(this, byteString.data); } catch (NoSuchFieldException e) { throw new AssertionError(); } catch (IllegalAccessException e) { throw new AssertionError(); } } private void writeObject(ObjectOutputStream out) throws IOException { out.writeInt(data.length); out.write(data); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy