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

com.sap.cds.util.CdsSearchUtils Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version
/*******************************************************************
 * © 2020 SAP SE or an SAP affiliate company. All rights reserved. *
 *******************************************************************/
package com.sap.cds.util;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;

import com.google.common.collect.Streams;
import com.sap.cds.CdsException;
import com.sap.cds.impl.builder.model.ElementRefImpl;
import com.sap.cds.impl.parser.token.RefSegmentImpl;
import com.sap.cds.ql.ElementRef;
import com.sap.cds.ql.RefSegment;
import com.sap.cds.reflect.CdsAnnotatable;
import com.sap.cds.reflect.CdsAnnotation;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;

public class CdsSearchUtils {

	private static final String DEFAULT_SEARCH_ELEMENT = "Search.defaultSearchElement";
	private static final String CDS_SEARCH = "cds.search";

	@Deprecated
	private static final String SEARCH_CASCADE = "Search.cascade";

	private static final boolean INCLUDE = true;
	private static final boolean EXCLUDE = false;

	private final CdsStructuredType targetType;

	private CdsSearchUtils(CdsStructuredType targetType) {
		this.targetType = targetType;
	}

	public static Collection> searchableElementRefs(CdsStructuredType targetType) {
		Set> segments = searchableSegments(targetType);

		return segments.stream().map(CdsSearchUtils::ref).collect(toList());
	}

	private static Set> searchableSegments(CdsStructuredType targetType) {
		CdsSearchUtils utils = new CdsSearchUtils(targetType);

		return utils.searchableRefs();
	}

	private Set> searchableRefs() {
		Set> refs = new HashSet<>();

		handleNonAssocElements(INCLUDE, refs::add);

		if (refs.isEmpty()) {
			defaultSearchElements().map(CdsSearchUtils::segments).forEach(refs::add);
		}

		handleAssociations(refs);

		handleNonAssocElements(EXCLUDE, refs::remove);

		return refs;
	}

	private void handleNonAssocElements(boolean include, Consumer> action) {
		Stream> cdsSearch = pathsDeclaredByCdsSearch(include);
		Stream> defaultSearch = elements().filter(annotatedWith(DEFAULT_SEARCH_ELEMENT, include))
				.map(CdsSearchUtils::segments);

		Streams.concat(cdsSearch, defaultSearch).filter(s -> !isAssoc(s)).forEach(action);
	}

	private void handleAssociations(Set> refs) {
		Stream> cdsSearch = pathsDeclaredByCdsSearch(INCLUDE);
		Stream> searchCascade = elements().filter(searchCascade(INCLUDE)).map(CdsSearchUtils::segments);

		Streams.concat(cdsSearch, searchCascade).filter(this::isAssoc).forEach(addSearchableRefsTo(refs));
	}

	private Consumer> addSearchableRefsTo(Set> refs) {
		return prefix -> {
			CdsEntity target = type(prefix).as(CdsAssociationType.class).getTarget();
			searchableSegments(target).forEach(path -> {
				List ref = new ArrayList<>(prefix);
				ref.addAll(path);
				refs.add(ref);
			});
		};
	}

	private Stream> pathsDeclaredByCdsSearch(boolean include) {
		return targetType.annotations().filter(cdsSearch(include)).map(CdsSearchUtils::segments);
	}

	private static Predicate> cdsSearch(boolean include) {
		return a -> a.getName().startsWith(CDS_SEARCH) && a.getValue().equals(include);
	}

	private static String path(CdsAnnotation a) {
		String name = a.getName();
		return name.substring(CDS_SEARCH.length() + 1, name.length());
	}

	private static List segments(CdsAnnotation a) {
		return segments(path(a));
	}

	private Stream elements() {
		return targetType.concreteElements();
	}

	private Stream defaultSearchElements() {
		return elements().filter(e -> e.getType().isSimpleType(CdsBaseType.STRING));
	}

	private CdsElement element(List segments) {
		Iterator iter = segments.iterator();
		CdsStructuredType current = targetType;
		CdsElement element;
		boolean isAssoc;
		do {
			String seg = iter.next();
			element = current.getElement(seg);
			isAssoc = element.getType().isAssociation();
			if (isAssoc) {
				current = current.getTargetOf(seg);
			} else if (iter.hasNext()) {
				throw new CdsException("Invalid path: " + segments);
			}
		} while (iter.hasNext());

		return element;
	}

	private CdsType type(List segments) {
		return element(segments).getType();
	}

	private boolean isAssoc(List s) {
		return isAssoc(element(s));
	}

	private static boolean isAssoc(CdsElement e) {
		return e.getType().isAssociation();
	}

	@Deprecated
	private static Predicate searchCascade(boolean include) {
		return annotatedWith(SEARCH_CASCADE, include);
	}

	private static Predicate annotatedWith(String annotationName, boolean include) {
		return t -> t.findAnnotation(annotationName).filter(a -> a.getValue().equals(include)).isPresent();
	}

	private static ElementRef ref(List segments) {
		List ids = new ArrayList<>();
		for (String id : segments) {
			ids.add(RefSegmentImpl.refSegment(id));
		}
		return ElementRefImpl.element(ids);
	}

	private static List segments(CdsElement e) {
		return singletonList(e.getName());
	}

	private static List segments(String path) {
		return asList(path.split("\\."));
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy