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

org.apache.fluo.api.data.Bytes Maven / Gradle / Ivy

Go to download

This module contains all API code for Apache Fluo. External dependencies should be limited. Any changes to public methods should be avoided. While the API module does not have compile time dependency on fluo-core, you must include fluo-core as a run-time dependency.

There is a newer version: 2.0.0
Show newest version
/*
 * 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.fluo.api.data;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;

import com.google.common.base.Preconditions;

/**
 * Represents bytes in Fluo. Bytes is an immutable wrapper around a byte array. Bytes always copies
 * on creation and never lets its internal byte array escape. Its modeled after Java's String which
 * is an immutable wrapper around a char array. It was created because there is nothing in Java like
 * it at the moment. Its very nice having this immutable type, it avoids having to do defensive
 * copies to ensure correctness. Maybe one day Java will have equivalents of String, StringBuilder,
 * and Charsequence for bytes.
 * 
 * 

* The reason Fluo did not use ByteBuffer is because its not immutable, even a read only ByteBuffer * has a mutable position. This makes ByteBuffer unsuitable for place where an immutable data type * is desirable, like a key for a map. * *

* Bytes.EMPTY is used to represent a Bytes object with no data. * * @since 1.0.0 */ public final class Bytes implements Comparable, Serializable { private static final long serialVersionUID = 1L; private final byte[] data; private final int offset; private final int length; public static final Bytes EMPTY = new Bytes(new byte[0]); private Integer hashCode = null; public Bytes() { data = EMPTY.data; offset = 0; length = 0; } private Bytes(byte[] data) { this.data = data; this.offset = 0; this.length = data.length; } private Bytes(byte[] data, int offset, int length) { if (offset < 0 || offset > data.length || length < 0 || (offset + length) > data.length) { throw new IndexOutOfBoundsException(" Bad offset and/or length data.length = " + data.length + " offset = " + offset + " length = " + length); } this.data = data; this.offset = offset; this.length = length; } /** * Gets a byte within this sequence of bytes * * @param i index into sequence * @return byte * @throws IllegalArgumentException if i is out of range */ public byte byteAt(int i) { if (i < 0) { throw new IndexOutOfBoundsException("i < 0, " + i); } if (i >= length) { throw new IndexOutOfBoundsException("i >= length, " + i + " >= " + length); } return data[offset + i]; } /** * Gets the length of bytes */ public int length() { return length; } /** * Returns a portion of the Bytes object * * @param start index of subsequence start (inclusive) * @param end index of subsequence end (exclusive) */ public Bytes subSequence(int start, int end) { if (start > end || start < 0 || end > length) { throw new IndexOutOfBoundsException("Bad start and/end start = " + start + " end=" + end + " offset=" + offset + " length=" + length); } return new Bytes(data, offset + start, end - start); } /** * Returns a byte array containing a copy of the bytes */ public byte[] toArray() { byte[] copy = new byte[length]; System.arraycopy(data, offset, copy, 0, length); return copy; } /** * Creates UTF-8 String using Bytes data */ @Override public String toString() { return new String(data, offset, length, StandardCharsets.UTF_8); } /** * @return A read only byte buffer thats backed by the internal byte array. */ public ByteBuffer toByteBuffer() { return ByteBuffer.wrap(data, offset, length).asReadOnlyBuffer(); } /** * @return An input stream thats backed by the internal byte array */ public InputStream toInputStream() { return new ByteArrayInputStream(data, offset, length); } public void writeTo(OutputStream out) throws IOException { // since Bytes is immutable, its important the we do not let the internal byte array escape if (length <= 32) { int end = offset + length; for (int i = offset; i < end; i++) { out.write(data[i]); } } else { out.write(toArray()); } } /** * Compares this to the passed bytes, byte by byte, returning a negative, zero, or positive result * if the first sequence is less than, equal to, or greater than the second. The comparison is * performed starting with the first byte of each sequence, and proceeds until a pair of bytes * differs, or one sequence runs out of byte (is shorter). A shorter sequence is considered less * than a longer one. * * @return comparison result */ @Override public final int compareTo(Bytes other) { int minLen = Math.min(this.length(), other.length()); for (int i = 0; i < minLen; i++) { int a = (this.byteAt(i) & 0xff); int b = (other.byteAt(i) & 0xff); if (a != b) { return a - b; } } return this.length() - other.length(); } /** * Returns true if this Bytes object equals another. */ @Override public final boolean equals(Object other) { if (other instanceof Bytes) { Bytes ob = (Bytes) other; if (this == other) { return true; } if (length() != ob.length()) { return false; } return compareTo(ob) == 0; } return false; } @Override public final int hashCode() { if (hashCode == null) { int hash = 1; for (int i = 0; i < length(); i++) { hash = (31 * hash) + byteAt(i); } hashCode = hash; } return hashCode; } /** * Creates a Bytes object by copying the data of the given byte array */ public static final Bytes of(byte[] array) { Objects.requireNonNull(array); if (array.length == 0) { return EMPTY; } byte[] copy = new byte[array.length]; System.arraycopy(array, 0, copy, 0, array.length); return new Bytes(copy); } /** * Creates a Bytes object by copying the data of a subsequence of the given byte array * * @param data Byte data * @param offset Starting offset in byte array (inclusive) * @param length Number of bytes to include */ public static final Bytes of(byte[] data, int offset, int length) { Objects.requireNonNull(data); if (length == 0) { return EMPTY; } byte[] copy = new byte[length]; System.arraycopy(data, offset, copy, 0, length); return new Bytes(copy); } /** * Creates a Bytes object by copying the data of the given ByteBuffer. * * @param bb Data will be read from this ByteBuffer in such a way that its position is not * changed. */ public static final Bytes of(ByteBuffer bb) { Objects.requireNonNull(bb); if (bb.remaining() == 0) { return EMPTY; } byte[] data; if (bb.hasArray()) { data = Arrays.copyOfRange(bb.array(), bb.position() + bb.arrayOffset(), bb.limit() + bb.arrayOffset()); } else { data = new byte[bb.remaining()]; // duplicate so that it does not change position bb.duplicate().get(data); } return new Bytes(data); } /** * Creates a Bytes object by copying the data of the CharSequence and encoding it using UTF-8. */ public static final Bytes of(CharSequence cs) { if (cs instanceof String) { return of((String) cs); } Objects.requireNonNull(cs); if (cs.length() == 0) { return EMPTY; } ByteBuffer bb = StandardCharsets.UTF_8.encode(CharBuffer.wrap(cs)); if (bb.hasArray()) { // this byte buffer has never escaped so can use its byte array directly return new Bytes(bb.array(), bb.position() + bb.arrayOffset(), bb.limit()); } else { byte[] data = new byte[bb.remaining()]; bb.get(data); return new Bytes(data); } } /** * Creates a Bytes object by copying the value of the given String */ public static final Bytes of(String s) { Objects.requireNonNull(s); if (s.length() == 0) { return EMPTY; } byte[] data = s.getBytes(StandardCharsets.UTF_8); return new Bytes(data); } /** * Creates a Bytes object by copying the value of the given String with a given charset */ public static final Bytes of(String s, Charset c) { Objects.requireNonNull(s); Objects.requireNonNull(c); if (s.length() == 0) { return EMPTY; } byte[] data = s.getBytes(c); return new Bytes(data); } /** * This class provides an easy, efficient, reusable mechanism for building immutable Bytes * objects. * * @since 1.0.0 */ public static class BytesBuilder { private byte[] ba; private int len; BytesBuilder(int initialCapacity) { ba = new byte[initialCapacity]; len = 0; } BytesBuilder() { this(32); } private void ensureCapacity(int min) { if (ba.length < min) { int newLen = ba.length * 2; if (newLen < min) { newLen = min; } ba = Arrays.copyOf(ba, newLen); } } public BytesBuilder append(CharSequence cs) { if (cs instanceof String) { return append((String) cs); } ByteBuffer bb = StandardCharsets.UTF_8.encode(CharBuffer.wrap(cs)); int length = bb.remaining(); ensureCapacity(len + length); bb.get(ba, len, length); len += length; return this; } /** * Converts string to bytes using UTF-8 encoding and appends bytes. * * @return self */ public BytesBuilder append(String s) { return append(s.getBytes(StandardCharsets.UTF_8)); } public BytesBuilder append(Bytes b) { ensureCapacity(len + b.length()); System.arraycopy(b.data, b.offset, ba, len, b.length); len += b.length(); return this; } public BytesBuilder append(byte[] bytes) { ensureCapacity(len + bytes.length); System.arraycopy(bytes, 0, ba, len, bytes.length); len += bytes.length; return this; } /** * Append a single byte. * * @param b take the lower 8 bits and appends it. * @return self */ public BytesBuilder append(int b) { ensureCapacity(len + 1); ba[len] = (byte) b; len += 1; return this; } /** * Append a section of bytes from array * * @param bytes - bytes to be appended * @param offset - start of bytes to be appended * @param length - how many bytes from 'offset' to be appended * @return self */ public BytesBuilder append(byte[] bytes, int offset, int length) { ensureCapacity(len + length); System.arraycopy(bytes, offset, ba, len, length); len += length; return this; } /** * Append a sequence of bytes from an InputStream * * @param in data source to append from * @param length number of bytes to read from data source * @return self */ public BytesBuilder append(InputStream in, int length) throws IOException { ensureCapacity(len + length); new DataInputStream(in).readFully(ba, len, length); len += length; return this; } /** * Append data from a ByteBuffer * * @param bb data is read from the ByteBuffer in such a way that its position is not changed. * @return self */ public BytesBuilder append(ByteBuffer bb) { int length = bb.remaining(); ensureCapacity(len + length); bb.duplicate().get(ba, len, length); len += length; return this; } /** * Sets the point at which appending will start. This method can shrink or grow the ByteBuilder * from its current state. If it grows it will zero pad. */ public void setLength(int newLen) { Preconditions.checkArgument(newLen >= 0, "Negative length passed : " + newLen); if (newLen > ba.length) { ba = Arrays.copyOf(ba, newLen); } if (newLen > len) { Arrays.fill(ba, len, newLen, (byte) 0); } len = newLen; } public int getLength() { return len; } public Bytes toBytes() { return Bytes.of(ba, 0, len); } } /** * Provides an efficient and reusable way to build immutable Bytes objects. */ public static BytesBuilder builder() { return new BytesBuilder(); } /** * @param initialCapacity The initial size of the byte builders internal array. */ public static BytesBuilder builder(int initialCapacity) { return new BytesBuilder(initialCapacity); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy