2626import java .io .InputStream ;
2727import java .io .OutputStream ;
2828import java .net .MalformedURLException ;
29+ import java .net .URI ;
30+ import java .net .URISyntaxException ;
2931import java .net .URL ;
3032import java .net .URLClassLoader ;
33+ import java .nio .file .FileSystem ;
34+ import java .nio .file .FileSystems ;
3135import java .nio .file .Files ;
3236import java .nio .file .Path ;
3337import java .nio .file .Paths ;
3438import java .security .AccessController ;
3539import java .security .PrivilegedAction ;
3640import java .util .Arrays ;
41+ import java .util .Collection ;
3742import java .util .Collections ;
3843import java .util .HashSet ;
44+ import java .util .List ;
3945import java .util .Set ;
4046import java .util .concurrent .Callable ;
47+ import java .util .stream .Collectors ;
48+ import java .util .stream .Stream ;
4149import org .apache .logging .log4j .converter .plugins .internal .PluginDescriptors .Namespace ;
4250import org .apache .logging .log4j .converter .plugins .internal .PluginDescriptors .PluginDescriptor ;
4351import org .apache .logging .log4j .converter .plugins .internal .ReflectConfigFilter ;
4452import org .jspecify .annotations .Nullable ;
4553import picocli .CommandLine ;
4654import picocli .CommandLine .Command ;
55+ import picocli .CommandLine .Option ;
4756import picocli .CommandLine .Parameters ;
4857
49- @ Command (name = "convert " )
58+ @ Command (name = "convertPlugin " )
5059public class PluginCacheConverter {
5160
61+ private static final String PLUGIN_DESCRIPTOR_FILE =
62+ "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat" ;
63+ private static final String PLUGIN_DESCRIPTOR_JSON_FILE = "Log4j2Plugins.json" ;
64+
5265 private static final JsonFactory jsonFactory = new ObjectMapper ().getFactory ();
5366
5467 public static void main (final String [] args ) {
@@ -57,106 +70,132 @@ public static void main(final String[] args) {
5770
5871 @ Command
5972 public void toJson (
60- @ Parameters (description = "Classpath containing Log4j Core plugins" ) final String classPath ,
61- @ Parameters ( description = "Output file " , defaultValue = "Log4j2Plugins.json" ) final File outputFile )
73+ @ Parameters (description = "Classpath containing Log4j Core plugins" ) final List < String > classPath ,
74+ @ Option ( names = { "-o " , "--outputDirectory" } ) final File outputDirectory )
6275 throws IOException {
63- new PluginDescriptorToJsonConverter (classPath , outputFile ).call ();
76+ new PluginDescriptorToJsonConverter (classPath , outputDirectory ).call ();
6477 }
6578
6679 @ Command
6780 public void fromJson (
68- @ Parameters (description = "Input JSON file" ) final File input ,
69- @ Parameters (description = "Output `Log4j2Plugins.dat` file" , defaultValue = "Log4j2Plugins.dat" )
70- final File output )
81+ @ Parameters (description = "Input `Log4j2Plugins.json` JSON file" ) final File input ,
82+ @ Option (names = {"-o" , "--outputDirectory" }) final File outputDirectory )
7183 throws IOException {
72- new JsonToPluginDescriptorConverter (input , output ).call ();
84+ new JsonToPluginDescriptorConverter (input , outputDirectory ).call ();
7385 }
7486
7587 @ Command
7688 public void filterReflectConfig (
7789 @ Parameters (description = "Plugin descriptor (as JSON)" ) final File pluginDescriptor ,
78- @ Parameters (description = "Reflection configuration" ) final File reflectConfigInput )
90+ @ Parameters (description = "Classpath containing GraalVM descriptors" ) final List <String > classPath ,
91+ @ Option (names = {"-o" , "--outputDirectory" }) final File outputDirectory )
7992 throws IOException {
80- new ReflectConfigTransformer (pluginDescriptor , reflectConfigInput ).call ();
93+ new ReflectConfigTransformer (pluginDescriptor , classPath , outputDirectory ).call ();
8194 }
8295
83- private static final class PluginDescriptorToJsonConverter implements Callable <@ Nullable Void > {
96+ private static Collection <Path > validateClassPath (final Collection <String > classPath ) {
97+ return classPath .stream ()
98+ .flatMap (classPathElement -> Arrays .stream (classPathElement .split (File .pathSeparator , -1 )))
99+ .map (classPathElement -> {
100+ final Path inputPath = Paths .get (classPathElement );
101+ if (!Files .isRegularFile (inputPath )) {
102+ throw new IllegalArgumentException ("Input file " + inputPath + " is not a file." );
103+ }
104+ if (!inputPath .getFileName ().toString ().endsWith (".jar" )) {
105+ throw new IllegalArgumentException (
106+ "Invalid input file, only JAR files are supported: " + inputPath );
107+ }
108+ return inputPath ;
109+ })
110+ .collect (Collectors .toList ());
111+ }
84112
85- private static final String PLUGIN_CACHE_FILE =
86- "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat" ;
113+ private static void createParentDirectories (final Path path ) throws IOException {
114+ final Path parent = path .getParent ();
115+ if (parent != null ) {
116+ Files .createDirectories (parent );
117+ }
118+ }
119+
120+ private static final class PluginDescriptorToJsonConverter implements Callable <@ Nullable Void > {
87121
88122 private final ClassLoader classLoader ;
89123
90- private final File outputFile ;
124+ private final @ Nullable Path outputDirectory ;
91125
92- public PluginDescriptorToJsonConverter (final String classPath , final File outputFile ) {
93- final URL [] urls = validateInput (classPath );
94- this .classLoader =
95- AccessController .doPrivileged ((PrivilegedAction <ClassLoader >) () -> new URLClassLoader (urls ));
96- this .outputFile = outputFile ;
126+ PluginDescriptorToJsonConverter (final Collection <String > classPath , final @ Nullable File outputDirectory ) {
127+ this .classLoader = AccessController .doPrivileged (
128+ (PrivilegedAction <ClassLoader >) () -> new URLClassLoader (validateClassPath (classPath ).stream ()
129+ .map (p -> {
130+ try {
131+ return p .toUri ().toURL ();
132+ } catch (final MalformedURLException e ) {
133+ throw new IllegalArgumentException (e );
134+ }
135+ })
136+ .toArray (URL []::new )));
137+ this .outputDirectory = outputDirectory != null ? outputDirectory .toPath () : null ;
97138 }
98139
99140 @ Override
100141 public @ Nullable Void call () throws IOException {
101- try (final OutputStream output = Files .newOutputStream (outputFile .toPath ());
102- final JsonGenerator generator = jsonFactory .createGenerator (output )) {
142+ final OutputStream output ;
143+ if (outputDirectory != null ) {
144+ Files .createDirectories (outputDirectory );
145+ output = Files .newOutputStream (outputDirectory .resolve (PLUGIN_DESCRIPTOR_JSON_FILE ));
146+ } else {
147+ output = System .out ;
148+ }
149+ try (final JsonGenerator generator = jsonFactory .createGenerator (output )) {
103150 final PluginDescriptor pluginDescriptor = new PluginDescriptor ();
104151 // Read all `Log4j2Plugins.dat` file on the classpath
105- for (final URL url : Collections .list (classLoader .getResources (PLUGIN_CACHE_FILE ))) {
152+ for (final URL url : Collections .list (classLoader .getResources (PLUGIN_DESCRIPTOR_FILE ))) {
106153 try (final BufferedInputStream input = new BufferedInputStream (url .openStream ())) {
107154 pluginDescriptor .readPluginDescriptor (input );
108155 }
109156 }
110157 // Write JSON file
111158 pluginDescriptor .withBuilderHierarchy (classLoader ).toJson (generator );
159+ } finally {
160+ if (output != System .out ) {
161+ output .close ();
162+ }
112163 }
113164 return null ;
114165 }
115-
116- private static URL [] validateInput (final String classPath ) {
117- final String [] classPathElements = classPath .split (File .pathSeparator , -1 );
118- return Arrays .stream (classPathElements )
119- .map (classPathElement -> {
120- final Path inputPath = Paths .get (classPathElement );
121- if (!Files .isRegularFile (inputPath )) {
122- throw new IllegalArgumentException ("Input file " + inputPath + " is not a file." );
123- }
124- if (!inputPath .getFileName ().toString ().endsWith (".jar" )) {
125- throw new IllegalArgumentException (
126- "Invalid input file, only JAR files are supported: " + inputPath );
127- }
128- try {
129- return inputPath .toUri ().toURL ();
130- } catch (final MalformedURLException e ) {
131-
132- throw new IllegalArgumentException (
133- "Innput file " + inputPath + " can not be accessed as URL." , e );
134- }
135- })
136- .toArray (URL []::new );
137- }
138166 }
139167
140168 private static class JsonToPluginDescriptorConverter implements Callable <@ Nullable Void > {
141169
142170 private final Path input ;
143- private final Path output ;
171+ private final @ Nullable Path outputDirectory ;
144172
145- public JsonToPluginDescriptorConverter (final File input , final File output ) {
173+ JsonToPluginDescriptorConverter (final File input , final @ Nullable File outputDirectory ) {
146174 this .input = input .toPath ();
147- this .output = output .toPath ();
175+ this .outputDirectory = outputDirectory != null ? outputDirectory .toPath () : null ;
148176 }
149177
150178 @ Override
151179 public @ Nullable Void call () throws IOException {
180+ final OutputStream output ;
181+ if (outputDirectory != null ) {
182+ final Path pluginDescriptorPath = outputDirectory .resolve (PLUGIN_DESCRIPTOR_FILE );
183+ createParentDirectories (pluginDescriptorPath );
184+ output = Files .newOutputStream (pluginDescriptorPath );
185+ } else {
186+ output = System .out ;
187+ }
152188 try (final InputStream inputStream = Files .newInputStream (input );
153- final JsonParser parser = jsonFactory .createParser (inputStream );
154- final OutputStream outputStream = Files .newOutputStream (output )) {
189+ final JsonParser parser = jsonFactory .createParser (inputStream )) {
155190 final PluginDescriptor pluginDescriptor = new PluginDescriptor ();
156191 // Input JSON
157192 pluginDescriptor .readJson (parser );
158193 // Output `Log4j2Plugins.dat` file
159- pluginDescriptor .toPluginDescriptor (outputStream );
194+ pluginDescriptor .toPluginDescriptor (output );
195+ } finally {
196+ if (output != System .out ) {
197+ output .close ();
198+ }
160199 }
161200 return null ;
162201 }
@@ -165,13 +204,56 @@ public JsonToPluginDescriptorConverter(final File input, final File output) {
165204 private static class ReflectConfigTransformer implements Callable <@ Nullable Void > {
166205
167206 private final Path pluginDescriptorPath ;
168- private final Path input ;
169- private final Path output ;
207+ private final Collection < Path > classPath ;
208+ private final @ Nullable Path outputDirectory ;
170209
171- public ReflectConfigTransformer (final File pluginDescriptorPath , final File input ) {
210+ ReflectConfigTransformer (
211+ final File pluginDescriptorPath ,
212+ final Collection <String > classPath ,
213+ final @ Nullable File outputDirectory ) {
172214 this .pluginDescriptorPath = pluginDescriptorPath .toPath ();
173- this .input = input .toPath ();
174- this .output = Paths .get ("reflect-config.json" );
215+ this .classPath = validateClassPath (classPath );
216+ this .outputDirectory = outputDirectory != null ? outputDirectory .toPath () : null ;
217+ }
218+
219+ void filterReflectConfigInJar (final Path jar , final ReflectConfigFilter filter ) throws IOException {
220+ final URI jarFileSystemRoot ;
221+ try {
222+ jarFileSystemRoot = new URI ("jar" , jar .toUri ().toASCIIString (), null );
223+ } catch (final URISyntaxException e ) {
224+ throw new IllegalArgumentException (e );
225+ }
226+ try (final FileSystem fileSystem = FileSystems .newFileSystem (jarFileSystemRoot , Collections .emptyMap ())) {
227+ final Path rootPath = fileSystem .getPath ("/" );
228+ final Path nativeImagePath = rootPath .resolve ("META-INF/native-image" );
229+ try (final Stream <Path > paths = Files .walk (nativeImagePath , 3 )) {
230+ paths .filter (p ->
231+ "reflect-config.json" .equals (p .getFileName ().toString ()))
232+ .forEach (p -> filterReflectConfig (rootPath , p , filter ));
233+ }
234+ }
235+ }
236+
237+ void filterReflectConfig (final Path rootPath , final Path reflectConfig , final ReflectConfigFilter filter ) {
238+ try {
239+ final OutputStream output ;
240+ if (outputDirectory != null ) {
241+ final String relativePath =
242+ rootPath .relativize (reflectConfig ).toString ();
243+ final Path outputPath = outputDirectory .resolve (relativePath );
244+ createParentDirectories (outputPath );
245+ output = Files .newOutputStream (outputPath );
246+ } else {
247+ output = System .out ;
248+ }
249+ try (final InputStream inputStream = Files .newInputStream (reflectConfig );
250+ final JsonParser parser = jsonFactory .createParser (inputStream );
251+ final JsonGenerator generator = jsonFactory .createGenerator (output )) {
252+ filter .filter (parser , generator );
253+ }
254+ } catch (final IOException e ) {
255+ throw new RuntimeException (e );
256+ }
175257 }
176258
177259 @ Override
@@ -189,11 +271,8 @@ public ReflectConfigTransformer(final File pluginDescriptorPath, final File inpu
189271 classNames .addAll (p .getBuilderHierarchy ());
190272 });
191273 final ReflectConfigFilter filter = new ReflectConfigFilter (classNames );
192- try (final InputStream inputStream = Files .newInputStream (input );
193- final JsonParser parser = jsonFactory .createParser (inputStream );
194- final OutputStream outputStream = Files .newOutputStream (output );
195- final JsonGenerator generator = jsonFactory .createGenerator (outputStream )) {
196- filter .filter (parser , generator );
274+ for (final Path jar : classPath ) {
275+ filterReflectConfigInJar (jar , filter );
197276 }
198277 return null ;
199278 }
0 commit comments