Skip to content

Commit c2efa7d

Browse files
committed
Implemented sweeping changes/fixes
1 parent aaca412 commit c2efa7d

7 files changed

Lines changed: 187 additions & 70 deletions

File tree

src/main/java/module-info.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212

1313
opens org.mangorage.bootstrap;
1414

15-
uses org.mangorage.bootstrap.api.transformer.IClassTransformer;
16-
uses org.mangorage.bootstrap.api.module.IModuleConfigurator;
1715
uses org.mangorage.bootstrap.api.launch.ILaunchTarget;
18-
uses org.mangorage.bootstrap.api.dependency.IDependencyLocator;
19-
uses org.mangorage.bootstrap.api.launch.ILaunchTargetEntrypoint;
16+
uses org.mangorage.bootstrap.api.lifecycle.IBootstrapLifecycle;
2017
}
Lines changed: 162 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.mangorage.bootstrap;
22

3-
import com.google.gson.Gson;
43
import org.mangorage.bootstrap.api.launch.ILaunchTarget;
54
import org.mangorage.bootstrap.internal.util.Util;
65

@@ -13,71 +12,186 @@
1312
import java.util.Map;
1413
import java.util.ServiceLoader;
1514
import java.util.Set;
16-
15+
import java.util.logging.Logger;
16+
import java.util.logging.Level;
17+
18+
/**
19+
* Bootstrap orchestrator for modular applications.
20+
*
21+
* <p>This class manages the startup sequence by:
22+
* <ol>
23+
* <li>Parsing and validating command-line arguments</li>
24+
* <li>Constructing module layers with proper hierarchy</li>
25+
* <li>Discovering launch targets via ServiceLoader</li>
26+
* <li>Delegating execution to the selected target</li>
27+
* </ol>
28+
*
29+
* <p><strong>Usage:</strong> {@code java -m org.mangorage.bootstrap --launchTarget <targetId>}
30+
*
31+
* @since 1.0.84
32+
* @see ILaunchTarget
33+
*/
1734
public final class Bootstrap {
1835

19-
private static final Gson GSON = new Gson();
20-
36+
private static final Logger LOGGER = Logger.getLogger(Bootstrap.class.getName());
37+
private static final String LAUNCH_TARGET_ARG = "--launchTarget";
38+
private static final String DEFAULT_LAUNCH_PATH = "launch";
39+
40+
/**
41+
* Main entry point for the bootstrap framework.
42+
*
43+
* @param args Command line arguments. Must include --launchTarget followed by target ID.
44+
* @throws IllegalArgumentException if arguments are invalid
45+
* @throws IllegalStateException if launch target cannot be found or executed
46+
*/
2147
public static void main(String[] args) throws Throwable {
22-
if (!(args.length >= 2)) {
23-
throw new IllegalStateException("Need to define a launchTarget, --launchTarget mangobot");
48+
LOGGER.info("Starting MangoBotBootstrap framework");
49+
50+
validateArguments(args);
51+
52+
final String launchTarget = args[1];
53+
validateLaunchTarget(launchTarget);
54+
55+
LOGGER.info("Initializing module layers for launch target: " + launchTarget);
56+
57+
ModuleLayer parent = getParentModuleLayer();
58+
Path launchPath = Path.of(DEFAULT_LAUNCH_PATH);
59+
60+
final ModuleLayer moduleLayer = createLaunchModuleLayer(parent, launchPath);
61+
final Map<String, ILaunchTarget> launchTargetMap = discoverLaunchTargets(moduleLayer);
62+
63+
if (!launchTargetMap.containsKey(launchTarget)) {
64+
throw new IllegalStateException(
65+
String.format("Launch target '%s' not found. Available targets: %s",
66+
launchTarget, launchTargetMap.keySet()));
67+
}
68+
69+
LOGGER.info("Loading BootstrapLifecycle hooks");
70+
final var lifecycleHooks = ServiceLoader.load(moduleLayer, org.mangorage.bootstrap.api.lifecycle.IBootstrapLifecycle.class)
71+
.stream()
72+
.map(ServiceLoader.Provider::get)
73+
.toList();
74+
75+
LOGGER.info("Launching target: " + launchTarget);
76+
77+
try {
78+
launchTargetMap.get(launchTarget).launch(moduleLayer, parent, args);
79+
} catch (Throwable t) {
80+
LOGGER.log(Level.SEVERE, "Error during launch target execution: " + launchTarget, t);
81+
lifecycleHooks.forEach(hook -> hook.onError(t, moduleLayer));
82+
throw t;
83+
}
84+
85+
LOGGER.info("Bootstrap completed successfully");
86+
}
87+
88+
/**
89+
* Validates command-line arguments format and content.
90+
*/
91+
private static void validateArguments(String[] args) {
92+
if (args.length < 2) {
93+
throw new IllegalArgumentException(
94+
"Missing required arguments. Usage: --launchTarget <targetId>");
95+
}
96+
97+
if (!LAUNCH_TARGET_ARG.equals(args[0])) {
98+
throw new IllegalArgumentException(
99+
String.format("First argument must be '%s', got: %s", LAUNCH_TARGET_ARG, args[0]));
24100
}
101+
}
25102

26-
if (!args[0].equals("--launchTarget")) {
27-
throw new IllegalStateException("Need to have --launchTarget be defined first...");
103+
/**
104+
* Validates the launch target identifier format.
105+
*/
106+
private static void validateLaunchTarget(String target) {
107+
if (target == null || target.trim().isEmpty()) {
108+
throw new IllegalArgumentException("Launch target cannot be null or empty");
28109
}
29110

30-
final var launchTarget = args[1];
111+
if (!target.matches("[a-zA-Z0-9._-]+")) {
112+
throw new IllegalArgumentException(
113+
String.format("Invalid launch target format: '%s'. Must contain only letters, numbers, dots, underscores, and hyphens", target));
114+
}
115+
}
31116

117+
/**
118+
* Determines the appropriate parent module layer.
119+
*/
120+
private static ModuleLayer getParentModuleLayer() {
32121
ModuleLayer parent = null;
33122

34123
if (Bootstrap.class.getModule() != null) {
35124
parent = Bootstrap.class.getModule().getLayer();
36125
}
37126

38-
if (parent == null)
39-
parent = ModuleLayer.boot();
40-
41-
// Where additional launch targets can be defined...
42-
Path launchPath = Path.of(
43-
"launch"
44-
);
45-
46-
final var moduleCfg = Configuration
47-
.resolveAndBind(
48-
ModuleFinder.of(
49-
launchPath
50-
),
51-
List.of(
52-
parent.configuration()
53-
),
54-
ModuleFinder.of(),
55-
Files.exists(launchPath) ?
56-
Util.getModuleNames(
57-
launchPath
58-
) : Set.of()
59-
);
60-
61-
final var moduleLayerController = ModuleLayer.defineModulesWithOneLoader(
62-
moduleCfg,
63-
List.of(parent),
64-
Thread.currentThread().getContextClassLoader()
65-
);
66-
final var moduleLayer = moduleLayerController.layer();
127+
return parent != null ? parent : ModuleLayer.boot();
128+
}
67129

68-
final Map<String, ILaunchTarget> launchTargetMap = new HashMap<>();
130+
/**
131+
* Creates the launch module layer from the specified path.
132+
*/
133+
private static ModuleLayer createLaunchModuleLayer(ModuleLayer parent, Path launchPath) {
134+
try {
135+
final Configuration moduleCfg = Configuration.resolveAndBind(
136+
ModuleFinder.of(launchPath),
137+
List.of(parent.configuration()),
138+
ModuleFinder.of(),
139+
Files.exists(launchPath) ? Util.getModuleNames(launchPath) : Set.of()
140+
);
141+
142+
final ModuleLayer.Controller moduleLayerController = ModuleLayer.defineModulesWithOneLoader(
143+
moduleCfg,
144+
List.of(parent),
145+
Thread.currentThread().getContextClassLoader()
146+
);
147+
148+
LOGGER.fine("Successfully created module layer with " + moduleCfg.modules().size() + " modules");
149+
return moduleLayerController.layer();
150+
151+
} catch (Exception e) {
152+
LOGGER.log(Level.SEVERE, "Failed to create module layer from path: " + launchPath, e);
153+
throw new IllegalStateException("Module layer creation failed", e);
154+
}
155+
}
69156

70-
ServiceLoader.load(moduleLayer, ILaunchTarget.class)
71-
.stream()
72-
.forEach(provider -> {
73-
final var target = provider.get();
74-
launchTargetMap.put(target.getId(), target);
75-
});
157+
/**
158+
* Discovers all available launch targets in the module layer.
159+
*/
160+
private static Map<String, ILaunchTarget> discoverLaunchTargets(ModuleLayer moduleLayer) {
161+
final Map<String, ILaunchTarget> launchTargetMap = new HashMap<>();
76162

77-
if (!launchTargetMap.containsKey(launchTarget)) {
78-
throw new IllegalStateException("Cant find launch target '%s'".formatted(launchTarget));
163+
try {
164+
ServiceLoader.load(moduleLayer, ILaunchTarget.class)
165+
.stream()
166+
.forEach(provider -> {
167+
try {
168+
final ILaunchTarget target = provider.get();
169+
final String targetId = target.getId();
170+
171+
if (targetId == null || targetId.trim().isEmpty()) {
172+
LOGGER.warning("Ignoring launch target with null or empty ID from provider: " + provider.type());
173+
return;
174+
}
175+
176+
if (launchTargetMap.containsKey(targetId)) {
177+
LOGGER.warning("Duplicate launch target ID detected: " + targetId + ". Using first occurrence.");
178+
return;
179+
}
180+
181+
launchTargetMap.put(targetId, target);
182+
LOGGER.fine("Discovered launch target: " + targetId + " (" + provider.type() + ")");
183+
184+
} catch (Exception e) {
185+
LOGGER.log(Level.WARNING, "Failed to load launch target provider: " + provider.type(), e);
186+
}
187+
});
188+
189+
} catch (Exception e) {
190+
LOGGER.log(Level.SEVERE, "Failed to discover launch targets", e);
191+
throw new IllegalStateException("Launch target discovery failed", e);
79192
}
80193

81-
launchTargetMap.get(launchTarget).launch(moduleLayer, parent, args);
194+
LOGGER.info("Discovered " + launchTargetMap.size() + " launch targets: " + launchTargetMap.keySet());
195+
return launchTargetMap;
82196
}
83197
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.mangorage.bootstrap.api.lifecycle;
2+
3+
public interface IBootstrapLifecycle {
4+
void onError(Throwable throwable, ModuleLayer moduleLayer);
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package org.mangorage.bootstrap.api.loader;
22

33

4+
import org.mangorage.bootstrap.api.transformer.IClassTransformerHistory;
5+
46
public interface IMangoLoader {
7+
8+
IClassTransformerHistory getTransformerHistory();
9+
510
byte[] getClassBytes(String name);
611
boolean hasClass(String name);
712
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.mangorage.bootstrap.api.transformer;
2+
3+
import java.util.Map;
4+
5+
public interface IClassTransformerHistory {
6+
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.mangorage.bootstrap.api.transformer;
2+
3+
public interface ITransformerResultHistory {
4+
Class<?> transformer();
5+
TransformerFlag transformerFlag();
6+
byte[] classData();
7+
}

src/main/java/org/mangorage/bootstrap/internal/util/Util.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,4 @@ public static String getModuleName(File jarFile) {
4747

4848
return null; // Jar was either not modular or you're just unlucky
4949
}
50-
51-
public static void callMain(String className, String[] args, Module module) {
52-
try {
53-
Class<?> clazz = Class.forName(className, false, module.getClassLoader());
54-
Method mainMethod = clazz.getMethod("main", String[].class);
55-
56-
// Make sure it's static and public
57-
if (!java.lang.reflect.Modifier.isStatic(mainMethod.getModifiers())) {
58-
throw new IllegalStateException("Main method is not static, are you high?");
59-
}
60-
61-
// Invoke the main method with a godawful cast
62-
mainMethod.invoke(null, (Object) args);
63-
} catch (Throwable e) {
64-
e.printStackTrace();
65-
throw new RuntimeException("Couldn't reflectively call main because something exploded.", e);
66-
}
67-
}
6850
}

0 commit comments

Comments
 (0)