com.tangosol.internal.util.invoke.ClassDefinition Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* Copyright (c) 2000, 2022, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
*/
package com.tangosol.internal.util.invoke;
import com.tangosol.io.ExternalizableLite;
import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;
import com.tangosol.util.Base;
import com.tangosol.util.ClassHelper;
import com.tangosol.util.ExternalizableHelper;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import jakarta.json.bind.annotation.JsonbProperty;
/**
* A {@code ClassDefinition} entirely encapsulates a {@link Remotable} class
* such that a receiver of a {@code ClassDefinition} will be able to define
* the class for subsequent use.
*
* The {@code ClassDefinition} is absent of instance information, which is
* required for the instantiation of the class, and is captured by the
* {@link RemoteConstructor} instance and applied to the {@code ClassDefinition}
* when calling {@link #createInstance(Object[]) createInstance} method.
*
* The common interactions with a ClassDefinition are as follows:
*
* Role Method
* Function Submitter {@link #ClassDefinition(ClassIdentity, byte[])}
* Function Receiver - Class Loading Phase {@link #setRemotableClass(Class)}
* Function Receiver - Execution Phase {@link #createInstance(Object[])}
*
*
* @author hr/as 2015.06.01
* @since 12.2.1
*/
public class ClassDefinition
implements ExternalizableLite, PortableObject
{
// ----- constructors ---------------------------------------------------
/**
* Deserialization constructor.
*/
public ClassDefinition()
{
}
/**
* Construct a ClassDefinition with the provided {@link ClassIdentity id}
* and byte array (representing the {@link Remotable} Class File).
*
* @param id a unique identity for the {@link Remotable} Class
* @param abClass a {@link Remotable} Class File byte array
*/
public ClassDefinition(ClassIdentity id, byte[] abClass)
{
m_id = id;
m_abClass = abClass;
// just to please Harvey ;-)
String sClassName = id.getName();
Base.azzert(sClassName.length() < 0xFFFF,
"The generated class name is too long:\n" + sClassName);
}
// ----- accessors ------------------------------------------------------
/**
* Return the {@link ClassIdentity identity} of the function.
*
* @return the identity of the function
*/
public ClassIdentity getId()
{
return m_id;
}
/**
* Return the Class File byte array.
*
* @return the Class File byte array
*/
public byte[] getBytes()
{
return m_abClass;
}
/**
* Return the {@link Remotable} Class this definition represents.
*
* @return the {@code Remotable} Class this definition represents
*/
public Class extends Remotable> getRemotableClass()
{
return m_clz;
}
/**
* Set the {@link Remotable} Class this definition represents.
*
* @param clz the {@link Remotable} Class this definition represents
*/
public void setRemotableClass(Class extends Remotable> clz)
{
m_clz = clz;
Constructor>[] aCtor = clz.getDeclaredConstructors();
if (aCtor.length == 1)
{
try
{
MethodType ctorType = MethodType.methodType(void.class, aCtor[0].getParameterTypes());
m_mhCtor = MethodHandles.publicLookup().findConstructor(clz, ctorType);
}
catch (NoSuchMethodException | IllegalAccessException e)
{
throw Base.ensureRuntimeException(e);
}
}
}
/**
* Create an instance of the {@link Remotable} class represented by this
* definition.
*
* @param aoArgs an array of constructor arguments
*
* @return an instance of the {@code Remotable} class represented by this
* definition
*/
public Object createInstance(Object... aoArgs)
{
try
{
return getConstructor(aoArgs).invokeWithArguments(aoArgs);
}
catch (NoSuchMethodException e)
{
// let's try constructors with a matching number of arguments one by one
Constructor[] aCtors = m_clz.getDeclaredConstructors();
for (Constructor ctor : aCtors)
{
if (ctor.getParameterTypes().length == aoArgs.length)
{
try
{
return ctor.newInstance(aoArgs);
}
catch (InstantiationException | InvocationTargetException |
IllegalAccessException | IllegalArgumentException ignore)
{
}
}
}
// no matching constructor found, rethrow the exception
throw Base.ensureRuntimeException(e);
}
catch (Throwable t)
{
throw Base.ensureRuntimeException(t);
}
}
// ----- helpers --------------------------------------------------------
/**
* Return a {@link MethodHandle} to the constructor of the {@link Remotable}
* class represented by this definition.
*
* Once constructed, the class may be 'used', which generally translates
* into the invocation of its methods via some interface.
*
* @param aoArgs the constructor arguments
*
* @return a {@code MethodHandle} to the constructor of the {@code Remotable} class
*/
protected MethodHandle getConstructor(Object[] aoArgs)
throws NoSuchMethodException
{
if (m_mhCtor != null)
{
// we have a cached constructor handle, so just return it
return m_mhCtor;
}
// otherwise, we need to find the constructor based on argument types
// if we do find one, we intentionally do not cache it for future use
// because, unlike lambdas or scripts, a remotable Class could have
// several constructor overloads that can be called using multiple
// RemoteConstructor instances against a single ClassDefinition
Class[] aParamTypes = ClassHelper.getClassArray(aoArgs);
try
{
// try with primitive types first
MethodType ctorType = MethodType.methodType(void.class, ClassHelper.unwrap(aParamTypes));
return MethodHandles.publicLookup().findConstructor(m_clz, ctorType);
}
catch (NoSuchMethodException e)
{
try
{
// and with wrapper types second
MethodType ctorType = MethodType.methodType(void.class, aParamTypes);
return MethodHandles.publicLookup().findConstructor(m_clz, ctorType);
}
catch (IllegalAccessException e1)
{
throw Base.ensureRuntimeException(e1);
}
}
catch (IllegalAccessException e)
{
throw Base.ensureRuntimeException(e);
}
}
/**
* Write the ClassFile represented by this ClassDefinition to the
* specified directory.
*
* If the dump directory is {@code null}, or a path to a class directory
* can not be created, the ClassFile will not be written to the file system.
*
* @param sDir the root directory for class dumps
*/
public void dumpClass(String sDir)
{
if (sDir != null)
{
File dirDump = new File(sDir, m_id.getPackage());
boolean fDisabled = dirDump.isFile() ||
!dirDump.exists() && !dirDump.mkdirs();
if (!fDisabled)
{
try (OutputStream os = new FileOutputStream(new File(dirDump, m_id.getSimpleName() + ".class")))
{
os.write(m_abClass);
}
catch (IOException ignore)
{
} // we tried
}
}
}
// ----- Object methods -------------------------------------------------
@Override
public boolean equals(Object o)
{
if (o instanceof ClassDefinition)
{
ClassDefinition that = (ClassDefinition) o;
return this == that ||
this.getClass() == that.getClass() &&
Base.equals(m_id, that.m_id);
}
return false;
}
@Override
public int hashCode()
{
return m_id.hashCode();
}
@Override
public String toString()
{
return "ClassDefinition{" +
"id=" + m_id +
'}';
}
// ----- ExternalizableLite interface -----------------------------------
@Override
public void readExternal(DataInput in)
throws IOException
{
m_id = ExternalizableHelper.readObject(in);
m_abClass = ExternalizableHelper.readByteArray(in);
}
@Override
public void writeExternal(DataOutput out)
throws IOException
{
ExternalizableHelper.writeObject(out, m_id);
ExternalizableHelper.writeByteArray(out, m_abClass);
}
// ----- PortableObject interface ---------------------------------------
@Override
public void readExternal(PofReader in)
throws IOException
{
m_id = in.readObject(0);
m_abClass = in.readByteArray(1);
}
@Override
public void writeExternal(PofWriter out)
throws IOException
{
out.writeObject(0, m_id);
out.writeByteArray(1, m_abClass);
}
// ----- data members ---------------------------------------------------
/**
* The {@link Remotable} Class represented by this definition.
*/
protected transient Class extends Remotable> m_clz;
/**
* A cached {@link MethodHandle} to the constructor of the remotable Class
*/
protected transient MethodHandle m_mhCtor;
/**
* A unique identity for the {@link Remotable} Class represented by this
* definition.
*/
@JsonbProperty("id")
protected ClassIdentity m_id;
/**
* The bytes of the {@link Remotable} Class File.
*/
@JsonbProperty("code")
protected byte[] m_abClass;
}