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

net.derquinse.common.base.ByteString Maven / Gradle / Ivy

Go to download

Module containing support classes depending on Java SE 6, Guava 11 and Joda-Time 2.0

There is a newer version: 1.0.37
Show newest version
/*
 * Copyright (C) the original author or authors.
 * Copyright 2008 Google Inc.  All rights reserved.
 *
 * 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 net.derquinse.common.base;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.nio.charset.Charset;
import java.util.List;

import com.google.common.annotations.Beta;
import com.google.common.base.Charsets;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashCodes;

/**
 * Immutable array of bytes. Based on Google's code in Protocol Buffers. A version of this class is
 * expected to appear in Guava. This implementation will be removed once that happens.
 * @author [email protected] Bob Lee
 * @author [email protected] Kenton Varda
 * @author Andres Rodriguez
 */
@Beta
public final class ByteString implements Serializable {
	/** Serial UID. */
	private static final long serialVersionUID = 5380545035055097521L;

	/** Used to build output as Hex. */
	private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
			'e', 'f' };

	/** Used to build output as Hex. */
	private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
			'E', 'F' };

	private final byte[] bytes;

	private ByteString(final byte[] bytes) {
		this.bytes = bytes;
	}

	/**
	 * Gets the byte at the given index.
	 * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size
	 */
	public byte byteAt(final int index) {
		return bytes[index];
	}

	/**
	 * Gets the number of bytes.
	 */
	public int size() {
		return bytes.length;
	}

	/**
	 * Returns {@code true} if the size is {@code 0}, {@code false} otherwise.
	 */
	public boolean isEmpty() {
		return bytes.length == 0;
	}

	// =================================================================
	// byte[] -> ByteString

	/**
	 * Empty ByteString.
	 */
	public static final ByteString EMPTY = new ByteString(new byte[0]);

	/**
	 * Copies the given bytes into a {@code ByteString}.
	 */
	public static ByteString copyFrom(final byte[] bytes, final int offset, final int size) {
		final byte[] copy = new byte[size];
		System.arraycopy(bytes, offset, copy, 0, size);
		return new ByteString(copy);
	}

	/**
	 * Copies the given bytes into a {@code ByteString}.
	 */
	public static ByteString copyFrom(final byte[] bytes) {
		return copyFrom(bytes, 0, bytes.length);
	}

	/**
	 * Copies the given {@link HashCode} into a {@code ByteString}.
	 */
	public static ByteString copyFrom(HashCode hashCode) {
		return copyFrom(hashCode.asBytes());
	}

	/**
	 * Copies {@code size} bytes from a {@code java.nio.ByteBuffer} into a {@code ByteString}.
	 */
	public static ByteString copyFrom(final ByteBuffer bytes, final int size) {
		final byte[] copy = new byte[size];
		bytes.get(copy);
		return new ByteString(copy);
	}

	/**
	 * Copies the remaining bytes from a {@code java.nio.ByteBuffer} into a {@code ByteString}.
	 */
	public static ByteString copyFrom(final ByteBuffer bytes) {
		return copyFrom(bytes, bytes.remaining());
	}

	/**
	 * Encodes {@code text} into a sequence of bytes using the named charset and returns the result as
	 * a {@code ByteString}.
	 */
	public static ByteString copyFrom(final String text, final String charsetName) throws UnsupportedEncodingException {
		return new ByteString(text.getBytes(charsetName));
	}

	/**
	 * Encodes {@code text} into a sequence of bytes using the provided charset and returns the result
	 * as a {@code ByteString}.
	 */
	public static ByteString copyFrom(final String text, final Charset charset) throws UnsupportedEncodingException {
		return new ByteString(text.getBytes(charset.name()));
	}

	/**
	 * Encodes {@code text} into a sequence of UTF-8 bytes and returns the result as a
	 * {@code ByteString}.
	 */
	public static ByteString copyFromUtf8(final String text) {
		try {
			return copyFrom(text, Charsets.UTF_8.name());
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("UTF-8 not supported?", e);
		}
	}

	/**
	 * Concatenates all byte strings in the list and returns the result.
	 * 

* The returned {@code ByteString} is not necessarily a unique object. If the list is empty, the * returned object is the singleton empty {@code ByteString}. If the list has only one element, * that {@code ByteString} will be returned without copying. */ public static ByteString copyFrom(List list) { if (list.size() == 0) { return EMPTY; } else if (list.size() == 1) { return list.get(0); } int size = 0; for (ByteString str : list) { size += str.size(); } byte[] bytes = new byte[size]; int pos = 0; for (ByteString str : list) { System.arraycopy(str.bytes, 0, bytes, pos, str.size()); pos += str.size(); } return new ByteString(bytes); } /** * Converts an array of characters representing hexadecimal values into an array of bytes of those * same values. The returned byte string will be half the length of the passed array, as it takes * two characters to represent any given byte. An exception is thrown if the passed char array has * an odd number of elements. * @param data An array of characters containing hexadecimal digits * @return A byte string containing binary data decoded from the supplied char array. * @throws IllegalArgumentException Thrown if an odd number or illegal of characters is supplied */ public static ByteString fromHexChars(char[] data) { int len = data.length; checkArgument((len & 0x01) == 0, "Odd number of characters."); byte[] out = new byte[len >> 1]; // two characters form the hex value. for (int i = 0, j = 0; j < len; i++) { int f = toDigit(data[j], j) << 4; j++; f = f | toDigit(data[j], j); j++; out[i] = (byte) (f & 0xFF); } return new ByteString(out); } /** * Converts an string representing hexadecimal values into an array of bytes of those same values. * The returned byte string will be half the length of the passed array, as it takes two * characters to represent any given byte. An exception is thrown if the passed char array has an * odd number of elements. * @param data An string containing hexadecimal digits * @return A byte string containing binary data decoded from the supplied string. * @throws IllegalArgumentException Thrown if an odd number or illegal of characters is supplied */ public static ByteString fromHexString(String data) { return fromHexChars(data.toCharArray()); } /** * Converts a hexadecimal character to an integer. * @param ch A character to convert to an integer digit * @param index The index of the character in the source * @return An integer * @throws IllegalArgumentException Thrown if ch is an illegal hex character */ private static int toDigit(char ch, int index) { int digit = Character.digit(ch, 16); if (digit == -1) { throw new IllegalArgumentException("Illegal hexadecimal character " + ch + " at index " + index); } return digit; } // ================================================================= // ByteString -> byte[] /** * Copies bytes into a buffer at the given offset. * @param target buffer to copy into * @param offset in the target buffer */ public void copyTo(final byte[] target, final int offset) { System.arraycopy(bytes, 0, target, offset, bytes.length); } /** * Copies bytes into a buffer. * @param target buffer to copy into * @param sourceOffset offset within these bytes * @param targetOffset offset within the target buffer * @param size number of bytes to copy */ public void copyTo(final byte[] target, final int sourceOffset, final int targetOffset, final int size) { System.arraycopy(bytes, sourceOffset, target, targetOffset, size); } /** * Copies bytes into a ByteBuffer. * @param target ByteBuffer to copy into. * @throws ReadOnlyBufferException if the {@code target} is read-only * @throws BufferOverflowException if the {@code target}'s remaining() space is not large enough * to hold the data. */ public void copyTo(ByteBuffer target) { target.put(bytes, 0, bytes.length); } /** * Copies bytes to a {@code byte[]}. */ public byte[] toByteArray() { final int size = bytes.length; final byte[] copy = new byte[size]; System.arraycopy(bytes, 0, copy, 0, size); return copy; } /** * Converts the byte string to a {@link HashCode}. * @throws IllegalStateException if the string is empty. */ public HashCode toHashCode() { checkState(bytes.length > 0, "The byte string must not be empty"); return HashCodes.fromBytes(bytes); } /** * Constructs a new read-only {@code java.nio.ByteBuffer} with the same backing byte array. */ public ByteBuffer asReadOnlyByteBuffer() { final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); return byteBuffer.asReadOnlyBuffer(); } /** * Constructs a new {@code String} by decoding the bytes using the specified charset. */ public String toString(final String charsetName) throws UnsupportedEncodingException { return new String(bytes, charsetName); } /** * Constructs a new {@code String} by decoding the bytes as UTF-8. */ public String toStringUtf8() { try { return new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 not supported?", e); } } /** * Constructs a new {@code String} by encoding the bytes in hexadecimal. */ public String toHexString(boolean lowercase) { return toHexString(lowercase ? DIGITS_LOWER : DIGITS_UPPER); } /** * Constructs a new {@code String} by encoding the bytes in hexadecimal. */ public String toHexString() { return toHexString(true); } private String toHexString(char[] toDigits) { final int length = 2 * bytes.length; final StringBuilder b = new StringBuilder(length); for (int i = 0; i < bytes.length; i++) { b.append(toDigits[(0xF0 & bytes[i]) >>> 4]); b.append(toDigits[0x0F & bytes[i]]); } return b.toString(); } private String toHexString(char[] toDigits, int maxBytes) { final int nBytes = Math.min(maxBytes, bytes.length); final int length = 2 * nBytes; final StringBuilder b = new StringBuilder(length); for (int i = 0; i < nBytes; i++) { b.append(toDigits[(0xF0 & bytes[i]) >>> 4]); b.append(toDigits[0x0F & bytes[i]]); } return b.toString(); } /** * Default toString = toHexString (max 32 chars) */ public String toString() { if (bytes.length <= 16) { return toHexString(); } return toHexString(DIGITS_LOWER, 14) + "..."; } // ================================================================= // equals() and hashCode() @Override public boolean equals(final Object o) { if (o == this) { return true; } if (!(o instanceof ByteString)) { return false; } final ByteString other = (ByteString) o; final int size = bytes.length; if (size != other.bytes.length) { return false; } final byte[] thisBytes = bytes; final byte[] otherBytes = other.bytes; for (int i = 0; i < size; i++) { if (thisBytes[i] != otherBytes[i]) { return false; } } return true; } private volatile int hash = 0; @Override public int hashCode() { int h = hash; if (h == 0) { final byte[] thisBytes = bytes; final int size = bytes.length; h = size; for (int i = 0; i < size; i++) { h = h * 31 + thisBytes[i]; } if (h == 0) { h = 1; } hash = h; } return h; } // ================================================================= // Input stream /** * Creates an {@code InputStream} which can be used to read the bytes. */ public InputStream newInput() { return new ByteArrayInputStream(bytes); } // ================================================================= // Output stream /** * Creates a new {@link Output} with the given initial capacity. */ public static Output newOutput(final int initialCapacity) { return new Output(new ByteArrayOutputStream(initialCapacity)); } /** * Creates a new {@link Output}. */ public static Output newOutput() { return newOutput(32); } /** * Builder based on an output stream. Call {@link #build()} to create the {@code ByteString} * instance. */ public static final class Output extends FilterOutputStream implements Builder { private final ByteArrayOutputStream bout; /** * Constructs a new output with the given initial capacity. */ private Output(final ByteArrayOutputStream bout) { super(bout); this.bout = bout; } /** * Creates a {@code ByteString} instance from this {@code Output}. */ public ByteString build() { final byte[] byteArray = bout.toByteArray(); return new ByteString(byteArray); } } // ================================================================= // Serialization proxy private static class SerializationProxy implements Serializable { /** Serial UID. */ private static final long serialVersionUID = 3486210277801140074L; private final byte[] bytes; public SerializationProxy(ByteString s) { this.bytes = s.bytes; } private Object readResolve() { return ByteString.copyFrom(bytes); } } private Object writeReplace() { return new SerializationProxy(this); } private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Proxy required"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy