org.springframework.data.relational.core.sql.render.DelegatingVisitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spring-data-relational Show documentation
Show all versions of spring-data-relational Show documentation
Spring Data Relational support
/*
* Copyright 2019-2023 the original author or authors.
*
* 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.springframework.data.relational.core.sql.render;
import java.util.Stack;
import org.springframework.data.relational.core.sql.Visitable;
import org.springframework.data.relational.core.sql.Visitor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Abstract base class for delegating {@link Visitor} implementations. This class implements a delegation pattern using
* visitors. A delegating {@link Visitor} can implement {@link #doEnter(Visitable)} and {@link #doLeave(Visitable)}
* methods to provide its functionality.
*
* Delegation
Typically, a {@link Visitor} is scoped to a single responsibility. If a {@link Visitor segment}
* requires {@link #doEnter(Visitable) processing} that is not directly implemented by the visitor itself, the current
* {@link Visitor} can delegate processing to a {@link DelegatingVisitor delegate}. Once a delegation is installed, the
* {@link DelegatingVisitor delegate} is used as {@link Visitor} for the current and all subsequent items until it
* {@link #doLeave(Visitable) signals} that it is no longer responsible.
*
* Nested visitors are required to properly signal once they are no longer responsible for a {@link Visitor segment} to
* step back from the delegation. Otherwise, parents are no longer involved in the visitation.
*
* Delegation is recursive and limited by the stack size.
*
* @author Mark Paluch
* @since 1.1
* @see FilteredSubtreeVisitor
* @see TypedSubtreeVisitor
*/
abstract class DelegatingVisitor implements Visitor {
private Stack delegation = new Stack<>();
/**
* Invoked for a {@link Visitable segment} when entering the segment.
*
* This method can signal whether it is responsible for handling the {@link Visitor segment} or whether the segment
* requires delegation to a sub-{@link Visitor}. When delegating to a sub-{@link Visitor}, {@link #doEnter(Visitable)}
* is called on the {@link DelegatingVisitor delegate}.
*
* @param segment must not be {@literal null}.
* @return
*/
@Nullable
public abstract Delegation doEnter(Visitable segment);
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.Visitor#enter(org.springframework.data.relational.core.sql.Visitable)
*/
@Override
public final void enter(Visitable segment) {
if (delegation.isEmpty()) {
Delegation visitor = doEnter(segment);
Assert.notNull(visitor,
() -> String.format("Visitor must not be null. Caused by %s.doEnter(…)", getClass().getName()));
Assert.state(!visitor.isLeave(),
() -> String.format("Delegation indicates leave. Caused by %s.doEnter(…)", getClass().getName()));
if (visitor.isDelegate()) {
delegation.push(visitor.getDelegate());
visitor.getDelegate().enter(segment);
}
} else {
delegation.peek().enter(segment);
}
}
/**
* Invoked for a {@link Visitable segment} when leaving the segment.
*
* This method can signal whether this {@link Visitor} should remain responsible for handling subsequent
* {@link Visitor segments} or whether it should step back from delegation. When stepping back from delegation,
* {@link #doLeave(Visitable)} is called on the {@link DelegatingVisitor parent delegate}.
*
* @param segment must not be {@literal null}.
* @return
*/
public abstract Delegation doLeave(Visitable segment);
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.Visitor#leave(org.springframework.data.relational.core.sql.Visitable)
*/
public final void leave(Visitable segment) {
doLeave0(segment);
}
private Delegation doLeave0(Visitable segment) {
if (delegation.isEmpty()) {
return doLeave(segment);
} else {
DelegatingVisitor visitor = delegation.peek();
while (visitor != null) {
Delegation result = visitor.doLeave0(segment);
Assert.notNull(visitor,
() -> String.format("Visitor must not be null. Caused by %s.doLeave(…)", getClass().getName()));
if (visitor == this) {
if (result.isLeave()) {
return delegation.isEmpty() ? Delegation.leave() : Delegation.retain();
}
return Delegation.retain();
}
if (result.isRetain()) {
return result;
}
if (result.isLeave()) {
if (!delegation.isEmpty()) {
delegation.pop();
}
if (!delegation.isEmpty()) {
visitor = delegation.peek();
} else {
visitor = this;
}
}
}
}
return Delegation.leave();
}
/**
* Value object to control delegation.
*/
static class Delegation {
private static Delegation RETAIN = new Delegation(true, false, null);
private static Delegation LEAVE = new Delegation(false, true, null);
private final boolean retain;
private final boolean leave;
private final @Nullable DelegatingVisitor delegate;
private Delegation(boolean retain, boolean leave, @Nullable DelegatingVisitor delegate) {
this.retain = retain;
this.leave = leave;
this.delegate = delegate;
}
public static Delegation retain() {
return RETAIN;
}
public static Delegation leave() {
return LEAVE;
}
public static Delegation delegateTo(DelegatingVisitor visitor) {
return new Delegation(false, false, visitor);
}
boolean isDelegate() {
return delegate != null;
}
boolean isRetain() {
return retain;
}
boolean isLeave() {
return leave;
}
DelegatingVisitor getDelegate() {
Assert.state(isDelegate(), "No delegate available");
return delegate;
}
}
}