/*
* Copyright 2007 Luigi Dell'Aquila (luigi.dellaquila@assetdata.it)
*
* 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.reverspring.engine;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.reverspring.annotations.ClassProperties;
import org.reverspring.strategy.DescriptionStrategy;
import org.reverspring.strategy.annotation.AnnotationDescriptionStrategy;
import org.reverspring.xml.XmlGenerator;
/**
* This class represents an abstraction of a Spring IoC xml descriptor. To generate
* a Spring descriptor you should create a {@link BeanSet} object and add your Java beans
* with the method {@link BeanSet#addBeanDescriptor(Object, String)}. A {@link BeanSet}
* can be written on a Spring IoC descriptor using {@link XmlGenerator} class<br/><br/>
* See the example below
* <pre>
* Person me = new Person();
* Person you = new Person();
* ....
* BeanSet set = new BeanSet();
* set.addBeanDescriptor(me, "myId");
* set.addBeanDescriptor(you, "yourId");
* XmlGenerator.save(set, new File("./mySpringDescriptor.xml"));
* </pre>
* @see DescriptorGenerator
* @see XmlGenerator
* @author Luigi Dell'Aquila
*
*/
public class BeanSet {
private Set<String> assignedIds;
private List<BeanDescriptor> beans;
private Map<BeanDescriptor, ObjectBinding> beanToBinding;
private DescriptionStrategy strategy;
/**
* creates a BeanSet with the given DescriptionStrategy.
* @param strategy the strategy to be adopted to represent objects and fieds in the XML.
* If this parameter is null, the BeanSet will use the default strategy, that is
* {@link AnnotationDescriptionStrategy}
*/
public BeanSet(DescriptionStrategy strategy) {
this.beans = new ArrayList<BeanDescriptor>();
this.beanToBinding = new HashMap<BeanDescriptor, ObjectBinding>();
this.assignedIds = new HashSet<String>();
this.strategy=strategy;
if(this.strategy==null){
this.strategy=new AnnotationDescriptionStrategy();
}
}
/**
* creates a BeanSet with {@link AnnotationDescriptionStrategy} strategy
* @see BeanSet#BeanSet(DescriptionStrategy)
*/
public BeanSet() {
this(new AnnotationDescriptionStrategy());
}
/**
* returns the strategy used by this BeanSet
* @return the strategy used by this BeanSet
*/
public DescriptionStrategy getStrategy(){
return strategy;
}
/**
* this method adds a bean to the current BeanSet. The bean will be
* written in the Spring IoC descriptor
* @param bean the bean to be added to the set
* @param id the id that the bean will have in the xml
*/
public void addBeanDescriptor(Object bean, String id) {
newBeanDescriptor(bean, id);
}
public List<BeanDescriptor> getBeans() {
return beans;
}
/**
* gets all the properties that can be set in a Spring descriptor for this
* object
*
* @param o
* the object
* @return
*/
private Set<String> getSpringProperties(Object o) {
return strategy.getPropertyNames(o);
}
/**
* Check if the bean is a a singleton class
*
* @param o
* @return is singleton class
*/
private static boolean isSingletonClass(Object o) {
if (o == null)
return false;
ClassProperties annotation = o.getClass().getAnnotation(ClassProperties.class);
if (annotation == null) {
return false;
} else if (annotation.singletonClass() == true) {
return true;
}
return false;
}
// /**
// * gets the constructor that has to be used in the spring descriptor for this
// * object
// *
// * @param o
// * the object
// * @return
// * @deprecated use {@link DescriptionStrategy#getConstructor(Object)} instead
// */
// public Constructor<?> getSpringConstructor(Object o) {
// return strategy.getConstructor(o);
// }
/**
* gets the value of a property by the object (by getter method or directly)
*
* @param obj
* the object
* @param property
* the object name
* @return
*/
private static Object getPropertyValue(Object obj, String property) {
Object value = null;
// try to load through getter method
try {
String getter = "get" + property.substring(0, 1).toUpperCase() + property.substring(1);
Method method = obj.getClass().getMethod(getter);
value = method.invoke(obj, new Object[0]);
} catch (Exception e) {
value = null;
}
if (value == null) {
try {
String getter = "is" + property.substring(0, 1).toUpperCase() + property.substring(1);
Method method = obj.getClass().getMethod(getter);
value = method.invoke(obj, new Object[0]);
} catch (Exception e) {
value = null;
}
}
return value;
}
/**
*
* @param obj
* an objec
* @return true if the object is a Number, a String or a Boolean.
*/
private static boolean isBaseType(Object obj) {
if (obj == null)
return false;
if (obj instanceof String)
return true;
if (obj instanceof Number)
return true;
if (obj instanceof Boolean)
return true;
if (obj instanceof Class<?>)
return true;
return false;
}
private static boolean isSetType(Object obj) {
if (obj instanceof Set<?>)
return true;
return false;
}
private static boolean isListType(Object obj) {
if (obj instanceof List<?>)
return true;
if (obj.getClass().isArray())
return true; // the object is an array
return false;
}
private static boolean isSetOfBaseTypeType(Object obj) {
if (!(obj instanceof Set<?>))
return false;
Set<?> set = (Set<?>) obj;
for (Object element : set) {
if (!isBaseType(element))
return false;
}
return true;
}
private static boolean isListOfBaseTypeType(Object obj) {
if (!(obj instanceof List<?>))
return false;
List<?> set = (List<?>) obj;
for (Object element : set) {
if (!isBaseType(element))
return false;
}
return true;
}
/**
* @param obj
* @return
*/
private static boolean isMapType(Object obj) {
if (!(obj instanceof Map<?, ?>))
return false;
return true;
}
private static boolean isKeyBaseType(Map<?, ?> map) {
if (map == null)
return false;
boolean isBase = true;
for (Object key : map.keySet()) {
if (!isBaseType(key)) {
isBase = false;
break;
}
}
return isBase;
}
private static boolean isValueBaseType(Map<?, ?> map) {
if (map == null)
return false;
boolean isBase = true;
for (Object value : map.values()) {
if (!isBaseType(value)) {
isBase = false;
break;
}
}
return isBase;
}
private ObjectBinding newBeanDescriptor(Object o, String id) {
BeanDescriptor descriptor = new BeanDescriptor(o);
if (o == null) {
ObjectBinding nullBind = new ObjectBinding();
nullBind.setDescriptor(descriptor);
return nullBind;
}
descriptor.setSingletonClass(isSingletonClass(o));
if (beans.contains(descriptor))
return beanToBinding.get(descriptor);
assignId(id, descriptor);
beans.add(descriptor);
ObjectBinding newBind = new ObjectBinding();
newBind.setDescriptor(descriptor);
beanToBinding.put(descriptor, newBind);
//constructor arguments
Constructor<?> constructor = strategy.getConstructor(o);
if(constructor!=null && !(o instanceof String)){
descriptor.setConstructor(constructor);
try {
Object[] parameters=strategy.getConstructorArgs(o);
if(parameters==null) parameters=new Object[0];
for (Object paramValue : parameters) {
ObjectBinding binding;
if (isBaseType(paramValue)) {
if (paramValue instanceof Class<?>)
paramValue = ((Class<?>) paramValue).getName();
BeanDescriptor tmpDesc = new BeanDescriptor(paramValue);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
binding = new ObjectBinding();
binding.setDescriptor(tmpDesc);
} else {
binding = beanToBinding.get(new BeanDescriptor(paramValue));
if (binding == null) {
binding = newBeanDescriptor(paramValue, "constArgument");
}
}
descriptor.addConstructorArgument(binding);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}else{ //for collections
ObjectBinding binding;
BeanDescriptor tmpDesc = new BeanDescriptor(o.toString());
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
binding = new ObjectBinding();
binding.setDescriptor(tmpDesc);
descriptor.addConstructorArgument(binding);
}
Set<String> properties = getSpringProperties(o);
for (String property : properties) {
Object value = getPropertyValue(o, property);
if (value == null) {
descriptor.setPropertyBinding(property, null);
continue;
}
if (value instanceof Class<?>)
value = ((Class<?>) value).getName();
if (isBaseType(value)) {
BeanDescriptor tmpDesc = new BeanDescriptor(value);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
ObjectBinding binding = new ObjectBinding();
binding.setDescriptor(tmpDesc);
if(strategy.nested(o, property)){
binding.setNested(true);
}
descriptor.setPropertyBinding(property, binding);
continue;
}
if (isListOfBaseTypeType(value)) { // the property is a list and
// its elements are base type
// elements
descriptor.addListProperty(property);
if (value instanceof Object[]) {
for (Object itemValue : ((Object[]) value)) {
BeanDescriptor tmpDesc = new BeanDescriptor(itemValue);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
ObjectBinding binding = new ObjectBinding();
binding.setDescriptor(tmpDesc);
descriptor.addListPropertyValue(property, binding);
}
} else {
for (Object itemValue : ((Collection<?>) value)) {
BeanDescriptor tmpDesc = new BeanDescriptor(itemValue);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
ObjectBinding binding = new ObjectBinding();
binding.setDescriptor(tmpDesc);
descriptor.addListPropertyValue(property, binding);
}
}
continue;
}
if (isListType(value)) { // the property is a generic list of
// objects
descriptor.addListProperty(property);
if (value instanceof Object[]) {
for (Object itemValue : ((Object[]) value)) {
ObjectBinding ref = newBeanDescriptor(itemValue, property + "_item");
if(strategy.nested(o, property))
ref.setNested(true);
descriptor.addListPropertyValue(property, ref);
}
} else {
for (Object itemValue : ((Collection<?>) value)) {
ObjectBinding ref = newBeanDescriptor(itemValue, property + "_item");
if(strategy.nested(o, property))
ref.setNested(true);
descriptor.addListPropertyValue(property, ref);
}
}
continue;
}
if (isSetOfBaseTypeType(value)) { // the property is a set and its
// elements are base type
// elements
descriptor.addSetProperty(property);
for (Object itemValue : ((Collection<?>) value)) {
BeanDescriptor tmpDesc = new BeanDescriptor(itemValue);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
ObjectBinding binding = new ObjectBinding();
binding.setDescriptor(tmpDesc);
descriptor.addSetPropertyValue(property, binding);
}
continue;
}
if (isSetType(value)) { // the property is a generic set of objects
descriptor.addSetProperty(property);
for (Object itemValue : ((Collection<?>) value)) {
ObjectBinding ref = newBeanDescriptor(itemValue, property + "_item");
if(strategy.nested(o, property))
ref.setNested(true);
descriptor.addSetPropertyValue(property, ref);
}
continue;
}
if (isMapType(value)) {
descriptor.addMapProperty(property);
Map<?, ?> castedValue = (Map<?, ?>) value;
boolean keysAreBaseType = isKeyBaseType(castedValue);
boolean valuesAreBaseType = isValueBaseType(castedValue);
for (Object mapKey : castedValue.keySet()) {
Object mapValue = castedValue.get(mapKey);
ObjectBinding keyBinding;
ObjectBinding valueBinding;
if (keysAreBaseType) {
BeanDescriptor tmpDesc = new BeanDescriptor(mapKey);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
keyBinding = new ObjectBinding();
keyBinding.setDescriptor(tmpDesc);
} else {
keyBinding = newBeanDescriptor(mapKey, property + "_key");
}
keyBinding.setNested(strategy.nested(o, property));
if (valuesAreBaseType) {
BeanDescriptor tmpDesc = new BeanDescriptor(mapValue);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
valueBinding = new ObjectBinding();
valueBinding.setDescriptor(tmpDesc);
} else if(mapValue instanceof List){
valueBinding = addListBeanDescriptor((List)mapValue, property + "_value");
} else if(mapValue instanceof Set){
valueBinding = addSetBeanDescriptor((Set)mapValue, property + "_value");
} else if(mapValue instanceof Map){
valueBinding = addMapBeanDescriptor((Map)mapValue, property + "_value");
} else {
valueBinding = newBeanDescriptor(mapValue, property + "_value");
}
valueBinding.setNested(strategy.nested(o, property));
descriptor.addMapPropertyValue(property, keyBinding, valueBinding);
}
continue;
}
ObjectBinding ref = newBeanDescriptor(value, property);
if(strategy.nested(o, property)){
ref.setNested(true);
}
descriptor.setPropertyBinding(property, ref);
}
ObjectBinding bind = new ObjectBinding();
bind.setDescriptor(descriptor);
return bind;
}
private ObjectBinding addListBeanDescriptor(List list, String id) {
BeanDescriptor descriptor = new BeanDescriptor(list);
descriptor.setType(BeanDescriptor.TYPE_LIST);
if (beans.contains(descriptor))
return beanToBinding.get(descriptor);
assignId(id, descriptor);
beans.add(descriptor);
ObjectBinding newBind = new ObjectBinding();
newBind.setDescriptor(descriptor);
beanToBinding.put(descriptor, newBind);
descriptor.addListProperty(BeanDescriptor.PROPERTY_CONTENT);
if (isListOfBaseTypeType(list)) { // the property is a list and
// its elements are base type
// elements
for (Object itemValue : ((Collection<?>) list)) {
BeanDescriptor tmpDesc = new BeanDescriptor(itemValue);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
ObjectBinding binding = new ObjectBinding();
binding.setDescriptor(tmpDesc);
descriptor.addListPropertyValue(BeanDescriptor.PROPERTY_CONTENT, binding);
}
}else{
for (Object itemValue : list) {
ObjectBinding ref;
if(itemValue instanceof List){
ref = addListBeanDescriptor((List)itemValue, BeanDescriptor.PROPERTY_CONTENT + "_item");
} else if(itemValue instanceof Set){
ref = addSetBeanDescriptor((Set)itemValue, BeanDescriptor.PROPERTY_CONTENT + "_item");
} else if(itemValue instanceof Map){
ref = addMapBeanDescriptor((Map)itemValue, BeanDescriptor.PROPERTY_CONTENT + "_item");
} else {
ref = newBeanDescriptor(itemValue, BeanDescriptor.PROPERTY_CONTENT + "_item");
}
descriptor.addListPropertyValue(BeanDescriptor.PROPERTY_CONTENT, ref);
}
}
ObjectBinding bind = new ObjectBinding();
bind.setDescriptor(descriptor);
bind.setNested(true);
return bind;
}
private ObjectBinding addSetBeanDescriptor(Set set, String id) {
BeanDescriptor descriptor = new BeanDescriptor(set);
descriptor.setType(BeanDescriptor.TYPE_SET);
if (beans.contains(descriptor))
return beanToBinding.get(descriptor);
assignId(id, descriptor);
beans.add(descriptor);
ObjectBinding newBind = new ObjectBinding();
newBind.setDescriptor(descriptor);
beanToBinding.put(descriptor, newBind);
descriptor.addSetProperty(BeanDescriptor.PROPERTY_CONTENT);
if (isSetOfBaseTypeType(set)) { // the property is a list and
// its elements are base type
// elements
for (Object itemValue : ((Collection<?>) set)) {
BeanDescriptor tmpDesc = new BeanDescriptor(itemValue);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
ObjectBinding binding = new ObjectBinding();
binding.setDescriptor(tmpDesc);
descriptor.addSetPropertyValue(BeanDescriptor.PROPERTY_CONTENT, binding);
}
}else{
for (Object itemValue : ((Collection<?>) set)) {
ObjectBinding ref;
if(itemValue instanceof List){
ref = addListBeanDescriptor((List)itemValue, BeanDescriptor.PROPERTY_CONTENT + "_item");
} else if(itemValue instanceof Set){
ref = addSetBeanDescriptor((Set)itemValue, BeanDescriptor.PROPERTY_CONTENT + "_item");
} else if(itemValue instanceof Map){
ref = addMapBeanDescriptor((Map)itemValue, BeanDescriptor.PROPERTY_CONTENT + "_item");
} else {
ref = newBeanDescriptor(itemValue, BeanDescriptor.PROPERTY_CONTENT + "_item");
}
descriptor.addSetPropertyValue(BeanDescriptor.PROPERTY_CONTENT, ref);
}
}
ObjectBinding bind = new ObjectBinding();
bind.setDescriptor(descriptor);
bind.setNested(true);
return bind;
}
private ObjectBinding addMapBeanDescriptor(Map map, String id) {
BeanDescriptor descriptor = new BeanDescriptor(map);
descriptor.setType(BeanDescriptor.TYPE_MAP);
if (beans.contains(descriptor))
return beanToBinding.get(descriptor);
assignId(id, descriptor);
beans.add(descriptor);
ObjectBinding newBind = new ObjectBinding();
newBind.setDescriptor(descriptor);
beanToBinding.put(descriptor, newBind);
descriptor.addMapProperty(BeanDescriptor.PROPERTY_CONTENT);
Map<?, ?> castedValue = (Map<?, ?>) map;
boolean keysAreBaseType = isKeyBaseType(castedValue);
boolean valuesAreBaseType = isValueBaseType(castedValue);
for (Object mapKey : castedValue.keySet()) {
Object mapValue = castedValue.get(mapKey);
ObjectBinding keyBinding;
ObjectBinding valueBinding;
if (keysAreBaseType) {
BeanDescriptor tmpDesc = new BeanDescriptor(mapKey);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
keyBinding = new ObjectBinding();
keyBinding.setDescriptor(tmpDesc);
} else {
keyBinding = newBeanDescriptor(mapKey, BeanDescriptor.PROPERTY_CONTENT + "_key");
}
keyBinding.setNested(true);
if (valuesAreBaseType) {
BeanDescriptor tmpDesc = new BeanDescriptor(mapValue);
tmpDesc.setType(BeanDescriptor.TYPE_BASE);
valueBinding = new ObjectBinding();
valueBinding.setDescriptor(tmpDesc);
} else if(mapValue instanceof List){
valueBinding = addListBeanDescriptor((List)mapValue, BeanDescriptor.PROPERTY_CONTENT + "_value");
} else if(mapValue instanceof Set){
valueBinding = addSetBeanDescriptor((Set)mapValue, BeanDescriptor.PROPERTY_CONTENT + "_value");
} else if(mapValue instanceof Map){
valueBinding = addMapBeanDescriptor((Map)mapValue, BeanDescriptor.PROPERTY_CONTENT + "_value");
} else {
valueBinding = newBeanDescriptor(mapValue, BeanDescriptor.PROPERTY_CONTENT + "_value");
}
valueBinding.setNested(true);
descriptor.addMapPropertyValue(BeanDescriptor.PROPERTY_CONTENT, keyBinding, valueBinding);
}
ObjectBinding bind = new ObjectBinding();
bind.setDescriptor(descriptor);
bind.setNested(true);
return bind;
}
/**
* Assigne an id to beanDescriptor
*
* @param id
* @param descriptor
*/
private void assignId(String id, BeanDescriptor descriptor) {
if (id == null)
id = getNewId();
id = getIdSourceValue(descriptor.getBean(), id);
String baseId = id;
while (assignedIds.contains(id))
id = getNewId(baseId);
assignedIds.add(id);
descriptor.setId(id);
}
private String getIdSourceValue(Object o, final String initialId) {
try{
String desired=strategy.getDesiredId(o);
if(desired!=null) return desired;
}catch(Exception e){
return initialId;
}
return initialId;
}
private static int prog = 0;
private static String getNewId() {
return getNewId("bean");
}
private static String getNewId(String baseName) {
if (baseName == null)
baseName = "bean";
return baseName + (prog++);
}
}