4
4
import org .w3c .dom .Element ;
5
5
import org .w3c .dom .Node ;
6
6
import org .w3c .dom .NodeList ;
7
+ import org .xml .sax .SAXException ;
7
8
8
9
import javax .xml .parsers .DocumentBuilder ;
9
10
import javax .xml .parsers .DocumentBuilderFactory ;
10
11
import javax .xml .parsers .ParserConfigurationException ;
11
12
import java .io .*;
12
13
import java .net .HttpURLConnection ;
13
14
import java .net .URL ;
15
+ import java .nio .charset .Charset ;
16
+ import java .nio .charset .StandardCharsets ;
14
17
import java .nio .file .Files ;
15
18
import java .nio .file .StandardCopyOption ;
19
+ import java .nio .file .StandardOpenOption ;
16
20
import java .util .*;
17
21
import java .util .concurrent .CopyOnWriteArrayList ;
18
22
import java .util .function .Consumer ;
19
23
20
24
public class JPM {
21
25
public static class ThisProject extends JPM .Project {
22
- public ThisProject () throws IOException , InterruptedException {
26
+ public ThisProject () throws Exception {
23
27
this (null );
24
28
}
25
- public ThisProject (List <String > args ) throws IOException , InterruptedException {
29
+ public ThisProject (List <String > args ) throws Exception {
26
30
// Override default configurations
27
31
this .groupId = "com.mycompany.myproject" ;
28
32
this .artifactId = "my-project" ;
29
33
this .version = "1.0.0" ;
30
- this .mainClass = "com.mycompany.myproject .MyMainClass" ;
31
- this .jarName = "my-project .jar" ;
32
- this .fatJarName = "my-project -with-dependencies.jar" ;
34
+ this .mainClass = groupId + " .MyMainClass" ;
35
+ this .jarName = artifactId + " .jar" ;
36
+ this .fatJarName = artifactId + " -with-dependencies.jar" ;
33
37
34
38
// If there are duplicate dependencies with different versions force a specific version like so:
35
39
//forceImplementation("org.apache.commons:commons-lang3:3.12.0");
@@ -62,20 +66,31 @@ public static class ThirdPartyPlugins extends JPM.Plugins{
62
66
// (If you want to develop a plugin take a look at "JPM.AssemblyPlugin" class further below to get started)
63
67
}
64
68
65
- // 1JPM version 3.0.4 by Osiris-Team: https://github.com/Osiris-Team/1JPM
69
+ // 1JPM version 3.1.0 by Osiris-Team: https://github.com/Osiris-Team/1JPM
66
70
// To upgrade JPM, replace everything below with its newer version
67
71
public static final List <Plugin > plugins = new ArrayList <>();
68
72
public static final String mavenVersion = "3.9.8" ;
69
73
public static final String mavenWrapperVersion = "3.3.2" ;
70
74
public static final String mavenWrapperScriptUrlBase = "https://raw.githubusercontent.com/apache/maven-wrapper/maven-wrapper-" + mavenWrapperVersion +"/maven-wrapper-distribution/src/resources/" ;
71
75
public static final String mavenWrapperJarUrl = "https://repo1.maven.org/maven2/org/apache/maven/wrapper/maven-wrapper/" + mavenWrapperVersion +"/maven-wrapper-" + mavenWrapperVersion +".jar" ;
72
76
public static final String mavenWrapperPropsContent = "distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/" + mavenVersion +"/apache-maven-" + mavenVersion +"-bin.zip" ;
77
+ public static final String jpmLatestUrl = "https://github.com/Osiris-Team/1JPM/raw/main/src/main/java/com/mycompany/myproject/JPM.java" ;
78
+
79
+ /**
80
+ * Running {@link #main(String[])} without arguments / empty arguments
81
+ * should always result in a pom.xml file being created anyway. <br>
82
+ * Passing over null instead of an arguments list should never create a pom.xml file.
83
+ */
84
+ public static Object expectation1 = new Object ();
73
85
74
86
static {
75
87
// Init this once to ensure their plugins are added if they use the static constructor
76
88
new ThirdPartyPlugins ();
77
89
}
78
90
91
+ /**
92
+ * Bound by {@link #expectation1}.
93
+ */
79
94
public static void main (String [] args ) throws Exception {
80
95
new ThisProject (new ArrayList <>(Arrays .asList (args )));
81
96
}
@@ -147,6 +162,122 @@ public static void createMavenWrapperProperties(File propertiesFile) throws IOEx
147
162
}
148
163
}
149
164
165
+ /**
166
+ * This is going to download and copy the latest JPM.java file into all child projects it can find in this directory,
167
+ * 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>
168
+ * <br>
169
+ * A child project is detected if a src/main/java folder structure exists, and the parent folder of src/ is then used
170
+ * as child project root. <br>
171
+ * <br>
172
+ * Note that a child project is expected to be directly inside a subdirectory of this project.<br>
173
+ * <br>
174
+ * Useful to quickly setup existing multi-module projects, since then {@link Project#isAutoParentsAndChildren} will work properly. <br>
175
+ * <br>
176
+ * Bound by {@link #expectation1}.
177
+ */
178
+ public static void portChildProjects () throws Exception {
179
+ List <File > childProjectDirs = new ArrayList <>();
180
+ File cwd = new File (System .getProperty ("user.dir" ));
181
+ File [] subDirs = cwd .listFiles (File ::isDirectory );
182
+ if (subDirs != null )
183
+ for (File subDir : subDirs ) {
184
+ fillSubProjectDirs (subDir , childProjectDirs );
185
+ }
186
+
187
+ if (childProjectDirs .isEmpty ()) System .out .println ("No child projects found in dir: " +cwd );
188
+ else {
189
+ for (File childProjectDir : childProjectDirs ) {
190
+ File jpmFile = new File (childProjectDir , "JPM.java" );
191
+ if (jpmFile .exists ()) {
192
+ System .out .println ("JPM.java file already exists for child project '" +childProjectDir .getName ()+"'." );
193
+ if (!new File (childProjectDir , "pom.xml" ).exists ()){
194
+ execJavaJpmJava (childProjectDir );
195
+ }
196
+ continue ;
197
+ }
198
+ System .out .println ("Downloading file from: " + jpmLatestUrl );
199
+ URL url = new URL (jpmLatestUrl );
200
+ jpmFile .getParentFile ().mkdirs ();
201
+ String jpmJavaContent = readUrlContentAsString (url );
202
+ jpmJavaContent = jpmJavaContent .replace (".myproject" , "." +childProjectDir .getName ())
203
+ .replace ("my-project" , childProjectDir .getName ());
204
+ Files .write (jpmFile .toPath (), jpmJavaContent .getBytes (StandardCharsets .UTF_8 ), StandardOpenOption .CREATE , StandardOpenOption .TRUNCATE_EXISTING );
205
+ System .out .println ("Created JPM.java file for child project '" +childProjectDir .getName ()+"'." );
206
+
207
+ execJavaJpmJava (childProjectDir );
208
+ }
209
+
210
+ for (File childProjectDir : childProjectDirs ) {
211
+ System .out .println (childProjectDir );
212
+ }
213
+ System .out .println ("Ported " +childProjectDirs .size ()+" child projects successfully!" );
214
+ }
215
+ }
216
+
217
+ private static void execJavaJpmJava (File childProjectDir ) throws IOException , InterruptedException {
218
+ ProcessBuilder p = new ProcessBuilder ();
219
+ p .command ("java" , "JPM.java" );
220
+ p .inheritIO ();
221
+ p .directory (childProjectDir );
222
+ System .out .println ("Executing in child project '" + childProjectDir .getName ()+"': java JPM.java" );
223
+ Process result = p .start ();
224
+ result .waitFor ();
225
+ if (result .exitValue () != 0 ){
226
+ RuntimeException ex = new RuntimeException ("Command finished with an error (" +result .exitValue ()+"), while executing: java JPM.java" );
227
+ if (new File (childProjectDir , "pom.xml" ).exists ()){
228
+ System .err .println ("IGNORED exception because pom.xml file was created. Make sure to look into this later:" );
229
+ ex .printStackTrace ();
230
+ return ;
231
+ }
232
+ throw ex ;
233
+ }
234
+ }
235
+
236
+ private static void fillSubProjectDirs (File dir , List <File > childProjectDirs ){
237
+ File javaDir = new File (dir +"/src/main/java" );
238
+ if (javaDir .exists ()){
239
+ childProjectDirs .add (dir );
240
+ File [] subDirs = dir .listFiles (File ::isDirectory );
241
+ if (subDirs != null )
242
+ for (File subDir : subDirs ) {
243
+ fillSubProjectDirs (subDir , childProjectDirs );
244
+ }
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Reads the content of a URL as binary data and converts it to a String.
250
+ *
251
+ * @param url The URL to read from.
252
+ * @param charset The charset to use for converting bytes to a String.
253
+ * @return The content of the URL as a String.
254
+ * @throws Exception If an I/O error occurs.
255
+ */
256
+ public static String readUrlContentAsString (URL url , Charset charset ) throws Exception {
257
+ try (InputStream inputStream = url .openStream ();
258
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream ()) {
259
+
260
+ byte [] buffer = new byte [1024 ];
261
+ int bytesRead ;
262
+ while ((bytesRead = inputStream .read (buffer )) != -1 ) {
263
+ byteArrayOutputStream .write (buffer , 0 , bytesRead );
264
+ }
265
+
266
+ return byteArrayOutputStream .toString (charset .name ());
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Overloaded method to use UTF-8 as the default charset.
272
+ *
273
+ * @param url The URL to read from.
274
+ * @return The content of the URL as a String.
275
+ * @throws Exception If an I/O error occurs.
276
+ */
277
+ public static String readUrlContentAsString (URL url ) throws Exception {
278
+ return readUrlContentAsString (url , StandardCharsets .UTF_8 );
279
+ }
280
+
150
281
//
151
282
// API and Models
152
283
//
@@ -359,6 +490,21 @@ public XML(String rootName) {
359
490
}
360
491
}
361
492
493
+ public XML (File file ){
494
+ try {
495
+ DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance ();
496
+ DocumentBuilder builder = factory .newDocumentBuilder ();
497
+ document = builder .parse (file );
498
+ root = document .getDocumentElement ();
499
+ } catch (ParserConfigurationException e ) {
500
+ e .printStackTrace ();
501
+ } catch (IOException e ) {
502
+ throw new RuntimeException (e );
503
+ } catch (SAXException e ) {
504
+ throw new RuntimeException (e );
505
+ }
506
+ }
507
+
362
508
// Method to append another XML object to this XML document's root.
363
509
public XML add (XML otherXML ) {
364
510
Node importedNode = document .importNode (otherXML .root , true );
@@ -421,25 +567,23 @@ public XML putAttributes(String key, Map<String, String> attributes) {
421
567
}
422
568
423
569
public XML remove (String key ) {
424
- String [] path = key .split (" " );
425
- Element element = getElementOrNull (path );
570
+ Element element = getElementOrNull (key );
426
571
if (element != null && element .getParentNode () != null ) {
427
572
element .getParentNode ().removeChild (element );
428
573
}
429
574
return this ;
430
575
}
431
576
432
577
public XML rename (String oldKey , String newName ) {
433
- String [] path = oldKey .split (" " );
434
- Element element = getElementOrNull (path );
578
+ Element element = getElementOrNull (oldKey );
435
579
if (element != null ) {
436
580
document .renameNode (element , null , newName );
437
581
}
438
582
return this ;
439
583
}
440
584
441
585
// Helper method to traverse or create elements based on a path.
442
- private Element getElementOrCreate (String key ) {
586
+ public Element getElementOrCreate (String key ) {
443
587
if (key == null || key .trim ().isEmpty ()) return root ;
444
588
String [] path = key .split (" " );
445
589
Element currentElement = root ;
@@ -465,7 +609,13 @@ private Element getElementOrCreate(String key) {
465
609
return currentElement ;
466
610
}
467
611
468
- private Element getElementOrNull (String [] path ) {
612
+ public Element getElementOrNull (String key ) {
613
+ if (key == null || key .trim ().isEmpty ()) return root ;
614
+ String [] path = key .split (" " );
615
+ return getElementOrNull (path );
616
+ }
617
+
618
+ protected Element getElementOrNull (String [] path ) {
469
619
Element currentElement = root ;
470
620
for (String nodeName : path ) {
471
621
NodeList children = currentElement .getChildNodes ();
@@ -505,6 +655,14 @@ public String toString() {
505
655
return null ;
506
656
}
507
657
658
+ public String toStringAt (File file ) throws IOException {
659
+ String s = toString ();
660
+ try (FileWriter writer = new FileWriter (file )) {
661
+ writer .write (s );
662
+ }
663
+ return s ;
664
+ }
665
+
508
666
public static void main (String [] args ) {
509
667
// Example usage of the XML class.
510
668
XML xml = new XML ("root" );
@@ -687,6 +845,14 @@ public static class Project {
687
845
public List <Plugin > plugins = JPM .plugins ;
688
846
public List <String > compilerArgs = new ArrayList <>();
689
847
public List <Project > profiles = new ArrayList <>();
848
+ /**
849
+ * If true updates current pom, all parent and all child pom.xml
850
+ * files with the respective parent details. <br>
851
+ * <br>
852
+ * This expects that the parent pom is always inside the parent directory,
853
+ * otherwise a performant search is not possible since the entire disk would need to be checked.
854
+ */
855
+ public boolean isAutoParentsAndChildren = true ;
690
856
691
857
public Project () {
692
858
repositories .add (Repository .fromUrl ("https://repo.maven.apache.org/maven2" ));
@@ -838,6 +1004,134 @@ public void generatePom() throws IOException {
838
1004
writer .write (pom .toString ());
839
1005
}
840
1006
System .out .println ("Generated pom.xml file." );
1007
+
1008
+ // If isAutoParentsAndChildren is true, handle parents and children automatically
1009
+ if (isAutoParentsAndChildren ) {
1010
+ updateParentsPoms (pom );
1011
+ updateChildrenPoms ();
1012
+ }
1013
+ }
1014
+
1015
+ protected void updateParentsPoms (XML currentPom ) throws IOException {
1016
+ updateParentsPoms (currentPom , new File (System .getProperty ("user.dir" )), null );
1017
+ }
1018
+
1019
+ protected void updateParentsPoms (XML currentPom , File currentDir , File forceStopAtDir ) throws IOException {
1020
+ if (currentDir == null ){
1021
+ System .out .println ("Force end probably at disk root, because currentDir is null." );
1022
+ return ;
1023
+ }
1024
+ File parentDir = currentDir .getParentFile ();
1025
+ File parentPom = null ;
1026
+
1027
+ while (parentDir != null ) {
1028
+ if (forceStopAtDir != null && forceStopAtDir .equals (currentDir )) {
1029
+ System .out .println ("Force end at: " + parentPom );
1030
+ return ;
1031
+ }
1032
+
1033
+ parentPom = new File (parentDir , "pom.xml" );
1034
+ if (parentPom .exists ()) {
1035
+ // Load and update parent pom.xml
1036
+ System .out .println ("Subproject '" +currentDir .getName ()+"', found parent pom.xml at: " + parentPom .getAbsolutePath ());
1037
+ XML parent = new XML (parentPom );
1038
+ Element parentGroup = parent .getElementOrNull ("groupId" );
1039
+ Element parentArtifactId = parent .getElementOrNull ("artifactId" );
1040
+ Element parentVersion = parent .getElementOrNull ("version" );
1041
+ String parentPomRelativePath = "../pom.xml" ;
1042
+ if (parentGroup == null ) {
1043
+ System .err .println ("Ensure that this parent pom.xml contains a groupId! Cannot proceed!" );
1044
+ return ;
1045
+ }
1046
+ if (parentArtifactId == null ) {
1047
+ System .err .println ("Ensure that this parent pom.xml contains a artifactId! Cannot proceed!" );
1048
+ return ;
1049
+ }
1050
+ if (parentVersion == null ) {
1051
+ System .err .println ("Ensure that this parent pom.xml contains a version! Cannot proceed!" );
1052
+ return ;
1053
+ }
1054
+ currentPom .put ("parent groupId" , parentGroup .getTextContent ());
1055
+ currentPom .put ("parent artifactId" , parentArtifactId .getTextContent ());
1056
+ currentPom .put ("parent version" , parentVersion .getTextContent ());
1057
+ currentPom .put ("parent relativePath" , parentPomRelativePath );
1058
+ currentPom .toStringAt (new File (currentDir , "pom.xml" ));
1059
+ System .out .println ("Updated pom.xml at: " + currentDir .getName ());
1060
+ currentPom = parent ;
1061
+ currentDir = parentPom ;
1062
+ } else {
1063
+ System .out .println ("No more parents found. End at: " + parentPom .getAbsolutePath ());
1064
+ break ;
1065
+ }
1066
+ parentDir = parentDir .getParentFile ();
1067
+ }
1068
+ }
1069
+
1070
+ protected void updateChildrenPoms () throws IOException {
1071
+ File currentDir = new File (System .getProperty ("user.dir" ));
1072
+ updateChildrenPoms (currentDir );
1073
+ }
1074
+
1075
+ /**
1076
+ * @param currentDir assume that this contains a pom.xml file that already was updated,
1077
+ * now we want to check its sub-dirs for child projects.
1078
+ */
1079
+ protected void updateChildrenPoms (File currentDir ) throws IOException {
1080
+ List <File > poms = new ArrayList <>();
1081
+ File [] subDirs = currentDir .listFiles (File ::isDirectory );
1082
+ if (subDirs != null )
1083
+ for (File subDir : subDirs ) {
1084
+ fillChildPoms (subDir , poms );
1085
+ }
1086
+
1087
+ // Inline sorting by file depth using separator counting
1088
+ // Sorting is done in descending order of depth (deepest first)
1089
+ poms .sort ((f1 , f2 ) -> {
1090
+ int depth1 = f1 .getAbsolutePath ().split (File .separator .equals ("\\ " ) ? "\\ \\ " : File .separator ).length ;
1091
+ int depth2 = f2 .getAbsolutePath ().split (File .separator .equals ("\\ " ) ? "\\ \\ " : File .separator ).length ;
1092
+ return Integer .compare (depth2 , depth1 );
1093
+ });
1094
+
1095
+
1096
+ HashSet <File > visitedFolders = new HashSet <>();
1097
+ for (File childPom : poms ) {
1098
+ if (visitedFolders .contains (childPom .getParentFile ()))
1099
+ continue ;
1100
+
1101
+ // We either force stop at currentDir or at the already visited folder up in the file tree
1102
+ File forceStopAtDir = childPom ;
1103
+ while (!visitedFolders .contains (forceStopAtDir )){
1104
+ forceStopAtDir = forceStopAtDir .getParentFile ();
1105
+ if (forceStopAtDir == null ){
1106
+ forceStopAtDir = currentDir ;
1107
+ break ;
1108
+ }
1109
+ }
1110
+
1111
+ // Update current child pom and all parent poms.
1112
+ XML pom = new XML (childPom );
1113
+ updateParentsPoms (pom , childPom .getParentFile (), forceStopAtDir );
1114
+
1115
+ // Update visited list
1116
+ File folder = childPom .getParentFile ();
1117
+ while (folder != null ){
1118
+ visitedFolders .add (folder );
1119
+ folder = folder .getParentFile ();
1120
+ }
1121
+
1122
+ }
1123
+ }
1124
+
1125
+ protected void fillChildPoms (File dir , List <File > poms ){
1126
+ File pom = new File (dir , "pom.xml" );
1127
+ if (pom .exists ()){
1128
+ poms .add (pom );
1129
+ File [] subDirs = dir .listFiles (File ::isDirectory );
1130
+ if (subDirs != null )
1131
+ for (File subDir : subDirs ) {
1132
+ fillChildPoms (subDir , poms );
1133
+ }
1134
+ }
841
1135
}
842
1136
}
843
1137
0 commit comments