/*
* 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.enricher.stock;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.Enricher;
import org.apache.brooklyn.api.sensor.EnricherSpec;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.api.sensor.SensorEvent;
import org.apache.brooklyn.core.enricher.AbstractEnricher;
import org.apache.brooklyn.enricher.stock.reducer.Reducer;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.collections.QuorumCheck;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.text.StringPredicates;
import org.apache.brooklyn.util.text.Strings;
import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
public class Enrichers {
private Enrichers() {}
public static InitialBuilder builder() {
return new InitialBuilder();
}
public abstract static class Builder<B extends Builder<B>> {
@SuppressWarnings("unchecked")
protected B self() {
return (B) this;
}
}
public abstract static class AbstractEnricherBuilder<B extends AbstractEnricherBuilder<B>> extends Builder<B> {
final Class<? extends Enricher> enricherType;
Boolean suppressDuplicates;
String uniqueTag;
Set<Object> tags = MutableSet.of();
public AbstractEnricherBuilder(Class<? extends Enricher> enricherType) {
this.enricherType = enricherType;
}
public B uniqueTag(String tag) {
uniqueTag = Preconditions.checkNotNull(tag);
return self();
}
public B addTag(Object tag) {
tags.add(Preconditions.checkNotNull(tag));
return self();
}
public B suppressDuplicates(Boolean suppressDuplicates) {
this.suppressDuplicates = suppressDuplicates;
return self();
}
protected abstract String getDefaultUniqueTag();
protected EnricherSpec<? extends Enricher> build() {
EnricherSpec<? extends Enricher> spec = EnricherSpec.create(enricherType);
String uniqueTag2 = uniqueTag;
if (uniqueTag2==null)
uniqueTag2 = getDefaultUniqueTag();
if (uniqueTag2!=null)
spec.uniqueTag(uniqueTag2);
if (!tags.isEmpty()) spec.tags(tags);
if (suppressDuplicates!=null)
spec.configure(AbstractEnricher.SUPPRESS_DUPLICATES, suppressDuplicates);
return spec;
}
}
protected abstract static class AbstractInitialBuilder<B extends AbstractInitialBuilder<B>> extends Builder<B> {
public PropagatorBuilder propagating(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
return new PropagatorBuilder(vals);
}
public PropagatorBuilder propagating(Iterable<? extends Sensor<?>> vals) {
return new PropagatorBuilder(vals);
}
public PropagatorBuilder propagating(Sensor<?>... vals) {
return new PropagatorBuilder(vals);
}
public PropagatorBuilder propagatingAll() {
return new PropagatorBuilder(true, null);
}
public PropagatorBuilder propagatingAllButUsualAnd(Sensor<?>... vals) {
return new PropagatorBuilder(true, ImmutableSet.<Sensor<?>>builder().addAll(Propagator.SENSORS_NOT_USUALLY_PROPAGATED).add(vals).build());
}
public PropagatorBuilder propagatingAllBut(Sensor<?>... vals) {
return new PropagatorBuilder(true, ImmutableSet.copyOf(vals));
}
public PropagatorBuilder propagatingAllBut(Iterable<? extends Sensor<?>> vals) {
return new PropagatorBuilder(true, vals);
}
/**
* Builds an enricher which transforms a given sensor:
* <li> applying a (required) function ({@link TransformerBuilder#computing(Function)}, or
* {@link AbstractAggregatorBuilder#computingAverage()}/
* {@link AbstractAggregatorBuilder#computingSum()}, mandatory);
* <li> and publishing it on the entity where the enricher is attached;
* <li> optionally taking the sensor from a different source entity ({@link TransformerBuilder#from(Entity)});
* <li> and optionally publishing it as a different sensor ({@link TransformerBuilder#publishing(AttributeSensor)});
* <p> You must supply at least one of the optional values, of course, otherwise the enricher may loop endlessly!
*/
public <S> TransformerBuilder<S, Object> transforming(AttributeSensor<S> val) {
return new TransformerBuilder<S, Object>(val);
}
/** as {@link #transforming(AttributeSensor)} but accepting multiple sensors, with the function acting on the set of values */
public <S> CombinerBuilder<S, Object> combining(Collection<AttributeSensor<? extends S>> vals) {
return new CombinerBuilder<S, Object>(vals);
}
/** as {@link #combining(Collection)} */
@SafeVarargs
public final <S> CombinerBuilder<S, Object> combining(AttributeSensor<? extends S>... vals) {
return new CombinerBuilder<S, Object>(vals);
}
/** as {@link #combining(Collection)} but the collection of values comes from the given sensor on multiple entities */
public <S> AggregatorBuilder<S, Object> aggregating(AttributeSensor<S> val) {
return new AggregatorBuilder<S,Object>(val);
}
/** creates an {@link UpdatingMap} enricher:
* {@link UpdatingMapBuilder#from(AttributeSensor)} and {@link UpdatingMapBuilder#computing(Function)} are required
**/
public <S,TKey,TVal> UpdatingMapBuilder<S, TKey, TVal> updatingMap(AttributeSensor<Map<TKey,TVal>> target) {
return new UpdatingMapBuilder<S, TKey, TVal>(target);
}
/** creates a {@link org.apache.brooklyn.enricher.stock.Joiner} enricher builder
* which joins entries in a list to produce a String
**/
public JoinerBuilder joining(AttributeSensor<?> source) {
return new JoinerBuilder(source);
}
public ReducerBuilder reducing(Class<? extends Reducer> clazz, List<AttributeSensor<?>> sourceSensors) {
return new ReducerBuilder(clazz, sourceSensors);
}
}
protected abstract static class AbstractAggregatorBuilder<S, T, B extends AbstractAggregatorBuilder<S, T, B>> extends AbstractEnricherBuilder<B> {
protected final AttributeSensor<S> aggregating;
protected AttributeSensor<T> publishing;
protected Entity fromEntity;
/** @deprecated since 0.7.0, kept for backwards compatibility for rebind, but not used otherwise */
@Deprecated protected Function<? super Collection<S>, ? extends T> computing;
// use supplier so latest values of other fields can be used
protected Supplier<Function<? super Collection<S>, ? extends T>> computingSupplier;
protected Boolean fromMembers;
protected Boolean fromChildren;
protected Boolean excludingBlank;
protected ImmutableSet<Entity> fromHardcodedProducers;
protected Predicate<? super Entity> entityFilter;
protected Predicate<Object> valueFilter;
protected Object defaultValueForUnreportedSensors;
protected Object valueToReportIfNoSensors;
public AbstractAggregatorBuilder(AttributeSensor<S> aggregating) {
super(Aggregator.class);
this.aggregating = aggregating;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T2 extends T> AggregatorBuilder<S,T2> publishing(AttributeSensor<? extends T2> val) {
this.publishing = (AttributeSensor) checkNotNull(val);
return (AggregatorBuilder) self();
}
public B from(Entity val) {
this.fromEntity = checkNotNull(val);
return self();
}
public B fromMembers() {
this.fromMembers = true;
return self();
}
public B fromChildren() {
this.fromChildren = true;
return self();
}
public B fromHardcodedProducers(Iterable<? extends Entity> val) {
this.fromHardcodedProducers = ImmutableSet.copyOf(val);
return self();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public B computing(Function<? super Collection<S>, ? extends T> val) {
this.computingSupplier = (Supplier)Suppliers.ofInstance(checkNotNull(val));
return self();
}
@SuppressWarnings({ "unchecked", "rawtypes", "unused" })
private B computingSumLegacy() {
// since 0.7.0, kept in case we need to rebind to this
Function<Collection<S>, Number> function = new Function<Collection<S>, Number>() {
@Override public Number apply(Collection<S> input) {
return sum((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, (TypeToken) publishing.getTypeToken());
}};
this.computing((Function)function);
return self();
}
@SuppressWarnings({ "unchecked", "rawtypes", "unused" })
private B computingAverageLegacy() {
// since 0.7.0, kept in case we need to rebind to this
Function<Collection<S>, Number> function = new Function<Collection<S>, Number>() {
@Override public Number apply(Collection<S> input) {
return average((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, (TypeToken) publishing.getTypeToken());
}};
this.computing((Function)function);
return self();
}
public B computingSum() {
this.computingSupplier = new Supplier<Function<? super Collection<S>, ? extends T>>() {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Function<? super Collection<S>, ? extends T> get() {
// relies on TypeCoercion of result from Number to T, and type erasure for us to get away with it!
return (Function)new ComputingSum((Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, publishing.getTypeToken());
}
};
return self();
}
public B computingAverage() {
this.computingSupplier = new Supplier<Function<? super Collection<S>, ? extends T>>() {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Function<? super Collection<S>, ? extends T> get() {
// relies on TypeCoercion of result from Number to T, and type erasure for us to get away with it!
return (Function)new ComputingAverage((Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, publishing.getTypeToken());
}
};
return self();
}
public B defaultValueForUnreportedSensors(S val) {
this.defaultValueForUnreportedSensors = val;
return self();
}
public B valueToReportIfNoSensors(Object val) {
this.valueToReportIfNoSensors = val;
return self();
}
public B entityFilter(Predicate<? super Entity> val) {
this.entityFilter = val;
return self();
}
public B excludingBlank() {
this.excludingBlank = true;
return self();
}
@Override
protected String getDefaultUniqueTag() {
if (publishing==null) return null;
return "aggregator:"+publishing.getName();
}
public EnricherSpec<?> build() {
Predicate<Object> valueFilter;
if (Boolean.TRUE.equals(excludingBlank)) {
valueFilter = new Predicate<Object>() {
@Override public boolean apply(Object input) {
return (input != null) &&
((input instanceof CharSequence) ? Strings.isNonBlank((CharSequence)input) : true);
}
};
// above kept for deserialization; not sure necessary
valueFilter = StringPredicates.isNonBlank();
} else {
valueFilter = null;
}
// FIXME excludingBlank; use valueFilter? exclude means ignored entirely or substituted for defaultMemberValue?
return super.build().configure(MutableMap.builder()
.putIfNotNull(Aggregator.PRODUCER, fromEntity)
.put(Aggregator.TARGET_SENSOR, publishing)
.put(Aggregator.SOURCE_SENSOR, aggregating)
.putIfNotNull(Aggregator.FROM_CHILDREN, fromChildren)
.putIfNotNull(Aggregator.FROM_MEMBERS, fromMembers)
.putIfNotNull(Aggregator.TRANSFORMATION, computingSupplier.get())
.putIfNotNull(Aggregator.FROM_HARDCODED_PRODUCERS, fromHardcodedProducers)
.putIfNotNull(Aggregator.ENTITY_FILTER, entityFilter)
.putIfNotNull(Aggregator.VALUE_FILTER, valueFilter)
.putIfNotNull(Aggregator.DEFAULT_MEMBER_VALUE, defaultValueForUnreportedSensors)
.build());
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.omitNullValues()
.add("aggregating", aggregating)
.add("publishing", publishing)
.add("fromEntity", fromEntity)
.add("computing", computingSupplier)
.add("fromMembers", fromMembers)
.add("fromChildren", fromChildren)
.add("excludingBlank", excludingBlank)
.add("fromHardcodedProducers", fromHardcodedProducers)
.add("entityFilter", entityFilter)
.add("valueFilter", valueFilter)
.add("defaultValueForUnreportedSensors", defaultValueForUnreportedSensors)
.add("valueToReportIfNoSensors", valueToReportIfNoSensors)
.toString();
}
}
protected abstract static class AbstractCombinerBuilder<S, T, B extends AbstractCombinerBuilder<S, T, B>> extends AbstractEnricherBuilder<B> {
protected final List<AttributeSensor<? extends S>> combining;
protected AttributeSensor<T> publishing;
protected Entity fromEntity;
protected Function<? super Collection<S>, ? extends T> computing;
protected Boolean excludingBlank;
protected Object valueToReportIfNoSensors;
protected Predicate<Object> valueFilter;
// For summing/averaging
protected Object defaultValueForUnreportedSensors;
@SafeVarargs
public AbstractCombinerBuilder(AttributeSensor<? extends S>... vals) {
this(ImmutableList.copyOf(vals));
}
public AbstractCombinerBuilder(Collection<AttributeSensor<? extends S>> vals) {
super(Combiner.class);
checkArgument(checkNotNull(vals).size() > 0, "combining-sensors must be non-empty");
this.combining = ImmutableList.<AttributeSensor<? extends S>>copyOf(vals);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T2 extends T> CombinerBuilder<S,T2> publishing(AttributeSensor<? extends T2> val) {
this.publishing = (AttributeSensor) checkNotNull(val);
return (CombinerBuilder) this;
}
public B from(Entity val) {
this.fromEntity = checkNotNull(val);
return self();
}
public B computing(Function<? super Collection<S>, ? extends T> val) {
this.computing = checkNotNull(val);
return self();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public B computingSum() {
Function<Collection<S>, Number> function = new Function<Collection<S>, Number>() {
@Override public Number apply(Collection<S> input) {
return sum((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, (TypeToken) publishing.getTypeToken());
}};
this.computing((Function)function);
return self();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public B computingAverage() {
Function<Collection<S>, Number> function = new Function<Collection<S>, Number>() {
@Override public Number apply(Collection<S> input) {
return average((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, (TypeToken) publishing.getTypeToken());
}};
this.computing((Function)function);
return self();
}
public B defaultValueForUnreportedSensors(Object val) {
this.defaultValueForUnreportedSensors = val;
return self();
}
public B valueToReportIfNoSensors(Object val) {
this.valueToReportIfNoSensors = val;
return self();
}
public B excludingBlank() {
this.excludingBlank = true;
return self();
}
@Override
protected String getDefaultUniqueTag() {
if (publishing==null) return null;
return "combiner:"+publishing.getName();
}
public EnricherSpec<?> build() {
return super.build().configure(MutableMap.builder()
.putIfNotNull(Combiner.PRODUCER, fromEntity)
.put(Combiner.TARGET_SENSOR, publishing)
.put(Combiner.SOURCE_SENSORS, combining)
.putIfNotNull(Combiner.TRANSFORMATION, computing)
.putIfNotNull(Combiner.VALUE_FILTER, valueFilter)
.build());
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.omitNullValues()
.add("combining", combining)
.add("publishing", publishing)
.add("fromEntity", fromEntity)
.add("computing", computing)
.add("excludingBlank", excludingBlank)
.add("valueToReportIfNoSensors", valueToReportIfNoSensors)
.add("valueFilter", valueFilter)
.toString();
}
}
protected abstract static class AbstractTransformerBuilder<S, T, B extends AbstractTransformerBuilder<S, T, B>> extends AbstractEnricherBuilder<B> {
protected final AttributeSensor<S> transforming;
protected AttributeSensor<T> publishing;
protected Entity fromEntity;
protected Function<? super S, ?> computing;
protected Function<? super SensorEvent<S>, ?> computingFromEvent;
public AbstractTransformerBuilder(AttributeSensor<S> val) {
super(Transformer.class);
this.transforming = checkNotNull(val);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T2 extends T> TransformerBuilder<S,T2> publishing(AttributeSensor<? extends T2> val) {
this.publishing = (AttributeSensor) checkNotNull(val);
return (TransformerBuilder) this;
}
public B from(Entity val) {
this.fromEntity = checkNotNull(val);
return self();
}
public B computing(Function<? super S, ? extends T> val) {
this.computing = checkNotNull(val);
return self();
}
public B computingFromEvent(Function<? super SensorEvent<S>, ? extends T> val) {
this.computingFromEvent = checkNotNull(val);
return self();
}
@Override
protected String getDefaultUniqueTag() {
if (publishing==null) return null;
return "transformer:"+publishing.getName();
}
public EnricherSpec<?> build() {
return super.build().configure(MutableMap.builder()
.putIfNotNull(Transformer.PRODUCER, fromEntity)
.put(Transformer.TARGET_SENSOR, publishing)
.put(Transformer.SOURCE_SENSOR, transforming)
.putIfNotNull(Transformer.TRANSFORMATION_FROM_VALUE, computing)
.putIfNotNull(Transformer.TRANSFORMATION_FROM_EVENT, computingFromEvent)
.build());
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.omitNullValues()
.add("publishing", publishing)
.add("transforming", transforming)
.add("fromEntity", fromEntity)
.add("computing", computing)
.toString();
}
}
protected abstract static class AbstractPropagatorBuilder<B extends AbstractPropagatorBuilder<B>> extends AbstractEnricherBuilder<B> {
protected final Map<? extends Sensor<?>, ? extends Sensor<?>> propagating;
protected final Boolean propagatingAll;
protected final Iterable<? extends Sensor<?>> propagatingAllBut;
protected Entity fromEntity;
protected Task<? extends Entity> fromEntitySupplier;
public AbstractPropagatorBuilder(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
super(Propagator.class);
checkArgument(checkNotNull(vals).size() > 0, "propagating-sensors must be non-empty");
this.propagating = vals;
this.propagatingAll = null;
this.propagatingAllBut = null;
}
public AbstractPropagatorBuilder(Iterable<? extends Sensor<?>> vals) {
this(newIdentityMap(ImmutableSet.copyOf(vals)));
}
public AbstractPropagatorBuilder(Sensor<?>... vals) {
this(newIdentityMap(ImmutableSet.copyOf(vals)));
}
AbstractPropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) {
super(Propagator.class);
// Ugly constructor! Taking boolean to differentiate it from others; could use a static builder
// but feels like overkill having a builder for a builder, being called by a builder!
checkArgument(propagatingAll, "Not propagating all; use PropagatingAll(vals)");
this.propagating = null;
this.propagatingAll = true;
this.propagatingAllBut = (butVals == null || Iterables.isEmpty(butVals)) ? null: butVals;
}
public B from(Entity val) {
this.fromEntity = checkNotNull(val);
return self();
}
public B from(Task<? extends Entity> val) {
this.fromEntitySupplier = checkNotNull(val);
return self();
}
@Override
protected String getDefaultUniqueTag() {
List<String> summary = MutableList.of();
if (propagating!=null) {
for (Map.Entry<? extends Sensor<?>, ? extends Sensor<?>> entry: propagating.entrySet()) {
if (entry.getKey().getName().equals(entry.getValue().getName()))
summary.add(entry.getKey().getName());
else
summary.add(entry.getKey().getName()+"->"+entry.getValue().getName());
}
}
if (Boolean.TRUE.equals(propagatingAll))
summary.add("ALL");
if (propagatingAllBut!=null && !Iterables.isEmpty(propagatingAllBut)) {
List<String> allBut = MutableList.of();
for (Sensor<?> s: propagatingAllBut) allBut.add(s.getName());
summary.add("ALL_BUT:"+com.google.common.base.Joiner.on(",").join(allBut));
}
// TODO What to use as the entity id if using fromEntitySupplier?
String fromId = (fromEntity != null) ? fromEntity.getId() : fromEntitySupplier.getId();
return "propagating["+fromId+":"+com.google.common.base.Joiner.on(",").join(summary)+"]";
}
public EnricherSpec<? extends Enricher> build() {
return super.build().configure(MutableMap.builder()
.putIfNotNull(Propagator.PRODUCER, fromEntity)
.putIfNotNull(Propagator.PRODUCER, fromEntitySupplier)
.putIfNotNull(Propagator.SENSOR_MAPPING, propagating)
.putIfNotNull(Propagator.PROPAGATING_ALL, propagatingAll)
.putIfNotNull(Propagator.PROPAGATING_ALL_BUT, propagatingAllBut)
.build());
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.omitNullValues()
.add("fromEntity", fromEntity)
.add("fromEntitySupplier", fromEntitySupplier)
.add("propagating", propagating)
.add("propagatingAll", propagatingAll)
.add("propagatingAllBut", propagatingAllBut)
.toString();
}
}
public abstract static class AbstractUpdatingMapBuilder<S, TKey, TVal, B extends AbstractUpdatingMapBuilder<S, TKey, TVal, B>> extends AbstractEnricherBuilder<B> {
protected AttributeSensor<Map<TKey,TVal>> targetSensor;
protected AttributeSensor<? extends S> fromSensor;
protected TKey key;
protected Function<S, ? extends TVal> computing;
protected Boolean removingIfResultIsNull;
public AbstractUpdatingMapBuilder(AttributeSensor<Map<TKey,TVal>> target) {
super(UpdatingMap.class);
this.targetSensor = target;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public <S2 extends S> UpdatingMapBuilder<S2,TKey,TVal> from(AttributeSensor<S2> fromSensor) {
this.fromSensor = checkNotNull(fromSensor);
return (UpdatingMapBuilder) this;
}
public B computing(Function<S,? extends TVal> val) {
this.computing = checkNotNull(val);
return self();
}
/** sets an explicit key to use; defaults to using the name of the source sensor specified in {@link #from(AttributeSensor)} */
public B key(TKey key) {
this.key = key;
return self();
}
/** sets explicit behaviour for treating <code>null</code> return values;
* default is to remove */
public B removingIfResultIsNull(boolean val) {
this.removingIfResultIsNull = val;
return self();
}
@Override
protected String getDefaultUniqueTag() {
if (targetSensor==null || fromSensor==null) return null;
return "updating:"+targetSensor.getName()+"<-"+fromSensor.getName();
}
public EnricherSpec<?> build() {
return super.build().configure(MutableMap.builder()
.put(UpdatingMap.TARGET_SENSOR, targetSensor)
.put(UpdatingMap.SOURCE_SENSOR, fromSensor)
.putIfNotNull(UpdatingMap.KEY_IN_TARGET_SENSOR, key)
.put(UpdatingMap.COMPUTING, computing)
.putIfNotNull(UpdatingMap.REMOVING_IF_RESULT_IS_NULL, removingIfResultIsNull)
.build());
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.omitNullValues()
.add("publishing", targetSensor)
.add("fromSensor", fromSensor)
.add("key", key)
.add("computing", computing)
.add("removingIfResultIsNull", removingIfResultIsNull)
.toString();
}
}
protected abstract static class AbstractJoinerBuilder<B extends AbstractJoinerBuilder<B>> extends AbstractEnricherBuilder<B> {
protected final AttributeSensor<?> transforming;
protected AttributeSensor<String> publishing;
protected Entity fromEntity;
protected String separator;
protected Boolean quote;
protected Integer minimum;
protected Integer maximum;
public AbstractJoinerBuilder(AttributeSensor<?> source) {
super(Joiner.class);
this.transforming = checkNotNull(source);
}
public B publishing(AttributeSensor<String> target) {
this.publishing = checkNotNull(target);
return self();
}
public B separator(String separator) {
this.separator = separator;
return self();
}
public B quote(Boolean quote) {
this.quote = quote;
return self();
}
public B minimum(Integer minimum) {
this.minimum = minimum;
return self();
}
public B maximum(Integer maximum) {
this.maximum = maximum;
return self();
}
@Override
protected String getDefaultUniqueTag() {
if (transforming==null || publishing==null) return null;
return "joiner:"+transforming.getName()+"->"+publishing.getName();
}
public EnricherSpec<?> build() {
return super.build().configure(MutableMap.builder()
.putIfNotNull(Joiner.PRODUCER, fromEntity)
.put(Joiner.TARGET_SENSOR, publishing)
.put(Joiner.SOURCE_SENSOR, transforming)
.putIfNotNull(Joiner.SEPARATOR, separator)
.putIfNotNull(Joiner.QUOTE, quote)
.putIfNotNull(Joiner.MINIMUM, minimum)
.putIfNotNull(Joiner.MAXIMUM, maximum)
.build());
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.omitNullValues()
.add("publishing", publishing)
.add("transforming", transforming)
.add("separator", separator)
.toString();
}
}
protected abstract static class AbstractReducerBuilder<B extends AbstractReducerBuilder<B>> extends AbstractEnricherBuilder<B> {
protected AttributeSensor<?> publishing;
protected Entity fromEntity;
protected List<AttributeSensor<?>> reducing;
protected Function<? extends Iterable<?>, ?> computing;
protected String functionName;
private Map<String, Object> parameters;
public AbstractReducerBuilder(Class<? extends Reducer> clazz, List<AttributeSensor<?>> val) {
super(checkNotNull(clazz));
this.reducing = checkNotNull(val);
}
public B publishing(AttributeSensor<?> val) {
this.publishing = checkNotNull(val);
return self();
}
public B from(Entity val) {
this.fromEntity = checkNotNull(val);
return self();
}
public B computing(Function<? extends Iterable<?>, ?> val) {
this.computing = checkNotNull(val);
return self();
}
public B computing(String functionName) {
return computing(functionName, ImmutableMap.<String, Object>of());
}
public B computing(String functionName, Map<String, Object> parameters) {
this.functionName = functionName;
this.parameters = parameters;
return self();
}
public EnricherSpec<?> build() {
return super.build().configure(MutableMap.builder()
.put(Reducer.SOURCE_SENSORS, reducing)
.put(Reducer.PRODUCER, fromEntity)
.put(Reducer.TARGET_SENSOR, publishing)
.putIfNotNull(Reducer.REDUCER_FUNCTION, computing)
.putIfNotNull(Reducer.REDUCER_FUNCTION_TRANSFORMATION, functionName)
.putIfNotNull(Reducer.PARAMETERS, parameters)
.build()
);
}
@Override
protected String getDefaultUniqueTag() {
return "reducer:" + reducing.toString();
}
}
public static class InitialBuilder extends AbstractInitialBuilder<InitialBuilder> {
}
public static class AggregatorBuilder<S, T> extends AbstractAggregatorBuilder<S, T, AggregatorBuilder<S, T>> {
public AggregatorBuilder(AttributeSensor<S> aggregating) {
super(aggregating);
}
}
public static class PropagatorBuilder extends AbstractPropagatorBuilder<PropagatorBuilder> {
public PropagatorBuilder(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) {
super(vals);
}
public PropagatorBuilder(Iterable<? extends Sensor<?>> vals) {
super(vals);
}
public PropagatorBuilder(Sensor<?>... vals) {
super(vals);
}
PropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) {
super(propagatingAll, butVals);
}
}
public static class CombinerBuilder<S, T> extends AbstractCombinerBuilder<S, T, CombinerBuilder<S, T>> {
@SafeVarargs
public CombinerBuilder(AttributeSensor<? extends S>... vals) {
super(vals);
}
public CombinerBuilder(Collection<AttributeSensor<? extends S>> vals) {
super(vals);
}
}
public static class TransformerBuilder<S, T> extends AbstractTransformerBuilder<S, T, TransformerBuilder<S, T>> {
public TransformerBuilder(AttributeSensor<S> val) {
super(val);
}
}
public static class UpdatingMapBuilder<S, TKey, TVal> extends AbstractUpdatingMapBuilder<S, TKey, TVal, UpdatingMapBuilder<S, TKey, TVal>> {
public UpdatingMapBuilder(AttributeSensor<Map<TKey,TVal>> val) {
super(val);
}
}
public static class JoinerBuilder extends AbstractJoinerBuilder<JoinerBuilder> {
public JoinerBuilder(AttributeSensor<?> source) {
super(source);
}
}
public static class ReducerBuilder extends AbstractReducerBuilder<ReducerBuilder> {
public ReducerBuilder(Class<? extends Reducer> clazz, List<AttributeSensor<?>> val) {
super(clazz, val);
}
}
@Beta
private abstract static class ComputingNumber<T extends Number> implements Function<Collection<T>, T> {
protected final Number defaultValueForUnreportedSensors;
protected final Number valueToReportIfNoSensors;
protected final TypeToken<T> typeToken;
@SuppressWarnings({ "rawtypes", "unchecked" })
public ComputingNumber(Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> typeToken) {
this.defaultValueForUnreportedSensors = defaultValueForUnreportedSensors;
this.valueToReportIfNoSensors = valueToReportIfNoSensors;
if (typeToken!=null && TypeToken.of(Number.class).isAssignableFrom(typeToken.getType())) {
this.typeToken = typeToken;
} else if (typeToken==null || typeToken.isAssignableFrom(Number.class)) {
// use double if e.g. Object is supplied
this.typeToken = (TypeToken)TypeToken.of(Double.class);
} else {
throw new IllegalArgumentException("Type "+typeToken+" is not valid for "+this);
}
}
@Override public abstract T apply(Collection<T> input);
}
@Beta
public static class ComputingSum<T extends Number> extends ComputingNumber<T> {
public ComputingSum(Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> typeToken) {
super(defaultValueForUnreportedSensors, valueToReportIfNoSensors, typeToken);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override public T apply(Collection<T> input) {
return (T) sum((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, typeToken);
}
}
@Beta
public static class ComputingAverage<T extends Number> extends ComputingNumber<T> {
public ComputingAverage(Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> typeToken) {
super(defaultValueForUnreportedSensors, valueToReportIfNoSensors, typeToken);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override public T apply(Collection<T> input) {
return (T) average((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, typeToken);
}
}
protected static <T extends Number> T average(Collection<T> vals, Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> type) {
Double doubleValueToReportIfNoSensors = (valueToReportIfNoSensors == null) ? null : valueToReportIfNoSensors.doubleValue();
int count = count(vals, defaultValueForUnreportedSensors!=null);
Double result = (count==0) ? doubleValueToReportIfNoSensors :
(Double) ((sum(vals, defaultValueForUnreportedSensors, 0, TypeToken.of(Double.class)) / count));
return cast(result, type);
}
@SuppressWarnings("unchecked")
protected static <N extends Number> N cast(Number n, TypeToken<? extends N> numberType) {
return (N) TypeCoercions.castPrimitive(n, numberType.getRawType());
}
@Beta //may be moved
public static <N extends Number> N sum(Iterable<? extends Number> vals, Number valueIfNull, Number valueIfNone, TypeToken<N> type) {
double result = 0d;
int count = 0;
if (vals!=null) {
for (Number val : vals) {
if (val!=null) {
result += val.doubleValue();
count++;
} else if (valueIfNull!=null) {
result += valueIfNull.doubleValue();
count++;
}
}
}
if (count==0) return cast(valueIfNone, type);
return cast(result, type);
}
protected static int count(Iterable<? extends Object> vals, boolean includeNullValues) {
int result = 0;
if (vals != null)
for (Object val : vals)
if (val!=null || includeNullValues) result++;
return result;
}
@Beta
public static class ComputingIsQuorate<T> implements Function<Collection<Boolean>, Boolean> {
protected final TypeToken<T> typeToken;
protected final QuorumCheck quorumCheck;
protected final int totalSize;
@SuppressWarnings({ "unchecked", "rawtypes" })
public ComputingIsQuorate(TypeToken<T> typeToken, QuorumCheck quorumCheck, int totalSize) {
this.quorumCheck = quorumCheck;
this.totalSize = totalSize;
if (typeToken!=null && TypeToken.of(Boolean.class).isAssignableFrom(typeToken.getType())) {
this.typeToken = typeToken;
} else if (typeToken==null || typeToken.isAssignableFrom(Boolean.class)) {
this.typeToken = (TypeToken)TypeToken.of(Boolean.class);
} else {
throw new IllegalArgumentException("Type " + typeToken + " is not valid for " + this + " -- expected " + TypeToken.of(Boolean.class));
}
}
@Override
public Boolean apply(Collection<Boolean> input) {
int numTrue = 0;
for (Boolean inputVal : input)
if (Boolean.TRUE.equals(inputVal))
numTrue++;
return Boolean.valueOf(quorumCheck.isQuorate(numTrue, totalSize));
}
}
private static <T> Map<T,T> newIdentityMap(Set<T> keys) {
Map<T,T> result = Maps.newLinkedHashMap();
for (T key : keys) {
result.put(key, key);
}
return result;
}
}