Skip to content

Commit 4ec9932

Browse files
committed
Use a completable future to obtain extracted object reference
1 parent ead7542 commit 4ec9932

4 files changed

Lines changed: 95 additions & 31 deletions

File tree

src/main/org/firebirdsql/jaybird/parser/ObjectReferenceExtractor.java

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import java.util.Collection;
1111
import java.util.List;
1212
import java.util.Locale;
13+
import java.util.concurrent.CompletableFuture;
14+
import java.util.function.Consumer;
1315

1416
/**
1517
* Token visitor to extract an object reference (identifier chain) from a string.
@@ -18,21 +20,40 @@
1820
* the object reference (ignoring whitespace and comments). It will unregister itself as soon as it receives a token
1921
* that is not part of an object reference.
2022
* </p>
21-
* <p>
22-
* If you want to reuse an instance of this token visitor, you must call {@link #reset()} before adding it to
23-
* the parser again.
24-
* </p>
2523
*
2624
* @author Mark Rotteveel
2725
* @since 7
2826
*/
2927
public final class ObjectReferenceExtractor implements TokenVisitor {
3028

29+
private static final Consumer<?> NO_ACTION_CONSUMER = ob -> {
30+
};
3131
// pre-sizing at 3, as for current usages we expect at most 3 identifiers (i.e. <schema>.<package>.<procedure>)
3232
private final List<Identifier> identifiers = new ArrayList<>(3);
3333
private @Nullable Token previousToken;
3434
private boolean previousTokenWasScopeSpecifierValue;
3535
private @Nullable UnexpectedTokenException unexpectedTokenException;
36+
private final Consumer<ObjectReference> objectReferenceConsumer;
37+
private final Consumer<RuntimeException> exceptionConsumer;
38+
39+
public ObjectReferenceExtractor() {
40+
this(noActionConsumer(), noActionConsumer());
41+
}
42+
43+
/**
44+
* Creates an object reference extractor that will notify completion or failure.
45+
*
46+
* @param objectReferenceConsumer
47+
* will receive the complete object reference when found
48+
* @param exceptionConsumer
49+
* for parsing errors, or when no or an incomplete object reference was found on (self)removal of this
50+
* visitor or parser completion; may receive multiple exceptions
51+
*/
52+
public ObjectReferenceExtractor(Consumer<ObjectReference> objectReferenceConsumer,
53+
Consumer<RuntimeException> exceptionConsumer) {
54+
this.objectReferenceConsumer = objectReferenceConsumer;
55+
this.exceptionConsumer = exceptionConsumer;
56+
}
3657

3758
@Override
3859
public void visitToken(Token token, VisitorRegistrar visitorRegistrar) {
@@ -86,16 +107,6 @@ public void visitToken(Token token, VisitorRegistrar visitorRegistrar) {
86107
previousToken = token;
87108
}
88109

89-
/**
90-
* Resets the state of the detector so it behaves as if it was just created.
91-
*/
92-
public void reset() {
93-
identifiers.clear();
94-
previousToken = null;
95-
previousTokenWasScopeSpecifierValue = false;
96-
unexpectedTokenException = null;
97-
}
98-
99110
private boolean isScopeSpecifier(@Nullable Token token) {
100111
return token instanceof OperatorToken operatorToken && operatorToken.charAt(0) == '%';
101112
}
@@ -115,6 +126,16 @@ public void complete(VisitorRegistrar visitorRegistrar) {
115126
unexpectedTokenException = new UnexpectedTokenException(
116127
"Last token was scope specifier (%), missing scope or other token", previousToken);
117128
}
129+
afterRemove(visitorRegistrar);
130+
}
131+
132+
@Override
133+
public void afterRemove(VisitorRegistrar visitorRegistrar) {
134+
try {
135+
objectReferenceConsumer.accept(toObjectReference());
136+
} catch (RuntimeException e) {
137+
exceptionConsumer.accept(e);
138+
}
118139
}
119140

120141
@Override
@@ -138,4 +159,23 @@ public ObjectReference toObjectReference() {
138159
return ObjectReference.ofIdentifiers(identifiers);
139160
}
140161

162+
/**
163+
* Creates an object reference extractor with associated completable future to obtain the object reference or
164+
* its failure.
165+
*
166+
* @return record providing access to both the object reference extractor and its future
167+
*/
168+
public static ObjectReferenceExtractor.WithFuture withFuture() {
169+
var future = new CompletableFuture<ObjectReference>();
170+
return new WithFuture(new ObjectReferenceExtractor(future::complete, future::completeExceptionally), future);
171+
}
172+
173+
@SuppressWarnings("unchecked")
174+
private static <T> Consumer<T> noActionConsumer() {
175+
return (Consumer<T>) NO_ACTION_CONSUMER;
176+
}
177+
178+
public record WithFuture(ObjectReferenceExtractor extractor, CompletableFuture<ObjectReference> future) {
179+
}
180+
141181
}

src/main/org/firebirdsql/jaybird/parser/RegisterOnRemoveTokenVisitor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ public void removeVisitor(TokenVisitor tokenVisitor) {
118118
public void afterRemove(VisitorRegistrar visitorRegistrar) {
119119
registrarStack.push(visitorRegistrar);
120120
try {
121+
decoratedTokenVisitor.afterRemove(this);
121122
registerOnRemove.forEach(visitorRegistrar::addVisitor);
122123
if (notifyLastToken && lastTokenSeen != DUMMY_NULL_TOKEN) {
123124
registerOnRemove.forEach(visitor -> {

src/main/org/firebirdsql/jaybird/parser/StatementDetector.java

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.Optional;
1111
import java.util.TreeMap;
1212

13+
import static java.lang.System.Logger.Level.TRACE;
1314
import static java.util.Collections.unmodifiableMap;
1415
import static org.firebirdsql.jaybird.parser.CharSequenceComparison.caseInsensitiveComparator;
1516

@@ -73,7 +74,6 @@ public final class StatementDetector implements TokenVisitor {
7374
private LocalStatementType statementType = LocalStatementType.UNKNOWN;
7475
private ParserState parserState = ParserState.START;
7576
private @Nullable ObjectReference targetObject;
76-
private @Nullable ObjectReferenceExtractor targetObjectExtractor;
7777
private @Nullable ReturningClauseDetector returningClauseDetector;
7878

7979
/**
@@ -131,8 +131,19 @@ public void visitToken(Token token, VisitorRegistrar visitorRegistrar) {
131131
case INSERT_INTO:
132132
case DML_TARGET:
133133
case DML_TARGET_FORWARD_TOKEN: {
134-
targetObjectExtractor = new ObjectReferenceExtractor();
135-
TokenVisitor newVisitor = targetObjectExtractor.onRemoveRegister(this);
134+
var objectExtractorWithFuture = ObjectReferenceExtractor.withFuture();
135+
objectExtractorWithFuture.future().handle((@Nullable ObjectReference ref, @Nullable Throwable t) -> {
136+
if (ref != null) {
137+
setTargetObject(ref);
138+
} else {
139+
if (t != null) {
140+
System.getLogger(getClass().getName()).log(TRACE, "Exception getting object reference", t);
141+
}
142+
updateStatementType(LocalStatementType.OTHER);
143+
}
144+
return null;
145+
});
146+
TokenVisitor newVisitor = objectExtractorWithFuture.extractor().onRemoveRegister(this);
136147
visitorRegistrar.addVisitor(newVisitor);
137148
visitorRegistrar.removeVisitor(this);
138149
if (parserState == ParserState.DML_TARGET_FORWARD_TOKEN) {
@@ -170,7 +181,7 @@ public void complete(VisitorRegistrar visitorRegistrar) {
170181
// Handle DELETE FROM ... without WHERE, and EXECUTE PROCEDURE ... without arguments
171182
case EXECUTE_PROCEDURE, DML_TARGET -> {
172183
// TODO Maybe remove complete(..) and instead have the parser post an EOF token?
173-
if (targetObject == null && !trySetTargetObject()) {
184+
if (targetObject == null) {
174185
updateStatementType(LocalStatementType.OTHER);
175186
}
176187
}
@@ -200,14 +211,8 @@ void setTargetObject(@Nullable ObjectReference targetObject) {
200211
this.targetObject = targetObject;
201212
}
202213

203-
boolean trySetTargetObject() {
204-
if (targetObjectExtractor == null) return false;
205-
try {
206-
setTargetObject(targetObjectExtractor.toObjectReference());
207-
return true;
208-
} catch (IllegalStateException ignored) {
209-
return false;
210-
}
214+
private boolean hasTargetObject() {
215+
return targetObject != null;
211216
}
212217

213218
private void updateStatementType(LocalStatementType statementType) {
@@ -242,7 +247,7 @@ ParserState next(Token token, StatementDetector detector) {
242247
EXECUTE_PROCEDURE {
243248
@Override
244249
ParserState next(Token token, StatementDetector detector) {
245-
if (detector.trySetTargetObject()) {
250+
if (detector.hasTargetObject()) {
246251
return EXEC_PROC_ARGS;
247252
}
248253
return forceOther(detector);
@@ -254,7 +259,7 @@ ParserState next(Token token, StatementDetector detector) {
254259
CALL {
255260
@Override
256261
ParserState next(Token token, StatementDetector detector) {
257-
if (detector.trySetTargetObject()) {
262+
if (detector.hasTargetObject()) {
258263
return CALL_PROC_ARGS;
259264
}
260265
return forceOther(detector);
@@ -303,7 +308,7 @@ ParserState next(Token token, StatementDetector detector) {
303308
JDBC_ESCAPE_CALL {
304309
@Override
305310
ParserState next(Token token, StatementDetector detector) {
306-
if (detector.trySetTargetObject()) {
311+
if (detector.hasTargetObject()) {
307312
return JDBC_CALL_PROC_ARGS;
308313
}
309314
return forceOther(detector);
@@ -349,7 +354,7 @@ ParserState next(Token token, StatementDetector detector) {
349354
DML_TARGET {
350355
@Override
351356
ParserState next(Token token, StatementDetector detector) {
352-
if (detector.trySetTargetObject()) {
357+
if (detector.hasTargetObject()) {
353358
return DML_POSSIBLE_ALIAS.next(token, detector);
354359
}
355360
return forceOther(detector);
@@ -396,7 +401,7 @@ ParserState next(Token token, StatementDetector detector) {
396401
INSERT_INTO {
397402
@Override
398403
ParserState next(Token token, StatementDetector detector) {
399-
if (detector.trySetTargetObject()) {
404+
if (detector.hasTargetObject()) {
400405
return FIND_RETURNING;
401406
}
402407
return forceOther(detector);

src/test/org/firebirdsql/jaybird/parser/RegisterOnRemoveTokenVisitorTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,17 @@ void selfRemoveDuringVisitToken_registersNewVisitor_noTokenOnNewVisitor(@Mock To
4646
invocation.getArgument(1, VisitorRegistrar.class).removeVisitor(decoratedVisitor);
4747
return null;
4848
}).when(decoratedVisitor).visitToken(any(), any());
49+
doAnswer(invocation -> {
50+
invocation.getArgument(0, TokenVisitor.class).afterRemove(visitorRegistrar);
51+
return null;
52+
}).when(visitorRegistrar).removeVisitor(any());
4953

5054
visitor.visitToken(token, visitorRegistrar);
5155

5256
// NOTE: The actually removed visitor is the RegisterOnRemoveTokenVisitor instance
5357
verify(visitorRegistrar).removeVisitor(visitor);
58+
// NOTE: The RegisterOnRemoveTokenVisitor is a visitor registrar for the decoratedVisitor
59+
verify(decoratedVisitor).afterRemove(visitor);
5460
verify(visitorRegistrar).addVisitor(newVisitor);
5561
verifyNoMoreInteractions(visitorRegistrar, decoratedVisitor, newVisitor);
5662
}
@@ -62,11 +68,17 @@ void selfRemoveDuringVisitToken_registersNewVisitors_tokenOnNewVisitors(@Mock To
6268
invocation.getArgument(1, VisitorRegistrar.class).removeVisitor(decoratedVisitor);
6369
return null;
6470
}).when(decoratedVisitor).visitToken(any(), any());
71+
doAnswer(invocation -> {
72+
invocation.getArgument(0, TokenVisitor.class).afterRemove(visitorRegistrar);
73+
return null;
74+
}).when(visitorRegistrar).removeVisitor(any());
6575

6676
visitor.visitToken(token, visitorRegistrar);
6777

6878
// NOTE: The actually removed visitor is the RegisterOnRemoveTokenVisitor instance
6979
verify(visitorRegistrar).removeVisitor(visitor);
80+
// NOTE: The RegisterOnRemoveTokenVisitor is a visitor registrar for the decoratedVisitor
81+
verify(decoratedVisitor).afterRemove(visitor);
7082
verify(visitorRegistrar).addVisitor(newVisitor);
7183
verify(visitorRegistrar).addVisitor(newVisitor2);
7284
verify(newVisitor).visitToken(eq(token), same(visitorRegistrar));
@@ -82,6 +94,10 @@ void selfRemoveDuringVisitToken_registersNewVisitors_tokenOnNewVisitors_exceptio
8294
invocation.getArgument(1, VisitorRegistrar.class).removeVisitor(decoratedVisitor);
8395
return null;
8496
}).when(decoratedVisitor).visitToken(any(), any());
97+
doAnswer(invocation -> {
98+
invocation.getArgument(0, TokenVisitor.class).afterRemove(visitorRegistrar);
99+
return null;
100+
}).when(visitorRegistrar).removeVisitor(any());
85101
doAnswer(invocation -> {
86102
throw new RuntimeException("From newVisitor");
87103
}).when(newVisitor).visitToken(any(), any());
@@ -93,6 +109,8 @@ void selfRemoveDuringVisitToken_registersNewVisitors_tokenOnNewVisitors_exceptio
93109

94110
// NOTE: The actually removed visitor is the RegisterOnRemoveTokenVisitor instance
95111
verify(visitorRegistrar).removeVisitor(visitor);
112+
// NOTE: The RegisterOnRemoveTokenVisitor is a visitor registrar for the decoratedVisitor
113+
verify(decoratedVisitor).afterRemove(visitor);
96114
verify(visitorRegistrar).addVisitor(newVisitor);
97115
verify(visitorRegistrar).addVisitor(newVisitor2);
98116
verify(newVisitor).visitToken(eq(token), same(visitorRegistrar));

0 commit comments

Comments
 (0)