com.sap.cds.generator.util.NamesUtils Maven / Gradle / Ivy
/************************************************************************
* © 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.generator.util;
import static com.sap.cds.generator.util.CaseFormatHelper.toLowerCamel;
import static com.sap.cds.generator.util.CaseFormatHelper.toUpperCamel;
import static com.sap.cds.generator.util.CaseFormatHelper.toUpperUnderscore;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import javax.lang.model.SourceVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.sap.cds.generator.Configuration;
import com.sap.cds.reflect.CdsAnnotatable;
import com.sap.cds.reflect.CdsAnnotation;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsDefinition;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsEnumType;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsOperation;
import com.sap.cds.reflect.CdsParameter;
import com.sap.cds.reflect.CdsService;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.reflect.impl.reader.model.CdsConstants;
import com.palantir.javapoet.ClassName;
import com.palantir.javapoet.NameAllocator;
public class NamesUtils {
private static final String CDS_MODEL = "CdsModel";
private static final String UP = "up_";
private static final String DOT = ".";
private static final Logger logger = LoggerFactory.getLogger(NamesUtils.class);
private static final String CONTEXT = "Context";
private final List excludes;
private final List includes;
public static final String ITEM_TYPE_NAME = "Item";
public static final String OPTIONAL_ARGS_TYPE_NAME = "OptionalArguments";
public static final String OPTIONAL_ARGS_PARAMETER_NAME = "optionalArguments";
public NamesUtils(Configuration config) {
this.excludes = config.getExcludes().stream().map(PatternMatcher::transformPattern).toList();
this.includes = config.getIncludes().stream().map(PatternMatcher::transformPattern).toList();
}
public static String qualifiedWrapperBuilderName(Configuration configuration, CdsDefinition def, String classNameSuffix, boolean isWrapper) {
boolean betterNames = configuration.getBetterNames();
String name = (isWrapper ? toUpperCamel(betterNames, unqualifiedContextName(def.getQualifiedName(), def.getName()))
: toUpperCamel(betterNames, def.getName())) + classNameSuffix;
if (def.getQualifiedName().equals(def.getName())) {// Wrapper name is 'CdsModel_'
return name;
}
return qualifiedContextName(def.getQualifiedName(), def.getName()) + DOT + name;
}
public static String qualifiedContextName(String qualifiedName, String name) {
if (name.contains(DOT)) {
return getQualifiedContextNameForDot(qualifiedName, name);
} else {
int lastDot = qualifiedName.lastIndexOf('.');
if (lastDot != -1) {
return qualifiedName.substring(0, lastDot);
}
return CDS_MODEL;
}
}
static String unqualifiedContextName(String qualifiedName, String name) {
if (name.contains(DOT)) {
String substring = getQualifiedContextNameForDot(qualifiedName, name);
if (substring.contains(DOT)) {
String[] bits = substring.split("\\.");
return bits[bits.length - 1];
}
return substring;
} else if (qualifiedName.contains(DOT)) {
String[] bits = qualifiedName.split("\\.");
return bits[bits.length - 2];
}
return CDS_MODEL;
}
private static String getQualifiedContextNameForDot(String qualifiedName, String name) {
String qualifiedContextName = null;
int endIndex = qualifiedName.lastIndexOf(name) - 1;
if (endIndex > 0) {
qualifiedContextName = qualifiedName.substring(0, endIndex);
}
if (Strings.isNullOrEmpty(qualifiedContextName)) {
return CDS_MODEL;
}
return qualifiedContextName;
}
public static String unqualifiedName(String qualifiedName) {
int lastDot = qualifiedName.lastIndexOf(DOT);
return qualifiedName.substring(lastDot + 1);
}
public static void warnOnJavaKeywords(String qualifiedName) {
int lastDot = qualifiedName.lastIndexOf(DOT);
if (lastDot != -1) {
String[] split = qualifiedName.split("\\.");
for (String word : split) {
if (SourceVersion.isKeyword(word)) {
logger.warn("The Entity {} contains a reserved Java keyword in its fully qualified name.",
qualifiedName);
}
}
}
}
public static boolean isValidTechnicalEntity(Configuration configuration, CdsModel model, CdsEntity entity) {
if (configuration.getInterfacesForAspects()) {
// Texts has no _up association, but generated
if (entity.getQualifiedName().endsWith("_texts") || entity.getQualifiedName().endsWith(".texts")) {
return true;
}
//REVISIT: This might change when the compiler will introduce
//marker for generated entities
boolean hasUpAssociation = hasUpElement(entity);
if (hasUpAssociation) {
String parentEntity = prefix(entity.getQualifiedName());
String association = unqualifiedName(entity.getQualifiedName());
Optional associationElement = model
.findEntity(parentEntity).flatMap(e -> e.findElement(association));
return associationElement
.filter(e -> e.getType().isAssociation())
.map(element -> {
Optional targetAspect = element.getType().as(CdsAssociationType.class).getTargetAspect();
return targetAspect.isPresent()
&& !targetAspect.get().isAnonymous();
}).orElse(false);
}
return true;
} else {
return !hasUpElement(entity);
}
}
public static String getResolvedWrapperName(String qualifiedBuilderName, String classNameSuffix) {
return qualifiedBuilderName.substring(0, qualifiedBuilderName.length() - 1) + "Model" + classNameSuffix;
}
/**
* Generates constant representing this definition without renaming support
*
* @param configuration the generator {@link Configuration}
* @param definition {@link CdsDefinition} to be represented
* @return constant name generated "as-is"
*/
public static String constantName(Configuration configuration, CdsDefinition definition) {
return toUpperUnderscore(toUpperCamel(configuration.getBetterNames(), definition.getName()));
}
/**
* Returns the effective package name of the given {@link CdsDefinition definition}.
*
* @param definition the {@link CdsDefinition}
* @param basePackage the base package
* @return the effective package name
*/
public static String packageName(CdsDefinition definition, String basePackage) {
String packageName = prefix(definition.getQualifiedName(), definition.getName());
return getPackageName(basePackage, packageName);
}
/**
* Returns the effective package name of the given {@link CdsService service} definition.
*
* @param service the {@link CdsService}
* @param basePackage the base package
* @return the effective package name
*/
public static String packageName(CdsService service, String basePackage) {
return getPackageName(basePackage, service.getQualifiedName());
}
/**
* Returns Java class name for given CDS type. The name is always qualified with the
* package derived from the namespace of the type and a base package.
* Uses @cds.java.this.name as override mechanism.
*
* @param configuration the instance of {@link Configuration}
* @param type the instance of {@link CdsType}
* @return instance of the {@link ClassName} representing the Java class name
*/
public static ClassName className(Configuration configuration, CdsType type) {
return ClassName.get(packageName(type, configuration.getBasePackage()), toUpperCamel(configuration.getBetterNames(), getJavaName(type, type::getName)));
}
/**
* Returns Java class name for given CDS element as a nested class to some
* other class. This is useful only for classes that represent nested types.
*
* @param configuration the generator {@link Configuration}
* @param parent {@link ClassName} of the parent class
* @param element a {@link CdsElement} that is nested within the parent class
* @return instance of the {@link ClassName} representing the Java class name
*/
public static ClassName className(Configuration configuration, ClassName parent, CdsElement element) {
return parent.nestedClass(toUpperCamel(configuration.getBetterNames(), getJavaName(element, element::getName)));
}
/**
* Returns Java class name for given CDS parameter as a nested class to some
* other class. This is useful only for classes that represent nested _anonymous_ types
* that never renamed.
*
* @param configuration the generator {@link Configuration}
* @param parent {@link ClassName} of the parent class
* @param parameter a {@link CdsParameter} that is nested within the parent class
* @return instance of the {@link ClassName} representing the Java class name
*/
public static ClassName className(Configuration configuration, ClassName parent, CdsParameter parameter) {
// We had no support for @cds.java.name there. Not sure if we need one.
return parent.nestedClass(toUpperCamel(configuration.getBetterNames(), parameter.getName()));
}
/**
* Returns Java class name for given CDS type with a suffix.
* Suffix is defined by the configuration, but is never configurable by the customer.
*
* @param configuration the instance of {@link Configuration}
* @param type the instance of {@link CdsType} that should be suffixed
* @return instance of the {@link ClassName} representing the Java class name
*/
public static ClassName suffixedClassName(Configuration configuration, CdsType type) {
return ClassName.get(packageName(type, configuration.getBasePackage()),
toUpperCamel(configuration.getBetterNames(), getJavaName(type, type::getName)) + configuration.getClassNameSuffix());
}
/**
* Returns suffixed class name that is nested within the parent class for a given element. E.g. Entity.ReturnType.
*
* @param configuration the instance of {@link Configuration}
* @param parent parent class name as {@link ClassName}
* @param element the element that should be nested within the parent class
* @return instance of the {@link ClassName} representing the Java class name
*/
public static ClassName suffixedClassName(Configuration configuration, ClassName parent, CdsElement element) {
return parent.nestedClass(toUpperCamel(configuration.getBetterNames(), getJavaName(element, element::getName)) + configuration.getClassNameSuffix());
}
/**
* Returns qualified typed service class name for the given {@link CdsService service} definition.
* NB: This is exception from standard rule
*
* @param configuration the generator {@link Configuration}
* @param service the {@link CdsService service} definition
* @return the full qualified typed service {@link ClassName}
*/
public static ClassName typedServiceClassName(Configuration configuration, CdsService service) {
//NB: service package name "skips" the service name in itself
String packageName = packageName(service, configuration.getBasePackage());
return ClassName.get(packageName, toUpperCamel(configuration.getBetterNames(), getJavaName(service, service::getName)));
}
/**
* Returns qualified typed service class name for the given {@link CdsService service} definition.
*
* @param configuration the generator {@link Configuration}
* @param service the {@link CdsService service} definition
* @return the full qualified typed service {@link String}
*/
public static ClassName typedServiceBuilderName(Configuration configuration, CdsService service) {
// Builder is never renamed. REVISIT this
String packageName = NamesUtils.packageName(service, configuration.getBasePackage());
return ClassName.get(packageName, toUpperCamel(configuration.getBetterNames(), service.getName()) + configuration.getClassNameSuffix());
}
/**
* Returns the name of the template handler class for an action or functions. Variant for unbound operation.
* Uses @cds.java.name as override mechanism.
*
* @param configuration the instance of {@link Configuration}
* @param operation the @{@link CdsOperation}
* @return instance of the {@link ClassName} representing the Java class name
*/
public static ClassName templateEventHandlerClassName(Configuration configuration, CdsOperation operation) {
String operationName = getJavaName(operation, operation::getName);
String handlerName = "%1$s_%2$s_handler".formatted(unqualifiedName(operation.getQualifier()), operationName);
return ClassName.get(configuration.getHandlerPackageName(), toUpperCamel(configuration.getBetterNames(), handlerName));
}
/**
* Returns the name of the template handler class for an action or functions. Variant for bound operation.
* Uses @cds.java.name as override mechanism.
*
* @param configuration the instance of {@link Configuration}
* @param entity the bound @{@link CdsEntity}
* @param operation the @{@link CdsOperation}
* @return instance of the {@link ClassName} representing the Java class name
*/
public static ClassName templateEventHandlerClassName(Configuration configuration, CdsEntity entity, CdsOperation operation) {
String operationName = getJavaName(operation, operation::getName);
ClassName entityClassName = className(configuration, entity);
String handlerName = "%1$s_%2$s_%3$s_handler".formatted(
unqualifiedName(entity.getQualifier()),
entityClassName.simpleName(),
operationName);
return ClassName.get(configuration.getHandlerPackageName(), toUpperCamel(configuration.getBetterNames(), handlerName));
}
/**
* Returns name for default on-handler method for operations
*
* @param configuration the instance of {@link Configuration}
* @param operation the instance of @{@link CdsOperation}
* @return name
*/
public static String getOnHandlerMethodName(Configuration configuration, CdsOperation operation) {
return "handle" + toUpperCamel(configuration.getBetterNames(), getJavaName(operation, operation::getName));
}
/**
* Returns qualified event context class name for the given CDS definition.
*
* @param configuration the generator {@link Configuration}
* @param boundEntity an optionally bound {@link CdsEntity entity}
* @param def the {@link CdsType}
* @return the full qualified event context {@link ClassName}
*/
public static ClassName eventContextClassName(Configuration configuration, CdsEntity boundEntity, CdsDefinition def) {
String packageName = boundEntity != null ? packageName(boundEntity, configuration.getBasePackage())
: packageName(def, configuration.getBasePackage());
boolean betterName = configuration.getBetterNames();
// NB: Keeping the Context within the argument of the toUpperCamel method is buggy,
// but that was already used by stakeholders
Optional annotation = def.findAnnotation(CdsConstants.ANNOTATION_CDS_JAVA_THIS_NAME)
.or(() -> def.findAnnotation(CdsConstants.ANNOTATION_CDS_JAVA_NAME))
.map(a -> (String) a.getValue());
if (annotation.isPresent()) {
String className = contextName(betterName, annotation.get());
return ClassName.get(packageName, className);
}
// use optional entity as prefix, if it's available and the switch "getUniqueEventContexts" is enabled
String ownName = contextName(betterName, def.getName());
if (configuration.getUniqueEventContexts() && boundEntity != null) {
ClassName prefix = className(configuration, boundEntity);
return ClassName.get(packageName, prefix.simpleName() + ownName);
} else {
return ClassName.get(packageName, ownName);
}
}
private static String contextName(boolean betterName, String value) {
return betterName ? toUpperCamel(true, value) + CONTEXT
: toUpperCamel(false, value + CONTEXT);
}
/**
* Returns method name that is derived from element name
*
* @param configuration the generator {@link Configuration}
* @param element the @{@link CdsElement}
* @return the name
*/
public static String methodName(Configuration configuration, CdsElement element) {
return toLowerCamel(configuration.getBetterNames(), elementName(element, element::getName));
}
/**
* Returns class name that is derived from element name. This exists for compatibility reasons
* and does not apply transformation
*
* @param element the @{@link CdsElement}
* @return the name
*/
public static String rawName(CdsElement element) {
return elementName(element, element::getName);
}
/**
* Returns method name that is derived from parameter name, should be used only in fluent mode
*
* @param configuration the generator {@link Configuration}
* @param parameter the @{@link CdsParameter}
* @return the name
*/
public static String methodName(Configuration configuration, CdsParameter parameter) {
return toLowerCamel(configuration.getBetterNames(), elementName(parameter, parameter::getName));
}
/**
* Returns method name that is derived from element name, should be used only in fluent mode
*
* @param configuration the generator {@link Configuration}
* @param operation the @{@link CdsOperation}
* @return the name
*/
public static String methodName(Configuration configuration, CdsOperation operation) {
if (!configuration.getBetterNames()) {
return getJavaName(operation, operation::getName).replace('.', '_');
} else {
return toLowerCamel(configuration.getBetterNames(), getJavaName(operation, operation::getName));
}
}
/**
* Returns classic setter name for an element
*
* @param configuration the generator {@link Configuration}
* @param element the {@link CdsElement}
* @return name
*/
public static String setterName(Configuration configuration, CdsElement element) {
return "set" + toUpperCamel(
configuration.getBetterNames(),
elementName(element, element::getName));
}
/**
* Returns classic getter name for an element
*
* @param configuration the generator {@link Configuration}
* @param element the {@link CdsElement}
* @return name
*/
public static String getterName(Configuration configuration, CdsElement element) {
return "get" + toUpperCamel(
configuration.getBetterNames(),
elementName(element, element::getName));
}
/**
* Returns classic setter name for a parameter
*
* @param configuration the generator {@link Configuration}
* @param parameter the {@link CdsParameter}
* @return name
*/
public static String setterName(Configuration configuration, CdsParameter parameter) {
return "set" + toUpperCamel(configuration.getBetterNames(), elementName(parameter, parameter::getName));
}
/**
* Returns classic getter name for a parameter
*
* @param configuration the generator {@link Configuration}
* @param parameter the {@link CdsParameter}
* @return name
*/
public static String getterName(Configuration configuration, CdsParameter parameter) {
return "get" + toUpperCamel(
configuration.getBetterNames(),
elementName(parameter, parameter::getName));
}
/**
* Returns argument name that is derived from element name
*
* @param element the @{@link CdsElement}
* @return the name
*/
public static String argumentName(Configuration configuration, CdsElement element) {
return methodName(configuration, element);
}
/**
* Returns argument name that is derived from parameter name
*
* @param parameter the @{@link CdsElement}
* @return the name
*/
public static String argumentName(Configuration configuration, CdsParameter parameter) {
return methodName(configuration, parameter);
}
public static String constantName(CdsElement element) {
return toUpperUnderscore(elementName(element, element::getName));
}
public static String normalizedConstantName(CdsEnumType.Enumeral> enumeral) {
return javaSafeConstantName(getJavaName(enumeral, enumeral::name));
}
private static String elementName(CdsAnnotatable element, Supplier defaultValue) {
Optional> cdsJavaName = Optional.empty();
// Compatibility: FK setters are never renamed
if (element.findAnnotation(CdsConstants.ANNOTATION_ODATA_FK4).isEmpty()) {
// TODO support cds.java.this.name -> currently unexpectedly propagated in reflection API due to derived types handling
cdsJavaName = element
.findAnnotation(CdsConstants.ANNOTATION_CDS_JAVA_NAME);
}
// This is a compatible variant for method names as they are used in the builder interfaces
// where the change might break the existing CQN queries.
return cdsJavaName.map(CdsAnnotation::getValue).orElse(defaultValue.get());
}
private static String getJavaName(CdsAnnotatable definition, Supplier defaultNameSupplier) {
return definition.findAnnotation(CdsConstants.ANNOTATION_CDS_JAVA_THIS_NAME)
.or(() -> definition.findAnnotation(CdsConstants.ANNOTATION_CDS_JAVA_NAME))
.map(a -> (String) a.getValue())
.orElseGet(defaultNameSupplier);
}
private static String javaSafeConstantName(String name) {
if (SourceVersion.isName(name)) {
return toUpperUnderscore(name);
}
return "_" + toUpperUnderscore(NameAllocator.toJavaIdentifier(name));
}
public boolean isExcluded(String qualifiedName) {
boolean included = includes.isEmpty() || matchesAny(qualifiedName, includes);
included &= !matchesAny(qualifiedName, excludes);
return !included;
}
@VisibleForTesting
static String prefix(String qualifiedName, String name) {
if (qualifiedName.equals(name)) {
return null;
} else {
return qualifiedName.substring(0, qualifiedName.lastIndexOf(name) - 1);
}
}
private static String getPackageName(String basePackage, String packageName) {
if (!Strings.isNullOrEmpty(basePackage)) {
packageName = Joiner.on('.').skipNulls().join(basePackage, packageName);
}
if (Strings.isNullOrEmpty(packageName)) {
packageName = "model";
}
return packageName.toLowerCase(Locale.US);
}
private static boolean matchesAny(String qualifiedName, List patternList) {
return patternList.stream().anyMatch(pm -> pm.matcher(qualifiedName).matches());
}
private static boolean hasUpElement(CdsEntity entity) {
return entity.elements().anyMatch(e -> e.getName().startsWith(UP) && e.getType().isAssociation() && e.isKey());
}
// Entity prefix e.g. Books for Books.texts
private static String prefix(String qualifiedName) {
int lastDot = qualifiedName.lastIndexOf('.');
if (lastDot != -1) {
return qualifiedName.substring(0, lastDot);
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy