1 package emissary.core;
2
3 import emissary.directory.KeyManipulator;
4 import emissary.place.IServiceProviderPlace;
5
6 import jakarta.annotation.Nullable;
7 import org.apache.commons.collections4.CollectionUtils;
8 import org.apache.commons.lang3.StringUtils;
9
10 import java.io.Serializable;
11 import java.util.ArrayDeque;
12 import java.util.ArrayList;
13 import java.util.Collections;
14 import java.util.Deque;
15 import java.util.List;
16 import java.util.stream.Collectors;
17
18 import static emissary.place.IServiceProviderPlace.SPROUT_KEY;
19
20 public class TransformHistory implements Serializable {
21
22 private static final long serialVersionUID = -7252497842562281631L;
23
24 private final List<History> history;
25
26 public TransformHistory() {
27 history = new ArrayList<>();
28 }
29
30 public TransformHistory(TransformHistory history) {
31 this.history = new ArrayList<>(history.history);
32 }
33
34
35
36
37
38
39 public void set(List<String> keys) {
40 clear();
41 addAll(keys.stream().map(History::new).collect(Collectors.toList()));
42 }
43
44
45
46
47
48
49 public void set(TransformHistory history) {
50 clear();
51 addAll(history.history);
52 }
53
54 private void addAll(List<History> history) {
55 this.history.addAll(history);
56 }
57
58
59
60
61
62
63
64
65 public void append(final String key) {
66 append(key, false);
67 }
68
69
70
71
72
73
74
75
76
77
78
79 public void append(String key, boolean coordinated) {
80 if (coordinated) {
81 History last = lastVisit();
82 if (last != null) {
83 last.addCoordinated(key);
84 }
85 } else {
86 history.add(new History(key));
87 }
88
89 }
90
91
92
93
94 public void clear() {
95 history.clear();
96 }
97
98
99
100
101
102
103 public List<String> get() {
104 return get(false);
105 }
106
107
108
109
110
111
112 public List<String> get(boolean includeCoordinated) {
113 if (includeCoordinated) {
114 List<String> keys = new ArrayList<>();
115 history.forEach(k -> {
116 keys.add(k.getKey());
117 keys.addAll(k.getCoordinated());
118 });
119 return keys;
120 } else {
121 return history.stream()
122 .map(History::getKey)
123 .collect(Collectors.toList());
124 }
125 }
126
127
128
129
130
131
132 public List<History> getHistory() {
133 return Collections.unmodifiableList(history);
134 }
135
136
137
138
139
140
141 @Nullable
142 public History lastVisit() {
143 if (CollectionUtils.isEmpty(history)) {
144 return null;
145 }
146 return history.get(history.size() - 1);
147 }
148
149
150
151
152
153
154 @Nullable
155 public History penultimateVisit() {
156 if (CollectionUtils.isEmpty(history) || history.size() < 2) {
157 return null;
158 }
159 return history.get(history.size() - 2);
160 }
161
162
163
164
165
166
167 public boolean hasVisited(final String pattern) {
168 for (final History hist : history) {
169 if (KeyManipulator.gmatch(hist.getKey(), pattern)) {
170 return true;
171 }
172 }
173 return false;
174 }
175
176
177
178
179
180
181 public boolean beforeStart() {
182 if (history.isEmpty()) {
183 return true;
184 }
185 final String s = history.get(history.size() - 1).getKey();
186 return s.contains(IServiceProviderPlace.SPROUT_KEY);
187 }
188
189 public int size() {
190 return size(false);
191 }
192
193 public int size(boolean includeCoordinated) {
194 int size = history.size();
195 if (includeCoordinated) {
196 size += (int) history.stream().mapToLong(h -> h.coordinated.size()).sum();
197 }
198 return size;
199 }
200
201 public Deque<String> format() {
202 Deque<String> formattedHistory = new ArrayDeque<>();
203
204 String prevDataAndServiceType = "";
205 for (final History h : this.history) {
206 String key = h.getKey();
207 String currentDataAndServiceType = "";
208 StringBuilder displayStrings = new StringBuilder();
209
210 if (key.contains(SPROUT_KEY)) {
211 displayStrings.append(StringUtils.substringBefore(formattedHistory.removeLast(), ".")).append(".")
212 .append(KeyManipulator.getServiceType(key)).append(": ");
213 while (!formattedHistory.isEmpty()) {
214 String last = formattedHistory.removeLast();
215 if (last.contains(SPROUT_KEY)) {
216 formattedHistory.add(last);
217 break;
218 }
219 }
220 } else {
221 currentDataAndServiceType = KeyManipulator.getDataType(key) + "." + KeyManipulator.getServiceType(key);
222 if (currentDataAndServiceType.equals(prevDataAndServiceType)) {
223 displayStrings.append(formattedHistory.removeLast()).append(", ");
224 } else {
225 displayStrings.append(currentDataAndServiceType).append(": ");
226 }
227 }
228
229 displayStrings.append(KeyManipulator.getServiceClassname(key));
230 if (CollectionUtils.isNotEmpty(h.getCoordinated())) {
231 displayStrings
232 .append(h.getCoordinated().stream().map(KeyManipulator::getServiceClassname).collect(Collectors.joining(", ", "(", ")")));
233 }
234
235 formattedHistory.add(displayStrings.toString());
236 prevDataAndServiceType = currentDataAndServiceType;
237 }
238 return formattedHistory;
239 }
240
241 @Override
242 public String toString() {
243 final StringBuilder myOutput = new StringBuilder();
244 final String ls = System.lineSeparator();
245 myOutput.append("transform history (").append(size(true)).append(") :").append(ls);
246 history.forEach(x -> myOutput.append(x.toString()).append(ls));
247 return myOutput.toString();
248 }
249
250 public static class History {
251 String key;
252 List<String> coordinated = new ArrayList<>();
253
254
255
256
257 private History() {}
258
259 public History(String key) {
260 this.key = key;
261 }
262
263 public String getKey() {
264 return getKey(false);
265 }
266
267 public String getKey(boolean stripUrl) {
268 return stripUrl ? stripUrl(key) : key;
269 }
270
271 public List<String> getCoordinated() {
272 return getCoordinated(false);
273 }
274
275 public List<String> getCoordinated(boolean stripUrl) {
276 return stripUrl ? coordinated.stream().map(History::stripUrl).collect(Collectors.toUnmodifiableList())
277 : Collections.unmodifiableList(coordinated);
278 }
279
280 public void addCoordinated(String key) {
281 coordinated.add(key);
282 }
283
284 protected static String stripUrl(String key) {
285 return StringUtils.substringBefore(key, ".http");
286 }
287
288 @Override
289 public String toString() {
290 StringBuilder hist = new StringBuilder(" -> " + getKey());
291 for (String coord : coordinated) {
292 hist.append(System.lineSeparator()).append(" +--> ").append(coord);
293 }
294 return hist.toString();
295 }
296 }
297 }