/*
* 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.core.objs;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.apache.brooklyn.api.objs.EntityAdjunct;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
/**
* This is the actual type of a policy instance at runtime.
*/
public class AdjunctType implements Serializable {
private static final long serialVersionUID = -662979234559595903L;
private static final Logger LOG = LoggerFactory.getLogger(AdjunctType.class);
private final String name;
private final Map<String, ConfigKey<?>> configKeys;
private final Set<ConfigKey<?>> configKeysSet;
public AdjunctType(AbstractEntityAdjunct adjunct) {
this(adjunct.getClass(), adjunct);
}
protected AdjunctType(Class<? extends EntityAdjunct> clazz) {
this(clazz, null);
}
private AdjunctType(Class<? extends EntityAdjunct> clazz, AbstractEntityAdjunct adjunct) {
name = clazz.getCanonicalName();
configKeys = Collections.unmodifiableMap(findConfigKeys(clazz, null));
configKeysSet = ImmutableSet.copyOf(this.configKeys.values());
if (LOG.isTraceEnabled())
LOG.trace("Policy {} config keys: {}", name, Joiner.on(", ").join(configKeys.keySet()));
}
AdjunctType(String name, Map<String, ConfigKey<?>> configKeys) {
this.name = name;
this.configKeys = ImmutableMap.copyOf(configKeys);
this.configKeysSet = ImmutableSet.copyOf(this.configKeys.values());
}
public String getName() {
return name;
}
public Set<ConfigKey<?>> getConfigKeys() {
return configKeysSet;
}
public ConfigKey<?> getConfigKey(String name) {
return configKeys.get(name);
}
@Override
public int hashCode() {
return Objects.hashCode(name, configKeys);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (getClass() != obj.getClass()) return false;
AdjunctType o = (AdjunctType) obj;
if (!Objects.equal(name, o.getName())) return false;
if (!Objects.equal(getConfigKeys(), o.getConfigKeys())) return false;
return true;
}
@Override
public String toString() {
return Objects.toStringHelper(name)
.add("configKeys", configKeys)
.toString();
}
/**
* Finds the config keys defined on the entity's class, statics and optionally any non-static (discouraged).
*/
// TODO Remove duplication from EntityDynamicType
protected static Map<String,ConfigKey<?>> findConfigKeys(Class<? extends EntityAdjunct> clazz, EntityAdjunct optionalInstance) {
try {
Map<String,ConfigKey<?>> result = Maps.newLinkedHashMap();
Map<String,Field> configFields = Maps.newLinkedHashMap();
for (Field f : clazz.getFields()) {
boolean isConfigKey = ConfigKey.class.isAssignableFrom(f.getType());
if (!isConfigKey) {
if (!HasConfigKey.class.isAssignableFrom(f.getType())) {
// neither ConfigKey nor HasConfigKey
continue;
}
}
if (!Modifier.isStatic(f.getModifiers())) {
// require it to be static or we have an instance
LOG.warn("Discouraged use of non-static config key "+f+" defined in " + (optionalInstance!=null ? optionalInstance : clazz));
if (optionalInstance==null) continue;
}
ConfigKey<?> k = isConfigKey ? (ConfigKey<?>) f.get(optionalInstance) :
((HasConfigKey<?>)f.get(optionalInstance)).getConfigKey();
Field alternativeField = configFields.get(k.getName());
// Allow overriding config keys (e.g. to set default values) when there is an assignable-from relationship between classes
Field definitiveField = alternativeField != null ? inferSubbestField(alternativeField, f) : f;
boolean skip = false;
if (definitiveField != f) {
// If they refer to the _same_ instance, just keep the one we already have
if (alternativeField.get(optionalInstance) == f.get(optionalInstance)) skip = true;
}
if (skip) {
//nothing
} else if (definitiveField == f) {
result.put(k.getName(), k);
configFields.put(k.getName(), f);
} else if (definitiveField != null) {
if (LOG.isDebugEnabled()) LOG.debug("multiple definitions for config key {} on {}; preferring that in sub-class: {} to {}", new Object[] {
k.getName(), optionalInstance!=null ? optionalInstance : clazz, alternativeField, f});
} else if (definitiveField == null) {
LOG.warn("multiple definitions for config key {} on {}; preferring {} to {}", new Object[] {
k.getName(), optionalInstance!=null ? optionalInstance : clazz, alternativeField, f});
}
}
return result;
} catch (IllegalAccessException e) {
throw Throwables.propagate(e);
}
}
/**
* Gets the field that is in the sub-class; or null if one field does not come from a sub-class of the other field's class
*/
// TODO Remove duplication from EntityDynamicType
private static Field inferSubbestField(Field f1, Field f2) {
Class<?> c1 = f1.getDeclaringClass();
Class<?> c2 = f2.getDeclaringClass();
boolean isSuper1 = c1.isAssignableFrom(c2);
boolean isSuper2 = c2.isAssignableFrom(c1);
return (isSuper1) ? (isSuper2 ? null : f2) : (isSuper2 ? f1 : null);
}
}