-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathJPM.java
2074 lines (1760 loc) · 82 KB
/
JPM.java
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import java.util.regex.Pattern;
import org.w3c.dom.Element;
import java.net.HttpURLConnection;
import java.nio.file.*;
import java.net.URI;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import java.util.function.Consumer;
import javax.xml.parsers.DocumentBuilderFactory;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.*;
import org.w3c.dom.NodeList;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.CopyOnWriteArrayList;
import java.io.*;
import java.nio.charset.Charset;
public class JPM {
public static class ThisProject extends JPM.Project {
public ThisProject() throws Exception {
this(null);
}
public ThisProject(List<String> args) throws Exception {
// Override default configurations
this.groupId = "com.author.myproject";
this.artifactId = "my-project";
this.version = $("version");
this.mainClass = groupId+".MyMainClass";
this.jarName = artifactId+".jar";
this.fatJarName = artifactId+"-with-dependencies.jar";
JPM.plugins.remove(AssemblyPlugin.get);
addRepository("https://s01.oss.sonatype.org");
addRepository("https://maven.google.com/");
addRepository("https://oss.sonatype.org/content/repositories/snapshots/");
addRepository("https://s01.oss.sonatype.org/content/repositories/snapshots/");
addRepository("https://jitpack.io");
// If there are duplicate dependencies with different versions force a specific version like so:
forceImplementation("com.github.Osiris-Team:jlib:18.8");
forceImplementation("org.slf4j:slf4j-api:1.7.36");
// Add dependencies
// Add compiler arguments
addCompilerArg("-Xlint:unchecked");
addCompilerArg("-Xlint:deprecation");
// Add additional plugins
//putPlugin("org.codehaus.mojo:exec-maven-plugin:1.6.0", d -> {
// d.putConfiguration("mainClass", this.mainClass);
//});
portChildProjects();
// Execute build
if(args != null){
generatePom();
if(!args.contains("skipMaven"))
JPM.executeMaven("clean", "install");//, "-DskipTests");
// or JPM.executeMaven(args); if you prefer the CLI, like "java JPM.java clean package"
}
}
@java.lang.Override
public XML toXML() {
XML pom = super.toXML();
pom.put("packaging", "pom");
return pom;
}
}
public static class ThirdPartyPlugins extends JPM.Plugins{
// Add third party plugins below, find them here: https://github.com/topics/1jpm-plugin?o=desc&s=updated
// (If you want to develop a plugin take a look at "JPM.AssemblyPlugin" class further below to get started)
}
// 1JPM version 3.3.7 by Osiris-Team: https://github.com/Osiris-Team/1JPM
// Do not edit anything below, since changes will be lost due to auto-updating.
// You can also do this manually, by replacing everything below with its newer version and updating the imports.
public static final List<Plugin> plugins = new ArrayList<>();
public static final String mavenVersion = "3.9.8";
public static final String mavenWrapperVersion = "3.3.2";
public static final String mavenWrapperScriptUrlBase = "https://raw.githubusercontent.com/apache/maven-wrapper/maven-wrapper-"+ mavenWrapperVersion +"/maven-wrapper-distribution/src/resources/";
public static final String mavenWrapperJarUrl = "https://repo1.maven.org/maven2/org/apache/maven/wrapper/maven-wrapper/"+ mavenWrapperVersion +"/maven-wrapper-"+ mavenWrapperVersion +".jar";
public static final String mavenWrapperPropsContent = "distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/"+ mavenVersion +"/apache-maven-"+ mavenVersion +"-bin.zip";
public static final String jpmLatestUrl = "https://github.com/Osiris-Team/1JPM/raw/main/src/main/java/com/mycompany/myproject/JPM.java";
public static final String propsFileName = "JPM.properties";
public static final Map<String, String> propsKeyValCache = new HashMap<>();
public static boolean isJpmUpdated = false;
/**
* Running {@link #main(String[])} without arguments / empty arguments
* should always result in a pom.xml file being created anyway. <br>
* Passing over null instead of an arguments list should never create a pom.xml file.
*/
public static Object expectation1 = new Object();
static{
updateSelfIfNeeded(); // Comment this to disable self-updating, remember to comment again after updating manually!
// Init this once to ensure their plugins are added if they use the static constructor
new ThirdPartyPlugins();
if(isJpmUpdated){
System.out.print("\n\n\n\n");
System.out.println("!!! JPM WAS JUST UPDATED, THUS THIS RUN DOES NOT YET BENEFIT FROM THE NEWEST CHANGES !!!");
System.out.println("--> RE-RUN THIS TO RUN WITH THE UPDATED JPM VERSION.");
System.out.print("\n\n\n\n");
}
}
/**
* Bound by {@link #expectation1}.
*/
public static void main(String[] args) throws Exception {
new ThisProject(new ArrayList<>(Arrays.asList(args)));
}
public static void executeMaven(List<String> args) throws IOException, InterruptedException {
executeMaven(args.toArray(new String[0]));
}
public static void executeMaven(String... args) throws IOException, InterruptedException {
executeMaven(null, args);
}
public static void executeMaven(File userDir, String... args) throws IOException, InterruptedException {
boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win");
ProcessBuilder p = new ProcessBuilder();
List<String> finalArgs = new ArrayList<>();
if(userDir == null) userDir = new File(System.getProperty("user.dir"));
File mavenWrapperFile = new File(userDir, "mvnw" + (isWindows ? ".cmd" : ""));
File propertiesFile = new File(userDir, ".mvn/wrapper/maven-wrapper.properties");
File mavenWrapperJarFile = new File(userDir, ".mvn/wrapper/maven-wrapper.jar");
if (!mavenWrapperFile.exists()) {
downloadMavenWrapper(mavenWrapperFile);
if(!isWindows) mavenWrapperFile.setExecutable(true);
}
if(!mavenWrapperJarFile.exists()) downloadMavenWrapperJar(mavenWrapperJarFile);
if (!propertiesFile.exists()) createMavenWrapperProperties(propertiesFile);
finalArgs.add(mavenWrapperFile.getAbsolutePath());
finalArgs.addAll(Arrays.asList(args));
p.command(finalArgs);
p.directory(userDir);
p.inheritIO();
System.out.print("Executing: ");
for (String arg : finalArgs) {
System.out.print(arg+" ");
}
System.out.println();
Process result = p.start();
result.waitFor();
if(result.exitValue() != 0)
throw new RuntimeException("Maven ("+mavenWrapperFile.getName()+") finished with an error ("+result.exitValue()+"): "+mavenWrapperFile.getAbsolutePath());
}
public static void downloadMavenWrapper(File script) throws IOException {
String wrapperUrl = mavenWrapperScriptUrlBase + script.getName();
System.out.println("Downloading file from: " + wrapperUrl);
URL url = toUrl(wrapperUrl);
script.getParentFile().mkdirs();
Files.copy(url.openStream(), script.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
public static void downloadMavenWrapperJar(File jar) throws IOException {
String wrapperUrl = mavenWrapperJarUrl;
System.out.println("Downloading file from: " + wrapperUrl);
URL url = toUrl(wrapperUrl);
jar.getParentFile().mkdirs();
Files.copy(url.openStream(), jar.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
public static void createMavenWrapperProperties(File propertiesFile) throws IOException {
// Create the .mvn directory if it doesn't exist
File mvnDir = propertiesFile.getParentFile();
if (!mvnDir.exists()) {
mvnDir.mkdirs();
}
// Write default properties content to the file
try (FileWriter writer = new FileWriter(propertiesFile)) {
writer.write(mavenWrapperPropsContent);
}
}
/**
* Returns the value as string from the nearest JPM.properties file, or an empty string if not found/defined in the complete dir tree.<br>
* <br>
* By "nearest" we mean: If the value didn't exist in a JPM.properties file in the current working directory,
* we check all JPM.properties files until the root of the disk.
*
* @param key The key to search for in the properties files.
* @return The value associated with the key, or an empty string if not defined.
*/
public static String $(String key) {
// Check if the key is already in the cache
if (propsKeyValCache.containsKey(key)) {
return propsKeyValCache.get(key);
}
File currentDir = new File(System.getProperty("user.dir"));
while (currentDir != null) {
File propertiesFile = new File(currentDir, propsFileName);
if (propertiesFile.exists() && propertiesFile.isFile()) {
try (FileInputStream fis = new FileInputStream(propertiesFile)) {
Properties properties = new Properties();
properties.load(fis);
String value = properties.getProperty(key);
if (value != null) {
propsKeyValCache.put(key, value); // Cache the result
return value;
}
} catch (IOException e) {
e.printStackTrace();
// If there's an issue reading the file, continue to the parent directory
}
}
currentDir = currentDir.getParentFile();
}
// If the key was not found in any properties file, return an empty string
propsKeyValCache.put(key, ""); // Cache the empty result
return "";
}
/**
* This updates the JPM.java file in the current working dir by doing the following: <br>
* 1. Downloads the latest JPM.java file into memory. <br>
* 2. Checks its version by searching for a string like "// 1JPM version 3.2.0 by Osiris-Team:" and extracting the version from that. <br>
* 3. Check the current version by doing the same for the current JPM.java file <br>
* 4. If the downloaded JPM.java file is newer we update. <br>
* 5. The actual update is done by extracting everything up to "// 1JPM version <version> by Osiris-Team:" from the current JPM.java file, <br>
* then extracting everything starting at "// 1JPM version <version> by Osiris-Team:" (this line will also be included) until the end of the latest JPM.java file, <br>
* then finally combining those 2 strings and overwriting the current JPM.java file, imports of both files will also be merged.
*/
public static void updateSelfIfNeeded() {
try{
System.out.println("Downloading file from: " + jpmLatestUrl);
File cwd = new File(System.getProperty("user.dir"));
File jpmFile = new File(cwd + "/JPM.java");
if (!jpmFile.exists()) {
jpmFile = new File(cwd + "/src/main/java/JPM.java");
if (!jpmFile.exists()) {
String jpmPackagePath = "/"+JPM.class.getPackage().getName().replace(".", "/");
jpmFile = new File(cwd+"/src/main/java"+jpmPackagePath+"/JPM.java");
// If still not found, search all subdirs by shallowest first
if (!jpmFile.exists()) {
jpmFile = searchForFileBreadthFirst(cwd, "JPM.java");
if (jpmFile == null) {
throw new FileNotFoundException("JPM.java file not found in the project directory.");
}
}
}
}
URL url = toUrl(jpmLatestUrl);
jpmFile.getParentFile().mkdirs();
String jpmJavaContent = contentToString(url);
String latestVersion = extractJPMVersion(jpmJavaContent);
String currentJpmJavaContent = contentToString(jpmFile);
String currentVersion = extractJPMVersion(currentJpmJavaContent);
// Compare versions
if (latestVersion.compareTo(currentVersion) > 0) {
System.out.println("A newer JPM version is available ("+currentVersion+" -> "+latestVersion+"). Updating...");
// Extract and merge imports
String mergedImports = mergeJPMImports(currentJpmJavaContent, jpmJavaContent);
// Extract parts for merging
String currentHeader = extractCurrentJPMHead(currentJpmJavaContent);
String latestBody = extractLatestJPMBody(jpmJavaContent);
// Combine the header of the current file, merged imports, and the body of the latest file
String updatedJpmJavaContent = mergedImports + "\n\n" + currentHeader + "\n" + latestBody;
// Overwrite the current JPM.java file with the updated content
try (FileWriter writer = new FileWriter(jpmFile)) {
writer.write(updatedJpmJavaContent);
}
isJpmUpdated = true;
System.out.println("JPM update completed to version " + latestVersion+", please re-run to use the latest version!");
} else {
System.out.println("No JPM update needed. You are already on the latest version (" + currentVersion + ").");
}
} catch (Exception e) {
System.err.println("Self-updating failed!");
e.printStackTrace();
}
}
private static File searchForFileBreadthFirst(File rootDir, String fileName) {
Queue<File> directories = new LinkedList<>();
directories.add(rootDir);
while (!directories.isEmpty()) {
File dir = directories.poll();
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
directories.add(file); // Add directories to the queue for further searching
} else if (file.getName().equals(fileName)) {
return file; // Return the file if found
}
}
}
}
return null; // File not found
}
private static String extractJPMVersion(String content) {
Pattern pattern = Pattern.compile("//\\s*1JPM\\s*version\\s*(\\d+\\.\\d+\\.\\d+)\\s*by\\s*Osiris-Team:");
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
return matcher.group(1);
} else {
throw new IllegalStateException("Could not find version information in the provided content.");
}
}
/**
* Starts after the imports.
*/
private static String extractCurrentJPMHead(String content) {
// Use regex to find the end of the imports section
Pattern importPattern = Pattern.compile("^import\\s+.*?;$", Pattern.MULTILINE);
Matcher importMatcher = importPattern.matcher(content);
int lastImportEndIndex = 0;
while (importMatcher.find()) {
lastImportEndIndex = importMatcher.end();
}
// Start searching for the version comment after the imports
Pattern versionPattern = Pattern.compile("//\\s*1JPM\\s*version\\s*\\d+\\.\\d+\\.\\d+\\s*by\\s*Osiris-Team:");
Matcher versionMatcher = versionPattern.matcher(content);
if (versionMatcher.find(lastImportEndIndex)) {
int versionStartIndex = versionMatcher.start();
return content.substring(lastImportEndIndex, versionStartIndex);
} else {
throw new IllegalStateException("Could not (extractCurrentJPMHead) find version information in the current content.");
}
}
private static String extractLatestJPMBody(String content) {
// Use regex to find the version comment line, matching any version number
Pattern pattern = Pattern.compile("//\\s*1JPM\\s*version\\s*\\d+\\.\\d+\\.\\d+\\s*by\\s*Osiris-Team:");
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
int index = matcher.start();
return content.substring(index);
} else {
throw new IllegalStateException("Could not (extractBody) find version information in the latest content.");
}
}
private static String mergeJPMImports(String currentContent, String latestContent) {
Set<String> imports = new HashSet<>();
Pattern importPattern = Pattern.compile("^import\\s+.*?;$", Pattern.MULTILINE);
// Extract imports from the current content
Matcher currentMatcher = importPattern.matcher(currentContent);
while (currentMatcher.find()) {
imports.add(currentMatcher.group());
}
// Extract imports from the latest content
Matcher latestMatcher = importPattern.matcher(latestContent);
while (latestMatcher.find()) {
imports.add(latestMatcher.group());
}
// Combine all unique imports into a single string
StringBuilder mergedImports = new StringBuilder();
for (String imp : imports) {
mergedImports.append(imp).append("\n");
}
return mergedImports.toString();
}
private static URL toUrl(String url) throws MalformedURLException {
return URL.of(URI.create(url), null);
}
/**
* This is going to download and copy the latest JPM.java file into all child projects it can find in this directory,
* and also run that file to generate an initial pom.xml. The child projects name will be the same as its root directory name.<br>
* <br>
* A child project is detected if a src/main/java folder structure exists, and the parent folder of src/ is then used
* as child project root. <br>
* <br>
* Note that a child project is expected to be directly inside a subdirectory of this project.<br>
* <br>
* Useful to quickly setup existing multi-module projects, since then {@link Project#isAutoParentsAndChildren} will work properly. <br>
* <br>
* Bound by {@link #expectation1}.
*/
public static void portChildProjects() throws Exception {
List<File> childProjectDirs = new ArrayList<>();
File cwd = new File(System.getProperty("user.dir"));
File[] subDirs = cwd.listFiles(File::isDirectory);
if(subDirs != null)
for (File subDir : subDirs) {
fillSubProjectDirs(subDir, childProjectDirs);
}
if(childProjectDirs.isEmpty()) System.out.println("No child projects found in dir: "+cwd);
else {
for (File childProjectDir : childProjectDirs) {
File jpmFile = new File(childProjectDir, "JPM.java");
if(jpmFile.exists()) {
System.out.println("JPM.java file already exists for child project '"+childProjectDir.getName()+"'.");
if(!new File(childProjectDir, "pom.xml").exists()){
execJavaJpmJava(childProjectDir);
}
continue;
}
System.out.println("Downloading file from: " + jpmLatestUrl);
URL url = toUrl(jpmLatestUrl);
jpmFile.getParentFile().mkdirs();
String jpmJavaContent = contentToString(url);
jpmJavaContent = jpmJavaContent.replace(".myproject", "."+childProjectDir.getName())
.replace("my-project", childProjectDir.getName());
Files.write(jpmFile.toPath(), jpmJavaContent.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
System.out.println("Created JPM.java file for child project '"+childProjectDir.getName()+"'.");
execJavaJpmJava(childProjectDir);
}
for (File childProjectDir : childProjectDirs) {
System.out.println(childProjectDir);
}
System.out.println("Ported "+childProjectDirs.size()+" child projects successfully!");
}
}
private static void execJavaJpmJava(File childProjectDir, String... additionalArgs) throws IOException, InterruptedException {
ProcessBuilder p = new ProcessBuilder();
List<String> list = new ArrayList<>();
list.add("java");
list.add("JPM.java");
if(additionalArgs != null && additionalArgs.length > 0) list.addAll(Arrays.asList(additionalArgs));
p.command(list);
p.inheritIO();
p.directory(childProjectDir);
System.out.println("Executing in child project '"+ childProjectDir.getName()+"': java JPM.java");
Process result = p.start();
result.waitFor();
if(result.exitValue() != 0){
RuntimeException ex = new RuntimeException("Command finished with an error ("+result.exitValue()+"), while executing: java JPM.java");
if(new File(childProjectDir, "pom.xml").exists()){
System.err.println("IGNORED exception because pom.xml file was created. Make sure to look into this later:");
ex.printStackTrace();
return;
}
throw ex;
}
}
private static void fillSubProjectDirs(File dir, List<File> childProjectDirs){
File javaDir = new File(dir+"/src/main/java");
if(javaDir.exists()){
childProjectDirs.add(dir);
File[] subDirs = dir.listFiles(File::isDirectory);
if(subDirs != null)
for (File subDir : subDirs) {
fillSubProjectDirs(subDir, childProjectDirs);
}
}
}
/**
* Overloaded method to use UTF-8 as the default charset.
*
* @param url The URL to read from.
* @return The content of the URL as a String.
* @throws Exception If an I/O error occurs.
*/
public static String contentToString(URL url) throws Exception {
return contentToString(url, StandardCharsets.UTF_8);
}
/**
* Reads the content of a URL as binary data and converts it to a String.
*
* @param url The URL to read from.
* @param charset The charset to use for converting bytes to a String.
* @return The content of the URL as a String.
* @throws Exception If an I/O error occurs.
*/
public static String contentToString(URL url, Charset charset) throws Exception {
try (InputStream inputStream = url.openStream()){
return contentToString(inputStream, charset);
}
}
private static String contentToString(File file) throws Exception {
try (FileInputStream fis = new FileInputStream(file)) {
return contentToString(fis, StandardCharsets.UTF_8);
}
}
public static String contentToString(InputStream in, Charset charset) throws IOException {
try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead);
}
return byteArrayOutputStream.toString(charset.name());
}
}
//
// API and Models
//
public static class Plugins {
}
public static interface ConsumerWithException<T> extends Serializable {
void accept(T t) throws Exception;
}
public static class Dependency {
public static Dependency fromGradleString(Project project, String s) {
String[] split = s.split(":");
String groupId = split.length >= 1 ? split[0] : "";
String artifactId = split.length >= 2 ? split[1] : "";
String versionId = split.length >= 3 ? split[2] : "";
String scope = split.length >= 4 ? split[3] : "compile";
Dependency dep = new Dependency(project, groupId, artifactId, versionId, scope);
if (split.length < 3) {
System.err.println("No version provided. This might cause issues. Dependency: " + s);
suggestVersions(project, groupId, artifactId);
}
return dep;
}
private static void suggestVersions(Project project, String groupId, String artifactId) {
Map<String, String> latestVersions = new HashMap<>();
for (Repository _repo : project.repositories) {
String repo = _repo.url;
try {
URL url = toUrl(repo + "/" + groupId.replace('.', '/') + "/" + artifactId + "/maven-metadata.xml");
System.out.println("Checking repository: " + url);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
if (connection.getResponseCode() == 200) {
List<String> versions = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("<version>")) {
String version = line.trim().replace("<version>", "").replace("</version>", "");
versions.add(version);
}
}
}
if (!versions.isEmpty()) {
Collections.sort(versions, new VersionComparator());
latestVersions.put(repo, versions.get(0));
}
}
} catch (Exception e) {
System.err.println("Error checking repository " + repo + ": " + e.getMessage());
}
}
if (!latestVersions.isEmpty()) {
System.out.println("Latest versions for " + groupId + ":" + artifactId + " across repositories:");
for (Map.Entry<String, String> entry : latestVersions.entrySet()) {
System.out.println(" - " + entry.getKey() + ": " + entry.getValue() +
(entry.getValue().contains("SNAPSHOT") ? " (SNAPSHOT)" : ""));
}
} else {
System.out.println("No versions found for " + groupId + ":" + artifactId + " in any repository");
}
}
private static class VersionComparator implements Comparator<String> {
@Override
public int compare(String v1, String v2) {
String[] parts1 = v1.split("\\.");
String[] parts2 = v2.split("\\.");
for (int i = 0; i < Math.max(parts1.length, parts2.length); i++) {
int p1 = i < parts1.length ? parseVersionPart(parts1[i]) : 0;
int p2 = i < parts2.length ? parseVersionPart(parts2[i]) : 0;
if (p1 != p2) {
return Integer.compare(p2, p1); // Reverse order for latest version first
}
}
return v2.compareTo(v1); // Reverse order for latest version first
}
private int parseVersionPart(String part) {
try {
return Integer.parseInt(part);
} catch (NumberFormatException e) {
return 0;
}
}
}
public Project project;
public String groupId;
public String artifactId;
public String version;
public String scope;
public List<Dependency> transitiveDependencies;
public List<Dependency> excludedDependencies = new ArrayList<>();
public String type = "";
/**
* If provided builds this dependency before building the project.
*/
public String localProjectPath = "";
public Dependency(Project project, String groupId, String artifactId, String version) {
this(project, groupId, artifactId, version, "compile", new ArrayList<>());
}
public Dependency(Project project, String groupId, String artifactId, String version, String scope) {
this(project, groupId, artifactId, version, scope, new ArrayList<>());
}
public Dependency(Project project, String groupId, String artifactId, String version, String scope, List<Dependency> transitiveDependencies) {
this.project = project;
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.scope = scope;
this.transitiveDependencies = transitiveDependencies;
}
public Dependency exclude(String s){
return exclude(Dependency.fromGradleString(project, s));
}
public Dependency exclude(Dependency dep){
excludedDependencies.add(dep);
return dep;
}
@Override
public String toString() {
return groupId + ":" + artifactId + ":" + version + ":" + scope;
}
public XML toXML(){
XML xml = new XML("dependency");
xml.put("groupId", groupId);
xml.put("artifactId", artifactId);
if (version != null && !version.isEmpty()) xml.put("version", version);
if (scope != null && !scope.isEmpty()) xml.put("scope", scope);
if (type != null && !type.isEmpty()) xml.put("type", type);
for (Dependency excludedDependency : excludedDependencies) {
XML exclusion = new XML("exclusion");
exclusion.put("groupId", excludedDependency.groupId);
exclusion.put("artifactId", excludedDependency.artifactId);
xml.add("exclusions", exclusion);
}
return xml;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dependency that = (Dependency) o;
return Objects.equals(groupId, that.groupId) &&
Objects.equals(artifactId, that.artifactId) &&
Objects.equals(version, that.version) &&
Objects.equals(scope, that.scope);
}
@Override
public int hashCode() {
return Objects.hash(groupId, artifactId, version, scope);
}
}
public static class Repository{
public String id;
public String url;
public boolean isSnapshotsAllowed = true;
public Repository(String id, String url) {
this.id = id;
this.url = url;
}
public static Repository fromUrl(String url){
String id = url.replaceAll("[\\\\/:\"<>|?*]", "");
return new Repository(id, url);
}
public XML toXML(){
XML xml = new XML("repository");
xml.put("id", id);
xml.put("url", url);
if(!isSnapshotsAllowed) xml.put("snapshots enabled", "false");
return xml;
}
}
public static class XML {
public Document document;
public Element root;
// Constructor initializes the XML document with a root element.
public XML(String rootName) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.newDocument();
root = document.createElement(rootName);
document.appendChild(root);
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
public XML(File file){
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse(file);
root = document.getDocumentElement();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
// Method to append another XML object to this XML document's root.
public XML add(XML otherXML) {
Node importedNode = document.importNode(otherXML.root, true);
root.appendChild(importedNode);
return this;
}
// Method to append another XML object to a specific element in this XML document.
public XML add(String key, XML otherXML) {
Element parentElement = getElementOrCreate(key);
Node importedNode = document.importNode(otherXML.root, true);
parentElement.appendChild(importedNode);
return this;
}
// Method to add a value to the XML document at the specified path.
public XML put(String key, String value) {
Element currentElement = getElementOrCreate(key);
if(value != null && !value.isEmpty())
currentElement.setTextContent(value);
return this;
}
// Method to add a comment to the XML document at the specified path.
public XML putComment(String key, String comment) {
Element currentElement = getElementOrCreate(key);
Node parentNode = currentElement.getParentNode();
Node commentNode = document.createComment(comment);
// Insert the comment before the specified element.
parentNode.insertBefore(commentNode, currentElement);
return this;
}
public XML putAttributes(String key, String... attributes) {
if (attributes.length % 2 != 0) {
throw new IllegalArgumentException("Attributes must be in key-value pairs.");
}
Element currentElement = getElementOrCreate(key);
// Iterate over pairs of strings to set each attribute on the element.
for (int i = 0; i < attributes.length; i += 2) {
String attrName = attributes[i];
String attrValue = attributes[i + 1];
currentElement.setAttribute(attrName, attrValue);
}
return this;
}
// Method to add attributes to an element in the XML document at the specified path.
public XML putAttributes(String key, Map<String, String> attributes) {
Element currentElement = getElementOrCreate(key);
// Set each attribute in the map on the element.
for (Map.Entry<String, String> entry : attributes.entrySet()) {
currentElement.setAttribute(entry.getKey(), entry.getValue());
}
return this;
}
public XML remove(String key) {
Element element = getElementOrNull(key);
if (element != null && element.getParentNode() != null) {
element.getParentNode().removeChild(element);
}
return this;
}
public XML rename(String oldKey, String newName) {
Element element = getElementOrNull(oldKey);
if (element != null) {
document.renameNode(element, null, newName);
}
return this;
}
// Helper method to traverse or create elements based on a path.
public Element getElementOrCreate(String key) {
if (key == null || key.trim().isEmpty()) return root;
String[] path = key.split(" ");
Element currentElement = root;
for (String nodeName : path) {
Element childElement = null;
NodeList children = currentElement.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(nodeName)) {
childElement = (Element) child;
break;
}
}
if (childElement == null) {
childElement = document.createElement(nodeName);
currentElement.appendChild(childElement);
}
currentElement = childElement;
}
return currentElement;
}
public Element getElementOrNull(String key) {
if (key == null || key.trim().isEmpty()) return root;
String[] path = key.split(" ");
return getElementOrNull(path);
}
protected Element getElementOrNull(String[] path) {
Element currentElement = root;
for (String nodeName : path) {
NodeList children = currentElement.getChildNodes();
boolean found = false;
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(nodeName)) {
currentElement = (Element) child;
found = true;
break;
}
}
if (!found) return null;
}
return currentElement;
}
// Method to convert the XML document to a pretty-printed string.
public String toString() {
try {
javax.xml.transform.TransformerFactory transformerFactory = javax.xml.transform.TransformerFactory.newInstance();
javax.xml.transform.Transformer transformer = transformerFactory.newTransformer();
// Enable pretty printing with indentation and newlines.
transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); // Adjust indent space as needed
javax.xml.transform.dom.DOMSource domSource = new javax.xml.transform.dom.DOMSource(document);
java.io.StringWriter writer = new java.io.StringWriter();
javax.xml.transform.stream.StreamResult result = new javax.xml.transform.stream.StreamResult(writer);
transformer.transform(domSource, result);
return writer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String toStringAt(File file) throws IOException {
String s = toString();
try (FileWriter writer = new FileWriter(file)) {
writer.write(s);
}
return s;
}
public static void main(String[] args) {
// Example usage of the XML class.
XML xml = new XML("root");
xml.put("this is a key", "value");
xml.put("this is another key", "another value");
xml.putComment("this is another", "This is a comment for 'another'");
Map<String, String> atr = new HashMap<>();
atr.put("attr1", "value1");
atr.put("attr2", "value2");
xml.putAttributes("this is a key", atr);
System.out.println(xml.toString());
}
}
public static class Plugin {
public List<Consumer<Details>> beforeToXMLListeners = new CopyOnWriteArrayList<>();
public String groupId;
public String artifactId;
public String version;
public Plugin(String groupId, String artifactId, String version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
}
public Plugin onBeforeToXML(Consumer<Details> code){
beforeToXMLListeners.add(code);
return this;
}
private void executeBeforeToXML(Details details) {
for (Consumer<Details> code : beforeToXMLListeners) {
code.accept(details);
}
}
/**
* Usually you will override this.
*/
public XML toXML(Project project, XML projectXML) {
// Create an XML object for the <plugin> element
XML xml = new XML("plugin");
Details details = new Details(this, project, projectXML, xml);
executeBeforeToXML(details);
xml.put("groupId", groupId);
xml.put("artifactId", artifactId);
if(version != null && !version.isEmpty()) xml.put("version", version);