Skip to content

Commit bd5c2e9

Browse files
committed
feat: implement PendingJavaScriptResult methods
1 parent eb84a1e commit bd5c2e9

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalPendingJavaScriptResult.java

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@
3636

3737
import com.vaadin.flow.component.page.PendingJavaScriptResult;
3838
import com.vaadin.flow.function.SerializableConsumer;
39+
import com.vaadin.flow.internal.JsonCodec;
3940
import elemental.json.JsonValue;
4041
import java.io.Serializable;
42+
import java.util.concurrent.CompletableFuture;
4143

4244
/**
4345
* A pending result from a JavaScript snippet sent to the browser for evaluation. This interface
@@ -87,4 +89,94 @@ default void then(SerializableConsumer<JsonValue> resultHandler) {
8789
then(resultHandler, null);
8890
}
8991

92+
/**
93+
* Adds a typed handler that will be run for a successful execution and a
94+
* handler that will be run for a failed execution. One of the handlers will
95+
* be invoked asynchronously when the result of the execution is sent back
96+
* to the server.
97+
* <p>
98+
* Handlers can only be added before the execution has been sent to the
99+
* browser.
100+
*
101+
* @param targetType
102+
* the type to convert the JavaScript return value to, not
103+
* <code>null</code>
104+
* @param resultHandler
105+
* a handler for the return value from a successful execution,
106+
* not <code>null</code>
107+
* @param errorHandler
108+
* a handler for an error message in case the execution failed,
109+
* or <code>null</code> to ignore errors
110+
*/
111+
default <T> void then(Class<T> targetType,
112+
SerializableConsumer<T> resultHandler,
113+
SerializableConsumer<String> errorHandler) {
114+
if (targetType == null) {
115+
throw new IllegalArgumentException("Target type cannot be null");
116+
}
117+
if (resultHandler == null) {
118+
throw new IllegalArgumentException("Result handler cannot be null");
119+
}
120+
121+
SerializableConsumer<JsonValue> convertingResultHandler = value -> resultHandler
122+
.accept(JsonCodec.decodeAs(value, targetType));
123+
124+
then(convertingResultHandler, errorHandler);
125+
}
126+
127+
/**
128+
* Adds a typed handler that will be run for a successful execution. The
129+
* handler will be invoked asynchronously if the execution was successful.
130+
* In case of a failure, no handler will be run.
131+
* <p>
132+
* A handler can only be added before the execution has been sent to the
133+
* browser.
134+
*
135+
* @param targetType
136+
* the type to convert the JavaScript return value to, not
137+
* <code>null</code>
138+
* @param resultHandler
139+
* a handler for the return value from a successful execution,
140+
* not <code>null</code>
141+
*/
142+
default <T> void then(Class<T> targetType,
143+
SerializableConsumer<T> resultHandler) {
144+
then(targetType, resultHandler, null);
145+
}
146+
147+
/**
148+
* Creates a typed completable future that will be completed with the result
149+
* of the execution. It will be completed asynchronously when the result of
150+
* the execution is sent back to the server. It is not possible to
151+
* synchronously wait for the result of the execution while holding the
152+
* session lock since the request handling thread that makes the result
153+
* available will also need to lock the session.
154+
* <p>
155+
* A completable future can only be created before the execution has been
156+
* sent to the browser.
157+
*
158+
* @param targetType
159+
* the type to convert the JavaScript return value to, not
160+
* <code>null</code>
161+
*
162+
* @return a completable future that will be completed based on the
163+
* execution results, not <code>null</code>
164+
*/
165+
<T> CompletableFuture<T> toCompletableFuture(Class<T> targetType);
166+
167+
/**
168+
* Creates an untyped completable future that will be completed with the
169+
* result of the execution. It will be completed asynchronously when the
170+
* result of the execution is sent back to the server.
171+
* <p>
172+
* A completable future can only be created before the execution has been
173+
* sent to the browser.
174+
*
175+
* @return a completable future that will be completed based on the
176+
* execution results, not <code>null</code>
177+
*/
178+
default CompletableFuture<JsonValue> toCompletableFuture() {
179+
return toCompletableFuture(JsonValue.class);
180+
}
181+
90182
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*-
2+
* #%L
3+
* Json Migration Helper
4+
* %%
5+
* Copyright (C) 2025 Flowing Code
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package com.flowingcode.vaadin.jsonmigration;
21+
/*
22+
* Copyright 2000-2020 Vaadin Ltd.
23+
*
24+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
25+
* use this file except in compliance with the License. You may obtain a copy of
26+
* the License at
27+
*
28+
* http://www.apache.org/licenses/LICENSE-2.0
29+
*
30+
* Unless required by applicable law or agreed to in writing, software
31+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
32+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
33+
* License for the specific language governing permissions and limitations under
34+
* the License.
35+
*/
36+
37+
import com.vaadin.flow.component.Component;
38+
import com.vaadin.flow.dom.Element;
39+
import com.vaadin.flow.internal.ReflectTools;
40+
import elemental.json.JsonType;
41+
import elemental.json.JsonValue;
42+
43+
/**
44+
* Utility for encoding objects to and from JSON.
45+
* <p>
46+
* Supported types are
47+
* <ul>
48+
* <li>{@link String}
49+
* <li>{@link Boolean} and <code>boolean</code>
50+
* <li>{@link Integer} and <code>int</code>
51+
* <li>{@link Double} and <code>double</code> (<code>NaN</code> and infinity not
52+
* supported)
53+
* <li>{@link JsonValue} and all its sub types
54+
* <li>{@link Element} (encoded as a reference to the element)
55+
* <li>{@link Component} (encoded as a reference to the root element)
56+
* </ul>
57+
*
58+
* <p>
59+
* @author Vaadin Ltd
60+
*/
61+
class JsonCodec {
62+
63+
/**
64+
* Decodes the given JSON value as the given type.
65+
* <p>
66+
* Supported types are {@link String}, {@link Boolean}, {@link Integer},
67+
* {@link Double} and primitives boolean, int, double
68+
*
69+
* @param <T>
70+
* the decoded type
71+
* @param json
72+
* the JSON value
73+
* @param type
74+
* the type to decode as
75+
* @return the value decoded as the given type
76+
* @throws IllegalArgumentException
77+
* if the type was unsupported
78+
*/
79+
@SuppressWarnings("unchecked")
80+
public static <T> T decodeAs(JsonValue json, Class<T> type) {
81+
assert json != null;
82+
if (json.getType() == JsonType.NULL && !type.isPrimitive()) {
83+
return null;
84+
}
85+
Class<?> convertedType = ReflectTools.convertPrimitiveType(type);
86+
if (type == String.class) {
87+
return type.cast(json.asString());
88+
} else if (convertedType == Boolean.class) {
89+
return (T) convertedType.cast(Boolean.valueOf(json.asBoolean()));
90+
} else if (convertedType == Double.class) {
91+
return (T) convertedType.cast(Double.valueOf(json.asNumber()));
92+
} else if (convertedType == Integer.class) {
93+
return (T) convertedType
94+
.cast(Integer.valueOf((int) json.asNumber()));
95+
} else if (JsonValue.class.isAssignableFrom(type)) {
96+
return type.cast(json);
97+
} else {
98+
throw new IllegalArgumentException(
99+
"Unknown type " + type.getName());
100+
}
101+
102+
}
103+
104+
}

src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigrationHelper25.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import elemental.json.JsonArray;
2828
import elemental.json.JsonObject;
2929
import elemental.json.JsonValue;
30+
import java.util.concurrent.CompletableFuture;
3031
import lombok.AllArgsConstructor;
3132
import lombok.NoArgsConstructor;
3233
import lombok.SneakyThrows;
@@ -156,13 +157,40 @@ private static SerializableConsumer wrap(SerializableConsumer<JsonValue> resultH
156157
return (SerializableConsumer<JsonNode>) node -> resultHandler.accept(convertToJsonValue(node));
157158
};
158159

160+
private static <T> T decodeAs(JsonNode node, Class<T> type) {
161+
return JsonCodec.decodeAs(convertToJsonValue(node), type);
162+
}
163+
159164
@Override
160165
@SuppressWarnings("unchecked")
161166
public void then(SerializableConsumer<JsonValue> resultHandler,
162167
SerializableConsumer<String> errorHandler) {
163168
delegate.then(wrap(resultHandler), errorHandler);
164169
}
165170

171+
@Override
172+
@SuppressWarnings("unchecked")
173+
public <T> void then(Class<T> targetType, SerializableConsumer<T> resultHandler,
174+
SerializableConsumer<String> errorHandler) {
175+
if (JsonValue.class.isAssignableFrom(targetType)) {
176+
delegate.then(JsonNode.class, wrap(value->{
177+
resultHandler.accept(JsonCodec.decodeAs(value, targetType));
178+
}), errorHandler);
179+
} else {
180+
delegate.then(targetType, resultHandler, errorHandler);
181+
}
182+
}
183+
184+
@Override
185+
public <T> CompletableFuture<T> toCompletableFuture(Class<T> targetType) {
186+
if (JsonValue.class.isAssignableFrom(targetType)) {
187+
return delegate.toCompletableFuture(JsonNode.class)
188+
.thenApply(node -> decodeAs(node, targetType));
189+
} else {
190+
return delegate.toCompletableFuture(targetType);
191+
}
192+
}
193+
166194
}
167195

168196
}

0 commit comments

Comments
 (0)