-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathClientTerminalAddon.java
More file actions
140 lines (127 loc) · 5.11 KB
/
ClientTerminalAddon.java
File metadata and controls
140 lines (127 loc) · 5.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/*-
* #%L
* XTerm Console Addon
* %%
* Copyright (C) 2020 - 2026 Flowing Code
* %%
* 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
*
* http://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.
* #L%
*/
package com.flowingcode.vaadin.addons.xterm;
import com.flowingcode.vaadin.jsonmigration.JsonMigration;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.server.Version;
import elemental.json.Json;
import elemental.json.JsonArray;
import elemental.json.JsonValue;
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import lombok.SneakyThrows;
/**
* Represents an abstract base class for server-side terminal add-ons that have a corresponding
* client-side (JavaScript) component or require interaction with the client-side terminal
* environment. It extends {@link TerminalAddon} and specializes its use for client-aware
* operations.
*
* @author Javier Godoy / Flowing Code S.A.
*/
@SuppressWarnings("serial")
public abstract class ClientTerminalAddon extends TerminalAddon {
private final XTermBase xterm;
/**
* Constructs a new {@code ClientTerminalAddon} and associates it with the specified
* {@link XTermBase} instance.
* <p>
* This constructor ensures the add-on is registered with the terminal and verifies that the
* add-on's name, as returned by {@link #getName()}, is not {@code null}. A non-null name is
* required for client-side add-ons to be uniquely identified and targeted for JavaScript
* execution.
* </p>
*
* @param xterm the {@link XTermBase} instance this add-on will be attached to. Must not be
* {@code null}.
* @throws NullPointerException if {@code xterm} is {@code null}
* @throws IllegalStateException if {@link #getName()} returns {@code null} immediately after
* superclass construction. This check relies on {@code getName()} being a static value.
*/
protected ClientTerminalAddon(XTermBase xterm) {
super(xterm);
this.xterm = xterm;
if (getName() == null) {
throw new IllegalStateException("getName() must return a non-null value");
}
}
/**
* The xterm instance that this add-on is associated with.
*/
protected XTermBase getXterm() {
return xterm;
}
/**
* Retrieves the unique name of this client-side add-on.
* <p>
* This name is used by {@link #executeJs(String, Serializable...)} to target the corresponding
* JavaScript object on the client (i.e., {@code this.addons[name]} within the client-side
* terminal's scope). The name effectively acts as a key in a client-side add-ons collection
* managed by the terminal.
* </p>
*
* @return the unique, non-null string identifier for the client-side counterpart of this add-on.
* Subclasses must implement this to provide a name for add-on-specific JavaScript
* execution.
*/
protected abstract String getName();
/**
* Executes a JavaScript {@code expression} in the context of this add-on, with the specified
* {@code parameters}.
*
* @see #getName()
* @see Element#executeJs(String, Serializable...)
*/
protected final void executeJs(String expression, Serializable... parameters) {
String name = getName();
JsonArray args = Json.createArray();
for (int i = 0; i < parameters.length; i++) {
args.set(i, encodeWithTypeInfo(parameters[i]));
}
expression = expression.replaceAll("\\$(\\d+)", "\\$1[$1]");
xterm.executeJs("(function(){" + expression + "}).apply(this.addons[$0],$1);", name, args);
}
private static final MethodHandle encodeWithTypeInfo = lookup_encodeWithTypeInfo();
@SneakyThrows
private static MethodHandle lookup_encodeWithTypeInfo() {
MethodHandle handle;
if (Version.getMajorVersion() > 24) {
Class<?> result = Class.forName("tools.jackson.databind.JsonNode");
Class<?> codec = Class.forName("com.vaadin.flow.internal.JacksonCodec");
MethodType type = MethodType.methodType(result, Object.class);
handle = MethodHandles.lookup().findStatic(codec, "encodeWithTypeInfo", type);
} else {
Class<?> codec = Class.forName("com.vaadin.flow.internal.JsonCodec");
MethodType type = MethodType.methodType(JsonValue.class, Object.class);
handle = MethodHandles.lookup().findStatic(codec, "encodeWithTypeInfo", type);
}
return handle.asType(MethodType.methodType(Object.class, Object.class));
}
private static JsonValue encodeWithTypeInfo(Object obj) {
try {
return JsonMigration.convertToJsonValue(encodeWithTypeInfo.invokeExact(obj));
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}