org.neo4j.cypherdsl.parser.Options Maven / Gradle / Ivy
Show all versions of neo4j-cypher-dsl-parser Show documentation
/*
* Copyright (c) 2019-2024 "Neo4j,"
* Neo4j Sweden AB [https://neo4j.com]
*
* This file is part of Neo4j.
*
* 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
*
* https://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 org.neo4j.cypherdsl.parser;
import static org.apiguardian.api.API.Status.STABLE;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import org.apiguardian.api.API;
import org.neo4j.cypherdsl.core.Clauses;
import org.neo4j.cypherdsl.core.Expression;
import org.neo4j.cypherdsl.core.Match;
import org.neo4j.cypherdsl.core.PatternElement;
import org.neo4j.cypherdsl.core.Return;
import org.neo4j.cypherdsl.core.Where;
import org.neo4j.cypherdsl.core.ast.Visitable;
/**
* Provides arguments to the {@link CypherParser cypher parser}. The options itself are thread safe and can be reused.
* The listener and modifications you provide are of course out of our control.
*
* @author Michael J. Simons
* @soundtrack Queen - The Game
* @since 2021.3.0
*/
@API(status = STABLE, since = "2021.3.0")
public final class Options {
private static final Options DEFAULT_OPTIONS = newOptions().build();
/**
* @return the default options
*/
public static Options defaultOptions() {
return DEFAULT_OPTIONS;
}
/**
* Use this method to start defining new options for a parser.
*
* @return A builder for new options.
*/
public static Builder newOptions() {
return Builder.newConfig();
}
/**
* Use this builder to create a new set of options..
*/
@SuppressWarnings("HiddenField")
public static final class Builder {
private BiFunction, Collection> labelFilter = (e, l) -> l;
private BiFunction, Collection> typeFilter = (e, t) -> t;
private final Map>> onNewExpressionCallbacks
= new EnumMap<>(ExpressionCreatedEventType.class);
private final Map>> onNewPatternElementCallbacks
= new EnumMap<>(PatternElementCreatedEventType.class);
private final Map>> onNewInvocationCallbacks
= new EnumMap<>(InvocationCreatedEventType.class);
private Function returnClauseFactory;
private Function matchClauseFactory;
private Map parameterValues = Map.of();
private boolean createSortedMaps = false;
private boolean alwaysCreateRelationshipsLTR = false;
private Builder() {
}
static Builder newConfig() {
return new Builder();
}
/**
* Configure a filter that is applied to labels.
*
* @param labelFilter Takes in an event type, a collection of labels and returns a probably new collection of labels.
* @return This builder.
*/
public Builder withLabelFilter(BiFunction, Collection> labelFilter) {
if (labelFilter == null) {
throw new IllegalArgumentException("Label filter may not be null.");
}
this.labelFilter = labelFilter;
return this;
}
/**
* Configure a filter that is applied to types.
*
* @param typeFilter Takes in an event type and the parsed type. May return the type itself or something else.
* @return This builder.
*/
public Builder withTypeFilter(BiFunction, Collection> typeFilter) {
if (typeFilter == null) {
throw new IllegalArgumentException("Type filter may not be null.");
}
this.typeFilter = typeFilter;
return this;
}
/**
* Adds a callback for when the specific {@link ExpressionCreatedEventType expression is created} event. For one type
* of event one or more callbacks can be declared which will be called in order in which they have been declared.
*
* Parsing will be aborted when a callback throws a {@link RuntimeException}.
*
* @param expressionCreatedEventType The type of the event
* @param resultingType The type of the expression the callback returns. Must match the one of the event type.
* @param callback A callback
* @param The type of the expression produced by the callback. Must match the one of the event type
* @return This builder
*/
public Builder withCallback(ExpressionCreatedEventType expressionCreatedEventType,
Class resultingType, Function callback) {
if (!expressionCreatedEventType.getTypeProduced().isAssignableFrom(resultingType)) {
throw new IllegalArgumentException(
"The type that is produced by '" + expressionCreatedEventType
+ "' is not compatible with " + resultingType);
}
var callbacks = this.onNewExpressionCallbacks
.computeIfAbsent(expressionCreatedEventType, k -> new ArrayList<>());
callbacks.add(callback);
return this;
}
/**
* Adds a callback for when a {@link PatternElement} is created during one of the phases described by {@link PatternElementCreatedEventType}.
* For one type of event one or more callbacks can be declared which will be called in order in which they have been declared.
* Callbacks can just collect or actually visit the elements created, or they are free to create new ones, effectively rewriting the query.
*
* Parsing will be aborted when a callback throws a {@link RuntimeException}.
*
* @param patternElementCreatedEventType The type of the event
* @param callback A callback
* @return This builder
* @since 2022.2.0
*/
public Builder withCallback(PatternElementCreatedEventType patternElementCreatedEventType, UnaryOperator callback) {
var callbacks = this.onNewPatternElementCallbacks
.computeIfAbsent(patternElementCreatedEventType, k -> new ArrayList<>());
callbacks.add(callback);
return this;
}
/**
* Adds a callback for when either a CALL-Procedure clause or a function-invocation expression is created.
* For one type of event one or more callbacks can be declared which will be called in order in which they have been declared.
* Callbacks can just collect or actually visit the elements created, or they are free to create new ones, effectively rewriting the query.
*
* Parsing will be aborted when a callback throws a {@link RuntimeException}.
*
* @param invocationCreatedEventType the event type
* @param resultingType the reified type being produced
* @param callback a callback
* @param The type of the result, must match the one of the event
* @return this builder
* @since 2022.8.6
*/
@SuppressWarnings("unchecked")
public Builder withCallback(InvocationCreatedEventType invocationCreatedEventType, Class resultingType, UnaryOperator callback) {
if (!invocationCreatedEventType.getTypeProduced().isAssignableFrom(resultingType)) {
throw new IllegalArgumentException(
"The type that is produced by '" + invocationCreatedEventType
+ "' is not compatible with " + resultingType);
}
var callbacks = this.onNewInvocationCallbacks
.computeIfAbsent(invocationCreatedEventType, k -> new ArrayList<>());
callbacks.add((UnaryOperator) callback);
return this;
}
/**
* Configures the factory for return clauses. The idea here is that you might intercept what is being returned
* or how it is sorted, limited and the like. The {@link ReturnDefinition definition} passed to the factory contains
* all necessary information for delegating to the {@link org.neo4j.cypherdsl.core.Clauses#returning(boolean, List, List, Expression, Expression)}
* factory.
*
* @param returnClauseFactory The factory producing return classes that should be used.
* @return This builder
*/
public Builder withReturnClauseFactory(Function returnClauseFactory) {
this.returnClauseFactory = returnClauseFactory;
return this;
}
/**
* Configures the factory for return clauses. The idea here is that you might intercept what is being matched
* and or how it is restricted. The {@link MatchDefinition definition} passed to the factory contains
* all necessary information for delegating to the {@link org.neo4j.cypherdsl.core.Clauses#match(boolean, List, Where, List)}
* factory.
*
* @param matchClauseFactory The factory producing return classes that should be used.
* @return This builder
* @since 2023.0.2
*/
public Builder withMatchClauseFactory(Function matchClauseFactory) {
this.matchClauseFactory = matchClauseFactory;
return this;
}
/**
* Set {@code createSortedMaps} to {@literal true} to parse existing maps into alphabetically sorted maps.
*
* @param createSortedMaps A flag whether to create sorted maps or not
* @return This builder
* @since 2023.2.0
*/
public Builder createSortedMaps(boolean createSortedMaps) {
this.createSortedMaps = createSortedMaps;
return this;
}
/**
* Instructs the parser to turn relationships that are given as {@code (a:A) <-[:TYPE]- (b:B)} into
* {@code (b:B) -[:TYPE]-> (a:A)}. Multi-hop patterns will be split into a relationship enumeration from left to right
* with all parts pointing from left to right
* @param alwaysCreateRelationshipsLTR A flag whether to only use left-to-right relationships
* @return This builder
* @since 2023.9.3
*/
public Builder alwaysCreateRelationshipsLTR(boolean alwaysCreateRelationshipsLTR) {
this.alwaysCreateRelationshipsLTR = alwaysCreateRelationshipsLTR;
return this;
}
/**
* Defines a lookup table for parameters. Everytime a parameter is parsed, we do check if a value in this table exists.
* If so, the parameter will be created as a named parameter carrying that value.
*
* Any previous lookup table will be overwritten when using this method multiple times.
*
* @param newParameterValues A new lookup table. Use an empty map or {@literal null} to clear any lookups in the config
* @return This builder
* @since 2023.4.0
*/
public Builder withParameterValues(Map newParameterValues) {
this.parameterValues = newParameterValues == null ? Map.of() : Collections.unmodifiableMap(new HashMap<>(newParameterValues));
return this;
}
/**
* @return A new, unmodifiable {@link Options options instance}.
*/
public Options build() {
return new Options(this);
}
}
private final BiFunction, Collection> labelFilter;
private final BiFunction, Collection> typeFilter;
private final Map>> onNewExpressionCallbacks;
private final Map>> onNewPatternElementCallbacks;
private final Map>> onNewInvocationCallbacks;
private final Function returnClauseFactory;
private final Function matchClauseFactory;
private final boolean createSortedMaps;
private boolean alwaysCreateRelationshipsLTR;
private final Map parameterValues;
private Options(Builder builder) {
this.labelFilter = builder.labelFilter;
this.typeFilter = builder.typeFilter;
Map>> tmp = new EnumMap<>(ExpressionCreatedEventType.class);
builder.onNewExpressionCallbacks.forEach((k, v) -> tmp.put(k, List.copyOf(v)));
this.onNewExpressionCallbacks = Map.copyOf(tmp);
Map>> tmp2 = new EnumMap<>(PatternElementCreatedEventType.class);
builder.onNewPatternElementCallbacks.forEach((k, v) -> tmp2.put(k, List.copyOf(v)));
this.onNewPatternElementCallbacks = Map.copyOf(tmp2);
Map>> tmp3 = new EnumMap<>(InvocationCreatedEventType.class);
builder.onNewInvocationCallbacks.forEach((k, v) -> tmp3.put(k, List.copyOf(v)));
this.onNewInvocationCallbacks = Map.copyOf(tmp3);
this.returnClauseFactory = builder.returnClauseFactory != null ?
builder.returnClauseFactory :
returnDefinition -> Clauses
.returning(returnDefinition.isDistinct(), returnDefinition.getExpressions(),
returnDefinition.getOptionalSortItems(),
returnDefinition.getOptionalSkip(), returnDefinition.getOptionalLimit());
this.matchClauseFactory = builder.matchClauseFactory != null ?
builder.matchClauseFactory :
returnDefinition -> (Match) Clauses
.match(returnDefinition.optional(), returnDefinition.patternElements(),
returnDefinition.optionalWhere(), returnDefinition.optionalHints());
this.createSortedMaps = builder.createSortedMaps;
this.alwaysCreateRelationshipsLTR = builder.alwaysCreateRelationshipsLTR;
this.parameterValues = builder.parameterValues;
}
BiFunction, Collection> getLabelFilter() {
return labelFilter;
}
BiFunction, Collection> getTypeFilter() {
return typeFilter;
}
Map>> getOnNewExpressionCallbacks() {
return onNewExpressionCallbacks;
}
Map>> getOnNewPatternElementCallbacks() {
return onNewPatternElementCallbacks;
}
Function getReturnClauseFactory() {
return returnClauseFactory;
}
Function getMatchClauseFactory() {
return matchClauseFactory;
}
boolean isCreateSortedMaps() {
return createSortedMaps;
}
boolean isAlwaysCreateRelationshipsLTR() {
return alwaysCreateRelationshipsLTR;
}
Map>> getOnNewInvocationCallbacks() {
return onNewInvocationCallbacks;
}
/**
* @return True if these are the default options.
*/
boolean areDefault() {
return this == DEFAULT_OPTIONS;
}
Map getParameterValues() {
return parameterValues;
}
}