/*
* ============================================================================
* GNU Lesser General Public License
* ============================================================================
*
* Beanlet - JSE Application Container.
* Copyright (C) 2006 Leon van Zantvoort
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Leon van Zantvoort
* 243 Acalanes Drive #11
* Sunnyvale, CA 94086
* USA
*
* zantvoort@users.sourceforge.net
* http://beanlet.org
*/
package org.beanlet.impl;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.beanlet.BeanletValidationException;
import org.beanlet.Operation;
import org.beanlet.annotation.AnnotationDomain;
import org.beanlet.annotation.ElementAnnotation;
import org.beanlet.annotation.MethodElement;
import org.beanlet.plugin.BeanletConfiguration;
import org.beanlet.common.Beanlets;
import org.beanlet.common.event.OperationEventImpl;
import org.jargo.ComponentConfiguration;
import org.jargo.Event;
/**
* @author Leon van Zantvoort
*/
public final class Operations {
private static final Map<ComponentConfiguration<?>, Operations> cache =
new HashMap<ComponentConfiguration<?>, Operations>();
public static final Operations getInstance() {
return getInstance(null);
}
public static final synchronized Operations getInstance(
final ComponentConfiguration<?> configuration) {
Operations operations = cache.get(configuration);
if (operations == null) {
if (configuration instanceof BeanletConfiguration) {
operations = new Operations((BeanletConfiguration<?>) configuration);
} else {
operations = new Operations();
}
configuration.getComponentUnit().addDestroyHook(new Runnable() {
public void run() {
synchronized (Operations.class) {
cache.remove(configuration);
}
}
});
cache.put(configuration, operations);
}
return operations;
}
private final BeanletConfiguration<?> configuration;
private final ProxyMethods proxyMethods;
private final Map<Object, Method> namedMethods;
private final Set<Method> methods;
private final List<Class<?>> interfaces;
private final boolean proxy;
private Operations() {
this.configuration = null;
this.proxyMethods = ProxyMethods.getInstance();
this.namedMethods = new HashMap<Object, Method>();
this.methods = new HashSet<Method>();
this.interfaces = new ArrayList<Class<?>>();
this.proxy = false;
}
private Operations(BeanletConfiguration configuration) {
this.configuration = configuration;
this.proxyMethods = ProxyMethods.getInstance(configuration);
this.namedMethods = new HashMap<Object, Method>();
Beanlets beanlets = Beanlets.getInstance(configuration);
AnnotationDomain domain = configuration.getAnnotationDomain();
Class<?> type = configuration.getType();
this.proxy = beanlets.isPropxy();
if (beanlets.isVanilla()) {
// Automatically includes all implemented interfaces.
namedMethods.putAll(getNamedMethods(configuration.getType()));
} else {
for (Class<?> inf : beanlets.getInterfaces()) {
// Add all methods of the interface as operations if it is
// implemented by type.
if (inf.isAssignableFrom(type)) {
namedMethods.putAll(getNamedMethods(inf));
}
}
}
for (ElementAnnotation<MethodElement, Operation> e :
domain.getDeclaration(Operation.class).
getTypedElements(MethodElement.class, type)) {
Operation operation = e.getAnnotation();
Method method = e.getElement().getMethod();
final String operationName;
if (!operation.name().equals("")) {
operationName = operation.name();
} else {
operationName = method.getName();
}
Object key = Arrays.asList(operationName,
Arrays.asList(method.getParameterTypes()));
Method org = namedMethods.put(key, method);
if (org != null) {
if (!org.equals(method)) {
throw new BeanletValidationException(configuration.getComponentName(),
"Operation is defined by multiple methods: '" + operationName + "'.");
}
}
}
this.interfaces = new ArrayList<Class<?>>(beanlets.getInterfaces());
if (proxyMethods.getProxyMethod() == null) {
// Check if all methods are exposed as operations.
List<Class> classes = new ArrayList<Class>();
if (beanlets.isVanilla()) {
classes.add(configuration.getType());
}
classes.addAll(interfaces);
for (Class<?> cls : classes) {
for (Method method : cls.getMethods()) {
if (!cls.isInterface()) {
// Object's methods are excluded from check.
try {
Object.class.getMethod(
method.getName(),
method.getParameterTypes());
continue;
} catch (NoSuchMethodException e) {
}
}
Object key = Arrays.asList(method.getName(),
Arrays.asList(method.getParameterTypes()));
Method m = namedMethods.get(key);
final String t;
if (cls.isInterface()) {
t = "Interface";
} else {
t = "Superclass";
}
if (m == null) {
throw new BeanletValidationException(configuration.getComponentName(),
t + " method not exposed as operation: '" + method + "'.");
}
// Only check if method does not return void.
if (!method.getReturnType().equals(Void.TYPE)) {
if (!method.getReturnType().isAssignableFrom(m.getReturnType())) {
throw new BeanletValidationException(configuration.getComponentName(),
t + " method specifies invalid return type: '" + method + "'.");
}
}
}
}
}
Set<Method> tmp = new HashSet<Method>(namedMethods.values());
// It has all been checked, now add the remaining methods.
for (Class<?> inf : beanlets.getInterfaces()) {
if (!inf.isAssignableFrom(type)) {
tmp.addAll(Arrays.asList(inf.getMethods()));
}
}
this.methods = Collections.unmodifiableSet(tmp);
}
public Event getEvent(Method method, Object[] args) {
final Event event;
if (proxyMethods.getProxyMethod() != null ||
getMethod(method.getName(), method.getParameterTypes()) != null) {
event = new OperationEventImpl(method.getName(), method.getParameterTypes(), args);
} else {
event = null;
}
return event;
}
public List<Class<?>> getInterfaces() {
return Collections.unmodifiableList(interfaces);
}
public boolean isProxy() {
return proxy;
}
public Set<Method> getMethods() {
return methods;
}
/**
* Returns a method for the specified operation {@code name} and
* {@code parameterTypes}, or {@code null} if no method can be found. This
* method will not return the proxy method.
*/
public Method getMethod(String name, Class<?>[] parameterTypes) {
return namedMethods.get(Arrays.asList(name,
Arrays.asList(parameterTypes)));
}
private Map<Object, Method> getNamedMethods(Class<?> cls) {
Map<Object, Method> namedMethods = new HashMap<Object, Method>();
for (Method method : cls.getMethods()) {
try {
Method classMethod = configuration.getType().getMethod(
method.getName(),
method.getParameterTypes());
if (!cls.isInterface()) {
try {
Object.class.getMethod(
method.getName(),
method.getParameterTypes());
continue;
} catch (NoSuchMethodException e) {
}
}
namedMethods.put(Arrays.asList(classMethod.getName(),
Arrays.asList(classMethod.getParameterTypes())),
classMethod);
} catch (NoSuchMethodException e) {
assert false : method;
}
}
return namedMethods;
}
}