
com.opencredo.concourse.data.tuples.TupleSchema Maven / Gradle / Ivy
The newest version!
package com.opencredo.concourse.data.tuples;
import com.google.common.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.joining;
/**
* An ordered collection of TupleSlots, defining what may be stored in a conforming Tuple.
*/
public final class TupleSchema {
/**
* Create a TupleSchema having the supplied TupleSlots.
* @param slots The TupleSlots in the schema.
* @return The created TupleSchema.
*/
public static TupleSchema of(String name, TupleSlot...slots) {
checkNotNull(name, "name must not be null");
checkNotNull(slots, "slots must not be null");
Map slotLookup = IntStream.range(0, slots.length)
.collect(HashMap::new, (m, i) -> m.put(slots[i].getName(), i), Map::putAll);
checkArgument(slots.length == slotLookup.size(), "Slot names are not unique");
return new TupleSchema(name, slots, slotLookup);
}
private final String name;
private final TupleSlot[] slots;
private final Map slotLookup;
private TupleSchema(String name, TupleSlot[] slots, Map slotLookup) {
this.name = name;
this.slots = slots;
this.slotLookup = slotLookup;
}
public String getName() {
return name;
}
/**
* Make a tuple of the supplied values, first validating that they conform to this schema.
* @param values The values to put in the Tuple.
* @return The created Tuple.
*/
public Tuple makeWith(Object...values) {
return make(values);
}
/**
* Make a tuple of the supplied values, first validating that they conform to this schema.
* @param values The values to put in the Tuple.
* @return The created Tuple.
*/
public Tuple make(Object[] values) {
checkNotNull(values, "value must not be null");
checkArgument(values.length == slots.length,
"Expected %s values, but received %s", slots.length, values.length);
if (!typesMatch(values)) {
throw new IllegalArgumentException(describeTypeMismatches(values));
}
return new Tuple(this, values);
}
/**
* Build a tuple using the supplied key/value pairs, first validating that the keys belong to this schema and are complete.
* @param keyValues The TupleKeyValues to use to create the Tuple.
* @return The created Tuple.
*/
public Tuple make(TupleKeyValue...keyValues) {
checkNotNull(keyValues, "keyValues must not be null");
if (!Stream.of(keyValues).allMatch(kv -> kv.belongsToSchema(this))) {
throw new IllegalArgumentException(String.format(
"Keys %s do not all belong to schema %s",
getKeyNames(keyValues), this));
}
if (Stream.of(keyValues).map(TupleKeyValue::getTupleKey).distinct().count() != slots.length) {
throw new IllegalArgumentException(String.format(
"Not all slots in %s filled by provided keys %s",
this, getKeyNames(keyValues)));
}
Object[] values = new Object[slots.length];
Stream.of(keyValues).forEach(kv -> kv.build(values));
return new Tuple(this, values);
}
private String getKeyNames(TupleKeyValue[] keyValues) {
return Stream.of(keyValues)
.map(TupleKeyValue::getTupleKey)
.map(Object::toString)
.collect(joining(",", "[", "]"));
}
/**
* Build a tuple using a map of tuple values constructed by the supplied builders.
* @param builders The builders to use to build up a map of tuple values.
* @return The created tuple.
*/
public Tuple make(NamedValue...builders) {
Map values = new HashMap<>();
Stream.of(builders).forEach(builder -> builder.accept(values));
return make(values);
}
/**
* Create a tuple from a map of name/value pairs.
* @param values The values to put in the tuple.
* @return The created tuple.
*/
public Tuple make(Map values) {
checkNotNull(values, "values must not be null");
checkMatchingKeys(values);
Object[] valueArray = new Object[slots.length];
getIndices().forEach(i -> valueArray[i] = values.get(slots[i].getName()));
return make(valueArray);
}
/**
* Create a tuple using the supplied deserialiser, out of a map of serialised values.
* @param deserialiser The deserialiser to use to deserialise values from the map.
* @param values A map of serialised tuple values.
* @param The type to which tuple value have been serialised, e.g. String.
* @return The created tuple.
*/
public Tuple deserialise(BiFunction deserialiser, Map values) {
checkNotNull(deserialiser, "deserialiser must not be null");
checkNotNull(values, "values must not be null");
checkMatchingKeys(values);
Object[] valueArray = new Object[slots.length];
getIndices().forEach(i -> valueArray[i] = slots[i].deserialise(deserialiser, values));
return make(valueArray);
}
private void checkMatchingKeys(Map values) {
checkArgument(values.keySet().equals(slotLookup.keySet()),
"Expected keys %s, but were %s", slotLookup.keySet(), values.keySet());
}
Object get(String name, Object[] values) {
Integer valueIndex = slotLookup.get(name);
checkArgument(valueIndex != null, "Schema %s does not have a slot named '%s'", this, name);
return values[valueIndex];
}
private boolean typesMatch(Object[] values) {
return getIndices().allMatch(i -> slots[i].accepts(values[i]));
}
private String describeTypeMismatches(Object[] values) {
return getIndices()
.filter(i -> !slots[i].accepts(values[i]))
.mapToObj(i -> String.format("Slot (%s) does not accept value <%s>", slots[i], values[i]))
.collect(joining(", "));
}
Map serialise(Function
© 2015 - 2025 Weber Informatics LLC | Privacy Policy