All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.joda.beans.impl.light.LightMetaBean Maven / Gradle / Ivy

/*
 *  Copyright 2001-2015 Stephen Colebourne
 *
 *  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.joda.beans.impl.light;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import org.joda.beans.Bean;
import org.joda.beans.BeanBuilder;
import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import org.joda.beans.PropertyDefinition;
import org.joda.beans.PropertyMap;
import org.joda.beans.impl.BasicPropertyMap;

/**
 * A light meta-bean implementation that operates using reflection.
 * 

* The properties are found using the {@link PropertyDefinition} annotation. * Only immutable beans are supported. * There must be a constructor matching the property definitions (arguments of same order and types). * * @author Stephen Colebourne * @param the type of the bean */ public final class LightMetaBean implements MetaBean { /** The bean type. */ private final Class beanType; /** The meta-property instances of the bean. */ private final Map> metaPropertyMap; /** The constructor to use. */ private final Constructor constructor; /** The construction data array. */ private final Object[] constructionData; /** * Obtains an instance of the meta-bean. *

* The properties will be determined using reflection to find the * {@link PropertyDefinition} annotation. * * @param the type of the bean * @param beanClass the bean class, not null * @return the meta-bean, not null */ public static LightMetaBean of(Class beanClass) { return new LightMetaBean(beanClass); } /** * Constructor. * * @param beanType the bean type, not null */ private LightMetaBean(Class beanType) { if (beanType == null) { throw new NullPointerException("Bean class must not be null"); } this.beanType = beanType; Map> map = new HashMap>(); Field[] fields = beanType.getDeclaredFields(); List> propertyTypes = new ArrayList>(); for (Field field : fields) { if (!Modifier.isStatic(field.getModifiers()) && field.getAnnotation(PropertyDefinition.class) != null) { PropertyDefinition pdef = field.getAnnotation(PropertyDefinition.class); String name = field.getName(); if (pdef.get().equals("field")) { map.put(name, LightMetaProperty.of(this, field, name, propertyTypes.size())); propertyTypes.add(field.getType()); } else if (!pdef.get().equals("")) { String getterName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1); Method method = null; if (field.getType() == boolean.class) { method = findMethod(beanType, "is" + name.substring(0, 1).toUpperCase() + name.substring(1)); } if (method == null) { method = findMethod(beanType, getterName); if (method == null) { throw new IllegalArgumentException( "Unable to find property getter: " + beanType.getSimpleName() + "." + getterName + "()"); } } method.setAccessible(true); map.put(name, LightMetaProperty.of(this, field, method, name, propertyTypes.size())); propertyTypes.add(field.getType()); } } } this.metaPropertyMap = Collections.unmodifiableMap(map); this.constructor = findConstructor(beanType, propertyTypes); this.constructionData = buildConstructionData(constructor); } // finds a method on class or public method on super-type private static Method findMethod(Class beanType, String getterName) { try { return beanType.getDeclaredMethod(getterName); } catch (NoSuchMethodException ex) { try { return beanType.getMethod(getterName); } catch (NoSuchMethodException ex2) { return null; } } } // finds constructor which matches types exactly private static Constructor findConstructor(Class beanType, List> propertyTypes) { Class[] types = propertyTypes.toArray(new Class[propertyTypes.size()]); try { Constructor con = beanType.getDeclaredConstructor(types); con.setAccessible(true); return con; } catch (NoSuchMethodException ex) { // try a more lenient search // this handles cases where field is a concrete class and constructor is an interface @SuppressWarnings("unchecked") Constructor[] cons = (Constructor[]) beanType.getDeclaredConstructors(); Constructor match = null; for (int i = 0; i < cons.length; i++) { Constructor con = cons[i]; Class[] conTypes = con.getParameterTypes(); if (conTypes.length == types.length) { for (int j = 0; j < types.length; j++) { if (!conTypes[j].isAssignableFrom(types[j])) { break; } } if (match != null) { throw new UnsupportedOperationException("Unable to find constructor: More than one matches"); } match = con; } } if (match == null) { throw new UnsupportedOperationException("Unable to find constructor: " + beanType.getSimpleName(), ex); } match.setAccessible(true); return match; } } // array used to collect data when building // needs to have default values for primitives private static Object[] buildConstructionData(Constructor constructor) { Class[] parameterTypes = constructor.getParameterTypes(); Object[] args = new Object[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { if (parameterTypes[i] == boolean.class) { args[i] = false; } else if (parameterTypes[i] == int.class) { args[i] = (int) 0; } else if (parameterTypes[i] == long.class) { args[i] = (long) 0; } else if (parameterTypes[i] == short.class) { args[i] = (short) 0; } else if (parameterTypes[i] == byte.class) { args[i] = (byte) 0; } else if (parameterTypes[i] == float.class) { args[i] = (float) 0; } else if (parameterTypes[i] == double.class) { args[i] = (double) 0; } else if (parameterTypes[i] == char.class) { args[i] = (char) 0; } } return args; } //----------------------------------------------------------------------- T build(Object[] args) { try { return constructor.newInstance(args); } catch (IllegalArgumentException ex) { throw new IllegalArgumentException( "Bean cannot be created: " + beanName() + " from " + args, ex); } catch (IllegalAccessException ex) { throw new UnsupportedOperationException( "Bean cannot be created: " + beanName() + " from " + args, ex); } catch (InstantiationException ex) { throw new UnsupportedOperationException( "Bean cannot be created: " + beanName() + " from " + args, ex); } catch (InvocationTargetException ex) { if (ex.getCause() instanceof RuntimeException) { throw (RuntimeException) ex.getCause(); } throw new RuntimeException(ex); } } //----------------------------------------------------------------------- @Override public BeanBuilder builder() { return new LightBeanBuilder(this, constructionData.clone()); } @Override public PropertyMap createPropertyMap(Bean bean) { return BasicPropertyMap.of(bean); } //----------------------------------------------------------------------- @Override public String beanName() { return beanType.getName(); } @Override public Class beanType() { return beanType; } //----------------------------------------------------------------------- @Override public int metaPropertyCount() { return metaPropertyMap.size(); } @Override public boolean metaPropertyExists(String propertyName) { return metaPropertyMap.containsKey(propertyName); } @SuppressWarnings("unchecked") @Override public MetaProperty metaProperty(String propertyName) { MetaProperty metaProperty = metaPropertyMap.get(propertyName); if (metaProperty == null) { throw new NoSuchElementException("Property not found: " + propertyName); } return (MetaProperty) metaProperty; } @Override public Iterable> metaPropertyIterable() { return metaPropertyMap.values(); } @Override public Map> metaPropertyMap() { return metaPropertyMap; } //----------------------------------------------------------------------- @Override public boolean equals(Object obj) { if (obj instanceof LightMetaBean) { LightMetaBean other = (LightMetaBean) obj; return this.beanType.equals(other.beanType); } return false; } @Override public int hashCode() { return beanType.hashCode() + 3; } /** * Returns a string that summarises the meta-bean. * * @return a summary string, not null */ @Override public String toString() { return "MetaBean:" + beanName(); } }