/* * Copyright (C) 2014 GG-Net GmbH - Oliver Günther * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package eu.ggnet.saft.core; import java.lang.reflect.*; import java.util.HashMap; import java.util.Map; /** * Creates an intermediate proxy object for a given interface. All calls are * cached. * * @author Martin Ankerl (martin.ankerl@gmail.at) * @version $Rev$ */ public class CachedProxy { /** * Query object to find out if the exact same query was already made. * * @author Martin Ankerl (martin.ankerl@profactor.at) * @version $Rev$ */ private static final class Args { private final Method mMethod; private final Object[] mArgs; private final int mHash; public Args(final Method m, final Object[] args) { mMethod = m; mArgs = args; // precalculate hash mHash = calcHash(); } /** * Method and all the arguments have to be equal. Assumes that obj is of * the same type. */ @Override public boolean equals(final Object obj) { final Args other = (Args)obj; if ( !mMethod.equals(other.mMethod) ) { return false; } if ( mArgs != null ) { for (int i = 0; i < mArgs.length; ++i) { Object o1 = mArgs[i]; Object o2 = other.mArgs[i]; if ( !(o1 == null ? o2 == null : o1.equals(o2)) ) { return false; } } } return true; } /** * Use the precalculated hash. */ @Override public int hashCode() { return mHash; } /** * Try to use a good & fast hash function here. */ public int calcHash() { int h = mMethod.hashCode(); if ( mArgs != null ) { for (final Object o : mArgs) { h = h * 65599 + (o == null ? 0 : o.hashCode()); } } return h; } } /** * Creates an intermediate proxy object that uses cached results if * available, otherwise calls the given code. * * @param <T> * Type of the class. * @param clazz * @param code * The actual calculation code that should be cached. * @return The proxy. */ @SuppressWarnings("unchecked") public static <T> T create(Class<T> clazz, final T code) { // create the cache final Map<Args, Object> argsToOutput = new HashMap<>(); // proxy for the interface T return (T)Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, new InvocationHandler() { @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { final Args input = new Args(method, args); Object result = argsToOutput.get(input); // check containsKey to support null values if ( result == null && !argsToOutput.containsKey(input) ) { // make sure exceptions are handled transparently try { result = method.invoke(code, args); argsToOutput.put(input, result); } catch (InvocationTargetException e) { throw e.getTargetException(); } } return result; } }); } }