/*
* Copyright (c) 2013-2017 Cinchapi 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.cinchapi.concourse.server.model;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import com.cinchapi.concourse.server.io.Byteable;
import com.cinchapi.concourse.server.storage.cache.LazyCache;
import com.cinchapi.concourse.util.ByteBuffers;
/**
* A {@link Byteable} wrapper for a string of UTF-8 encoded characters.
*
* @author Jeff Nelson
*/
@Immutable
public final class Text implements Byteable, Comparable<Text> {
/**
* Return the Text encoded in {@code bytes} so long as those bytes adhere
* to the format specified by the {@link #getBytes()} method. This method
* assumes that all the bytes in the {@code bytes} belong to the Text. In
* general, it is necessary to get the appropriate Text slice from the
* parent ByteBuffer using {@link ByteBuffers#slice(ByteBuffer, int, int)}.
*
* @param buffer
* @return the Text
*/
public static Text fromByteBuffer(ByteBuffer bytes) {
return new Text(ByteBuffers.getString(bytes, StandardCharsets.UTF_8),
bytes);
}
/**
* Return Text that is backed by {@code string}.
*
* @param string
* @return the Text
*/
public static Text wrap(String string) {
return new Text(string);
}
/**
* Return Text that is backed by {@code string}. It is possible that the
* object will be a cached instance. This should only be called when
* wrapping record keys since they are expected to be used often.
*
* @param string
* @return the Text
*/
public static Text wrapCached(String string) {
Text text = cache.get(string);
if(text == null) {
text = new Text(string);
cache.put(string, text);
}
return text;
}
/**
* The cache that holds the objects created from the
* {@link #wrapCached(String)} method. This is primary used for string keys
* since those are expected to be used often.
*/
private static final LazyCache<String, Text> cache = LazyCache
.withExpectedSize(5000);
/**
* Represents an empty text string.
*/
public static final Text EMPTY = Text.wrap("");
/**
* Master byte sequence that represents this object. Read-only duplicates
* are made when returning from {@link #getBytes()}.
*/
private transient ByteBuffer bytes = null;
/**
* The wrapped string.
*/
private final String text;
/**
* A mutex used to synchronized the lazy setting of the byte buffer.
*/
private final Object mutex = new Object();
/**
* Construct an instance that wraps the {@code text} string.
*
* @param text
*/
private Text(String text) {
this(text, null);
}
/**
* Construct a new instance.
*
* @param text
* @param bytes
*/
private Text(String text, @Nullable ByteBuffer bytes) {
this.text = text;
this.bytes = bytes;
}
@Override
public int compareTo(Text o) {
return toString().compareTo(o.toString());
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Text) {
Text other = (Text) obj;
return toString().equals(other.toString());
}
return false;
}
@Override
public ByteBuffer getBytes() {
if(bytes == null) {
synchronized (mutex) {
if(bytes == null) { // must check again to prevent duplicate
// copy if there is a race condition
bytes = ByteBuffers.fromString(text);
}
}
}
return ByteBuffers.asReadOnlyBuffer(bytes);
}
@Override
public int hashCode() {
return text.hashCode();
}
@Override
public int size() {
return bytes == null ? getBytes().capacity() : bytes.capacity();
}
@Override
public String toString() {
return text;
}
@Override
public void copyTo(ByteBuffer buffer) {
if(bytes == null) {
ByteBuffers.putString(text, buffer);
}
else {
buffer.put(getBytes());
}
}
}