/**
*
* Copyright 2003-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.geronimo.gbean;
import java.beans.Introspector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @version $Rev$ $Date$
*/
public class GBeanInfoBuilder {
public static GBeanInfoBuilder createStatic(Class gbeanType) {
if (gbeanType == null) throw new NullPointerException("gbeanType is null");
return createStatic(gbeanType, gbeanType.getName(), gbeanType, null, null);
}
public static GBeanInfoBuilder createStatic(Class gbeanType, String j2eeType) {
if (gbeanType == null) throw new NullPointerException("gbeanType is null");
return createStatic(gbeanType, gbeanType.getName(), gbeanType, null, j2eeType);
}
public static GBeanInfoBuilder createStatic(String name, Class gbeanType) {
if (gbeanType == null) throw new NullPointerException("gbeanType is null");
return createStatic(gbeanType, name, gbeanType, null, null);
}
public static GBeanInfoBuilder createStatic(String name, Class gbeanType, String j2eeType) {
if (gbeanType == null) throw new NullPointerException("gbeanType is null");
return createStatic(gbeanType, name, gbeanType, null, j2eeType);
}
public static GBeanInfoBuilder createStatic(Class gbeanType, GBeanInfo source) {
if (gbeanType == null) throw new NullPointerException("gbeanType is null");
return createStatic(gbeanType, gbeanType.getName(), gbeanType, source, null);
}
public static GBeanInfoBuilder createStatic(Class gbeanType, GBeanInfo source, String j2eeType) {
if (gbeanType == null) throw new NullPointerException("gbeanType is null");
return createStatic(gbeanType, gbeanType.getName(), gbeanType, source, j2eeType);
}
public static GBeanInfoBuilder createStatic(String name, Class gbeanType, GBeanInfo source) {
if (name == null) throw new NullPointerException("name is null");
if (gbeanType == null) throw new NullPointerException("gbeanType is null");
return createStatic(gbeanType, name, gbeanType, source, null);
}
//
// These methods are used by classes that declare a GBeanInfo for another class
//
public static GBeanInfoBuilder createStatic(Class sourceClass, Class gbeanType) {
if (gbeanType == null) throw new NullPointerException("gbeanType is null");
return createStatic(sourceClass, gbeanType.getName(), gbeanType, null, null);
}
public static GBeanInfoBuilder createStatic(Class sourceClass, Class gbeanType, String j2eeType) {
if (sourceClass == null) throw new NullPointerException("sourceClass is null");
if (gbeanType == null) throw new NullPointerException("gbeanType is null");
return createStatic(sourceClass, gbeanType.getName(), gbeanType, null, j2eeType);
}
public static GBeanInfoBuilder createStatic(Class sourceClass, String name, Class gbeanType, GBeanInfo source, String j2eeType) {
if (sourceClass == null) throw new NullPointerException("sourceClass is null");
if (name == null) throw new NullPointerException("name is null");
if (gbeanType == null) throw new NullPointerException("gbeanType is null");
return new GBeanInfoBuilder(sourceClass.getName(), name, gbeanType, source, j2eeType);
}
public static final String DEFAULT_J2EE_TYPE = "GBean"; //NameFactory.GERONIMO_SERVICE
private static final Class[] NO_ARGS = {};
/**
* The class from which the info can be retrieved using GBeanInfo.getGBeanInfo(className, classLoader)
*/
private final String sourceClass;
private final String name;
private final String j2eeType;
private final Class gbeanType;
private final Map attributes = new HashMap();
private GConstructorInfo constructor = new GConstructorInfo();
private final Map operations = new HashMap();
private final Map references = new HashMap();
private final Set interfaces = new HashSet();
public GBeanInfoBuilder(Class gbeanType) {
this(checkNotNull(gbeanType).getName(), gbeanType, null, null);
}
public GBeanInfoBuilder(Class gbeanType, String j2eeType) {
this(checkNotNull(gbeanType).getName(), gbeanType, null, j2eeType);
}
public GBeanInfoBuilder(String name, Class gbeanType) {
this(name, checkNotNull(gbeanType), null, null);
}
public GBeanInfoBuilder(String name, Class gbeanType, String j2eeType) {
this(name, checkNotNull(gbeanType), null, j2eeType);
}
public GBeanInfoBuilder(Class gbeanType, GBeanInfo source) {
this(checkNotNull(gbeanType).getName(), gbeanType, source);
}
public GBeanInfoBuilder(Class gbeanType, GBeanInfo source, String j2eeType) {
this(checkNotNull(gbeanType).getName(), gbeanType, source, j2eeType);
}
//TODO remove this
/**
* @deprecated This will be removed in a future release
*/
public GBeanInfoBuilder(String name, ClassLoader classLoader) {
this(checkNotNull(name), loadClass(classLoader, name), GBeanInfo.getGBeanInfo(name, classLoader));
}
public GBeanInfoBuilder(String name, Class gbeanType, GBeanInfo source) {
this(name, gbeanType, source, null);
}
public GBeanInfoBuilder(String name, Class gbeanType, GBeanInfo source, String j2eeType) {
this(null, name, gbeanType, source, j2eeType);
}
private GBeanInfoBuilder(String sourceClass, String name, Class gbeanType, GBeanInfo source, String j2eeType) {
checkNotNull(name);
checkNotNull(gbeanType);
this.name = name;
this.gbeanType = gbeanType;
this.sourceClass = sourceClass;
if (source != null) {
for (Iterator i = source.getAttributes().iterator(); i.hasNext();) {
GAttributeInfo attributeInfo = (GAttributeInfo) i.next();
attributes.put(attributeInfo.getName(), attributeInfo);
}
for (Iterator i = source.getOperations().iterator(); i.hasNext();) {
GOperationInfo operationInfo = (GOperationInfo) i.next();
operations.put(new GOperationSignature(operationInfo.getName(),
operationInfo.getParameterList()), operationInfo);
}
for (Iterator iterator = source.getReferences().iterator(); iterator.hasNext();) {
GReferenceInfo referenceInfo = (GReferenceInfo) iterator.next();
references.put(referenceInfo.getName(), new RefInfo(referenceInfo.getReferenceType(), referenceInfo.getNameTypeName()));
}
for (Iterator iterator = source.getInterfaces().iterator(); iterator.hasNext();) {
String intf = (String) iterator.next();
interfaces.add(intf);
}
//in case subclass constructor has same parameters as superclass.
constructor = source.getConstructor();
}
if (j2eeType != null) {
this.j2eeType = j2eeType;
} else if (source != null) {
this.j2eeType = source.getJ2eeType();
} else {
this.j2eeType = DEFAULT_J2EE_TYPE; //NameFactory.GERONIMO_SERVICE
}
}
public void addInterface(Class intf) {
addInterface(intf, new String[0]);
}
//do not use beaninfo Introspector to list the properties. This method is primarily for interfaces,
//and it does not process superinterfaces. It seems to really only work well for classes.
public void addInterface(Class intf, String[] persistentAttributes) {
addInterface(intf, persistentAttributes, new String[0]);
}
public void addInterface(Class intf, String[] persistentAttributes, String[] manageableAttributes) {
Set persistentNames = new HashSet(Arrays.asList(persistentAttributes));
Set manageableNames = new HashSet(Arrays.asList(manageableAttributes));
Method[] methods = intf.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (isGetter(method)) {
String attributeName = getAttributeName(method);
GAttributeInfo attribute = (GAttributeInfo) attributes.get(attributeName);
String attributeType = method.getReturnType().getName();
if (attribute == null) {
attributes.put(attributeName,
new GAttributeInfo(attributeName,
attributeType,
persistentNames.contains(attributeName),
manageableNames.contains(attributeName),
method.getName(),
null));
} else {
if (!attributeType.equals(attribute.getType())) {
throw new IllegalArgumentException("Getter and setter type do not match: " + attributeName);
}
attributes.put(attributeName,
new GAttributeInfo(attributeName,
attributeType,
attribute.isPersistent(),
attribute.isManageable(),
method.getName(),
attribute.getSetterName()));
}
} else if (isSetter(method)) {
String attributeName = getAttributeName(method);
String attributeType = method.getParameterTypes()[0].getName();
GAttributeInfo attribute = (GAttributeInfo) attributes.get(attributeName);
if (attribute == null) {
attributes.put(attributeName,
new GAttributeInfo(attributeName,
attributeType,
persistentNames.contains(attributeName),
manageableNames.contains(attributeName),
null,
method.getName()));
} else {
if (!attributeType.equals(attribute.getType())) {
throw new IllegalArgumentException("Getter and setter type do not match: " + attributeName);
}
attributes.put(attributeName,
new GAttributeInfo(attributeName,
attributeType,
attribute.isPersistent(),
attribute.isManageable(),
attribute.getGetterName(),
method.getName()));
}
} else {
addOperation(new GOperationInfo(method.getName(), method.getParameterTypes()));
}
}
if(intf.isInterface()) {
addInterface(interfaces, intf);
}
}
private static void addInterface(Set set, Class intf) {
String name = intf.getName();
if(set.contains(name)) {
return;
}
set.add(name);
Class cls[] = intf.getInterfaces();
for (int i = 0; i < cls.length; i++) {
addInterface(set, cls[i]);
}
}
public void addAttribute(String name, Class type, boolean persistent) {
addAttribute(name, type.getName(), persistent, true);
}
public void addAttribute(String name, String type, boolean persistent) {
addAttribute(name, type, persistent, true);
}
public void addAttribute(String name, Class type, boolean persistent, boolean manageable) {
addAttribute(name, type.getName(), persistent, manageable);
}
public void addAttribute(String name, String type, boolean persistent, boolean manageable) {
String getter = searchForGetter(name, type, gbeanType);
String setter = searchForSetter(name, type, gbeanType);
addAttribute(new GAttributeInfo(name, type, persistent, manageable, getter, setter));
}
public void addAttribute(GAttributeInfo info) {
attributes.put(info.getName(), info);
}
public void setConstructor(GConstructorInfo constructor) {
assert constructor != null;
this.constructor = constructor;
}
public void setConstructor(String[] names) {
constructor = new GConstructorInfo(names);
}
public void addOperation(GOperationInfo operationInfo) {
operations.put(new GOperationSignature(operationInfo.getName(), operationInfo.getParameterList()), operationInfo);
}
public void addOperation(String name) {
addOperation(new GOperationInfo(name, NO_ARGS));
}
public void addOperation(String name, Class[] paramTypes) {
addOperation(new GOperationInfo(name, paramTypes));
}
public void addReference(GReferenceInfo info) {
references.put(info.getName(), new RefInfo(info.getReferenceType(), info.getNameTypeName()));
}
/**
* Add a reference to another GBean or collection of GBeans
* @param name the name of the reference
* @param type The proxy type of the GBean or objects in a ReferenceCollection
* @param namingType the string expected as the type component of the name. For jsr-77 names this is the j2eeType value
*/
public void addReference(String name, Class type, String namingType) {
references.put(name, new RefInfo(type.getName(), namingType));
}
public void addReference(String name, Class type) {
references.put(name, new RefInfo(type.getName(), null));
}
public GBeanInfo getBeanInfo() {
// get the types of the constructor args
// this also verifies that we have a valid constructor
Map constructorTypes = getConstructorTypes();
// build the reference infos now that we know the constructor types
Set referenceInfos = new HashSet();
for (Iterator iterator = references.entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String referenceName = (String) entry.getKey();
RefInfo refInfo = (RefInfo) entry.getValue();
String referenceType = refInfo.getJavaType();
String namingType = refInfo.getNamingType();
String proxyType = (String) constructorTypes.get(referenceName);
String setterName = null;
if (proxyType == null) {
Method setter = searchForSetterMethod(referenceName, referenceType, gbeanType);
if (setter == null) {
setter = searchForSetterMethod(referenceName, Collection.class.getName(), gbeanType);
if (setter == null) {
throw new InvalidConfigurationException("Reference must be a constructor argument or have a setter: name=" + referenceName);
}
}
proxyType = setter.getParameterTypes()[0].getName();
setterName = setter.getName();
}
if (!proxyType.equals(Collection.class.getName()) && !proxyType.equals(referenceType)) {
throw new InvalidConfigurationException("Reference proxy type must be Collection or " + referenceType + ": name=" + referenceName);
}
referenceInfos.add(new GReferenceInfo(referenceName, referenceType, proxyType, setterName, namingType));
}
return new GBeanInfo(sourceClass, name, gbeanType.getName(), j2eeType, attributes.values(), constructor, operations.values(), referenceInfos, interfaces);
}
private Map getConstructorTypes() throws InvalidConfigurationException {
List arguments = constructor.getAttributeNames();
String[] argumentTypes = new String[arguments.size()];
boolean[] isReference = new boolean[arguments.size()];
for (int i = 0; i < argumentTypes.length; i++) {
String argumentName = (String) arguments.get(i);
if (attributes.containsKey(argumentName)) {
GAttributeInfo attribute = (GAttributeInfo) attributes.get(argumentName);
argumentTypes[i] = attribute.getType();
isReference[i] = false;
} else if (references.containsKey(argumentName)) {
argumentTypes[i] = ((RefInfo) references.get(argumentName)).getJavaType();
isReference[i] = true;
}
}
Constructor[] constructors = gbeanType.getConstructors();
Set validConstructors = new HashSet();
for (int i = 0; i < constructors.length; i++) {
Constructor constructor = constructors[i];
if (isValidConstructor(constructor, argumentTypes, isReference)) {
validConstructors.add(constructor);
}
}
if (validConstructors.isEmpty()) {
throw new InvalidConfigurationException("Could not find a valid constructor for GBean: " + gbeanType.getName());
}
if (validConstructors.size() > 1) {
throw new InvalidConfigurationException("More then one valid constructors found for GBean: " + gbeanType.getName());
}
Map constructorTypes = new HashMap();
Constructor constructor = (Constructor) validConstructors.iterator().next();
Class[] parameterTypes = constructor.getParameterTypes();
Iterator argumentIterator = arguments.iterator();
for (int i = 0; i < parameterTypes.length; i++) {
String parameterType = parameterTypes[i].getName();
String argumentName = (String) argumentIterator.next();
constructorTypes.put(argumentName, parameterType);
}
return constructorTypes;
}
private static String searchForGetter(String name, String type, Class gbeanType) throws InvalidConfigurationException {
Method getterMethod = null;
// no explicit name give so we must search for a name
String getterName = "get" + name;
String isName = "is" + name;
Method[] methods = gbeanType.getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getParameterTypes().length == 0 && methods[i].getReturnType() != Void.TYPE
&& (getterName.equalsIgnoreCase(methods[i].getName()) || isName.equalsIgnoreCase(methods[i].getName()))) {
// found it
getterMethod = methods[i];
break;
}
}
// if the return type of the getter doesn't match, throw an exception
if (getterMethod != null && !type.equals(getterMethod.getReturnType().getName())) {
throw new InvalidConfigurationException("Incorrect return type for getter method:" +
" name=" + name +
", targetClass=" + gbeanType.getName() +
", getter type=" + getterMethod.getReturnType() +
", expected type=" + type);
}
if (getterMethod == null) {
return null;
}
return getterMethod.getName();
}
private static String searchForSetter(String name, String type, Class gbeanType) throws InvalidConfigurationException {
Method method = searchForSetterMethod(name, type, gbeanType);
if (method == null) {
return null;
}
return method.getName();
}
private static Method searchForSetterMethod(String name, String type, Class gbeanType) throws InvalidConfigurationException {
// no explicit name give so we must search for a name
String setterName = "set" + name;
Method[] methods = gbeanType.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (method.getParameterTypes().length == 1 &&
method.getParameterTypes()[0].getName().equals(type) &&
method.getReturnType() == Void.TYPE &&
setterName.equalsIgnoreCase(method.getName())) {
return method;
}
}
// a setter is not necessary for this attribute
return null;
}
private static boolean isValidConstructor(Constructor constructor, String[] argumentTypes, boolean[] isReference) {
Class[] parameterTypes = constructor.getParameterTypes();
// same number of parameters?
if (parameterTypes.length != argumentTypes.length) {
return false;
}
// is each parameter the correct type?
for (int i = 0; i < parameterTypes.length; i++) {
String parameterType = parameterTypes[i].getName();
if (isReference[i]) {
// reference: does type match
// OR is it a java.util.Collection
// OR is it a java.util.Set?
if (!parameterType.equals(argumentTypes[i]) &&
!parameterType.equals(Collection.class.getName()) &&
!parameterType.equals(Set.class.getName())) {
return false;
}
} else {
// attribute: does type match?
if (!parameterType.equals(argumentTypes[i])) {
return false;
}
}
}
return true;
}
private String getAttributeName(Method method) {
String name = method.getName();
String attributeName = (name.startsWith("get") || name.startsWith("set")) ? name.substring(3) : name.substring(2);
attributeName = Introspector.decapitalize(attributeName);
return attributeName;
}
private boolean isSetter(Method method) {
return method.getName().startsWith("set") && method.getParameterTypes().length == 1;
}
private static boolean isGetter(Method method) {
String name = method.getName();
return (name.startsWith("get") || name.startsWith("is")) && method.getParameterTypes().length == 0;
}
/**
* Checks whether or not the input argument is null; otherwise it throws
* {@link IllegalArgumentException}.
*
* @param clazz the input argument to validate
* @throws IllegalArgumentException if input is null
*/
private static Class checkNotNull(final Class clazz) {
if (clazz == null) {
throw new IllegalArgumentException("null argument supplied");
}
return clazz;
}
/**
* Checks whether or not the input argument is null; otherwise it throws
* {@link IllegalArgumentException}.
*
* @param string the input argument to validate
* @throws IllegalArgumentException if input is null
*/
private static String checkNotNull(final String string) {
if (string == null) {
throw new IllegalArgumentException("null argument supplied");
}
return string;
}
private static Class loadClass(ClassLoader classLoader, String name) {
try {
return classLoader.loadClass(name);
} catch (ClassNotFoundException e) {
throw new InvalidConfigurationException("Could not load class " + name, e);
}
}
private static class RefInfo {
private final String javaType;
private final String namingType;
public RefInfo(String javaType, String namingType) {
this.javaType = javaType;
this.namingType = namingType;
}
public String getJavaType() {
return javaType;
}
public String getNamingType() {
return namingType;
}
}
}