View Javadoc
1   package org.codehaus.plexus.languages.java.jpms;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedWriter;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.nio.charset.Charset;
28  import java.nio.file.FileVisitResult;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  import java.nio.file.Paths;
32  import java.nio.file.SimpleFileVisitor;
33  import java.nio.file.attribute.BasicFileAttributes;
34  import java.util.HashMap;
35  import java.util.Map;
36  import java.util.Properties;
37  import java.util.Set;
38  
39  /**
40   * Extract the module name by calling the main method with an external JVM
41   * 
42   * @author Robert Scholte
43   * @since 1.0.0
44   */
45  public class MainClassModuleNameExtractor
46  {
47      private final Path jdkHome;
48  
49      public MainClassModuleNameExtractor( Path jdkHome )
50      {
51          this.jdkHome = jdkHome;
52      }
53  
54      public <T> Map<T, String> extract( Map<T, Path> files )
55          throws IOException
56      {
57          Path workDir = Files.createTempDirectory( "plexus-java_jpms-" );
58  
59          try (InputStream is =
60              MainClassModuleNameExtractor.class.getResourceAsStream( this.getClass().getSimpleName() + ".class" ))
61          {
62              Path pckg = workDir.resolve( this.getClass().getPackage().getName().replace( '.', '/' ) );
63  
64              Files.createDirectories( pckg );
65  
66              Files.copy( is, pckg.resolve( this.getClass().getSimpleName() + ".class" ) );
67          }
68  
69          try (BufferedWriter argsWriter = Files.newBufferedWriter( workDir.resolve( "args" ), Charset.defaultCharset() ))
70          {
71              argsWriter.append( "--class-path" );
72              argsWriter.newLine();
73  
74              argsWriter.append( "." );
75              argsWriter.newLine();
76  
77              argsWriter.append( this.getClass().getName() );
78              argsWriter.newLine();
79  
80              for ( Path p : files.values() )
81              {
82                  // make sure the path is surrounded with quotes in case there is space
83                  argsWriter.append( '"' );
84                  // make sure to escape Windows paths
85                  argsWriter.append( p.toAbsolutePath().toString().replace( "\\", "\\\\" ) );
86                  argsWriter.append( '"' );
87                  argsWriter.newLine();
88              }
89          }
90  
91          ProcessBuilder builder = new ProcessBuilder( jdkHome.resolve( "bin/java" ).toAbsolutePath().toString(),
92                                                       "@args" ).directory( workDir.toFile() );
93  
94          Process p = builder.start();
95  
96          Properties output = new Properties();
97          try (InputStream is = p.getInputStream())
98          {
99              output.load( is );
100         }
101 
102         Map<T, String> moduleNames = new HashMap<>( files.size() );
103         for ( Map.Entry<T, Path> entry : files.entrySet() )
104         {
105             moduleNames.put( entry.getKey(), output.getProperty( entry.getValue().toAbsolutePath().toString(), null ) );
106         }
107 
108         try
109         {
110             Files.walkFileTree( workDir, new SimpleFileVisitor<Path>()
111             {
112                 @Override
113                 public FileVisitResult visitFile( Path file, BasicFileAttributes attrs )
114                     throws IOException
115                 {
116                     Files.delete( file );
117                     return FileVisitResult.CONTINUE;
118                 }
119 
120                 @Override
121                 public FileVisitResult postVisitDirectory( Path dir, IOException exc )
122                     throws IOException
123                 {
124                     Files.delete( dir );
125                     return FileVisitResult.CONTINUE;
126                 }
127             } );
128         }
129         catch ( IOException e )
130         {
131             // noop, we did our best to clean it up
132         }
133 
134         return moduleNames;
135     }
136 
137     public static void main( String[] args )
138     {
139         Properties properties = new Properties();
140 
141         for ( String path : args )
142         {
143             try
144             {
145                 String moduleName = getModuleName( Paths.get( path ) );
146                 if ( moduleName != null )
147                 {
148                     properties.setProperty( path, moduleName );
149                 }
150             }
151             catch ( Exception e )
152             {
153                 System.err.append( e.getMessage() );
154             }
155         }
156 
157         try
158         {
159             properties.store( System.out, "" );
160         }
161         catch ( IOException e )
162         {
163             System.exit( 1 );
164         }
165     }
166 
167     public static String getModuleName( Path modulePath ) throws Exception
168     {
169         String name = null;
170         try
171         {
172             // Use Java9 code to get moduleName, don't try to do it better with own implementation
173             Class<?> moduleFinderClass = Class.forName( "java.lang.module.ModuleFinder" );
174 
175             Method ofMethod = moduleFinderClass.getMethod( "of", java.nio.file.Path[].class );
176             Object moduleFinderInstance =
177                 ofMethod.invoke( null, new Object[] { new java.nio.file.Path[] { modulePath } } );
178 
179             Method findAllMethod = moduleFinderClass.getMethod( "findAll" );
180 
181             @SuppressWarnings( "unchecked" )
182             Set<Object> moduleReferences = (Set<Object>) findAllMethod.invoke( moduleFinderInstance );
183 
184             if ( moduleReferences.isEmpty() )
185             {
186                 return null;
187             }
188 
189             Object moduleReference = moduleReferences.iterator().next();
190             Method descriptorMethod = moduleReference.getClass().getMethod( "descriptor" );
191             Object moduleDescriptorInstance = descriptorMethod.invoke( moduleReference );
192 
193             Method nameMethod = moduleDescriptorInstance.getClass().getMethod( "name" );
194             name = (String) nameMethod.invoke( moduleDescriptorInstance );
195         }
196         catch ( InvocationTargetException e )
197         {
198             if ( e.getCause() instanceof Exception )
199             {
200                 throw (Exception) e.getCause();
201             }
202         }
203         catch ( ReflectiveOperationException e )
204         {
205             // noop
206         }
207         catch ( SecurityException e )
208         {
209             // noop
210         }
211         catch ( IllegalArgumentException e )
212         {
213             // noop
214         }
215         return name;
216     }
217 }