From 08dca2d5b7c2c02c19d8d613ef9fd7660f8c4f0f Mon Sep 17 00:00:00 2001 From: Plamen Totev Date: Sun, 8 Apr 2018 12:06:25 +0300 Subject: [PATCH] Add Archiver that creates modular JAR files using the JDK jar tool. It would allow clients of Plexus Archiver to specify the version and the main class of a Java module. Also the jar tool have the nice side effect of verifying the resulting modular JAR file. Closes #84, Closes #67, Closes #68 --- .../jar/JarToolModularJarArchiver.java | 256 +++++++++++++++ .../archiver/jar/ModularJarArchiver.java | 76 +++++ .../resources/META-INF/plexus/components.xml | 6 + .../archiver/jar/BaseJarArchiverTest.java | 71 ++++ .../plexus/archiver/jar/JarArchiverTest.java | 18 +- .../jar/JarToolModularJarArchiverTest.java | 308 ++++++++++++++++++ .../java-classes/com/example/app/Main.class | Bin 0 -> 551 bytes .../com/example/resources/test.properties | 1 + .../java-module-descriptor/module-info.class | Bin 0 -> 189 bytes src/test/resources/java-src/REAMDE.md | 2 + .../java-src/com/example/app/Main.java | 10 + src/test/resources/java-src/module-info.java | 4 + 12 files changed, 747 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/codehaus/plexus/archiver/jar/JarToolModularJarArchiver.java create mode 100644 src/main/java/org/codehaus/plexus/archiver/jar/ModularJarArchiver.java create mode 100644 src/test/java/org/codehaus/plexus/archiver/jar/BaseJarArchiverTest.java create mode 100644 src/test/java/org/codehaus/plexus/archiver/jar/JarToolModularJarArchiverTest.java create mode 100644 src/test/resources/java-classes/com/example/app/Main.class create mode 100644 src/test/resources/java-classes/com/example/resources/test.properties create mode 100644 src/test/resources/java-module-descriptor/module-info.class create mode 100644 src/test/resources/java-src/REAMDE.md create mode 100644 src/test/resources/java-src/com/example/app/Main.java create mode 100644 src/test/resources/java-src/module-info.java diff --git a/src/main/java/org/codehaus/plexus/archiver/jar/JarToolModularJarArchiver.java b/src/main/java/org/codehaus/plexus/archiver/jar/JarToolModularJarArchiver.java new file mode 100644 index 000000000..e8cb67834 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/archiver/jar/JarToolModularJarArchiver.java @@ -0,0 +1,256 @@ +/** + * + * Copyright 2018 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.codehaus.plexus.archiver.jar; + +import org.apache.commons.compress.parallel.InputStreamSupplier; +import org.codehaus.plexus.archiver.ArchiverException; +import org.codehaus.plexus.archiver.util.ArchiveEntryUtils; +import org.codehaus.plexus.archiver.util.ResourceUtils; +import org.codehaus.plexus.archiver.zip.ConcurrentJarCreator; +import org.codehaus.plexus.components.io.resources.PlexusIoResource; +import org.codehaus.plexus.util.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * A {@link ModularJarArchiver} implementation that uses + * the {@code jar} tool provided by + * {@code java.util.spi.ToolProvider} to create + * modular JAR files. + * + *

+ * The basic JAR archive is created by {@link JarArchiver} + * and the {@code jar} tool is used to upgrade it to modular JAR. + * + *

+ * If the JAR file does not contain module descriptor + * or the JDK does not provide the {@code jar} tool + * (for example JDK prior to Java 9), then the + * archive created by {@link JarArchiver} + * is left unchanged. + */ +public class JarToolModularJarArchiver + extends ModularJarArchiver +{ + private static final String MODULE_DESCRIPTOR_FILE_NAME + = "module-info.class"; + + private static final Pattern MRJAR_VERSION_AREA + = Pattern.compile( "META-INF/versions/\\d+/" ); + + private Object jarTool; + + private boolean moduleDescriptorFound; + + private Path tempDir; + + public JarToolModularJarArchiver() + { + try + { + Class toolProviderClass = + Class.forName( "java.util.spi.ToolProvider" ); + Object jarToolOptional = toolProviderClass + .getMethod( "findFirst", String.class ) + .invoke( null, "jar" ); + + jarTool = jarToolOptional.getClass().getMethod( "get" ) + .invoke( jarToolOptional ); + } + catch ( ReflectiveOperationException | SecurityException e ) + { + // Ignore. It is expected that the jar tool + // may not be available. + } + } + + @Override + protected void zipFile( InputStreamSupplier is, ConcurrentJarCreator zOut, + String vPath, long lastModified, File fromArchive, + int mode, String symlinkDestination, + boolean addInParallel ) + throws IOException, ArchiverException + { + // We store the module descriptors in temporary location + // and then add it to the JAR file using the JDK jar tool. + // It may look strange at first, but to update a JAR file + // you need to add new files[1] and the only files + // we're sure that exists in modular JAR file + // are the module descriptors. + // + // [1] There are some exceptions but we need at least one file to + // ensure it will work in all cases. + if ( jarTool != null && isModuleDescriptor( vPath ) ) + { + getLogger().debug( "Module descriptor found: " + vPath ); + + moduleDescriptorFound = true; + + // Copy the module descriptor to temporary directory + // so later then can be added to the JAR archive + // by the jar tool. + + if ( tempDir == null ) + { + tempDir = Files + .createTempDirectory( "plexus-archiver-modular_jar-" ); + tempDir.toFile().deleteOnExit(); + } + + File destFile = tempDir.resolve( vPath ).toFile(); + destFile.getParentFile().mkdirs(); + destFile.deleteOnExit(); + + ResourceUtils.copyFile( is.get(), destFile ); + ArchiveEntryUtils.chmod( destFile, mode ); + destFile.setLastModified( lastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE + ? System.currentTimeMillis() + : lastModified ); + } + else + { + super.zipFile( is, zOut, vPath, lastModified, + fromArchive, mode, symlinkDestination, addInParallel ); + } + } + + @Override + protected void postCreateArchive() + throws ArchiverException + { + if ( !moduleDescriptorFound ) + { + // no need to update the JAR archive + return; + } + + try + { + getLogger().debug( "Using the jar tool to " + + "update the archive to modular JAR." ); + + Integer result = (Integer) jarTool.getClass() + .getMethod( "run", + PrintStream.class, PrintStream.class, String[].class ) + .invoke( jarTool, + System.out, System.err, + getJarToolArguments() ); + + if ( result != null && result != 0 ) + { + throw new ArchiverException( "Could not create modular JAR file. " + + "The JDK jar tool exited with " + result ); + } + } + catch ( ReflectiveOperationException | SecurityException e ) + { + throw new ArchiverException( "Exception occurred " + + "while creating modular JAR file", e ); + } + finally + { + clearTempDirectory(); + } + } + + /** + * Returns {@code true} if {@code path} + * is a module descriptor. + */ + private boolean isModuleDescriptor( String path ) + { + if ( path.endsWith( MODULE_DESCRIPTOR_FILE_NAME ) ) + { + String prefix = path.substring( 0, + path.lastIndexOf( MODULE_DESCRIPTOR_FILE_NAME ) ); + + // the path is a module descriptor if it located + // into the root of the archive or into the + // version are of a multi-release JAR file + return prefix.isEmpty() || + MRJAR_VERSION_AREA.matcher( prefix ).matches(); + } + else + { + return false; + } + } + + /** + * Prepares the arguments for the jar tool. + * It takes into account the module version, + * main class, etc. + */ + private String[] getJarToolArguments() + { + List args = new ArrayList<>(); + + args.add( "--update" ); + args.add( "--file" ); + args.add( getDestFile().getAbsolutePath() ); + + if ( getModuleMainClass() != null ) + { + args.add( "--main-class" ); + args.add( getModuleMainClass() ); + } + + if ( getModuleVersion() != null ) + { + args.add( "--module-version" ); + args.add( getModuleVersion() ); + } + + if ( !isCompress() ) + { + args.add( "--no-compress" ); + } + + args.add( "-C" ); + args.add( tempDir.toFile().getAbsolutePath() ); + args.add( "." ); + + return args.toArray( new String[]{} ); + } + + /** + * Makes best effort the clean up + * the temporary directory used. + */ + private void clearTempDirectory() + { + try + { + if ( tempDir != null ) + { + FileUtils.deleteDirectory( tempDir.toFile() ); + } + } + catch ( IOException e ) + { + // Ignore. It is just best effort. + } + } + +} diff --git a/src/main/java/org/codehaus/plexus/archiver/jar/ModularJarArchiver.java b/src/main/java/org/codehaus/plexus/archiver/jar/ModularJarArchiver.java new file mode 100644 index 000000000..bf636e110 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/archiver/jar/ModularJarArchiver.java @@ -0,0 +1,76 @@ +/** + * + * Copyright 2018 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.codehaus.plexus.archiver.jar; + +/** + * Base class for creating modular JAR archives. + * + * Subclasses are required to be able to handle both + * JAR archives with module descriptor (modular JAR) + * and without ("regular" JAR). + * That would allow clients of this class to use + * it without prior knowledge if the classes + * they are going to add are part of module + * (contain module descriptor class) or not. + * + * @since 3.6 + */ +public abstract class ModularJarArchiver + extends JarArchiver +{ + private String moduleMainClass; + + private String moduleVersion; + + public String getModuleMainClass() + { + return moduleMainClass; + } + + /** + * Sets the module main class. + * Ignored if the JAR file does not contain + * module descriptor. + * + *

Note that implementations may choose + * to replace the value set in the manifest as well. + * + * @param moduleMainClass the module main class. + */ + public void setModuleMainClass( String moduleMainClass ) + { + this.moduleMainClass = moduleMainClass; + } + + public String getModuleVersion() + { + return moduleVersion; + } + + /** + * Sets the module version. + * Ignored if the JAR file does not contain + * module descriptor. + * + * @param moduleVersion the module version. + */ + public void setModuleVersion( String moduleVersion ) + { + this.moduleVersion = moduleVersion; + } + +} diff --git a/src/main/resources/META-INF/plexus/components.xml b/src/main/resources/META-INF/plexus/components.xml index d19b7a89f..71d47ac1f 100644 --- a/src/main/resources/META-INF/plexus/components.xml +++ b/src/main/resources/META-INF/plexus/components.xml @@ -41,6 +41,12 @@ org.codehaus.plexus.archiver.jar.JarArchiver per-lookup + + org.codehaus.plexus.archiver.Archiver + mjar + org.codehaus.plexus.archiver.jar.JarToolModularJarArchiver + per-lookup + org.codehaus.plexus.archiver.Archiver diff --git a/src/test/java/org/codehaus/plexus/archiver/jar/BaseJarArchiverTest.java b/src/test/java/org/codehaus/plexus/archiver/jar/BaseJarArchiverTest.java new file mode 100644 index 000000000..383ea7f75 --- /dev/null +++ b/src/test/java/org/codehaus/plexus/archiver/jar/BaseJarArchiverTest.java @@ -0,0 +1,71 @@ +/** + * + * Copyright 2018 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.codehaus.plexus.archiver.jar; + +import org.codehaus.plexus.archiver.ArchiverException; +import org.codehaus.plexus.util.IOUtil; +import org.junit.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public abstract class BaseJarArchiverTest +{ + + /* + * Verify that the JarArchiver implementation + * could create basic JAR file + */ + @Test + public void testCreateJar() + throws IOException, ArchiverException + { + File jarFile = new File( "target/output/testJar.jar" ); + jarFile.delete(); + + JarArchiver archiver = getJarArchiver(); + archiver.setDestFile( jarFile ); + archiver.addDirectory( new File( "src/test/resources/java-classes" ) ); + + archiver.createArchive(); + + // verify that the JAR file is created and contains the expected files + try ( ZipFile resultingArchive = new ZipFile( jarFile ) ) + { + // verify that the JAR file contains manifest file + assertNotNull( resultingArchive.getEntry( "META-INF/MANIFEST.MF" ) ); + + // verify the JAR contains the class and it is not corrupted + ZipEntry classFileEntry = resultingArchive.getEntry( "com/example/app/Main.class" ); + InputStream resultingClassFile = resultingArchive.getInputStream( classFileEntry ); + InputStream originalClassFile = + new FileInputStream( "src/test/resources/java-classes/com/example/app/Main.class" ); + + assertTrue( IOUtil.contentEquals( originalClassFile, resultingClassFile ) ); + } + } + + protected abstract JarArchiver getJarArchiver(); + +} diff --git a/src/test/java/org/codehaus/plexus/archiver/jar/JarArchiverTest.java b/src/test/java/org/codehaus/plexus/archiver/jar/JarArchiverTest.java index 8529977a0..ee118cf4e 100644 --- a/src/test/java/org/codehaus/plexus/archiver/jar/JarArchiverTest.java +++ b/src/test/java/org/codehaus/plexus/archiver/jar/JarArchiverTest.java @@ -5,19 +5,20 @@ import java.io.IOException; import java.util.Random; import org.codehaus.plexus.archiver.ArchiverException; -import junit.framework.TestCase; +import org.junit.Test; public class JarArchiverTest - extends TestCase + extends BaseJarArchiverTest { + @Test public void testCreateManifestOnlyJar() throws IOException, ManifestException, ArchiverException { File jarFile = File.createTempFile( "JarArchiverTest.", ".jar" ); jarFile.deleteOnExit(); - JarArchiver archiver = new JarArchiver(); + JarArchiver archiver = getJarArchiver(); archiver.setDestFile( jarFile ); Manifest manifest = new Manifest(); @@ -30,18 +31,20 @@ public void testCreateManifestOnlyJar() archiver.createArchive(); } + @Test public void testNonCompressed() throws IOException, ManifestException, ArchiverException { File jarFile = new File( "target/output/jarArchiveNonCompressed.jar" ); - JarArchiver archiver = new JarArchiver(); + JarArchiver archiver = getJarArchiver(); archiver.setDestFile( jarFile ); archiver.setCompress( false ); archiver.addDirectory( new File( "src/test/resources/mjar179" ) ); archiver.createArchive(); } + @Test public void testVeryLargeJar() throws IOException, ManifestException, ArchiverException { @@ -65,10 +68,15 @@ public void testVeryLargeJar() File jarFile = new File( "target/output/veryLargeJar.jar" ); - JarArchiver archiver = new JarArchiver(); + JarArchiver archiver = getJarArchiver(); archiver.setDestFile( jarFile ); archiver.addDirectory( tmpDir ); archiver.createArchive(); } + @Override + protected JarArchiver getJarArchiver() + { + return new JarArchiver(); + } } diff --git a/src/test/java/org/codehaus/plexus/archiver/jar/JarToolModularJarArchiverTest.java b/src/test/java/org/codehaus/plexus/archiver/jar/JarToolModularJarArchiverTest.java new file mode 100644 index 000000000..609692f4c --- /dev/null +++ b/src/test/java/org/codehaus/plexus/archiver/jar/JarToolModularJarArchiverTest.java @@ -0,0 +1,308 @@ +/** + * + * Copyright 2018 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.codehaus.plexus.archiver.jar; + +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.codehaus.plexus.archiver.ArchiverException; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; + +public class JarToolModularJarArchiverTest + extends BaseJarArchiverTest +{ + + private ModularJarArchiver archiver; + + /* + * Configures the ModularJarArchiver for the test cases. + */ + @Before + public void ModularJarArchiver() + throws Exception + { + File jarFile = new File( "target/output/modular.jar" ); + jarFile.delete(); + + archiver = getJarArchiver(); + archiver.setDestFile( jarFile ); + archiver.addDirectory( new File( "src/test/resources/java-classes" ) ); + } + + /* + * Verify that the main class and the version are properly set for a modular JAR file. + */ + @Test + public void testModularJarWithMainClassAndVersion() + throws Exception + { + assumeTrue( modulesAreSupported() ); + + archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) ); + archiver.setModuleVersion( "1.0.0" ); + archiver.setModuleMainClass( "com.example.app.Main" ); + + archiver.createArchive(); + + // verify that the proper version and main class are set + assertModularJarFile( archiver.getDestFile(), + "1.0.0", "com.example.app.Main", "com.example.app", "com.example.resources" ); + } + + /* + * Verify that a modular JAR file is created even when no additional attributes are set. + */ + @Test + public void testModularJar() + throws Exception + { + assumeTrue( modulesAreSupported() ); + + archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) ); + archiver.createArchive(); + + // verify that the proper version and main class are set + assertModularJarFile( archiver.getDestFile(), + null, null, "com.example.app", "com.example.resources" ); + } + + /* + * Verify that exception is thrown when the modular JAR is not valid. + */ + @Test( expected = ArchiverException.class ) + public void testInvalidModularJar() + throws Exception + { + assumeTrue( modulesAreSupported() ); + + archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) ); + // Not a valid version + archiver.setModuleVersion( "notAValidVersion" ); + + archiver.createArchive(); + } + + /* + * Verify that modular JAR files could be created even + * if the Java version does not support modules. + */ + @Test + public void testModularJarPriorJava9() + throws Exception + { + assumeFalse( modulesAreSupported() ); + + archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) ); + archiver.setModuleVersion( "1.0.0" ); + archiver.setModuleMainClass( "com.example.app.Main" ); + + archiver.createArchive(); + + // verify that the modular jar is created + try ( ZipFile resultingArchive = new ZipFile( archiver.getDestFile() ) ) + { + assertNotNull( resultingArchive.getEntry( "module-info.class" ) ); + } + } + + /* + * Verify that the compression flag is respected. + */ + @Test + public void testNoCompression() + throws Exception + { + assumeTrue( modulesAreSupported() ); + + archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) ); + archiver.setCompress( false ); + + archiver.createArchive(); + + // verify that the entries are not compressed + try ( ZipFile resultingArchive = new ZipFile( archiver.getDestFile() ) ) + { + Enumeration entries = resultingArchive.entries(); + + while ( entries.hasMoreElements() ) + { + ZipEntry entry = entries.nextElement(); + + assertEquals( ZipEntry.STORED, entry.getMethod() ); + } + } + } + + /* + * Verify that the compression set in the "plain" JAR file + * is kept after it is updated to modular JAR file. + */ + @Test + public void testCompression() + throws Exception + { + assumeTrue( modulesAreSupported() ); + + archiver.addDirectory( new File( "src/test/resources/java-module-descriptor" ) ); + archiver.addFile( new File( "src/test/jars/test.jar" ), "META-INF/lib/test.jar" ); + archiver.setRecompressAddedZips( false ); + + archiver.createArchive(); + + // verify that the compression is kept + try ( ZipFile resultingArchive = new ZipFile( archiver.getDestFile() ) ) + { + Enumeration entries = resultingArchive.entries(); + + while ( entries.hasMoreElements() ) + { + ZipEntry entry = entries.nextElement(); + + int expectedMethod = entry.isDirectory() || entry.getName().endsWith( ".jar" ) + ? ZipEntry.STORED + : ZipEntry.DEFLATED; + assertEquals( expectedMethod, entry.getMethod() ); + } + } + } + + /* + * Verify that a module descriptor in the versioned area is handled correctly. + */ + @Test + public void testModularMultiReleaseJar() + throws Exception + { + assumeTrue( modulesAreSupported() ); + + archiver.addFile( new File( "src/test/resources/java-module-descriptor/module-info.class" ), + "META-INF/versions/9/module-info.class" ); + Manifest manifest = new Manifest(); + manifest.addConfiguredAttribute( new Manifest.Attribute( "Multi-Release", "true" ) ); + archiver.addConfiguredManifest( manifest ); + archiver.setModuleVersion( "1.0.0" ); + archiver.setModuleMainClass( "com.example.app.Main" ); + + archiver.createArchive(); + + // verify that the resulting modular jar has the proper version and main class set + try ( ZipFile resultingArchive = new ZipFile( archiver.getDestFile() ) ) + { + ZipEntry moduleDescriptorEntry = + resultingArchive.getEntry( "META-INF/versions/9/module-info.class" ); + InputStream resultingModuleDescriptor = resultingArchive.getInputStream( moduleDescriptorEntry ); + + assertModuleDescriptor( resultingModuleDescriptor, + "1.0.0", "com.example.app.Main", "com.example.app", "com.example.resources" ); + } + } + + @Override + protected JarToolModularJarArchiver getJarArchiver() + { + return new JarToolModularJarArchiver(); + } + + private void assertModularJarFile( File jarFile , + String expectedVersion, String expectedMainClass, + String... expectedPackages ) + throws Exception + { + try ( ZipFile resultingArchive = new ZipFile( jarFile ) ) + { + ZipEntry moduleDescriptorEntry = resultingArchive.getEntry( "module-info.class" ); + InputStream resultingModuleDescriptor = resultingArchive.getInputStream( moduleDescriptorEntry ); + + assertModuleDescriptor( resultingModuleDescriptor, + expectedVersion, expectedMainClass, expectedPackages ); + } + } + + private void assertModuleDescriptor( InputStream moduleDescriptorInputStream, + String expectedVersion, String expectedMainClass, + String... expectedPackages ) + throws Exception + { + // ModuleDescriptor methods are available from Java 9 so let's get by reflection + Class moduleDescriptorClass = Class.forName( "java.lang.module.ModuleDescriptor" ); + Class optionalClass = Class.forName( "java.util.Optional" ); + Method readMethod = moduleDescriptorClass.getMethod( "read", InputStream.class ); + Method mainClassMethod = moduleDescriptorClass.getMethod( "mainClass" ); + Method rawVersionMethod = moduleDescriptorClass.getMethod( "rawVersion" ); + Method packagesMethod = moduleDescriptorClass.getMethod( "packages" ); + Method isPresentMethod = optionalClass.getMethod( "isPresent" ); + Method getMethod = optionalClass.getMethod( "get" ); + + // Read the module from the input stream + Object moduleDescriptor = readMethod.invoke( null, moduleDescriptorInputStream ); + + // Get the module main class + Object mainClassOptional = mainClassMethod.invoke( moduleDescriptor ); + String actualMainClass = null; + if ( (boolean) isPresentMethod.invoke( mainClassOptional ) ) + { + actualMainClass = (String) getMethod.invoke( mainClassOptional ); + } + + // Get the module version + Object versionOptional = rawVersionMethod.invoke( moduleDescriptor ); + String actualVersion = null; + if ( (boolean) isPresentMethod.invoke( versionOptional ) ) + { + actualVersion = (String) getMethod.invoke( versionOptional ); + } + + // Get the module packages + Set actualPackagesSet = (Set) packagesMethod.invoke( moduleDescriptor ); + Set expectedPackagesSet = new HashSet<>( Arrays.asList( expectedPackages ) ); + + assertEquals( expectedMainClass, actualMainClass ); + assertEquals( expectedVersion, actualVersion ); + assertEquals( expectedPackagesSet, actualPackagesSet ); + } + + /* + * Returns true if the current version of Java does support modules. + */ + private boolean modulesAreSupported() + { + try + { + Class.forName( "java.lang.module.ModuleDescriptor" ); + } + catch ( ClassNotFoundException e ) + { + return false; + } + + return true; + } + +} diff --git a/src/test/resources/java-classes/com/example/app/Main.class b/src/test/resources/java-classes/com/example/app/Main.class new file mode 100644 index 0000000000000000000000000000000000000000..5deea9cf3cefbcc14327cf0967e3733cd408ab64 GIT binary patch literal 551 zcmZuuxlY4C5Ph4=2Zn@P+>lTO3fv%31koTN3Uf$7X{a`42^-m7%W)8V78D>+@Bw@j zV%9;5BE`kb&YQPy-gtk0zrFz+W8X#zbqftNO|02i$A*DT16vHGQyIzhj3M7S=ra_~ z)q`LtdomK&lW-uCJ3a^qsd~!iL7yj5*Z*K4eUf8_rsu2B6)!xD1L5*GcCWaMx}+9T zrhez%8}VoE20R+Ny)=>0u$xWe$&l{X7PJ}co|+`SxR83hr4^5~hyw$rfo%sn*d?cz zA_!FLF;QVla}4Y`XraweUu24*GIMe>7zsa}gL<#yRD|?5)Px$1scos;Tk@QebHPK} ztCnfZ!wg2ONdYa~=q%b7Z4~Jp^p^wCVN$fHaw#*)#lugqx2$bbEM;IGOLTUo1j@@) pSy(}t5PkV16C>;$xoZ9cg_(d&EzlH`h%*rj6|53YZ%Q$T+8^}vbUgq7 literal 0 HcmV?d00001 diff --git a/src/test/resources/java-classes/com/example/resources/test.properties b/src/test/resources/java-classes/com/example/resources/test.properties new file mode 100644 index 000000000..2c2dd0fd9 --- /dev/null +++ b/src/test/resources/java-classes/com/example/resources/test.properties @@ -0,0 +1 @@ +ley=value diff --git a/src/test/resources/java-module-descriptor/module-info.class b/src/test/resources/java-module-descriptor/module-info.class new file mode 100644 index 0000000000000000000000000000000000000000..c172254ac3a7fb702b70563e74d91a37a938b733 GIT binary patch literal 189 zcmX^0Z`VEs1_o0GUUmjfMh33n{L-T2RJY8WR7M7Y-29Z%oK)S+ytI71ti-ZJMg}%t zu#hkVmoNi2BLl0Yo`If;2m=oz12>XlMh5=m{9L`%ip1Q4oK(HUf&xYcPLKh5Nr}Zk ygF&+TP+5JTYy$%WBhV2Lz{J1=WH2!>Gq3<@83tCcFwp7-1_pMZIu0-&OaK6}IVNrZ literal 0 HcmV?d00001 diff --git a/src/test/resources/java-src/REAMDE.md b/src/test/resources/java-src/REAMDE.md new file mode 100644 index 000000000..2e7cb0e79 --- /dev/null +++ b/src/test/resources/java-src/REAMDE.md @@ -0,0 +1,2 @@ +This is the source code for the Java classes +inside `java-classes` and `java-module` directories. diff --git a/src/test/resources/java-src/com/example/app/Main.java b/src/test/resources/java-src/com/example/app/Main.java new file mode 100644 index 000000000..5de2c61fa --- /dev/null +++ b/src/test/resources/java-src/com/example/app/Main.java @@ -0,0 +1,10 @@ +package com.example.app; + +public class Main +{ + + public static void main( String[] args ) + { + System.out.println( "Hello from Main" ); + } +} diff --git a/src/test/resources/java-src/module-info.java b/src/test/resources/java-src/module-info.java new file mode 100644 index 000000000..194dd8422 --- /dev/null +++ b/src/test/resources/java-src/module-info.java @@ -0,0 +1,4 @@ +module com.example.app +{ + exports com.example.app; +}