1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.themis.xanalysis.parser.proj;
18
19 import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20 import io.github.tonywasher.joceanus.oceanus.base.OceanusSystem;
21 import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
22 import io.github.tonywasher.joceanus.themis.exc.ThemisIOException;
23 import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisChar;
24 import org.w3c.dom.Document;
25 import org.w3c.dom.Element;
26 import org.w3c.dom.Node;
27 import org.xml.sax.SAXException;
28
29 import javax.xml.XMLConstants;
30 import javax.xml.parsers.DocumentBuilder;
31 import javax.xml.parsers.DocumentBuilderFactory;
32 import javax.xml.parsers.ParserConfigurationException;
33 import javax.xml.xpath.XPath;
34 import javax.xml.xpath.XPathConstants;
35 import javax.xml.xpath.XPathExpressionException;
36 import javax.xml.xpath.XPathFactory;
37 import java.io.BufferedInputStream;
38 import java.io.File;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.util.ArrayList;
42 import java.util.LinkedHashMap;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Objects;
46
47
48
49
50 public class ThemisXAnalysisMaven {
51
52
53
54 public static final String POM = "pom.xml";
55
56
57
58
59 private static final String DOC_NAME = "project";
60
61
62
63
64 private static final String XPATH_PROPERTIES = "/project/properties";
65
66
67
68
69 private static final String XPATH_PARENT = "/project/parent";
70
71
72
73
74 private static final String XPATH_MODULES = "/project/modules";
75
76
77
78
79 private static final String XPATH_DEPENDENCIES = "/project/dependencies";
80
81
82
83
84 private static final String XPATH_XTRADIRS = "/project/build/plugins/plugin[artifactId='build-helper-maven-plugin']"
85 + "/executions/execution/configuration/sources";
86
87
88
89
90 private static final String EL_MODULE = "module";
91
92
93
94
95 private static final String EL_DEPENDENCY = "dependency";
96
97
98
99
100 private static final String EL_SOURCE = "source";
101
102
103
104
105 private static final String PARENT_GROUP = "${parent.project.groupId}";
106
107
108
109
110 private static final String PARENT_VERSION = "${parent.parent.version}";
111
112
113
114
115 private static final String PROJECT_GROUP = "${project.groupId}";
116
117
118
119
120 private static final String PROJECT_VERSION = "${project.version}";
121
122
123
124
125 private final XPath theXPath;
126
127
128
129
130 private final Document theDoc;
131
132
133
134
135 private final ThemisXAnalysisMavenId theId;
136
137
138
139
140 private final List<String> theModules;
141
142
143
144
145 private final List<ThemisXAnalysisMavenId> theDependencies;
146
147
148
149
150 private final List<String> theXtraDirs;
151
152
153
154
155 private final ThemisXAnalysisMaven theParent;
156
157
158
159
160 private final Map<String, String> theProperties;
161
162
163
164
165
166
167
168
169 ThemisXAnalysisMaven(final ThemisXAnalysisMaven pParent,
170 final InputStream pInputStream) throws OceanusException {
171
172 theParent = pParent;
173
174
175 theModules = new ArrayList<>();
176 theDependencies = new ArrayList<>();
177 theXtraDirs = new ArrayList<>();
178 theProperties = new LinkedHashMap<>();
179 theProperties.put("${javafx.platform}", OceanusSystem.determineSystem().getClassifier());
180
181
182 try (BufferedInputStream myInBuffer = new BufferedInputStream(pInputStream)) {
183 final DocumentBuilderFactory myFactory = DocumentBuilderFactory.newInstance();
184 myFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
185 myFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
186 myFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
187 final DocumentBuilder myBuilder = myFactory.newDocumentBuilder();
188
189
190 theXPath = XPathFactory.newInstance().newXPath();
191
192
193 theDoc = myBuilder.parse(myInBuffer);
194 theId = parseProjectFile();
195
196
197 } catch (IOException
198 | ParserConfigurationException
199 | SAXException e) {
200 throw new ThemisIOException("Exception accessing Pom file", e);
201 }
202 }
203
204 @Override
205 public String toString() {
206 return theId.toString();
207 }
208
209
210
211
212
213
214 public ThemisXAnalysisMavenId getMavenId() {
215 return theId;
216 }
217
218
219
220
221
222
223 public List<String> getModules() {
224 return theModules;
225 }
226
227
228
229
230
231
232 public List<ThemisXAnalysisMavenId> getDependencies() {
233 return theDependencies;
234 }
235
236
237
238
239
240
241 public List<String> getXtraDirs() {
242 return theXtraDirs;
243 }
244
245
246
247
248
249
250
251 public ThemisXAnalysisMavenId parseProjectFile() throws OceanusException {
252
253 final Element myDoc = theDoc.getDocumentElement();
254
255
256 if (!Objects.equals(myDoc.getNodeName(), DOC_NAME)) {
257 throw new ThemisDataException("Invalid document type");
258 }
259
260
261 processProperties();
262
263
264 final Element myParentEl = (Element) findNode(XPATH_PARENT);
265 final ThemisXAnalysisMavenId myParent = myParentEl == null
266 ? null
267 : new ThemisXAnalysisMavenId(myParentEl);
268 storeParentProperties(myParent);
269
270
271 final ThemisXAnalysisMavenId myId = new ThemisXAnalysisMavenId(myDoc, myParent);
272 storeProjectProperties(myId);
273
274
275 processModules();
276
277
278 processDependencies(myId);
279
280
281 processXtraDirs();
282
283
284 return myId;
285 }
286
287
288
289
290
291
292
293
294 String getElementValue(final Element pElement,
295 final String pValue) {
296
297 if (pElement == null) {
298 return null;
299 }
300
301
302 for (Node myChild = pElement.getFirstChild();
303 myChild != null;
304 myChild = myChild.getNextSibling()) {
305
306 if (myChild instanceof Element
307 && pValue.equals(myChild.getNodeName())) {
308 return replaceProperty(myChild.getTextContent());
309 }
310 }
311
312
313 return null;
314 }
315
316
317
318
319
320
321
322
323 private Node findNode(final String pPath) throws OceanusException {
324
325 try {
326 return (Node) theXPath.compile(pPath).evaluate(theDoc, XPathConstants.NODE);
327 } catch (XPathExpressionException e) {
328 throw new ThemisDataException("Exception locating XPath: " + pPath, e);
329 }
330 }
331
332
333
334
335
336
337 private void processProperties() throws OceanusException {
338
339 final Node myProps = findNode(XPATH_PROPERTIES);
340 if (myProps != null) {
341 for (Node myNode = myProps.getFirstChild(); myNode != null; myNode = myNode.getNextSibling()) {
342 if (myNode instanceof Element myElement) {
343 theProperties.put("${" + myElement.getNodeName() + "}", myElement.getTextContent());
344 }
345 }
346 }
347 }
348
349
350
351
352
353
354 private void storeParentProperties(final ThemisXAnalysisMavenId pParent) {
355
356 theProperties.put(PARENT_GROUP, pParent == null ? null : pParent.getGroupId());
357 theProperties.put(PARENT_VERSION, pParent == null ? null : pParent.getVersion());
358 }
359
360
361
362
363
364
365 private void storeProjectProperties(final ThemisXAnalysisMavenId pProject) {
366
367 String myGroupId = pProject.getGroupId();
368 myGroupId = myGroupId != null ? myGroupId : theProperties.get(PARENT_GROUP);
369
370
371 String myVersion = pProject.getVersion();
372 myVersion = myVersion != null ? myVersion : theProperties.get(PARENT_VERSION);
373
374
375 theProperties.put(PROJECT_GROUP, myGroupId);
376 theProperties.put(PROJECT_VERSION, myVersion);
377 }
378
379
380
381
382
383
384 private void processModules() throws OceanusException {
385
386 final Node myModules = findNode(XPATH_MODULES);
387 if (myModules != null) {
388
389 for (Node myChild = myModules.getFirstChild();
390 myChild != null;
391 myChild = myChild.getNextSibling()) {
392
393 if (myChild instanceof Element
394 && EL_MODULE.equals(myChild.getNodeName())) {
395 theModules.add(myChild.getTextContent());
396 }
397 }
398 }
399 }
400
401
402
403
404
405
406
407 private void processDependencies(final ThemisXAnalysisMavenId pParent) throws OceanusException {
408
409 final Node myDependencies = findNode(XPATH_DEPENDENCIES);
410 if (myDependencies != null) {
411
412 for (Node myChild = myDependencies.getFirstChild();
413 myChild != null;
414 myChild = myChild.getNextSibling()) {
415
416 if (myChild instanceof Element myElement
417 && EL_DEPENDENCY.equals(myChild.getNodeName())) {
418 final ThemisXAnalysisMavenId myId = new ThemisXAnalysisMavenId(myElement);
419 if (!myId.isSkippable()) {
420 theDependencies.add(myId);
421 }
422 }
423 }
424 }
425 }
426
427
428
429
430
431
432 private void processXtraDirs() throws OceanusException {
433
434 final Node myXtraDirs = findNode(XPATH_XTRADIRS);
435 if (myXtraDirs != null) {
436
437 for (Node myChild = myXtraDirs.getFirstChild();
438 myChild != null;
439 myChild = myChild.getNextSibling()) {
440
441 if (myChild instanceof Element
442 && EL_SOURCE.equals(myChild.getNodeName())) {
443 theXtraDirs.add(myChild.getTextContent());
444 }
445 }
446 }
447 }
448
449
450
451
452
453
454
455 private String replaceProperty(final String pValue) {
456 String myResult = pValue;
457 for (Map.Entry<String, String> myEntry : theProperties.entrySet()) {
458 if (myResult.contains(myEntry.getKey())) {
459 myResult = myResult.replace(myEntry.getKey(), myEntry.getValue());
460 }
461 }
462 return theParent != null ? theParent.replaceProperty(myResult) : myResult;
463 }
464
465
466
467
468 public final class ThemisXAnalysisMavenId {
469
470
471
472 private static final String EL_GROUPID = "groupId";
473
474
475
476
477 private static final String EL_ARTIFACTID = "artifactId";
478
479
480
481
482 private static final String EL_VERSION = "version";
483
484
485
486
487 private static final String EL_SCOPE = "scope";
488
489
490
491
492 private static final String EL_CLASSIFIER = "classifier";
493
494
495
496
497 private static final String EL_OPTIONAL = "optional";
498
499
500
501
502 private final String theArtifactId;
503
504
505
506
507 private String theGroupId;
508
509
510
511
512 private String theVersion;
513
514
515
516
517 private final String theScope;
518
519
520
521
522 private final String theClassifier;
523
524
525
526
527 private final String isOptional;
528
529
530
531
532
533
534 private ThemisXAnalysisMavenId(final Element pElement) {
535
536 theGroupId = getElementValue(pElement, EL_GROUPID);
537 theArtifactId = getElementValue(pElement, EL_ARTIFACTID);
538 theVersion = getElementValue(pElement, EL_VERSION);
539 theScope = getElementValue(pElement, EL_SCOPE);
540 theClassifier = getElementValue(pElement, EL_CLASSIFIER);
541 isOptional = getElementValue(pElement, EL_OPTIONAL);
542 }
543
544
545
546
547
548
549
550 private ThemisXAnalysisMavenId(final Element pElement,
551 final ThemisXAnalysisMavenId pParent) {
552
553 this(pElement);
554
555
556 if (theGroupId == null) {
557 theGroupId = pParent.getGroupId();
558 }
559 if (theVersion == null) {
560 theVersion = pParent.getVersion();
561 }
562
563
564 if (theVersion != null
565 && theVersion.startsWith(String.valueOf(ThemisXAnalysisChar.ARRAY_OPEN))) {
566 theVersion = null;
567 }
568 }
569
570
571
572
573
574
575 public String getGroupId() {
576 return theGroupId;
577 }
578
579
580
581
582
583
584 public String getArtifactId() {
585 return theArtifactId;
586 }
587
588
589
590
591
592
593 public String getVersion() {
594 return theVersion;
595 }
596
597
598
599
600
601
602 public String getScope() {
603 return theScope;
604 }
605
606
607
608
609
610
611 public String getClassifier() {
612 return theClassifier;
613 }
614
615
616
617
618
619
620 public String isOptional() {
621 return isOptional;
622 }
623
624
625
626
627
628
629 public boolean isSkippable() {
630 return "test".equals(theScope)
631 || "runtime".equals(theScope)
632 || "provided".equals(theScope)
633 || theVersion == null
634 || isOptional != null;
635 }
636
637 @Override
638 public boolean equals(final Object pThat) {
639
640 if (this == pThat) {
641 return true;
642 }
643 if (pThat == null) {
644 return false;
645 }
646
647
648 if (!(pThat instanceof ThemisXAnalysisMavenId myThat)) {
649 return false;
650 }
651
652
653 return Objects.equals(theGroupId, myThat.getGroupId())
654 && Objects.equals(theArtifactId, myThat.getArtifactId())
655 && Objects.equals(theVersion, myThat.getVersion())
656 && Objects.equals(theScope, myThat.getScope())
657 && Objects.equals(theClassifier, myThat.getClassifier());
658 }
659
660 @Override
661 public int hashCode() {
662 return Objects.hash(theGroupId, theArtifactId, theVersion, theScope, theClassifier);
663 }
664
665 @Override
666 public String toString() {
667 final String myName = theGroupId + ThemisXAnalysisChar.COLON + theArtifactId + ThemisXAnalysisChar.COLON + theVersion;
668 return theClassifier == null ? myName : myName + ThemisXAnalysisChar.COLON + theClassifier;
669 }
670
671
672
673
674
675
676 private File getMavenBasePath() {
677
678 File myBase = new File(System.getProperty("user.home"));
679 myBase = new File(myBase, ".m2");
680 myBase = new File(myBase, "repository");
681 myBase = new File(myBase, theGroupId.replace(ThemisXAnalysisChar.PERIOD, ThemisXAnalysisChar.COMMENT));
682 myBase = new File(myBase, theArtifactId);
683 myBase = new File(myBase, theVersion);
684 return myBase;
685 }
686
687
688
689
690
691
692 public File getMavenJarPath() {
693
694 File myBase = getMavenBasePath();
695 String myName = theArtifactId + ThemisXAnalysisChar.HYPHEN + theVersion;
696 if (theClassifier != null) {
697 myName += ThemisXAnalysisChar.HYPHEN + theClassifier;
698 }
699 myBase = new File(myBase, myName + ".jar");
700 return myBase;
701 }
702
703
704
705
706
707
708 public File getMavenPomPath() {
709
710 File myBase = getMavenBasePath();
711 myBase = new File(myBase, theArtifactId + ThemisXAnalysisChar.HYPHEN + theVersion + ".pom");
712 return myBase;
713 }
714 }
715 }