/* * 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.groovy.GroovyJavaMethods.elvis; import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.truth; 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.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 * @deprecated since 0.7.0 use {@link #setAllConfigKeys(Map, Configurable, boolean)} */ public static Map<String, ?> setAllConfigKeys(Map<String, ?> flagsOrConfig, Configurable instance) { return setAllConfigKeys(flagsOrConfig, instance, false); } /** 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 * @deprecated since 0.7.0 use {@link #setAllConfigKeys(Configurable, ConfigBag, boolean)} */ public static void setAllConfigKeys(Configurable o, ConfigBag bag) { setAllConfigKeys(o, bag, false); } /** 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 Objects.toStringHelper(this).omitNullValues() .add("flag", flagName) .add("configKey", configKey) .add("flagValue", flagValue.orNull()) .add("configKeyValue", configKeyValue.orNull()) .toString(); } } /** 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 = Maybe.<Object>of(input.getStringKey(key.getName())); if (f != null) { SetFromFlag flag = f.getAnnotation(SetFromFlag.class); if (flag!=null) { result.flagName = flag.value(); if (input.containsKey(flag.value())) result.flagValue = Maybe.of(input.getStringKey(flag.value())); } } return result; } /** returns all fields on the given class, superclasses, and interfaces thereof, in that order of preference, * (excluding fields on Object) */ 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()); } 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); } }); } 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 (truth(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 : (String)elvis(optionalAnnotation.value(), f.getName()); // prefer flag name, if present if (truth(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.getStringKey(key.getName()); setField(o, f, uncoercedValue, optionalAnnotation); return; } if (setDefaultVals && optionalAnnotation!=null && truth(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 (truth(cf)) 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 (truth(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 (truth(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); // } // } }