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

com.sap.cds.impl.DataProcessor Maven / Gradle / Ivy

/************************************************************************
 * © 2020-2024 SAP SE or an SAP affiliate company. All rights reserved. *
 ************************************************************************/
package com.sap.cds.impl;

import static java.util.Arrays.asList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import com.google.common.collect.Lists;
import com.sap.cds.CdsDataProcessor;
import com.sap.cds.ql.cqn.Path;
import com.sap.cds.ql.impl.PathImpl;
import com.sap.cds.reflect.CdsArrayedType;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsSimpleType;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.util.CdsModelUtils;
import com.sap.cds.util.CdsModelUtils.CascadeType;

public class DataProcessor implements CdsDataProcessor {

	private final List actions = new ArrayList<>();
	private CascadeType cascadeType;
	private boolean depthFirst;

	public static class Factory implements CdsDataProcessor.Factory {
		@Override
		public CdsDataProcessor create() {
			return DataProcessor.create();
		}
	}

	public static DataProcessor create() {
		return new DataProcessor();
	}

	public DataProcessor forInsert() {
		cascadeType = CascadeType.INSERT;
		return this;
	}

	public DataProcessor forUpdate() {
		cascadeType = CascadeType.UPDATE;
		return this;
	}

	public DataProcessor withDepthFirst() {
		depthFirst = true;
		return this;
	}

	public CdsDataProcessor action(Action action) {
		actions.add(action);

		return this;
	}

	public DataProcessor action(BiConsumer> action) {
		actions.add(new Action() {
			@Override
			public void entry(Path path, CdsElement unused, CdsStructuredType type, Map entry) {
				action.accept(type, entry);
			}
		});
		return this;
	}

	public DataProcessor bulkAction(BiConsumer>> action) {
		actions.add(new Action() {
			@Override
			public void entries(Path path, CdsElement unused, CdsStructuredType type,
					Iterable> entries) {
				action.accept(type, entries);
			}
		});
		return this;
	}

	@Override
	public DataProcessor addConverter(Filter filter, Converter valConverter) {
		actions.add(new Action() {
			@Override
			public void entries(Path path2e, CdsElement e, CdsStructuredType type,
					Iterable> entries) {
				forAllEntries(path(path2e, e, type), entries, type, filter, (element, entry) -> {
					if (entry.containsKey(element.getName())) {
						Path path = path(path2e, e, type, entry);
						String name = element.getName();
						Object value = entry.get(name);
						convert(path, element, entry, name, value, valConverter);
					}
				});
			}

			private void convert(Path path, CdsElement element, Map entry, String name, Object value,
					Converter converter) {
				if (value instanceof List list) {
					ListIterator iterator = list.listIterator();
					while (iterator.hasNext()) {
						Object val = converter.convert(path, element, iterator.next());
						if (val == Converter.REMOVE) {
							iterator.remove();
						} else {
							iterator.set(val);
						}
					}
				} else {
					Object val = converter.convert(path, element, entry.get(name));
					if (val == Converter.REMOVE) {
						entry.remove(name);
					} else {
						entry.put(name, val);
					}
				}
			}

			@Override
			public void array(Path path, CdsElement element, CdsSimpleType type, List items) {
				if (filter.test(path, element, type)) {
					for (int i = 0; i < items.size(); i++) {
						items.set(i, valConverter.convert(path, element, items.get(i)));
					}
				}
			}
		});
		return this;
	}

	@Override
	public DataProcessor addGenerator(Filter filter, Generator valGenerator) {
		actions.add(new Action() {
			@Override
			public void entries(Path path, CdsElement element, CdsStructuredType type,
					Iterable> entries) {
				type.concreteNonAssociationElements().filter(e -> filter.test(path(path, element, type), e)) //
						.forEach(e -> entries.forEach(entry -> {
							String name = e.getName();
							if (entry.get(name) == null) {
								entry.put(name, valGenerator.generate(path(path, element, type, entry), e,
										entry.containsKey(name)));
							}
						}));
			}
		});
		return this;
	}

	@Override
	public DataProcessor addValidator(Filter filter, Validator validator, Mode mode) {
		actions.add(new Action() {
			final Handler handler = validationHandler(mode);

			@Override
			public void entries(Path path2e, CdsElement e, CdsStructuredType type,
					Iterable> entries) {
				forAllEntries(path(path2e, e, type), entries, type, filter, (element, entry) -> {
					Path path = path(path2e, e, type, entry);
					handler.apply(entry, element.getName(), value -> validate(path, element, value, validator));
				});
			}

			@SuppressWarnings("unchecked")
			private void validate(Path path, CdsElement element, Object value, Validator validator) {
				if (value instanceof List list) {
					list.forEach(v -> validator.validate(path, element, v));
				} else {
					validator.validate(path, element, value);
				}
			}

			@Override
			public void array(Path path, CdsElement element, CdsSimpleType type, List items) {
				if (filter.test(path, element, type)) {
					items.forEach(value -> validator.validate(path, element, value));
				}
			}
		});
		return this;
	}

	@FunctionalInterface
	private static interface Handler {
		void apply(Map map, String key, Consumer processor);
	}

	private static Handler validationHandler(Mode mode) {
		switch (mode) {
		case DECLARED:
			return (map, key, consumer) -> {
				Object value = map.getOrDefault(key, CdsDataProcessor.ABSENT);
				consumer.accept(value);
			};
		case NOT_NULL:
			return (map, key, consumer) -> {
				Object value = map.get(key);
				if (value != null) {
					consumer.accept(value);
				}
			};
		case NULL:
			return (map, key, consumer) -> {
				Object value = map.getOrDefault(key, CdsDataProcessor.ABSENT);
				if (value == null || value == CdsDataProcessor.ABSENT) {
					consumer.accept(value);
				}
			};
		default: // PRESENT
			return (map, key, consumer) -> {
				if (map.containsKey(key)) {
					consumer.accept(map.get(key));
				}
			};
		}
	}

	private static Path path(Path path, CdsElement e, CdsStructuredType type) {
		return ((PathImpl) path).append(e, type, new HashMap<>());
	}

	private static Path path(Path path, CdsElement e, CdsStructuredType type, Map entry) {
		return ((PathImpl) path).append(e, type, entry);
	}

	private static void forAllEntries(Path path, Iterable> entries, CdsStructuredType type,
			Filter filter, BiConsumer> handler) {
		type.elements().filter(e -> filter.test(path, e, e.getType())).forEach(element -> entries.forEach(entry -> {
			handler.accept(element, entry);
		}));
	}

	@Override
	public void process(Map entry, CdsStructuredType entryType) {
		process(asList(entry), entryType);
	}

	@Override
	@SuppressWarnings("unchecked")
	public void process(Iterable> entries, CdsStructuredType entryType) {
		if (entryType == null) {
			throw new IllegalArgumentException("Entry type must not be null");
		}
		Path path = new PathImpl(new LinkedList<>());
		executeAndTraverse(() -> performActions(path, null, entryType, (Iterable>) entries),
				() -> traverseEntries(path, null, entryType, entries));
	}

	private void performActions(Path path, CdsElement element, CdsStructuredType type,
			Iterable> entries) {
		actions.stream().forEach(action -> action.entries(path, element, type, entries));
	}

	private void performActions(Path path, CdsElement element, CdsStructuredType type, Map entry) {
		actions.stream().forEach(action -> action.entry(path, element, type, entry));
	}

	private void performActions(Path path, CdsElement element, CdsSimpleType type, List items) {
		for (Action action : actions) {
			action.array(path, element, type, items);
		}
	}

	@SuppressWarnings("unchecked")
	private void traverseEntries(Path path, CdsElement element, CdsStructuredType struct,
			Iterable> entries) {
		entries.forEach(entry -> entry.forEach((key, value) -> {
			if (value instanceof List list) {
				struct.findElement(key).filter(this::cascade)
						.ifPresent(e -> traverseMany(path(path, element, struct, entry), e, list));
			} else if (value instanceof Map) {
				struct.findElement(key).filter(this::cascade).ifPresent(
						e -> traverseOne(path(path, element, struct, entry), e, (Map) value));
			}
		}));
	}

	private boolean cascade(CdsElement e) {
		return !e.getType().isSimpleType(CdsBaseType.MAP)
				&& (cascadeType == null || !e.getType().isAssociation() || CdsModelUtils.isCascading(cascadeType, e));
	}

	private void traverseOne(Path path, CdsElement element, Map entry) {
		CdsStructuredType struct = struct(element);
		executeAndTraverse(() -> performActions(path, element, struct, entry),
				() -> traverseEntries(path, element, struct, asList(entry)));
	}

	@SuppressWarnings("unchecked")
	private void traverseMany(Path path, CdsElement element, List entries) {
		CdsType type = type(element);
		if (type.isSimple()) {
			performActions(path, element, type.as(CdsSimpleType.class), (List) entries);
		} else {
			CdsStructuredType struct = type.as(CdsStructuredType.class);
			List> structEntries = (List>) entries;
			executeAndTraverse(() -> performActions(path, element, struct, structEntries),
					() -> traverseEntries(path, element, struct, structEntries));
		}
	}

	private void executeAndTraverse(Runnable execution, Runnable traversal) {
		if (depthFirst) {
			traversal.run();
			execution.run();
		} else {
			execution.run();
			traversal.run();
		}
	}

	private static CdsStructuredType struct(CdsElement element) {
		CdsType type = element.getType();
		if (type.isAssociation()) {
			return type.as(CdsAssociationType.class).getTarget();
		}
		return type.as(CdsStructuredType.class);
	}

	private static CdsType type(CdsElement element) {
		CdsType type = element.getType();
		if (type.isStructured() || type.isAssociation()) {
			return struct(element);
		}
		if (type.isArrayed()) {
			return type.as(CdsArrayedType.class).getItemsType();
		}
		return type; // map type
	}

	public interface Action {

		default void entry(Path path, CdsElement element, CdsStructuredType type, Map entry) {
			entries(path, element, type, Lists.newArrayList(entry));
		}

		default void entries(Path path, CdsElement element, CdsStructuredType type,
				Iterable> entries) {
			entries.forEach(entry -> entry(path, element, type, entry));
		}

		default void array(Path path, CdsElement element, CdsSimpleType type, List items) {

		}
	}
}