All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datakernel.aggregation.AggregationUtils Maven / Gradle / Ivy

/*
 * Copyright (C) 2015-2018 SoftIndex LLC.
 *
 * Licensed 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 io.datakernel.aggregation;

import io.datakernel.aggregation.annotation.Key;
import io.datakernel.aggregation.annotation.Measures;
import io.datakernel.aggregation.fieldtype.FieldType;
import io.datakernel.aggregation.measure.Measure;
import io.datakernel.aggregation.ot.AggregationStructure;
import io.datakernel.aggregation.util.PartitionPredicate;
import io.datakernel.codec.StructuredCodec;
import io.datakernel.codegen.*;
import io.datakernel.serializer.BinarySerializer;
import io.datakernel.serializer.SerializerBuilder;
import io.datakernel.serializer.asm.SerializerGenClass;
import io.datakernel.stream.processor.StreamReducers.Reducer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;

import static io.datakernel.codec.StructuredCodecs.ofTupleArray;
import static io.datakernel.codegen.Expressions.*;
import static io.datakernel.util.CollectionUtils.concat;
import static io.datakernel.util.CollectionUtils.keysToMap;
import static io.datakernel.util.Preconditions.checkArgument;
import static io.datakernel.util.ReflectionUtils.extractFieldNameFromGetter;

/**
 * Defines a structure of an aggregation.
 * It is defined by keys, fields and their types.
 * Contains methods for defining dynamic classes, that are used for different operations.
 * Provides serializer for records that have the defined structure.
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class AggregationUtils {
	private AggregationUtils() {
	}

	public static  Class createKeyClass(Map keys, DefiningClassLoader classLoader) {
		List keyList = new ArrayList<>(keys.keySet());
		return ClassBuilder.create(classLoader, Comparable.class)
				.initialize(cb ->
						keys.forEach((key, value) ->
								cb.withField(key, value.getInternalDataType())))
				.withMethod("compareTo", compareTo(keyList))
				.withMethod("equals", asEquals(keyList))
				.withMethod("hashCode", hashCodeOfThis(keyList))
				.withMethod("toString", asString(keyList))
				.build();
	}

	public static  Comparator createKeyComparator(Class recordClass, List keys, DefiningClassLoader classLoader) {
		return ClassBuilder.create(classLoader, Comparator.class)
				.withMethod("compare", compare(recordClass, keys))
				.buildClassAndCreateNewInstance();
	}

	public static  Function createMapper(Class recordClass, Class resultClass,
			List keys, List fields,
			DefiningClassLoader classLoader) {
		return ClassBuilder.create(classLoader, Function.class)
				.withMethod("apply", () -> {
					Expression result1 = let(constructor(resultClass));
					ExpressionSequence sequence = ExpressionSequence.create();
					for (String fieldName : (Iterable) Stream.concat(keys.stream(), fields.stream())::iterator) {
						sequence.add(set(
								property(result1, fieldName),
								property(cast(arg(0), recordClass), fieldName)));
					}
					return sequence.add(result1);
				})
				.buildClassAndCreateNewInstance();
	}

	public static  Function createKeyFunction(Class recordClass, Class keyClass,
			List keys,
			DefiningClassLoader classLoader) {
		return ClassBuilder.create(classLoader, Function.class)
				.withMethod("apply", () -> {
					Expression key = let(constructor(keyClass));
					ExpressionSequence sequence = ExpressionSequence.create();
					for (String keyString : keys) {
						sequence.add(set(
								property(key, keyString),
								property(cast(arg(0), recordClass), keyString)));
					}
					return sequence.add(key);
				})
				.buildClassAndCreateNewInstance();
	}

	public static  Class createRecordClass(AggregationStructure aggregation,
			Collection keys, Collection fields,
			DefiningClassLoader classLoader) {
		return createRecordClass(
				keysToMap(keys.stream(), aggregation.getKeyTypes()::get),
				keysToMap(fields.stream(), aggregation.getMeasureTypes()::get),
				classLoader);
	}

	public static  Class createRecordClass(Map keys, Map fields,
			DefiningClassLoader classLoader) {
		return ClassBuilder.create(classLoader, (Class) Object.class)
				.initialize(cb ->
						keys.forEach((key, value) ->
								cb.withField(key, value.getInternalDataType())))
				.initialize(cb ->
						fields.forEach((key, value) ->
								cb.withField(key, value.getInternalDataType())))
				.withMethod("toString", asString(concat(keys.keySet(), fields.keySet())))
				.build();
	}

	public static  BinarySerializer createBinarySerializer(AggregationStructure aggregation, Class recordClass,
			List keys, List fields,
			DefiningClassLoader classLoader) {
		return createBinarySerializer(recordClass,
				keysToMap(keys.stream(), aggregation.getKeyTypes()::get),
				keysToMap(fields.stream(), aggregation.getMeasureTypes()::get),
				classLoader);
	}

	private static  BinarySerializer createBinarySerializer(Class recordClass,
			Map keys, Map fields,
			DefiningClassLoader classLoader) {
		SerializerGenClass serializerGenClass = new SerializerGenClass(recordClass);
		for (String key : keys.keySet()) {
			FieldType keyType = keys.get(key);
			try {
				Field recordClassKey = recordClass.getField(key);
				serializerGenClass.addField(recordClassKey, keyType.getSerializer(), -1, -1);
			} catch (NoSuchFieldException e) {
				throw new RuntimeException(e);
			}
		}
		for (String field : fields.keySet()) {
			try {
				Field recordClassField = recordClass.getField(field);
				serializerGenClass.addField(recordClassField, fields.get(field).getSerializer(), -1, -1);
			} catch (NoSuchFieldException e) {
				throw new RuntimeException(e);
			}
		}
		return SerializerBuilder.create(classLoader).build(serializerGenClass);
	}

	public static  Reducer aggregationReducer(AggregationStructure aggregation, Class inputClass, Class outputClass,
			List keys, List fields,
			DefiningClassLoader classLoader) {

		Expression accumulator = let(constructor(outputClass));
		ExpressionSequence onFirstItem = ExpressionSequence.create();
		ExpressionSequence onNextItem = ExpressionSequence.create();

		for (String key : keys) {
			onFirstItem.add(set(
					property(accumulator, key),
					property(cast(arg(2), inputClass), key)));
		}

		for (String field : fields) {
			Measure aggregateFunction = aggregation.getMeasure(field);
			onFirstItem.add(aggregateFunction.initAccumulatorWithAccumulator(
					property(accumulator, field),
					property(cast(arg(2), inputClass), field)
			));
			onNextItem.add(aggregateFunction.reduce(
					property(cast(arg(3), outputClass), field),
					property(cast(arg(2), inputClass), field)
			));
		}

		onFirstItem.add(accumulator);
		onNextItem.add(arg(3));

		return ClassBuilder.create(classLoader, Reducer.class)
				.withMethod("onFirstItem", onFirstItem)
				.withMethod("onNextItem", onNextItem)
				.withMethod("onComplete", call(arg(0), "accept", arg(2)))
				.buildClassAndCreateNewInstance();
	}

	public static  Aggregate createPreaggregator(AggregationStructure aggregation, Class inputClass, Class outputClass,
			Map keyFields, Map measureFields,
			DefiningClassLoader classLoader) {

		Expression accumulator = let(constructor(outputClass));
		ExpressionSequence createAccumulator = ExpressionSequence.create();
		ExpressionSequence accumulate = ExpressionSequence.create();

		for (String key : keyFields.keySet()) {
			String inputField = keyFields.get(key);
			createAccumulator.add(set(
					property(accumulator, key),
					property(cast(arg(0), inputClass), inputField)));
		}

		for (String measure : measureFields.keySet()) {
			String inputFields = measureFields.get(measure);
			Measure aggregateFunction = aggregation.getMeasure(measure);

			createAccumulator.add(aggregateFunction.initAccumulatorWithValue(
					property(accumulator, measure),
					inputFields == null ? null : property(cast(arg(0), inputClass), inputFields)));
			accumulate.add(aggregateFunction.accumulate(
					property(cast(arg(0), outputClass), measure),
					inputFields == null ? null : property(cast(arg(1), inputClass), inputFields)));
		}

		createAccumulator.add(accumulator);

		return ClassBuilder.create(classLoader, Aggregate.class)
				.withMethod("createAccumulator", createAccumulator)
				.withMethod("accumulate", accumulate)
				.buildClassAndCreateNewInstance();
	}

	private static final PartitionPredicate SINGLE_PARTITION = (t, u) -> true;

	public static  PartitionPredicate singlePartition() {
		return SINGLE_PARTITION;
	}

	public static PartitionPredicate createPartitionPredicate(Class recordClass, List partitioningKey,
			DefiningClassLoader classLoader) {
		if (partitioningKey.isEmpty())
			return singlePartition();

		PredicateDefAnd predicate = PredicateDefAnd.create();
		for (String keyComponent : partitioningKey) {
			predicate.add(cmpEq(
					property(cast(arg(0), recordClass), keyComponent),
					property(cast(arg(1), recordClass), keyComponent)));
		}

		return ClassBuilder.create(classLoader, PartitionPredicate.class)
				.withMethod("isSamePartition", predicate)
				.buildClassAndCreateNewInstance();
	}

	public static  Map scanKeyFields(Class inputClass) {
		Map keyFields = new LinkedHashMap<>();
		for (Field field : inputClass.getFields()) {
			for (Annotation annotation : field.getAnnotations()) {
				if (annotation.annotationType() == Key.class) {
					String value = ((Key) annotation).value();
					keyFields.put("".equals(value) ? field.getName() : value, field.getName());
				}
			}
		}
		for (Method method : inputClass.getMethods()) {
			for (Annotation annotation : method.getAnnotations()) {
				if (annotation.annotationType() == Key.class) {
					String value = ((Key) annotation).value();
					keyFields.put("".equals(value) ? method.getName() : value, method.getName());
				}
			}
		}
		checkArgument(!keyFields.isEmpty(), "Missing @Key annotations in %s", inputClass);
		return keyFields;
	}

	public static  Map scanMeasureFields(Class inputClass) {
		Map measureFields = new LinkedHashMap<>();
		for (Annotation annotation : inputClass.getAnnotations()) {
			if (annotation.annotationType() == Measures.class) {
				for (String measure : ((Measures) annotation).value()) {
					measureFields.put(measure, null);
				}
			}
		}
		for (Field field : inputClass.getFields()) {
			for (Annotation annotation : field.getAnnotations()) {
				if (annotation.annotationType() == Measures.class) {
					for (String measure : ((Measures) annotation).value()) {
						measureFields.put(measure.equals("") ? field.getName() : measure, field.getName());
					}
				}
			}
		}
		for (Method method : inputClass.getMethods()) {
			for (Annotation annotation : method.getAnnotations()) {
				if (annotation.annotationType() == Measures.class) {
					for (String measure : ((Measures) annotation).value()) {
						measureFields.put(measure.equals("") ? extractFieldNameFromGetter(method) : measure, method.getName());
					}
				}
			}
		}
		checkArgument(!measureFields.isEmpty(), "Missing @Measure(s) annotations in %s", inputClass);
		return measureFields;
	}

	public static StructuredCodec getPrimaryKeyCodec(AggregationStructure aggregation) {
		StructuredCodec[] keyCodec = new StructuredCodec[aggregation.getKeys().size()];
		for (int i = 0; i < aggregation.getKeys().size(); i++) {
			String key = aggregation.getKeys().get(i);
			FieldType keyType = aggregation.getKeyTypes().get(key);
			keyCodec[i] = keyType.getInternalCodec();
		}
		return ofTupleArray(keyCodec)
				.transform(PrimaryKey::ofArray, PrimaryKey::getArray);
	}

}