OpenTypeBuilder.java

package com.ziesemer.utils.jmx.server.beta;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;

import javax.management.JMException;
import javax.management.ObjectName;
import javax.management.openmbean.ArrayType;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularType;

import com.ziesemer.utils.jmx.beanInfo.MBeanDescription;

/**
 * <p>An <b>incomplete</b> replacement implementation for converting types to be
 * 		used for OpenMBeans / MXBeans.
 * 	Originally designed to provide some level of backwards-compatible support
 * 		for MXBeans under Java 1.5/5.0.</p>
 * <p>Notably, does not handle &quot;Mappings for MXBean interfaces&quot;.
 * 	Also, despite a strong initial set of JUnit tests, there are lots of rules,
 * 		exceptions, and other inconsistencies in the specification, and I am not
 * 		confident that I have covered everything.</p>
 * 	<p>This class only provides methods for assisting with the one-way
 * 			mapping from classes to {@link OpenType}s, with no methods currently
 * 			available to help convert back to the original class types.
 * 		Part of the difficulty in this is that Java 1.5/5.0 does not provide
 * 			the <code>java.beans.ConstructorProperties</code> annotation, or
 * 			<code>javax.management.openmbean.CompositeDataInvocationHandler</code>.
 * 		It also does nothing to create or read from
 * 			{@link javax.management.openmbean.CompositeData} instances.</p>
 * @author Mark A. Ziesemer
 * 	<a href="http://www.ziesemer.com.">&lt;www.ziesemer.com&gt;</a>
 */
public class OpenTypeBuilder{
	
	public static final Map<Class<?>, SimpleType> SIMPLE_TYPES_MAP;
	
	static{
		final Map<Class<?>, SimpleType> simpleTypesMap = new HashMap<Class<?>, SimpleType>(32);
		
		simpleTypesMap.put(boolean.class, SimpleType.BOOLEAN);
		simpleTypesMap.put(Boolean.class, SimpleType.BOOLEAN);
		simpleTypesMap.put(byte.class, SimpleType.BYTE);
		simpleTypesMap.put(Byte.class, SimpleType.BYTE);
		simpleTypesMap.put(char.class, SimpleType.CHARACTER);
		simpleTypesMap.put(Character.class, SimpleType.CHARACTER);
		simpleTypesMap.put(short.class, SimpleType.SHORT);
		simpleTypesMap.put(Short.class, SimpleType.SHORT);
		simpleTypesMap.put(int.class, SimpleType.INTEGER);
		simpleTypesMap.put(Integer.class, SimpleType.INTEGER);
		simpleTypesMap.put(long.class, SimpleType.LONG);
		simpleTypesMap.put(Long.class, SimpleType.LONG);
		simpleTypesMap.put(float.class, SimpleType.FLOAT);
		simpleTypesMap.put(Float.class, SimpleType.FLOAT);
		simpleTypesMap.put(double.class, SimpleType.DOUBLE);
		simpleTypesMap.put(Double.class, SimpleType.DOUBLE);
		simpleTypesMap.put(void.class, SimpleType.VOID);
		simpleTypesMap.put(Void.class, SimpleType.VOID);
		
		simpleTypesMap.put(BigDecimal.class, SimpleType.BIGDECIMAL);
		simpleTypesMap.put(BigInteger.class, SimpleType.BIGINTEGER);
		simpleTypesMap.put(Date.class, SimpleType.DATE);
		simpleTypesMap.put(String.class, SimpleType.STRING);
		simpleTypesMap.put(ObjectName.class, SimpleType.OBJECTNAME);
		
		SIMPLE_TYPES_MAP = Collections.unmodifiableMap(simpleTypesMap);
	}
	
	public static CompositeType buildCompositeType(final Class<?> c) throws JMException{
		return buildCompositeType(c, new LinkedHashSet<Class<?>>());
	}
	
	protected static CompositeType buildCompositeType(
			final Class<?> c, final Set<Class<?>> typeChain) throws OpenDataException{
		
		final List<String> itemNames = new LinkedList<String>();
		final List<String> itemDescriptions = new LinkedList<String>();
		final List<OpenType> itemTypes = new LinkedList<OpenType>();
		
		if(!typeChain.add(c)){
			final List<Class<?>> errorTypeChain = new ArrayList<Class<?>>(typeChain.size() + 1);
			errorTypeChain.addAll(typeChain);
			errorTypeChain.add(c);
			throw new OpenDataException("Recursive data structure: " + errorTypeChain);
		}
		
		for(final Method m : c.getMethods()){
			if(Modifier.isStatic(m.getModifiers())
					|| !Modifier.isPublic(m.getModifiers())){
				continue;
			}
			
			final String mName = m.getName();
			final Class<?>[] paramTypes = m.getParameterTypes();
			final int paramCount = paramTypes.length;
			
			final Class<?> rt = m.getReturnType();
			String aName;
			
			if(mName.startsWith("get") && !"get".equals(mName)
					&& paramCount == 0 && !void.class.equals(rt)){
				aName = mName.substring(3);
				
				if("Class".equals(aName) && paramCount == 0){
					continue;
				}
			}else if(mName.startsWith("is") && !"is".equals(mName)
					&& paramCount == 0 && boolean.class.equals(rt)){
				aName = mName.substring(2);
			}else{
				continue;
			}
			if(aName.length() > 1){
				aName = Character.toLowerCase(aName.charAt(0))
					+ aName.substring(1);
			}
			
			String aDescription = aName;
			final MBeanDescription descriptionA = m.getAnnotation(MBeanDescription.class);
			if(descriptionA != null){
				aDescription = descriptionA.value();
			}
			
			OpenType ot = SIMPLE_TYPES_MAP.get(rt);
			
			if(ot != null){
				// "Mappings for primitive types"
				// Nothing special to do.
			}else if(rt.isArray()){
				Class<?> ct = rt;
				int dimension = 0;
				while(ct.isArray()){
					ct = ct.getComponentType();
					dimension++;
				}
				
				ot = new ArrayType(dimension, getOpenType(ct, typeChain));
			}else if(List.class.isAssignableFrom(rt) || Set.class.isAssignableFrom(rt)){
				// "Mappings for collections"
				final Class<?> grt = findGenericType(m.getGenericReturnType());
				if(grt != null){
					
					if(SortedSet.class.isAssignableFrom(rt) && !Comparable.class.isAssignableFrom(grt)){
						throw new OpenDataException("Parameter " + grt + " of " + rt.getName()
							+ " does not implement " + Comparable.class.getName() + ".");
					}
					
					ot = new ArrayType(1, getOpenType(grt, typeChain));
				}else{
					throw new OpenDataException("Cannot map type: " + m.getGenericReturnType());
				}
			}else if(Map.class.isAssignableFrom(rt)){
				// "Mappings for maps"
				final Type grt = m.getGenericReturnType();
				if(grt instanceof ParameterizedType){
					final String typeName = m.getGenericReturnType().toString();
					final String[] keyValue = new String[]{"key", "value"};
					final Type[] ats = ((ParameterizedType)grt).getActualTypeArguments();
					
					if(SortedMap.class.isAssignableFrom(rt)
							&& !Comparable.class.isAssignableFrom((Class<?>)ats[0])){
						throw new OpenDataException("Parameter " + grt + " of " + rt.getName()
							+ " does not implement " + Comparable.class.getName() + ".");
					}
					
					final OpenType[] openTypes = new OpenType[ats.length]; // Should always be 2.
					for(int i=0; i < ats.length; i++){
						openTypes[i] = getOpenType((Class<?>)ats[i], typeChain);
					}
					
					final CompositeType rowType = new CompositeType(typeName, typeName, keyValue, keyValue, openTypes);
					ot = new TabularType(typeName, typeName, rowType, new String[]{"key"});
				}else{
					throw new OpenDataException("Cannot map type: " + grt);
				}
			}else if(rt.isEnum()){
				ot = SimpleType.STRING;
			}else{
				// "Mappings for other types"
				ot = buildCompositeType(rt, typeChain);
			}
			
			itemNames.add(aName);
			itemDescriptions.add(aDescription);
			itemTypes.add(ot);
		}
		
		typeChain.remove(c);
		
		String cDescription = c.getName();
		final MBeanDescription cDescriptionA = c.getAnnotation(MBeanDescription.class);
		if(cDescriptionA != null){
			cDescription = cDescriptionA.value();
		}
		
		final CompositeType ct = new CompositeType(
			c.getName(),
			cDescription,
			itemNames.toArray(new String[itemNames.size()]),
			itemDescriptions.toArray(new String[itemDescriptions.size()]),
			itemTypes.toArray(new OpenType[itemTypes.size()]));
		return ct;
	}
	
	public static OpenType getOpenType(final Class<?> c) throws OpenDataException{
		return getOpenType(c, new LinkedHashSet<Class<?>>());
	}
	
	protected static OpenType getOpenType(final Class<?> c, final Set<Class<?>> typeChain) throws OpenDataException{
		OpenType ot = SIMPLE_TYPES_MAP.get(c);
		if(ot == null){
			ot = buildCompositeType(c, typeChain);
		}
		return ot;
	}
	
	protected static Class<?> findGenericType(final Type t){
		if(t instanceof ParameterizedType){
			final ParameterizedType pt = (ParameterizedType)t;
			final Type[] ats = pt.getActualTypeArguments();
			if(ats.length == 1){
				final Type at = ats[0];
				if(at instanceof Class<?>){
					return (Class<?>)at;
				}
			}
		}
		return null;
	}
	
}