com.tangosol.util.extractor.UniversalUpdater Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* Copyright (c) 2000, 2024, 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.util.extractor;
import com.tangosol.internal.util.extractor.TargetReflectionDescriptor;
import com.tangosol.io.ExternalizableLite;
import com.tangosol.io.ResolvingObjectInputStream;
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.ValueUpdater;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import javax.json.bind.annotation.JsonbProperty;
/**
* Universal ValueUpdater implementation.
*
* Either a property-based and method-based {@link com.tangosol.util.ValueUpdater}
* based on whether constructor parameter sName
is evaluated to be a property or method.
* Depending on the target
parameter of {@link #update(Object, Object)} target
,
* the property can reference a JavaBean property or {@link Map} key.
*
* @author gg 2005.10.27, jf 2017.11.28
* @see CompositeUpdater
* @since 12.2.1.4
*/
public class UniversalUpdater
extends AbstractUpdater
implements ExternalizableLite, PortableObject
{
// ----- constructors ---------------------------------------------------
/**
* Default constructor (necessary for the ExternalizableLite interface).
*/
public UniversalUpdater()
{
}
/**
* Construct a UniversalUpdater for the provided name.
* If sName
ends in a {@link #METHOD_SUFFIX},
* then the name is a method name. This implementation assumes that a
* target's class will have one and only one method with the
* specified name and this method will have exactly one parameter;
* if the name is a property name, there should be a corresponding
* JavaBean property modifier method or it will be used as a key in a {@link Map}.
*
* @param sName a method or property name
*
* @see #computeCanonicalName(String)
*/
public UniversalUpdater(String sName)
{
azzert(sName != null);
m_sName = sName;
}
// ----- ValueUpdater interface -----------------------------------------
/**
* {@inheritDoc}
*
* @see #updateComplex(Object, Object)
*/
public void update(Object oTarget, Object oValue)
{
if (oTarget == null)
{
throw new IllegalArgumentException(
"Target object is missing for the Updater: " + this);
}
Class> clzTarget = oTarget.getClass();
TargetReflectionDescriptor targetPrev = m_targetPrev;
try
{
if (targetPrev != null && clzTarget == targetPrev.getTargetClass())
{
if (targetPrev.isMap())
{
((Map) oTarget).put(getCanonicalName(), oValue);
}
else
{
targetPrev.getMethod().invoke(oTarget, oValue);
}
}
else
{
updateComplex(oTarget, oValue);
}
}
catch (NullPointerException e)
{
throw new RuntimeException("Missing or inaccessible method: " + clzTarget.getName() + '#' + m_sName);
}
catch (Exception e)
{
throw ensureRuntimeException(e, clzTarget.getName() + '#' + m_sName + '(' + oTarget + ')');
}
}
// ----- accessors and helpers ------------------------------------------
/**
* Return true iff this updater references a setter method.
*
* @return true iff this is setter method updater.
*/
public boolean isMethodUpdater()
{
return m_sName.endsWith(METHOD_SUFFIX);
}
/**
* Determine the name of the method that this extractor is configured to
* invoke.
*
* @return method name without the {@link #METHOD_SUFFIX}
*/
public String getMethodName()
{
final int METHOD_SUFFIX_LENGTH = METHOD_SUFFIX.length();
String sName = m_sName;
return isMethodUpdater() ?
sName.substring(0, sName.length() - METHOD_SUFFIX_LENGTH) :
"set" + Character.toUpperCase(sName.charAt(0)) +
(sName.length() > 1 ? sName.substring(1) : "");
}
/**
* Return the canonical name for this updater.
*
* A canonical name uniquely identifies an updater, but not how it
* is to be updated. Thus, two different updater implementations with the same
* non-null canonical name are considered to be equal, and should reflect this in
* their implementations of hashCode and equals.
*
* Canonical names for properties are designated by their property name in camel case,
* for instance a Java Bean with method setFooBar would have a property named fooBar,
* and fooBar would also be its canonical name.
*
* @return the updater's canonical name
*
* @since Coherence 12.3.1
*/
public String getCanonicalName()
{
String sCName = m_sNameCanon;
if (sCName == null)
{
sCName = m_sNameCanon = computeCanonicalName(m_sName);
}
return sCName;
}
/**
* Implement update of target using reflection or property setter.
* Cache the reflection computation to enable avoiding
* reflection lookup if next target has same class type.
*
* If unable to find method name via reflection and {@code oTarget}
* is a {@link Map}, use canonical name as a key
* to update target.
*
* @param oTarget the Object to update the state of
* @param oValue the new value to update the state with
*
* @throws InvocationTargetException if reflection method lookup fails
* @throws IllegalAccessException if reflection method lookup fails
*
* @since Coherence 12.3.1
*/
protected void updateComplex(Object oTarget, Object oValue)
throws InvocationTargetException, IllegalAccessException
{
String sCName = getCanonicalName();
Class clzTarget = oTarget.getClass();
Class[] aclzParam = new Class[]{oValue == null ? Object.class : oValue.getClass()};
Method method = isMethodUpdater()
? ClassHelper.findMethod(clzTarget, getMethodName(), aclzParam, false)
: null;
// check for case that name is a property modifier.
if (method == null)
{
String sBeanAttribute = sCName.length() == 0
? sCName
: Character.toUpperCase(sCName.charAt(0)) + sCName.substring(1);
String sBeanSetter = "set" + sBeanAttribute;
method = ClassHelper.findMethod(
clzTarget, sBeanSetter, aclzParam, false);
}
if (method == null)
{
if (oTarget instanceof Map)
{
m_targetPrev = new TargetReflectionDescriptor(clzTarget);
((Map) oTarget).put(sCName, oValue);
return;
}
}
else
{
// only check if reflection is allowed when method is non null and not cached from previous update
if (!ClassHelper.isReflectionAllowed(oTarget))
{
throw new IllegalArgumentException("The type, " + clzTarget.getName() +
", is disallowed as a reflection target by the current " +
"reflection filter configuration");
}
m_targetPrev = new TargetReflectionDescriptor(clzTarget, method);
}
method.invoke(oTarget, oValue);
}
/**
* Compute the canonical name for this updater.
*
* If sName
does not end with a {@link #METHOD_SUFFIX}, it
* is the canonical name of a property.
* If sName
begins with {@link #BEAN_MODIFIER_PREFIX} and
* ends with {@link #METHOD_SUFFIX}, the canonical name is sName
* value with prefix and suffix removed and the canonical name is for a property.
* Otherwise, the canonical name is the sName
and refers to
* method name.
*
* @param sName a method or property name
*
* @return return canonical name of sName
*/
public static String computeCanonicalName(String sName)
{
final int PREFIX_LEN = BEAN_MODIFIER_PREFIX.length();
final int SUFFIX_LEN = METHOD_SUFFIX.length();
String sNameCanonical = null;
int nNameLength = sName == null ? 0 : sName.length();
// map the "setFoo()" case to property "foo"
if ((nNameLength > PREFIX_LEN + SUFFIX_LEN) && sName.startsWith(BEAN_MODIFIER_PREFIX) && sName.endsWith(METHOD_SUFFIX))
{
sNameCanonical = Character.toLowerCase(sName.charAt(PREFIX_LEN)) +
sName.substring(PREFIX_LEN + 1, nNameLength - SUFFIX_LEN);
}
if (sNameCanonical == null)
{
sNameCanonical = sName;
}
return sNameCanonical;
}
/**
* Return a {@link ValueUpdater} for sNames
*
* @param sNames property or setter method name;
* this parameter can also be a dot-delimited sequence
* of property and/or method names which would result in using a
* {@link com.tangosol.util.extractor.CompositeUpdater}
* @return ValueUpdater for sNames
*/
public static ValueUpdater createUpdater(String sNames)
{
return sNames.indexOf('.') < 0
? new UniversalUpdater(sNames)
: new CompositeUpdater(sNames);
}
// ----- Object methods -------------------------------------------------
/**
* Compare the UniversalUpdater with another object to determine
* equality. Compare by canonical names when both are non-null.
*
* @return true iff this UniversalUpdater and the passed object are
* equivalent ReflectionUpdaters
*/
public boolean equals(Object o)
{
if (o == this)
{
return true;
}
else if (o instanceof UniversalUpdater)
{
String sCNameThat = ((UniversalUpdater) o).getCanonicalName();
String sCNameThis = getCanonicalName();
// optimization of String.equals() - see the assignment below
if (sCNameThis == sCNameThat)
{
return true;
}
else if (Base.equals(sCNameThis, sCNameThat))
{
// encourage future comparisons to succeed via simple reference equality checks
m_sNameCanon = sCNameThat;
return true;
}
}
return false;
}
/**
* Determine a hash value for the UniversalUpdater object according to
* the general {@link Object#hashCode()} contract.
*
* @return an integer hash value for this UniversalUpdater object
*/
public int hashCode()
{
return getCanonicalName().hashCode();
}
/**
* Provide a human-readable description of this ValueUpdater object.
*
* @return a human-readable description of this ValueUpdater object
*/
public String toString()
{
return getCanonicalName();
}
// ----- ExternalizableLite interface -----------------------------------
/**
* {@inheritDoc}
*/
public void readExternal(DataInput in)
throws IOException
{
m_sName = in.readUTF();
}
/**
* {@inheritDoc}
*/
public void writeExternal(DataOutput out)
throws IOException
{
out.writeUTF(m_sName);
}
// ----- PortableObject interface ---------------------------------------
/**
* {@inheritDoc}
*/
public void readExternal(PofReader in)
throws IOException
{
m_sName = in.readString(0);
}
/**
* {@inheritDoc}
*/
public void writeExternal(PofWriter out)
throws IOException
{
out.writeString(0, m_sName);
}
// ----- Serializable methods -------------------------------------------
/**
* See {@link java.io.Serializable} for documentation on this method.
*
* @param inputStream the input stream
*
* @throws NotSerializableException, ClassNotFoundException, IOException
*
* @since 12.2.1.4.22
*/
private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException
{
if (inputStream instanceof ResolvingObjectInputStream)
{
// the input stream was created by ExternalizableHelper; proceed with deserialization
inputStream.defaultReadObject();
}
else
{
// this class is not intended for "external" use
throw new NotSerializableException();
}
}
// ----- constants ------------------------------------------------------
/**
* JavaBean property modifier prefix.
*/
static final public String BEAN_MODIFIER_PREFIX = "set";
/**
* If {@link #m_sName} ends with this suffix, it represents a method name.
*/
public final static String METHOD_SUFFIX = "()";
// ----- data members ---------------------------------------------------
/**
* A method name, or a property name.
*/
@JsonbProperty("name")
protected String m_sName;
/**
* Canonical name for {@link UniversalUpdater}.
*/
private transient String m_sNameCanon;
/**
* Cached reflection computations for previous target parameter.
* This cache enables very fast reference based matching in a homogeneous cache.
*/
private transient TargetReflectionDescriptor m_targetPrev;
}