/**
* Copyright 2011 multibit.org
*
* Licensed under the MIT license (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.php
*
* 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 net.bither.platform.builder.mac;
import net.bither.platform.handler.GenericHandler;
import net.bither.platform.listener.GenericEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* <p>Base class to provide the following to Mac invocation handlers:</p>
* <ul>
* <li>Provision of standard template handling for events <strong>with responses</strong></li>
* </ul>
* <p>Template keys are:<br/>
* <ul>
* <li><code>H</code>: The generic handler class (e.g. {@link net.bither.platform.builder.mac.QuitHandlerInvocationHandler}</li>
* <li><code>E</code>: The generic event class (e.g. {@link net.bither.platform.listener.GenericQuitEvent}</li>
* <li><code>R</code>: The generic response class (e.g. {@link net.bither.platform.listener.GenericQuitResponse}</li>
* </ul>
*
* @since 0.3.0
*/
public abstract class BaseMacResponseInvocationHandler<H extends GenericHandler, E extends GenericEvent, R extends GenericEvent> implements InvocationHandler {
private static final Logger log = LoggerFactory.getLogger(QuitHandlerInvocationHandler.class);
private final H genericHandler;
private final Class<E> genericEventClass;
private final Class<R> genericResponseClass;
BaseMacResponseInvocationHandler(H genericHandler, Class<E> genericEventClass, Class<R> genericResponseClass) {
this.genericHandler = genericHandler;
this.genericEventClass = genericEventClass;
this.genericResponseClass = genericResponseClass;
}
/**
* Handles the invocation
*
* @param object The object
* @param nativeMethod The native method
* @param objects The arguments
* @return The result of the call
* @throws Throwable If something goes wrong
*/
@Override
@SuppressWarnings("unchecked")
public Object invoke(Object object, Method nativeMethod, Object[] objects) throws Throwable {
log.debug("Invoked response method. NativeMethod={}, method args length={}", nativeMethod.getName(), objects.length);
// Create a bi-directional generic event using based on 2 parameters (the native event, the native response)
// Require unchecked casts here to avoid this issue:
// http://blog.sarathonline.com/2010/08/maven-only-type-parameters-of-x-cannot.html
E event = (E) createGenericEvent(objects[0]);
R response = (R) createGenericResponse(objects[1]);
try {
log.debug("Created event {}", genericEventClass.getSimpleName());
// Access the equivalent method on the generic handler (e.g. handleQuitRequestWith(GenericQuitEvent event, GenericQuitResponse resonse))
Method method = genericHandler.getClass().getMethod(nativeMethod.getName(), new Class[]{genericEventClass, genericResponseClass});
// Invoke the method passing in the event (e.g. GenericURIEvent)
log.debug("Invoking {}.{}({},{}) ", new Object[]{genericHandler.getClass().getSimpleName(), method.getName(), method.getParameterTypes()[0].getSimpleName(), method.getParameterTypes()[1].getSimpleName()});
return method.invoke(genericHandler, event, response);
} catch (NoSuchMethodException e) {
log.warn("Got a NoSuchMethodException. Method = '" + nativeMethod.getName() + "'");
e.printStackTrace();
if (nativeMethod.getName().equals("equals") && objects.length == 1) {
return object == objects[0];
}
return null;
}
}
/**
* <p>Create a proxy instance of {@link net.bither.platform.listener.GenericEvent}
* that delegates to the native Apple event</p>
*
* @param nativeEvent The Apple native event (e.g. OpenURIEvent)
* @return The generic event acting as a proxy to the native underlying event
*/
@SuppressWarnings("unchecked")
private <E> E createGenericEvent(final Object nativeEvent) {
log.debug("Building invocation handler. Native {} -> {}", nativeEvent.getClass().getSimpleName(), genericEventClass.getSimpleName());
// The invocation handler manages all method calls against the proxy
// Relies on the proxy having the same method signatures
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Method nativeMethod = nativeEvent.getClass().getMethod(method.getName(), method.getParameterTypes());
log.debug("Invoking method {}.{}", nativeEvent.getClass().getSimpleName(), method.getName());
return nativeMethod.invoke(nativeEvent, objects);
}
};
log.debug("Building proxy for generic event");
// Create a proxy that delegates the call to the native instance
// when the invocation handler is called against it's methods
// Must use the application ClassLoader
return (E) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[]{genericEventClass}, invocationHandler);
}
/**
* <p>Create a proxy instance of {@link net.bither.platform.listener.GenericEvent}
* that delegates to the native Apple event</p>
*
* @param nativeResponse The Apple native response (e.g. QuitResponse)
* @return The generic response acting as a proxy to the native underlying response
*/
@SuppressWarnings("unchecked")
private <R> R createGenericResponse(final Object nativeResponse) {
log.debug("Building response invocation handler. Native {} -> {}", nativeResponse.getClass().getSimpleName(), genericResponseClass.getSimpleName());
// The invocation handler manages all method calls against the proxy
// Relies on the proxy having the same method signatures
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Method nativeMethod = nativeResponse.getClass().getMethod(method.getName(), method.getParameterTypes());
log.debug("Invoking method {}.{}", nativeResponse.getClass().getSimpleName(), method.getName());
return nativeMethod.invoke(nativeResponse, objects);
}
};
log.debug("Building proxy for generic event");
// Create a proxy that delegates the call to the native instance
// when the invocation handler is called against it's methods
// Must use the application ClassLoader
return (R) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[]{genericResponseClass}, invocationHandler);
}
}