Skip to content

Commit c873872

Browse files
committedAug 31, 2024
3.1.0 release
1 parent 0acfe1a commit c873872

File tree

3 files changed

+337
-13
lines changed

3 files changed

+337
-13
lines changed
 

‎README.md

+25-1
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,39 @@ public class JPM {
7272
}
7373
```
7474

75+
### Additional goodies
76+
77+
#### 1JPM automatically resolves parent and child projects
78+
See `project.isAutoParentsAndChildren`.
79+
If true updates current pom, all parent and all child pom.xml
80+
files with the respective parent details, adding seamless multi-module/project support.
81+
82+
This expects that the parent pom is always inside the parent directory,
83+
otherwise a performant search is not possible since the entire disk would need to be checked.
84+
85+
#### 1JPM helps porting your multi-module project
86+
Add JPM.java to your root project directory and add `JPM.portChildProjects();` before building.
87+
This is going to download and copy the latest JPM.java file into all child projects it can find
88+
in this directory, and also run it to generate an initial pom.xml for that child project.
89+
The child projects name will be the same as its directory name.
90+
91+
A child project is detected
92+
if a src/main/java folder structure exists, and the parent folder of src/ is then used as child project root.
93+
Note that a child project is expected to be directly inside a subdirectory of this project.
94+
95+
Now `project.isAutoParentsAndChildren` will work properly, since all needed pom.xml files should exist.
96+
97+
#### 1JPM is Maven based
7598
Note that 1JPM is now using **Maven under the hood**, since the complexity as a fully independent build tool
7699
(see version [1.0.3](https://github.com/Osiris-Team/1JPM/blob/1.0.3/src/main/java/JPM.java)) was too high for a single file. Besides, this gives us access to more features, a rich and mature plugin ecosystem, as well as **great IDE compatibility**. 1JPM will take care of generating the pom.xml, downloading the Maven-Wrapper, and then executing Maven as you can see above`.
77100

101+
#### 1JPM has plugins
78102
A 1JPM plugin is basically a wrapper around a Maven plugin (its xml), providing easy access to its features, but can also be anything else to make building easier.
79103
These third-party plugins can be added simply by appending their Java code inside the ThirdPartyPlugins class.
80104
You can find a list here at [#1jpm-plugin](https://github.com/topics/1jpm-plugin?o=desc&s=updated).
81105
(these must be written in Java 8 and not use external dependencies).
82106

83-
## 1JPM in Production
107+
#### 1JPM saves you time
84108
How many lines of relevant build code do we save compared to Maven?
85109
- 1JPM: 128 lines (see [here](https://github.com/Osiris-Team/AutoPlug-Client/blob/bd580033dea4f0cb7399496e9a01bf8047fb5d88/src/main/java/JPM.java))
86110
- Maven: 391 lines (see [here](https://github.com/Osiris-Team/AutoPlug-Client/blob/bd580033dea4f0cb7399496e9a01bf8047fb5d88/pom.xml))

‎pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ AUTO-GENERATED FILE, CHANGES SHOULD BE DONE IN ./JPM.java or ./src/main/java/com
1515
<properties>
1616
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1717
</properties>
18+
<repositories>
19+
<repository>
20+
<id>repomavenapacheorg</id>
21+
<url>https://repo.maven.apache.org/maven2</url>
22+
</repository>
23+
</repositories>
1824
<dependencies>
1925
<dependency>
2026
<groupId>org.apache.commons</groupId>

‎src/main/java/com/mycompany/myproject/JPM.java

+306-12
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,36 @@
44
import org.w3c.dom.Element;
55
import org.w3c.dom.Node;
66
import org.w3c.dom.NodeList;
7+
import org.xml.sax.SAXException;
78

89
import javax.xml.parsers.DocumentBuilder;
910
import javax.xml.parsers.DocumentBuilderFactory;
1011
import javax.xml.parsers.ParserConfigurationException;
1112
import java.io.*;
1213
import java.net.HttpURLConnection;
1314
import java.net.URL;
15+
import java.nio.charset.Charset;
16+
import java.nio.charset.StandardCharsets;
1417
import java.nio.file.Files;
1518
import java.nio.file.StandardCopyOption;
19+
import java.nio.file.StandardOpenOption;
1620
import java.util.*;
1721
import java.util.concurrent.CopyOnWriteArrayList;
1822
import java.util.function.Consumer;
1923

2024
public class JPM {
2125
public static class ThisProject extends JPM.Project {
22-
public ThisProject() throws IOException, InterruptedException {
26+
public ThisProject() throws Exception {
2327
this(null);
2428
}
25-
public ThisProject(List<String> args) throws IOException, InterruptedException {
29+
public ThisProject(List<String> args) throws Exception {
2630
// Override default configurations
2731
this.groupId = "com.mycompany.myproject";
2832
this.artifactId = "my-project";
2933
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";
3337

3438
// If there are duplicate dependencies with different versions force a specific version like so:
3539
//forceImplementation("org.apache.commons:commons-lang3:3.12.0");
@@ -62,20 +66,31 @@ public static class ThirdPartyPlugins extends JPM.Plugins{
6266
// (If you want to develop a plugin take a look at "JPM.AssemblyPlugin" class further below to get started)
6367
}
6468

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
6670
// To upgrade JPM, replace everything below with its newer version
6771
public static final List<Plugin> plugins = new ArrayList<>();
6872
public static final String mavenVersion = "3.9.8";
6973
public static final String mavenWrapperVersion = "3.3.2";
7074
public static final String mavenWrapperScriptUrlBase = "https://raw.githubusercontent.com/apache/maven-wrapper/maven-wrapper-"+ mavenWrapperVersion +"/maven-wrapper-distribution/src/resources/";
7175
public static final String mavenWrapperJarUrl = "https://repo1.maven.org/maven2/org/apache/maven/wrapper/maven-wrapper/"+ mavenWrapperVersion +"/maven-wrapper-"+ mavenWrapperVersion +".jar";
7276
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();
7385

7486
static{
7587
// Init this once to ensure their plugins are added if they use the static constructor
7688
new ThirdPartyPlugins();
7789
}
7890

91+
/**
92+
* Bound by {@link #expectation1}.
93+
*/
7994
public static void main(String[] args) throws Exception {
8095
new ThisProject(new ArrayList<>(Arrays.asList(args)));
8196
}
@@ -147,6 +162,122 @@ public static void createMavenWrapperProperties(File propertiesFile) throws IOEx
147162
}
148163
}
149164

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+
150281
//
151282
// API and Models
152283
//
@@ -359,6 +490,21 @@ public XML(String rootName) {
359490
}
360491
}
361492

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+
362508
// Method to append another XML object to this XML document's root.
363509
public XML add(XML otherXML) {
364510
Node importedNode = document.importNode(otherXML.root, true);
@@ -421,25 +567,23 @@ public XML putAttributes(String key, Map<String, String> attributes) {
421567
}
422568

423569
public XML remove(String key) {
424-
String[] path = key.split(" ");
425-
Element element = getElementOrNull(path);
570+
Element element = getElementOrNull(key);
426571
if (element != null && element.getParentNode() != null) {
427572
element.getParentNode().removeChild(element);
428573
}
429574
return this;
430575
}
431576

432577
public XML rename(String oldKey, String newName) {
433-
String[] path = oldKey.split(" ");
434-
Element element = getElementOrNull(path);
578+
Element element = getElementOrNull(oldKey);
435579
if (element != null) {
436580
document.renameNode(element, null, newName);
437581
}
438582
return this;
439583
}
440584

441585
// Helper method to traverse or create elements based on a path.
442-
private Element getElementOrCreate(String key) {
586+
public Element getElementOrCreate(String key) {
443587
if (key == null || key.trim().isEmpty()) return root;
444588
String[] path = key.split(" ");
445589
Element currentElement = root;
@@ -465,7 +609,13 @@ private Element getElementOrCreate(String key) {
465609
return currentElement;
466610
}
467611

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) {
469619
Element currentElement = root;
470620
for (String nodeName : path) {
471621
NodeList children = currentElement.getChildNodes();
@@ -505,6 +655,14 @@ public String toString() {
505655
return null;
506656
}
507657

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+
508666
public static void main(String[] args) {
509667
// Example usage of the XML class.
510668
XML xml = new XML("root");
@@ -687,6 +845,14 @@ public static class Project {
687845
public List<Plugin> plugins = JPM.plugins;
688846
public List<String> compilerArgs = new ArrayList<>();
689847
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;
690856

691857
public Project() {
692858
repositories.add(Repository.fromUrl("https://repo.maven.apache.org/maven2"));
@@ -838,6 +1004,134 @@ public void generatePom() throws IOException {
8381004
writer.write(pom.toString());
8391005
}
8401006
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+
}
8411135
}
8421136
}
8431137

0 commit comments

Comments
 (0)
Please sign in to comment.