| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you 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 org.apache.calcite.avatica.util; |
| |
| import com.fasterxml.jackson.annotation.JsonValue; |
| |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.util.Arrays; |
| |
| /** |
| * Collection of bytes. |
| * |
| * <p>ByteString is to bytes what {@link String} is to chars: It is immutable, |
| * implements equality ({@link #hashCode} and {@link #equals}), |
| * comparison ({@link #compareTo}) and |
| * {@link Serializable serialization} correctly.</p> |
| */ |
| public class ByteString implements Comparable<ByteString>, Serializable { |
| private final byte[] bytes; |
| |
| /** An empty byte string. */ |
| public static final ByteString EMPTY = new ByteString(new byte[0], false); |
| |
| private static final char[] DIGITS = { |
| '0', '1', '2', '3', '4', '5', '6', '7', |
| '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' |
| }; |
| |
| /** |
| * Creates a ByteString. |
| * |
| * @param bytes Bytes |
| */ |
| public ByteString(byte[] bytes) { |
| this(bytes.clone(), false); |
| } |
| |
| // private constructor that does not copy |
| private ByteString(byte[] bytes, boolean dummy) { |
| this.bytes = bytes; |
| } |
| |
| @Override public int hashCode() { |
| return Arrays.hashCode(bytes); |
| } |
| |
| @Override public boolean equals(Object obj) { |
| return this == obj |
| || obj instanceof ByteString |
| && Arrays.equals(bytes, ((ByteString) obj).bytes); |
| } |
| |
| public int compareTo(ByteString that) { |
| final byte[] v1 = bytes; |
| final byte[] v2 = that.bytes; |
| final int n = Math.min(v1.length, v2.length); |
| for (int i = 0; i < n; i++) { |
| int c1 = v1[i] & 0xff; |
| int c2 = v2[i] & 0xff; |
| if (c1 != c2) { |
| return c1 - c2; |
| } |
| } |
| return v1.length - v2.length; |
| } |
| |
| /** |
| * Returns this byte string in hexadecimal format. |
| * |
| * @return Hexadecimal string |
| */ |
| @Override public String toString() { |
| return toString(16); |
| } |
| |
| /** |
| * Returns this byte string in a given base. |
| * |
| * @return String in given base |
| */ |
| public String toString(int base) { |
| return toString(bytes, base); |
| } |
| |
| /** |
| * Returns the given byte array in hexadecimal format. |
| * |
| * <p>For example, <code>toString(new byte[] {0xDE, 0xAD})</code> |
| * returns {@code "DEAD"}.</p> |
| * |
| * @param bytes Array of bytes |
| * @param base Base (2 or 16) |
| * @return String |
| */ |
| public static String toString(byte[] bytes, int base) { |
| char[] chars; |
| int j = 0; |
| switch (base) { |
| case 2: |
| chars = new char[bytes.length * 8]; |
| for (byte b : bytes) { |
| chars[j++] = DIGITS[(b & 0x80) >> 7]; |
| chars[j++] = DIGITS[(b & 0x40) >> 6]; |
| chars[j++] = DIGITS[(b & 0x20) >> 5]; |
| chars[j++] = DIGITS[(b & 0x10) >> 4]; |
| chars[j++] = DIGITS[(b & 0x08) >> 3]; |
| chars[j++] = DIGITS[(b & 0x04) >> 2]; |
| chars[j++] = DIGITS[(b & 0x02) >> 1]; |
| chars[j++] = DIGITS[b & 0x01]; |
| } |
| break; |
| case 16: |
| chars = new char[bytes.length * 2]; |
| for (byte b : bytes) { |
| chars[j++] = DIGITS[(b & 0xF0) >> 4]; |
| chars[j++] = DIGITS[b & 0x0F]; |
| } |
| break; |
| default: |
| throw new IllegalArgumentException("bad base " + base); |
| } |
| return new String(chars, 0, j); |
| } |
| |
| /** |
| * Returns this byte string in Base64 format. |
| * |
| * @return Base64 string |
| */ |
| public String toBase64String() { |
| return Base64.encodeBytes(bytes); |
| } |
| |
| /** |
| * Creates a byte string from a hexadecimal or binary string. |
| * |
| * <p>For example, <code>of("DEAD", 16)</code> |
| * returns the same as {@code ByteString(new byte[] {0xDE, 0xAD})}. |
| * |
| * @param string Array of bytes |
| * @param base Base (2 or 16) |
| * @return String |
| */ |
| public static ByteString of(String string, int base) { |
| final byte[] bytes = parse(string, base); |
| return new ByteString(bytes, false); |
| } |
| |
| /** |
| * Parses a hexadecimal or binary string to a byte array. |
| * |
| * @param string Hexadecimal or binary string |
| * @param base Base (2 or 16) |
| * @return Byte array |
| */ |
| public static byte[] parse(String string, int base) { |
| char[] chars = string.toCharArray(); |
| byte[] bytes; |
| int j = 0; |
| byte b = 0; |
| switch (base) { |
| case 2: |
| bytes = new byte[chars.length / 8]; |
| for (char c : chars) { |
| b <<= 1; |
| if (c == '1') { |
| b |= 0x1; |
| } |
| if (j % 8 == 7) { |
| bytes[j / 8] = b; |
| b = 0; |
| } |
| ++j; |
| } |
| break; |
| case 16: |
| if (chars.length % 2 != 0) { |
| throw new IllegalArgumentException("hex string has odd length"); |
| } |
| bytes = new byte[chars.length / 2]; |
| for (char c : chars) { |
| b <<= 4; |
| byte i = decodeHex(c); |
| b |= i & 0x0F; |
| if (j % 2 == 1) { |
| bytes[j / 2] = b; |
| b = 0; |
| } |
| ++j; |
| } |
| break; |
| default: |
| throw new IllegalArgumentException("bad base " + base); |
| } |
| return bytes; |
| } |
| |
| private static byte decodeHex(char c) { |
| if (c >= '0' && c <= '9') { |
| return (byte) (c - '0'); |
| } |
| if (c >= 'a' && c <= 'f') { |
| return (byte) (c - 'a' + 10); |
| } |
| if (c >= 'A' && c <= 'F') { |
| return (byte) (c - 'A' + 10); |
| } |
| throw new IllegalArgumentException("invalid hex character: " + c); |
| } |
| |
| /** |
| * Creates a byte string from a Base64 string. |
| * |
| * @param string Base64 string |
| * @return Byte string |
| */ |
| public static ByteString ofBase64(String string) { |
| final byte[] bytes = parseBase64(string); |
| return new ByteString(bytes, false); |
| } |
| |
| /** |
| * Parses a Base64 to a byte array. |
| * |
| * @param string Base64 string |
| * @return Byte array |
| */ |
| public static byte[] parseBase64(String string) { |
| try { |
| return Base64.decode(string); |
| } catch (IOException e) { |
| throw new IllegalArgumentException("bad base64 string", e); |
| } |
| } |
| |
| @SuppressWarnings({ |
| "CloneDoesntCallSuperClone", |
| "CloneDoesntDeclareCloneNotSupportedException" |
| }) |
| @Override public Object clone() { |
| return this; |
| } |
| |
| /** |
| * Returns the number of bytes in this byte string. |
| * |
| * @return Length of this byte string |
| */ |
| public int length() { |
| return bytes.length; |
| } |
| |
| /** |
| * Returns the byte at a given position in the byte string. |
| * |
| * @param i Index |
| * @throws IndexOutOfBoundsException if the <code>index</code> argument is |
| * negative or not less than <code>length()</code> |
| * @return Byte at given position |
| */ |
| public byte byteAt(int i) { |
| return bytes[i]; |
| } |
| |
| /** |
| * Returns a ByteString that consists of a given range. |
| * |
| * @param start Start of range |
| * @param end Position after end of range |
| * @return Substring |
| */ |
| public ByteString substring(int start, int end) { |
| byte[] bytes = Arrays.copyOfRange(this.bytes, start, end); |
| return new ByteString(bytes, false); |
| } |
| |
| /** |
| * Returns a ByteString that starts at a given position. |
| * |
| * @param start Start of range |
| * @return Substring |
| */ |
| public ByteString substring(int start) { |
| return substring(start, length()); |
| } |
| |
| /** |
| * Returns a copy of the byte array. |
| */ |
| @JsonValue |
| public byte[] getBytes() { |
| return bytes.clone(); |
| } |
| |
| /** |
| * Returns a ByteString consisting of the concatenation of this and another |
| * string. |
| * |
| * @param other Byte string to concatenate |
| * @return Combined byte string |
| */ |
| public ByteString concat(ByteString other) { |
| int otherLen = other.length(); |
| if (otherLen == 0) { |
| return this; |
| } |
| int len = bytes.length; |
| byte[] buf = Arrays.copyOf(bytes, len + otherLen); |
| System.arraycopy(other.bytes, 0, buf, len, other.bytes.length); |
| return new ByteString(buf, false); |
| } |
| |
| /** Returns the position at which {@code seek} first occurs in this byte |
| * string, or -1 if it does not occur. */ |
| public int indexOf(ByteString seek) { |
| return indexOf(seek, 0); |
| } |
| |
| /** Returns the position at which {@code seek} first occurs in this byte |
| * string, starting at the specified index, or -1 if it does not occur. */ |
| public int indexOf(ByteString seek, int start) { |
| iLoop: |
| for (int i = start; i < bytes.length - seek.bytes.length + 1; i++) { |
| for (int j = 0;; j++) { |
| if (j == seek.bytes.length) { |
| return i; |
| } |
| if (bytes[i + j] != seek.bytes[j]) { |
| continue iLoop; |
| } |
| } |
| } |
| return -1; |
| } |
| } |
| |
| // End ByteString.java |