1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package io.earcam.instrumental.module.jpms;
20
21 import static io.earcam.instrumental.module.jpms.RequireModifier.STATIC;
22 import static io.earcam.instrumental.module.jpms.RequireModifier.TRANSITIVE;
23 import static java.util.Arrays.stream;
24 import static java.util.EnumSet.of;
25 import static java.util.Locale.ROOT;
26 import static java.util.stream.Collectors.joining;
27 import static java.util.stream.Collectors.toList;
28
29 import java.io.Serializable;
30 import java.util.Arrays;
31 import java.util.EnumSet;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Objects;
38 import java.util.Set;
39 import java.util.SortedSet;
40 import java.util.TreeSet;
41 import java.util.stream.Collectors;
42
43 class DefaultModuleInfo implements ModuleInfo, ModuleInfoBuilder, Serializable {
44
45 private static final long serialVersionUID = 8059255940493321393L;
46 private static final EnumSet<RequireModifier> MUTUALLY_EXCLUSIVE_REQUIRE_MODIFIERS = of(TRANSITIVE, STATIC);
47 private static final String LIST_JOINING_DELIMITER = ",\n\t\t";
48
49 private String name;
50 private String version;
51 private int access;
52
53 private TreeSet<CharSequence> packages = new TreeSet<>();
54 private TreeSet<String> uses = new TreeSet<>();
55 private HashMap<String, String[]> provides = new HashMap<>();
56 private String mainClass;
57 private HashSet<Require> requires = new HashSet<>();
58 private HashSet<Export> exports = new HashSet<>();
59 private HashSet<Export> opens = new HashSet<>();
60
61
62 @Override
63 public boolean equals(Object other)
64 {
65 return other instanceof ModuleInfo && equals((ModuleInfo) other);
66 }
67
68
69
70
71
72
73
74
75
76
77 public boolean equals(ModuleInfo that)
78 {
79 return that != null
80 && Objects.equals(that.name(), name)
81 && Objects.equals(that.version(), version)
82 && Objects.equals(that.access(), access)
83 && Objects.equals(that.packages(), packages)
84 && Objects.equals(that.uses(), uses)
85 && deepEqualsMap(that.provides(), provides)
86 && Objects.equals(that.mainClass(), mainClass)
87 && Objects.equals(that.requires(), requires)
88 && Objects.equals(that.exports(), exports)
89 && Objects.equals(that.opens(), opens);
90 }
91
92
93 private static boolean deepEqualsMap(Map<String, String[]> a, Map<String, String[]> b)
94 {
95 if(Objects.deepEquals(a.keySet(), b.keySet())) {
96 Iterator<String[]> aIt = a.values().iterator();
97 Iterator<String[]> bIt = b.values().iterator();
98 while(aIt.hasNext()) {
99 if(!Arrays.equals(aIt.next(), bIt.next())) {
100 return false;
101 }
102 }
103 return true;
104 }
105 return false;
106 }
107
108
109 @Override
110 public int hashCode()
111 {
112 return Objects.hash(
113 name, version, packages,
114 uses, provides.keySet(), mainClass,
115 requires, exports, opens);
116 }
117
118
119 @Override
120 public String name()
121 {
122 return name;
123 }
124
125
126 @Override
127 public String version()
128 {
129 return version;
130 }
131
132
133 @Override
134 public SortedSet<CharSequence> packages()
135 {
136 return packages;
137 }
138
139
140 @Override
141 public SortedSet<String> uses()
142 {
143 return uses;
144 }
145
146
147 @Override
148 public Map<String, String[]> provides()
149 {
150 return provides;
151 }
152
153
154 @Override
155 public String mainClass()
156 {
157 return mainClass;
158 }
159
160
161 @Override
162 public Set<Require> requires()
163 {
164 return requires;
165 }
166
167
168 @Override
169 public Set<Export> exports()
170 {
171 return exports;
172 }
173
174
175 @Override
176 public Set<Export> opens()
177 {
178 return opens;
179 }
180
181
182 @Override
183 public ModuleInfoBuilder named(String moduleName)
184 {
185 name = moduleName;
186 return this;
187 }
188
189
190 @Override
191 public ModuleInfoBuilder versioned(String moduleVersion)
192 {
193 version = moduleVersion;
194 return this;
195 }
196
197
198 @Override
199 public ModuleInfoBuilder withAccess(int accessFlags)
200 {
201 access = accessFlags;
202 return this;
203 }
204
205
206 @Override
207 public ModuleInfoBuilder packaging(CharSequence paquet)
208 {
209 packages.add(paquet.toString());
210 return this;
211 }
212
213
214 @Override
215 public ModuleInfoBuilder using(String serviceApi)
216 {
217 uses.add(serviceApi);
218 return this;
219 }
220
221
222 @Override
223 public ModuleInfoBuilder providing(String contract, String... concretes)
224 {
225 provides.put(contract, concretes);
226 return this;
227 }
228
229
230 @Override
231 public ModuleInfoBuilder launching(String mainClass)
232 {
233 this.mainClass = mainClass;
234 return this;
235 }
236
237
238 @Override
239 public ModuleInfoBuilder requiring(String module, int access, String version)
240 {
241 requires.add(new Require(module, access, version));
242 return this;
243 }
244
245
246 @Override
247 public ModuleInfoBuilder exporting(String paquet, int access, String... modules)
248 {
249 exports.add(new Export(paquet, access, modules));
250 return this;
251 }
252
253
254 @Override
255 public ModuleInfoBuilder opening(String paquet, int access, String... modules)
256 {
257 opens.add(new Export(paquet, access, modules));
258 return this;
259 }
260
261
262 @Override
263 public ModuleInfo construct()
264 {
265
266 Objects.requireNonNull(name, "name");
267
268 List<Require> erroneousRequires = requires.stream()
269 .filter(r -> r.modifiers().containsAll(MUTUALLY_EXCLUSIVE_REQUIRE_MODIFIERS))
270 .collect(toList());
271 if(!erroneousRequires.isEmpty()) {
272 throw new IllegalStateException("require statements contain mutually exclusive modifiers: " +
273 erroneousRequires.stream().map(Require::module).collect(joining(", ")));
274 }
275 return this;
276 }
277
278
279 @Override
280 public ModuleInfoBuilder deconstruct()
281 {
282 return this;
283 }
284
285
286
287
288
289
290
291
292 @Override
293 public String toString()
294 {
295 StringBuilder output = new StringBuilder();
296
297 addModuleComment(output);
298
299 output.append(visibleModifiers(modifiers()));
300 output.append("module ").append(name).append(" {\n");
301
302 requiresToString(output);
303 portsToString(output, exports, "exports");
304 portsToString(output, opens, "opens");
305 usesToString(output);
306 providesToString(output);
307
308 return output.append('}').toString();
309 }
310
311
312 private String visibleModifiers(Set<? extends Modifier> modifiers)
313 {
314 String mods = modifiers.stream()
315 .filter(Modifier::sourceVisible)
316 .map(Modifier::name)
317 .map(m -> m.toLowerCase(ROOT))
318 .collect(joining(" "));
319
320 return (mods.length() > 0) ? mods + ' ' : mods;
321 }
322
323
324 private void addModuleComment(StringBuilder output)
325 {
326 if(version != null || access != 0 || !packages.isEmpty()) {
327 startComment(output, "");
328 versionComment(output, "", version);
329 accessComment(output, "", Access.modifiers(ModuleModifier.class, access));
330 packagesComment(output, packages);
331 endComment(output, "");
332 }
333 }
334
335
336 private static void packagesComment(StringBuilder output, Set<CharSequence> packages)
337 {
338 packages.forEach(p -> output.append(" * @package ").append(p).append('\n'));
339 }
340
341
342 private void requiresToString(StringBuilder output)
343 {
344 for(Require require : requires) {
345 addComment(output, "\t", require.modifiers(), require.version());
346 output.append('\t').append("requires ");
347 output.append(visibleModifiers(require.modifiers()));
348 output.append(require.module()).append(";\n");
349 }
350 }
351
352
353 private void portsToString(StringBuilder output, HashSet<Export> ports, String portsLabel)
354 {
355 for(Export export : ports) {
356 addComment(output, "\t", export.modifiers());
357 output.append('\t').append(portsLabel).append(' ').append(export.paquet());
358 if(export.modules().length > 0) {
359 output.append(stream(export.modules()).collect(joining(LIST_JOINING_DELIMITER, " to \n\t\t", "")));
360 }
361 output.append(";\n");
362 }
363 }
364
365
366 private void usesToString(StringBuilder output)
367 {
368 if(!uses.isEmpty()) {
369 output.append(uses.stream().collect(Collectors.joining(";\n\tuses ", "\tuses ", ";\n")));
370 }
371 }
372
373
374 private void providesToString(StringBuilder output)
375 {
376 for(Map.Entry<String, String[]> e : provides.entrySet()) {
377 output.append('\t').append("provides ").append(e.getKey())
378 .append(Arrays.stream(e.getValue()).collect(joining(LIST_JOINING_DELIMITER, " with \n\t\t", "")));
379 output.append(";\n");
380 }
381 }
382
383
384 private static <M extends Enum<M> & Modifier> void addComment(StringBuilder output, String indent, Set<M> modifiers)
385 {
386 if(!modifiers.isEmpty()) {
387 startComment(output, indent);
388 accessComment(output, indent, modifiers);
389 endComment(output, indent);
390 }
391 }
392
393
394 private static void addComment(StringBuilder output, String indent, Set<RequireModifier> modifiers, String version)
395 {
396 if(version != null || !modifiers.isEmpty()) {
397 startComment(output, indent);
398 versionComment(output, indent, version);
399 accessComment(output, indent, modifiers);
400 endComment(output, indent);
401 }
402 }
403
404
405 private static void startComment(StringBuilder output, String indent)
406 {
407 output.append(indent).append("/**\n");
408 }
409
410
411 private static void versionComment(StringBuilder output, String indent, String version)
412 {
413 if(version != null) {
414 output.append(indent).append(" * @version ").append(version).append('\n');
415 }
416 }
417
418
419 private static void accessComment(StringBuilder output, String indent, Set<? extends Modifier> modifiers)
420 {
421 if(!modifiers.isEmpty()) {
422 output.append(indent).append(" * @modifiers ")
423 .append(modifiers.stream()
424 .map(Modifier::name)
425 .map(m -> m.toLowerCase(ROOT))
426 .collect(joining(", ")))
427 .append('\n');
428 }
429 }
430
431
432 private static void endComment(StringBuilder output, String indent)
433 {
434 output.append(indent).append(" */\n");
435 }
436
437
438 @Override
439 public byte[] toBytecode()
440 {
441 return BytecodeWriter.toBytecode(this);
442 }
443
444
445 @Override
446 public int access()
447 {
448 return access;
449 }
450 }