ReflectiveFieldInitializer.java

/***************************************************************************
   Copyright 2012 Emily Estes

   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 net.metanotion.util.reflect;


import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.metanotion.util.Dictionary;
import net.metanotion.util.types.ArrayP;
import net.metanotion.util.types.ListP;
import net.metanotion.util.types.Parser;
import net.metanotion.util.types.SetP;
import net.metanotion.util.types.TypeDictionary;

/** This class uses reflection to create an {@link net.metanotion.util.reflect.Initializer} instance for a given
	class. It provides two static utility methods to create {@link net.metanotion.util.reflect.GetInitializer} that
	create instances of this class.
	@param <S> The type of object this initializer creates.
*/
public final class ReflectiveFieldInitializer<S> implements Initializer<S> {
	private static final Logger logger = LoggerFactory.getLogger(ReflectiveFieldInitializer.class);

	private static final class GI<T> implements GetInitializer<T> {
		private final Class<T> klazz;
		private final Map<String,GetField> fieldMap;
		public GI(final Class<T> klazz, final Map<String,GetField> fieldMap) {
			this.klazz = klazz;
			this.fieldMap = fieldMap;
		}
		@Override public Initializer<T> initializer() { return new ReflectiveFieldInitializer<T>(klazz, fieldMap); }
		@Override public Iterator<String> iterator() { return fieldMap.keySet().iterator(); }
		@Override public Dictionary<String,Object> struct(final T instance) {
			return new Dictionary<String,Object>() {
				public Object get(String field) { return fieldMap.get(field).get(instance); }
			};
		}
		@Override public String type(String field) { return fieldMap.get(field).type().getCanonicalName(); }
	}

	/** Create a default {@link net.metanotion.util.reflect.GetInitializer} instance from a class.
		@param <T>  the type of the class modeled by the class parameter, which is also the type of the GetInitializer.
		@param klazz The class to create a GetInitializer from.
		@return A GetInitializer to create instances of {@link net.metanotion.util.reflect.Initializer}'s for the class.
	*/
	public static <T> GetInitializer<T> getInitializer(Class<T> klazz) {
		return ReflectiveFieldInitializer.getInitializer(klazz, new TypeDictionary());
	}

	/** Create a default {@link net.metanotion.util.reflect.GetInitializer} instance from a class using a custome type
		dictionary.
		@param <T>  the type of the class modeled by the class parameter, which is also the type of the GetInitializer.
		@param klazz The class to create a GetInitializer from.
		@param types The custom type dictionary to use to interpret values.
		@return A GetInitializer to create instances of {@link net.metanotion.util.reflect.Initializer}'s for the class.
	*/
	public static <T> GetInitializer<T> getInitializer(Class<T> klazz, Dictionary<Class,Parser> types) {
		final Map<String,GetField> fieldMap = new LinkedHashMap<>();
		final Parser def = types.get(Object.class);
		for(final Field f: klazz.getFields()) {
			f.setAccessible(true);
			final Class c = f.getType();
			Parser p = def;
			if(c.isArray()) {
				final Class e = c.getComponentType();
				p = types.get(e);
				p = (p!=null) ? p : def;
				p = new ArrayP(p);
			} else if(Iterable.class.isAssignableFrom(c)) {
				final Class e = (Class) (((ParameterizedType) f.getGenericType()).getActualTypeArguments())[0];
				p = types.get(e);
				p = (p!=null) ? p : def;
				if(Set.class.isAssignableFrom(c)) {
					p = new SetP(new ListP(p));
				} else {
					p = new ListP(p);
				}
			} else {
				p = types.get(c);
				p = (p!=null) ? p : def;
			}
			fieldMap.put(f.getName(), new GetField(f, p));
		}
		return new GI(klazz, fieldMap);
	}


	private final Map<String,Object> valueMap = new HashMap<>();

	private final Class<S> klazz;
	private final Map<String,GetField> fieldMap;
	private final Constructor<S> instance;
	private ReflectiveFieldInitializer(final Class<S> klazz, final Map<String,GetField> fieldMap) {
		this.klazz = klazz;
		this.fieldMap = fieldMap;
		try {
			this.instance = klazz.getDeclaredConstructor(new Class[0]);
		} catch (NoSuchMethodException nsme) { throw new RuntimeException(nsme); }
		this.instance.setAccessible(true);
	}

	@Override public void put(final String name, final Object value) { valueMap.put(name, value); }

	@Override public S instance() throws InstantiationException, IllegalAccessException {
		try {
			final S s = instance.newInstance();
			for(final Map.Entry<String,GetField> f: fieldMap.entrySet()) {
				f.getValue().set(s, valueMap.get(f.getKey()));
			}
			return s;
		} catch (InvocationTargetException ite) {
			throw new InstantiationException("InvocationTargetException");
		}
	}
}