/* * 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.io; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.Map; import com.cinchapi.concourse.util.ByteBuffers; import com.google.common.base.Throwables; import com.google.common.collect.Maps; /** * Contains static factory methods to construct {@link Byteable} objects from * ByteBuffers. * * @author Jeff Nelson */ public abstract class Byteables { /** * Return an instance of {@code classObj} by reading {@code bytes}. This * method uses reflection to invoke the single argument ByteBuffer * constructor in {@code classObj}. * <p> * <tt>Byteables.read(bytes, Foo.class)</tt> * </p> * It is assumed that all the contents of {@code bytes} are relevant to the * object being read, so so call * {@link ByteBuffers#slice(ByteBuffer, int, int)} or follow this * protocol when using this method: * <ul> * <li>Set the position of the parent ByteBuffer to the index of the first * byte relevant to the object, using {@link ByteBuffer#position(int)}.</li> * <li>Set the limit of the parent ByteBuffer to the index of its current * position + the size of the object (which is usually stored in the 4 bytes * preceding the object) using {@link ByteBuffer#limit(int)}.</li> * <li>Slice the parent buffer using {@link ByteBuffer#slice()}, which will * create a child buffer with the same content of the parent buffer between * its current position and its limit.</li> * </ul> * * @param bytes * @param classObj * @return an instance of {@code classObj} read from {@code bytes} */ @SuppressWarnings("unchecked") public static <T> T read(ByteBuffer bytes, Class<T> classObj) { try { Constructor<T> constructor = (Constructor<T>) constructorCache .get(classObj); if(constructor == null) { constructor = classObj.getDeclaredConstructor(ByteBuffer.class); constructor.setAccessible(true); constructorCache.put(classObj, constructor); } return constructor.newInstance(bytes); } catch (ReflectiveOperationException e) { throw Throwables.propagate(e); } } /** * Return an instance of {@code className} by reading {@code bytes}. This * method uses reflection to invoke the single argument ByteBuffer * constructor in {@code className}. * <p> * <tt>Byteables.{@literal <Foo>}read(bytes, com.organization.module.Foo)</tt> * </p> * It is assumed that all the contents of {@code bytes} are relevant to the * object being read, so call * {@link ByteBuffers#slice(ByteBuffer, int, int)} or follow this * protocol when using this method: * <ul> * <li>Set the position of the parent ByteBuffer to the index of the first * byte relevant to the object, using {@link ByteBuffer#position(int)}.</li> * <li>Set the limit of the parent ByteBuffer to the index of its current * position + the size of the object (which is usually stored in the 4 bytes * preceding the object) using {@link ByteBuffer#limit(int)}.</li> * <li>Slice the parent buffer using {@link ByteBuffer#slice()}, which will * create a child buffer with the same content of the parent buffer between * its current position and its limit.</li> * </ul> * * @param bytes * @param className the fully qualified class name * @return an instance of {@code className} read from {@code bytes} */ @SuppressWarnings("unchecked") public static <T> T read(ByteBuffer bytes, final String className) { try { return (T) read(bytes, Class.forName(className)); } catch (ReflectiveOperationException e) { throw Throwables.propagate(e); } } /** * Return an instance of {@code classObj} by reading {@code bytes}. This * method uses reflection to invoke the single argument static method named * <strong>fromByteBuffer</strong> in {@code classObj}. * <p> * <tt>Byteables.read(bytes, Foo.class)</tt> * </p> * It is assumed that all the contents of {@code bytes} are relevant to the * object being read, so so call * {@link ByteBuffers#slice(ByteBuffer, int, int)} or follow this * protocol when using this method: * <ul> * <li>Set the position of the parent ByteBuffer to the index of the first * byte relevant to the object, using {@link ByteBuffer#position(int)}.</li> * <li>Set the limit of the parent ByteBuffer to the index of its current * position + the size of the object (which is usually stored in the 4 bytes * preceding the object) using {@link ByteBuffer#limit(int)}.</li> * <li>Slice the parent buffer using {@link ByteBuffer#slice()}, which will * create a child buffer with the same content of the parent buffer between * its current position and its limit.</li> * </ul> * * @param bytes * @param classObj * @return an instance of {@code classObj} read from {@code bytes} */ @SuppressWarnings("unchecked") public static <T> T readStatic(ByteBuffer bytes, Class<T> classObj) { try { Method method = staticFactoryCache.get(classObj); if(method == null) { method = classObj.getMethod("fromByteBuffer", ByteBuffer.class); staticFactoryCache.put(classObj, method); } return (T) method.invoke(null, bytes); } catch (ReflectiveOperationException e) { throw Throwables.propagate(e); } } /** * Return an instance of {@code className} by reading {@code bytes}. This * method uses reflection to invoke the single argument static method named * <strong>fromByteBuffer</strong> in {@code className}. * <p> * <tt>Byteables.read(bytes, Foo.class)</tt> * </p> * It is assumed that all the contents of {@code bytes} are relevant to the * object being read, so so call * {@link ByteBuffers#slice(ByteBuffer, int, int)} or follow this * protocol when using this method: * <ul> * <li>Set the position of the parent ByteBuffer to the index of the first * byte relevant to the object, using {@link ByteBuffer#position(int)}.</li> * <li>Set the limit of the parent ByteBuffer to the index of its current * position + the size of the object (which is usually stored in the 4 bytes * preceding the object) using {@link ByteBuffer#limit(int)}.</li> * <li>Slice the parent buffer using {@link ByteBuffer#slice()}, which will * create a child buffer with the same content of the parent buffer between * its current position and its limit.</li> * </ul> * * @param bytes * @param classObj * @return an instance of {@code className} read from {@code bytes} */ @SuppressWarnings("unchecked") public static <T> T readStatic(ByteBuffer bytes, String className) { try { return (T) readStatic(bytes, Class.forName(className)); } catch (ClassNotFoundException e) { throw Throwables.propagate(e); } } /** * Write {@code object} to {@code channel} starting at the channel's current * position. * * @param obj * @param channel */ public static void write(Byteable object, FileChannel channel) { try { FileLock lock = channel.lock(channel.position(), object.size(), false); channel.write(object.getBytes()); channel.force(true); lock.release(); } catch (IOException e) { throw Throwables.propagate(e); } } /** * Cache of constructors that are captured using reflection. */ private static final Map<Class<?>, Constructor<?>> constructorCache = Maps .newIdentityHashMap(); /** * Cache of static factory methods that are captured using reflection. */ private static final Map<Class<?>, Method> staticFactoryCache = Maps .newIdentityHashMap(); }