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

net.freeutils.util.DelegatingProxy Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright © 2003-2024 Amichai Rothman
 *
 *  This file is part of JElementary - the Java Elementary Utilities package.
 *
 *  JElementary is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JElementary is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with JElementary.  If not, see .
 *
 *  For additional info see https://www.freeutils.net/source/jelementary/
 */

package net.freeutils.util;

import java.lang.reflect.*;
import java.util.*;

/**
 * The {@code DelegatingProxy} class wraps a delegation target object using a proxy class.
 * 

* The default behavior of this class is to invoke any called method * of the proxy interfaces on the target object. *

* However, if the self-invoke mechanism is specified during initialization, * then any method that is implemented in this class (or its subclass) with * the exact same signature as an interface method will be invoked directly * on this instance instead of invoking it on the target class, effectively * allowing target methods to be overridden by this class. *

* Such overriding methods can internally invoke the original methods directly * on the {@link #getTarget target} object if necessary. *

* Alternatively, a subclass can override the {@link #invoke} method itself * to customize the invocation mechanism. * * @param the type of the proxy class */ public class DelegatingProxy implements InvocationHandler { /** * Holds the data for an overridden method. */ protected static class MethodOverride { protected final int hash; protected final Method method; protected final Method override; /** * Constructs a new MethodOverride instance. * * @param method the overridden method * @param overload the overriding method * @param hash the hash code used to look up the method */ public MethodOverride(Method method, Method overload, int hash) { this.method = method; this.override = overload; this.hash = hash; } } protected final T target; protected final T proxy; protected final MethodOverride[] overrides; /** * Constructs a {@link DelegatingProxy} for the given target. * * @param selfInvoke if true, any invocation of a proxy method will invoke a * method of this object with an identical signature if one exists; * otherwise, it is invoked directly on the target * @param target the delegated target object * @param interfaces the list of interfaces for the proxy class to implement */ public DelegatingProxy(boolean selfInvoke, T target, Class... interfaces) { int methodCount = 0; for (Class i : interfaces) methodCount += i.getMethods().length; this.target = target; this.proxy = Reflect.newProxyInstance(this, interfaces); this.overrides = selfInvoke ? createLookup(canonizeProxyMethods(getOverridingMethods(interfaces)), methodCount) : null; } /** * Returns the proxy object. * * @return the proxy object */ public T getProxy() { return proxy; } /** * Returns the original target object. *

* This can be used to invoke methods directly on the target. * * @return the target object */ public T getTarget() { return target; } /** * Creates a lookup hashtable for efficient method override lookups. *

* Unfortunately, Method.hashCode/equals are not too efficient and * HashMap isn't flexible enough to override them without also * generating a lot of garbage for custom key objects. *

* Instead, we make our own simple but efficient read-only hashtable * using a spacious array, cached method name hash and linear probing, * resulting in a fairly efficient lookup (reference comparison on * canonized methods, cached name-only hashcode comparison to quickly * skip slots during linear probing, power-of-two masking for modulo, etc.) * * @param overridesMap the map of overridden methods for which the lookup * hashtable is created * @param totalMethodCount the total number of methods that may be looked * up in this hashtable (including ones that are not overridden) * @return the lookup hashtable */ protected MethodOverride[] createLookup(Map overridesMap, int totalMethodCount) { // table size is always larger than map (loops always end on empty slot), // power of two for quick mask instead of modulo, and spacious (compared // to total lookup method count, not only overridden method count), // to make collisions less likely and linear probes short totalMethodCount = Math.max(totalMethodCount, overridesMap.size()); int size = Integer.highestOneBit(totalMethodCount) << 3; int mask = size - 1; MethodOverride[] overrides = new MethodOverride[size]; for (Map.Entry e : overridesMap.entrySet()) { int hash = e.getKey().getName().hashCode(); // quick hash int i = hash & mask; // index while (overrides[i] != null) // linear probing to find empty slot i = (i + 1) & mask; // wrap around overrides[i] = new MethodOverride(e.getKey(), e.getValue(), hash); // store override data } return overrides; } /** * Looks up an overridden method in the previously created * {@link #createLookup lookup hashtable}. * * @param method the method to look up * @return the overriding method, or null if the given method has no override */ public Method lookup(Method method) { MethodOverride[] overrides = this.overrides; if (overrides != null) { int mask = overrides.length - 1; int hash = method.getName().hashCode(); int i = hash & mask; while (true) { MethodOverride override = overrides[i]; if (override == null) return null; // compare methods as efficiently as we can - // reference equality for canonized methods, // cached hashcode comparison for quick fail, // and full equals only as last resort if (method == override.method || hash == override.hash && method.equals(override.method)) return override.override; i = (i + 1) & mask; } } return null; } /** * Creates a map from methods of the given interfaces * to methods of this object that have an identical signature. * * @param interfaces classes of methods to lookup in this object * @return a map from methods of the given interfaces to methods of this object * @throws RuntimeException if two methods have the same signature * but declare different thrown exceptions or different return types */ protected Map getOverridingMethods(Class... interfaces) { // iterate over all interfaces methods and look for same signature in this class Map map = new HashMap<>(); for (Class i : interfaces) { for (Method m : i.getMethods()) { Method override = Reflect.getMethod( this.getClass(), m.getName(), m.getParameterTypes()); if (override != null) { if (!Containers.equalsIgnoreOrder( override.getExceptionTypes(), m.getExceptionTypes())) throw new RuntimeException( "delegated methods must declare identical exceptions: " + m); if (!override.getReturnType().equals(m.getReturnType())) throw new RuntimeException( "delegated methods must have identical return type"); override.setAccessible(true); map.put(m, override); } } } return map; } /** * Creates a method map equivalent to the given one, but with keys replaced * with equivalent methods that exist as fields of the proxy class. *

* These fields are the ones actually passed in invoke method calls, * which allows for very quick reference equality checks rather than * the slower {@link Method#equals} invocations. *

* This is an implementation-specific optimization dependent on the current * proxy implementation of the OpenJDK/Oracle JVM, however it should be * harmless on other implementations (if the canonical keys cannot be found, * the original key is retained). * * @param methods the methods map whose keys are canonized * @return a new map equivalent to the given map, but with canonized keys */ protected Map canonizeProxyMethods(Map methods) { // first create a lookup map with the replacements to use Map proxyMethods = new HashMap<>(); for (Field field : proxy.getClass().getDeclaredFields()) { if (field.getType() == Method.class) { try { field.setAccessible(true); // may be inaccessible in newer JDKs Method method = (Method)field.get(proxy); proxyMethods.put(method, method); } catch (Exception ignore) {} } } // then replace the keys where possible Map canonized = new HashMap<>(methods.size()); for (Map.Entry e : methods.entrySet()) { Method proxyMethod = proxyMethods.get(e.getKey()); canonized.put(proxyMethod != null ? proxyMethod : e.getKey(), e.getValue()); } return canonized; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // invoke either the overriding method in this class, // or the original method on target Method override = lookup(method); return override != null ? override.invoke(this, args) : method.invoke(target, args); } catch (InvocationTargetException ite) { throw ite.getCause(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy