/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.brooklyn.util.core.flags;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.brooklyn.util.JavaGroovyEquivalents.elvis;
import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import org.apache.brooklyn.api.objs.Configurable;
import org.apache.brooklyn.api.objs.SpecParameter;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.groovy.GroovyJavaMethods;
import org.apache.brooklyn.util.guava.Maybe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import groovy.lang.Closure;
import groovy.lang.GroovyObject;


/** class to help transfer values passed as named arguments to other well-known variables/fields/objects;
 * see the test case for example usage */
public class FlagUtils {

    public static final Logger log = LoggerFactory.getLogger(FlagUtils.class);
    
    private FlagUtils() {}
    
    /** see {@link #setFieldsFromFlags(Object o, ConfigBag)} */
    public static Map<?, ?> setPublicFieldsFromFlags(Map<?, ?> flags, Object o) {
        return setFieldsFromFlagsInternal(o, Arrays.asList(o.getClass().getFields()), flags, null, true);
    }

    /** see {@link #setFieldsFromFlags(Object, ConfigBag)} */
    public static Map<?, ?> setFieldsFromFlags(Map<?, ?> flags, Object o) {
        return setFieldsFromFlagsInternal(o, getAllFields(o.getClass()), flags, null, true);
    }
    
    /** sets all fields (including private and static, local and inherited) annotated {@link SetFromFlag} on the given object, 
     * from the given flags map, returning just those flag-value pairs passed in which do not correspond to SetFromFlags fields 
     * annotated ConfigKey and HasConfigKey fields are _configured_ (and we assume the object in that case is {@link Configurable});
     * keys should be ConfigKey, HasConfigKey, or String;
     * default values are also applied unless that is specified false on one of the variants of this method which takes such an argument
     */
    public static void setFieldsFromFlags(Object o, ConfigBag configBag) {
        setFieldsFromFlagsInternal(o, getAllFields(o.getClass()), configBag.getAllConfig(), configBag, true);
    }

    /** as {@link #setFieldsFromFlags(Object, ConfigBag)}, but allowing control over whether default values should be set */
    public static void setFieldsFromFlags(Object o, ConfigBag configBag, boolean setDefaultVals) {
        setFieldsFromFlagsInternal(o, getAllFields(o.getClass()), configBag.getAllConfig(), configBag, setDefaultVals);
    }

    /** as {@link #setFieldsFromFlags(Object, ConfigBag)}, but specifying a subset of flags to use */
    public static void setFieldsFromFlagsWithBag(Object o, Map<?,?> flags, ConfigBag configBag, boolean setDefaultVals) {
        setFieldsFromFlagsInternal(o, getAllFields(o.getClass()), flags, configBag, setDefaultVals);
    }

    /**
     * Sets the field with the given flag (if it exists) to the given value.
     * Will attempt to coerce the value to the required type.
     * Will respect "nullable" on the SetFromFlag annotation.
     * 
     * @throws IllegalArgumentException If fieldVal is null and the SetFromFlag annotation set nullable=false
     */
    public static boolean setFieldFromFlag(Object o, String flagName, Object fieldVal) {
        return setFieldFromFlagInternal(checkNotNull(flagName, "flagName"), fieldVal, o, getAllFields(o.getClass()));
    }
    
    /** get all fields (including private and static) on the given object and all supertypes, 
     * that are annotated with SetFromFlags. 
     */
    public static Map<String, ?> getFieldsWithFlags(Object o) {
        return getFieldsWithFlagsInternal(o, getAllFields(o.getClass()));
    }
    
    /**
     * Finds the {@link Field} on the given object annotated with the given name flag.
     */
    public static Field findFieldForFlag(String flagName, Object o) {
        return findFieldForFlagInternal(flagName, o, getAllFields(o.getClass()));
    }

    /** get all fields (including private and static) and their values on the given object and all supertypes, 
     * where the field is annotated with SetFromFlags. 
     */
    public static Map<String, Object> getFieldsWithFlagsExcludingModifiers(Object o, int excludingModifiers) {
        List<Field> filteredFields = Lists.newArrayList();
        for (Field contender : getAllFields(o.getClass())) {
            if ((contender.getModifiers() & excludingModifiers) == 0) {
                filteredFields.add(contender);
            }
        }
        return getFieldsWithFlagsInternal(o, filteredFields);
    }
    
    /** get all fields with the given modifiers, and their values on the given object and all supertypes, 
     * where the field is annotated with SetFromFlags. 
     */
    public static Map<String, Object> getFieldsWithFlagsWithModifiers(Object o, int requiredModifiers) {
        List<Field> filteredFields = Lists.newArrayList();
        for (Field contender : getAllFields(o.getClass())) {
            if ((contender.getModifiers() & requiredModifiers) == requiredModifiers) {
                filteredFields.add(contender);
            }
        }
        return getFieldsWithFlagsInternal(o, filteredFields);
    }
    
    /** sets _all_ accessible _{@link ConfigKey}_ and {@link HasConfigKey} fields on the given object, 
     * using the indicated flags/config-bag */
    public static Map<String, ?> setAllConfigKeys(Map<String, ?> flagsOrConfig, Configurable instance, boolean includeFlags) {
        ConfigBag bag = new ConfigBag().putAll(flagsOrConfig);
        setAllConfigKeys(instance, bag, includeFlags);
        return bag.getUnusedConfigMutable();
    }
    
    /** sets _all_ accessible _{@link ConfigKey}_ and {@link HasConfigKey} fields on the given object, 
     * using the indicated flags/config-bag */
    public static void setAllConfigKeys(Configurable o, ConfigBag bag, boolean includeFlags) {
        for (Field f: getAllFields(o.getClass())) {
            ConfigKey<?> key = getFieldAsConfigKey(o, f);
            if (key!=null) {
                FlagConfigKeyAndValueRecord record = getFlagConfigKeyRecord(f, key, bag);
                if ((includeFlags && record.isValuePresent()) || record.getConfigKeyMaybeValue().isPresent()) {
                    setField(o, f, record.getValueOrNullPreferringConfigKey(), null);
                }
            }
        }
    }
    
    public static class FlagConfigKeyAndValueRecord {
        private String flagName = null;
        private ConfigKey<?> configKey = null;
        private Maybe<Object> flagValue = Maybe.absent();
        private Maybe<Object> configKeyValue = Maybe.absent();
        
        public String getFlagName() {
            return flagName;
        }
        public ConfigKey<?> getConfigKey() {
            return configKey;
        }
        public Maybe<Object> getFlagMaybeValue() {
            return flagValue;
        }
        public Maybe<Object> getConfigKeyMaybeValue() {
            return configKeyValue;
        }
        public Object getValueOrNullPreferringConfigKey() {
            return getConfigKeyMaybeValue().or(getFlagMaybeValue()).orNull();
        }
        public Object getValueOrNullPreferringFlag() {
            return getFlagMaybeValue().or(getConfigKeyMaybeValue()).orNull();
        }
        /** true if value is present for either flag or config key */
        public boolean isValuePresent() {
            return flagValue.isPresent() || configKeyValue.isPresent();
        }
        
        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this).omitNullValues()
                .add("flag", flagName)
                .add("configKey", configKey)
                .add("flagValue", flagValue.orNull())
                .add("configKeyValue", configKeyValue.orNull())
                .toString();
        }
    }
    
    /**
     * Gets all the config keys for the given types (for duplicates, the first class in the list of types takes precedence).
     */
    public static Map<String, ConfigKey<?>> findAllConfigKeys(Object optionalInstance, Iterable<? extends Class<?>> types) {
        Map<String, ConfigKey<?>> result = Maps.newLinkedHashMap();
        for (Class<?> type : types) {
            List<ConfigKey<?>> keys = FlagUtils.findAllConfigKeys(optionalInstance, type);
            for (ConfigKey<?> key : keys) {
                if (!result.containsKey(key.getName())) result.put(key.getName(), key);
            }
        }
        return result;
    }

    /** gets all the config keys for the given type */
    public static <T> List<ConfigKey<?>> findAllConfigKeys(T optionalInstance, Class<? extends T> type) {
        List<ConfigKey<?>> output = new ArrayList<ConfigKey<?>>();
        for (Field f: getAllFields(type)) {
            ConfigKey<?> key = getFieldAsConfigKey(optionalInstance, f);
            if (key != null) output.add(key);
        }
        return output;
    }
    
    /** gets all the flags/keys in the given config bag which are applicable to the given type's config keys and flags */
    public static <T> List<FlagConfigKeyAndValueRecord> findAllFlagsAndConfigKeys(T optionalInstance, Class<? extends T> type, ConfigBag input) {
        List<FlagConfigKeyAndValueRecord> output = new ArrayList<FlagUtils.FlagConfigKeyAndValueRecord>();
        for (Field f: getAllFields(type)) {
            ConfigKey<?> key = getFieldAsConfigKey(optionalInstance, f);
            FlagConfigKeyAndValueRecord record = getFlagConfigKeyRecord(f, key, input);
            if (record.isValuePresent())
                output.add(record);
        }
        return output;
    }

    /** gets all the keys in the given config bag which are applicable to the given list of parameters */
    public static List<FlagConfigKeyAndValueRecord> findAllParameterConfigKeys(List<SpecParameter<?>> parameters, ConfigBag input) {
        List<FlagConfigKeyAndValueRecord> output = new ArrayList<FlagUtils.FlagConfigKeyAndValueRecord>();
        for (SpecParameter<?> param : parameters) {
            FlagConfigKeyAndValueRecord record = getFlagConfigKeyRecord(null, param.getConfigKey(), input);
            if (record.isValuePresent())
                output.add(record);
        }
        return output;
    }

    /** returns the flag/config-key record for the given input */
    private static FlagConfigKeyAndValueRecord getFlagConfigKeyRecord(Field f, ConfigKey<?> key, ConfigBag input) {
        FlagConfigKeyAndValueRecord result = new FlagConfigKeyAndValueRecord(); 
        result.configKey = key;
        if (key!=null && input.containsKey(key))
            result.configKeyValue = input.getObjKeyMaybe(key);
        if (f != null) {
            SetFromFlag flag = f.getAnnotation(SetFromFlag.class);
            if (flag!=null) {
                String flagName = elvis(flag.value(), f.getName());
                result.flagName = flagName;
                if (input.containsKey(flagName))
                    result.flagValue = Maybe.of(input.getStringKey(flagName));
            }
        }
        return result;
    }

    /** returns all fields on the given class, superclasses, and interfaces thereof, in that order of preference,
     * (excluding fields on Object)
     * 
     * @deprecated since 0.11.0; explicit groovy utilities/support will be deleted.
     */
    @Deprecated
    public static List<Field> getAllFields(Class<?> base, Closure<Boolean> filter) {
        return getAllFields(base, GroovyJavaMethods.<Field>predicateFromClosure(filter));
    }
    public static List<Field> getAllFields(Class<?> base) {
        return getAllFields(base, Predicates.<Field>alwaysTrue());
    }
    public static List<Field> getAllFields(Class<?> base, Predicate<Field> filter) {
        return getLocalFields(getAllAssignableTypes(base), filter);
    }
    /** returns all fields explicitly declared on the given classes */
    public static List<Field> getLocalFields(List<Class<?>> classes) {
        return getLocalFields(classes, Predicates.<Field>alwaysTrue());
    }
    /**
     * @deprecated since 0.11.0; explicit groovy utilities/support will be deleted.
     */
    @Deprecated
    public static List<Field> getLocalFields(List<Class<?>> classes, Closure<Boolean> filter) {
        return getLocalFields(classes, GroovyJavaMethods.<Field>predicateFromClosure(filter));
    }
    public static List<Field> getLocalFields(List<Class<?>> classes, Predicate<Field> filter) {
        List<Field> fields = Lists.newArrayList();
        for (Class<?> c : classes) {
            for (Field f : c.getDeclaredFields()) {
                if (filter.apply(f)) fields.add(f);
            }
        }
        return fields;
    }
    
    /** returns base, superclasses, then interfaces */
    public static List<Class<?>> getAllAssignableTypes(Class<?> base) {
        return getAllAssignableTypes(base, new Predicate<Class<?>>() {
            @Override public boolean apply(Class<?> it) {
                return (it != Object.class) && (it != GroovyObject.class);
            }
        });
    }
    /**
     * @deprecated since 0.11.0; explicit groovy utilities/support will be deleted.
     */
    @Deprecated
    public static List<Class<?>> getAllAssignableTypes(Class<?> base, Closure<Boolean> filter) {
        return getAllAssignableTypes(base, GroovyJavaMethods.<Class<?>>predicateFromClosure(filter));
    }
    public static List<Class<?>> getAllAssignableTypes(Class<?> base, Predicate<Class<?>> filter) {
        List<Class<?>> classes = Lists.newArrayList();
        for (Class<?> c = base; c != null; c = c.getSuperclass()) {
            if (filter.apply(c)) classes.add(c);
        }
        for (int i=0; i<classes.size(); i++) {
            for (Class<?> interf : classes.get(i).getInterfaces()) {
                if (filter.apply(interf) && !(classes.contains(interf))) classes.add(interf);
            }
        }
        return classes;
    }
    
    private static Map<String, Object> getFieldsWithFlagsInternal(Object o, Collection<Field> fields) {
        Map<String, Object> result = Maps.newLinkedHashMap();
        for (Field f: fields) {
            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
            if (cf != null) {
                String flagName = elvis(cf.value(), f.getName());
                if (groovyTruth(flagName)) {
                    result.put(flagName, getField(o, f));
                } else {
                    log.warn("Ignoring field {} of object {} as no flag name available", f, o);
                }
            }
        }
        return result;
    }

    private static Field findFieldForFlagInternal(String flagName, Object o, Collection<Field> fields) {
        for (Field f: fields) {
            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
            if (cf != null) {
                String contenderName = elvis(cf.value(), f.getName());
                if (flagName.equals(contenderName)) {
                    return f;
                }
            }
        }
        throw new NoSuchElementException("Field with flag "+flagName+" not found on "+o+" of type "+(o != null ? o.getClass() : null));
    }

    private static boolean setFieldFromFlagInternal(String flagName, Object fieldVal, Object o, Collection<Field> fields) {
        for (Field f: fields) {
            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
            if (cf != null && flagName.equals(elvis(cf.value(), f.getName()))) {
                setField(o, f, fieldVal, cf);
                return true;
            }
        }
        return false;
    }

    private static Map<String, ?> setFieldsFromFlagsInternal(Object o, Collection<Field> fields, Map<?,?> flagsOrConfig, ConfigBag bag, boolean setDefaultVals) {
        if (bag==null) bag = new ConfigBag().putAll(flagsOrConfig);
        for (Field f: fields) {
            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
            if (cf!=null) setFieldFromConfig(o, f, bag, cf, setDefaultVals);
        }
        return bag.getUnusedConfigMutable();
    }

    private static void setFieldFromConfig(Object o, Field f, ConfigBag bag, SetFromFlag optionalAnnotation, boolean setDefaultVals) {
        String flagName = optionalAnnotation==null ? null : elvis(optionalAnnotation.value(), f.getName());
        // prefer flag name, if present
        if (groovyTruth(flagName) && bag.containsKey(flagName)) {
            setField(o, f, bag.getStringKey(flagName), optionalAnnotation);
            return;
        }
        // first check whether it is a key
        ConfigKey<?> key = getFieldAsConfigKey(o, f);
        if (key!=null && bag.containsKey(key)) {
            Object uncoercedValue = bag.getObjKeyMaybe(key).get();
            setField(o, f, uncoercedValue, optionalAnnotation);
            return;
        }
        if (setDefaultVals && optionalAnnotation!=null && groovyTruth(optionalAnnotation.defaultVal())) {
            Object oldValue;
            try {
                f.setAccessible(true);
                oldValue = f.get(o);
                if (oldValue==null || oldValue.equals(getDefaultValueForType(f.getType()))) {
                    setField(o, f, optionalAnnotation.defaultVal(), optionalAnnotation);
                }
            } catch (Exception e) {
                Exceptions.propagate(e);
            }
            return;
        }
    }

    /** returns the given field as a config key, if it is an accessible config key, otherwise null */
    private static ConfigKey<?> getFieldAsConfigKey(Object optionalInstance, Field f) {
        if (optionalInstance==null) {
            if ((f.getModifiers() & Modifier.STATIC)==0)
                // non-static field on null instance, can't be set
                return null;
        }
        if (ConfigKey.class.isAssignableFrom(f.getType())) {
            return (ConfigKey<?>) getField(optionalInstance, f);
        } else if (HasConfigKey.class.isAssignableFrom(f.getType())) {
            return ((HasConfigKey<?>)getField(optionalInstance, f)).getConfigKey();
        }
        return null;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void setConfig(Object objectOfField, ConfigKey<?> key, Object value, SetFromFlag optionalAnnotation) {
        if (objectOfField instanceof Configurable) {
            ((Configurable)objectOfField).config().set((ConfigKey)key, value);
            return;
        } else {
            if (optionalAnnotation==null) {
                log.warn("Cannot set key "+key.getName()+" on "+objectOfField+": containing class is not Configurable");
            } else if (!key.getName().equals(optionalAnnotation.value())) {
                log.warn("Cannot set key "+key.getName()+" on "+objectOfField+" from flag "+optionalAnnotation.value()+": containing class is not Configurable");
            } else {
                // if key and flag are the same, then it will probably happen automatically
                if (log.isDebugEnabled())
                    log.debug("Cannot set key "+key.getName()+" on "+objectOfField+" from flag "+optionalAnnotation.value()+": containing class is not Configurable");
            }
            return;
        }
    }
    
    /** sets the field to the value, after checking whether the given value can be set 
     * respecting the constraints of the annotation 
     */
    public static void setField(Object objectOfField, Field f, Object value, SetFromFlag optionalAnnotation) {
        try {
            ConfigKey<?> key = getFieldAsConfigKey(objectOfField, f);
            if (key!=null) {
                setConfig(objectOfField, key, value, optionalAnnotation);
                return;
            }
            
            if (!f.isAccessible()) f.setAccessible(true);
            if (optionalAnnotation!=null && optionalAnnotation.immutable()) {
                Object oldValue = f.get(objectOfField);
                if (!Objects.equal(oldValue, getDefaultValueForType(f.getType())) && oldValue != value) {
                    throw new IllegalStateException("Forbidden modification to immutable field "+
                        f+" in "+objectOfField+": attempting to change to "+value+" when was already "+oldValue);
                }
            }
            if (optionalAnnotation!=null && !optionalAnnotation.nullable() && value==null) {
                throw new IllegalArgumentException("Forbidden null assignment to non-nullable field "+
                        f+" in "+objectOfField);
            }
            if (optionalAnnotation!=null && (f.getModifiers() & Modifier.STATIC)==Modifier.STATIC)
                log.warn("Setting static field "+f+" in "+objectOfField+" from flag "+optionalAnnotation.value()+": discouraged");

            Object newValue;
            try {
                newValue = TypeCoercions.coerce(value, f.getType());
            } catch (Exception e) {
                throw new IllegalArgumentException("Cannot set "+f+" in "+objectOfField+" from type "+value.getClass()+" ("+value+"): "+e, e);
            }
            f.set(objectOfField, newValue);
            if (log.isTraceEnabled()) log.trace("FlagUtils for "+objectOfField+", setting field="+f.getName()+"; val="+value+"; newVal="+newValue+"; key="+key);

        } catch (IllegalAccessException e) {
            throw Throwables.propagate(e);
        }
    }

    /** gets the value of the field. 
     */
    public static Object getField(Object objectOfField, Field f) {
        try {
            if (!f.isAccessible()) f.setAccessible(true);
            return f.get(objectOfField);
        } catch (IllegalAccessException e) {
            throw Throwables.propagate(e);
        }
    }
    
    /** returns the default/inital value that is assigned to fields of the givien type;
     * if the type is not primitive this value is null;
     * for primitive types it is obvious but not AFAIK programmatically visible
     * (e.g. 0 for int, false for boolean)  
     */
    public static Object getDefaultValueForType(Class<?> t) {
        if (!t.isPrimitive()) return null;
        if (t==Integer.TYPE) return (int)0;
        if (t==Long.TYPE) return (long)0;
        if (t==Double.TYPE) return (double)0;
        if (t==Float.TYPE) return (float)0;
        if (t==Byte.TYPE) return (byte)0;
        if (t==Short.TYPE) return (short)0;
        if (t==Character.TYPE) return (char)0;
        if (t==Boolean.TYPE) return false;
        //should never happen
        throw new IllegalStateException("Class "+t+" is an unknown primitive.");
    }

    /** returns a map of all fields which are annotated 'SetFromFlag', along with the annotation */
    public static Map<Field,SetFromFlag> getAnnotatedFields(Class<?> type) {
        Map<Field, SetFromFlag> result = Maps.newLinkedHashMap();
        for (Field f: getAllFields(type)) {
            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
            if (cf != null) result.put(f, cf);
        }
        return result;
    }

    /** returns a map of all {@link ConfigKey} fields which are annotated 'SetFromFlag', along with the annotation */
    public static Map<ConfigKey<?>,SetFromFlag> getAnnotatedConfigKeys(Class<?> type) {
        Map<ConfigKey<?>, SetFromFlag> result = Maps.newLinkedHashMap();
        List<Field> fields = getAllFields(type, new Predicate<Field>() {
            @Override public boolean apply(Field f) {
                return (f != null) && ConfigKey.class.isAssignableFrom(f.getType()) && ((f.getModifiers() & Modifier.STATIC)!=0);
            }});
        for (Field f: fields) {
            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
            if (cf != null) {
                ConfigKey<?> key = getFieldAsConfigKey(null, f);
                if (key != null) {
                    result.put(key, cf);
                }
            }
        }
        return result;
    }

    /** returns a map of all fields which are annotated 'SetFromFlag' with their current values;
     * useful if you want to clone settings from one object
     */
    public static Map<String,Object> getFieldsWithValues(Object o) {
        try {
            Map<String, Object> result = Maps.newLinkedHashMap();
            for (Map.Entry<Field, SetFromFlag> entry : getAnnotatedFields(o.getClass()).entrySet()) {
                Field f = entry.getKey();
                SetFromFlag cf = entry.getValue();
                String flagName = elvis(cf.value(), f.getName());
                if (groovyTruth(flagName)) {
                    if (!f.isAccessible()) f.setAccessible(true);
                    result.put(flagName, f.get(o));
                }
            }
            return result;
        } catch (IllegalAccessException e) {
            throw Throwables.propagate(e);
        }
    }
        
    /**
     * @throws an IllegalStateException if there are fields required (nullable=false) which are unset 
     * @throws wrapped IllegalAccessException
     */
    public static void checkRequiredFields(Object o) {
        try {
            Set<String> unsetFields = Sets.newLinkedHashSet();
            for (Map.Entry<Field, SetFromFlag> entry : getAnnotatedFields(o.getClass()).entrySet()) {
                Field f = entry.getKey();
                SetFromFlag cf = entry.getValue();
                if (!cf.nullable()) {
                    String flagName = elvis(cf.value(), f.getName());
                    if (!f.isAccessible()) f.setAccessible(true);
                    Object v = f.get(o);
                    if (v==null) unsetFields.add(flagName);
                }
            }
            if (groovyTruth(unsetFields)) {
                throw new IllegalStateException("Missing required "+(unsetFields.size()>1 ? "fields" : "field")+": "+unsetFields);
            }
        } catch (IllegalAccessException e) {
            throw Throwables.propagate(e);
        }
    }

//    /** sets all fields in target annotated with @SetFromFlag using the configuration in the given config bag */
//    public static void setFieldsFromConfigFlags(Object target, ConfigBag configBag) {
//        setFieldsFromConfigFlags(target, configBag.getAllConfig(), configBag);
//    }
//
//    
//    /** sets all fields in target annotated with @SetFromFlag using the configuration in the given configToUse,
//     * marking used in the given configBag */
//    public static void setFieldsFromConfigFlags(Object target, Map<?,?> configToUse, ConfigBag configBag) {
//        for (Map.Entry<?,?> entry: configToUse.entrySet()) {
//            setFieldFromConfigFlag(target, entry.getKey(), entry.getValue(), configBag);
//        }
//    }
//
//    public static void setFieldFromConfigFlag(Object target, Object key, Object value, ConfigBag optionalConfigBag) {
//        String name = null;
//        if (key instanceof String) name = (String)key;
//        else if (key instanceof ConfigKey<?>) name = ((ConfigKey<?>)key).getName();
//        else if (key instanceof HasConfigKey<?>) name = ((HasConfigKey<?>)key).getConfigKey().getName();
//        else {
//            if (key!=null) {
//                log.warn("Invalid config type "+key.getClass().getCanonicalName()+" ("+key+") when configuring "+target+"; ignoring");
//            }
//            return;
//        }
//        if (setFieldFromFlag(name, value, target)) {
//            if (optionalConfigBag!=null)
//                optionalConfigBag.markUsed(name);
//        }
//    }
    
}
