View Javadoc
1   package org.codehaus.mojo.natives.plugin;
2   
3   /*
4    * The MIT License
5    *
6    * Copyright (c) 2004, The Codehaus
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
9    * associated documentation files (the "Software"), to deal in the Software without restriction,
10   * including without limitation the rights to use, copy, modify, merge, publish, distribute,
11   * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
12   * furnished to do so, subject to the following conditions:
13   *
14   * The above copyright notice and this permission notice shall be included in all copies or
15   * substantial portions of the Software.
16   *
17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
18   * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20   * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22   */
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Enumeration;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.zip.ZipEntry;
31  import java.util.zip.ZipFile;
32  
33  import org.apache.bcel.classfile.ClassParser;
34  import org.apache.bcel.classfile.JavaClass;
35  import org.apache.bcel.classfile.Method;
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.plugin.MojoExecutionException;
38  import org.apache.maven.plugins.annotations.Component;
39  import org.apache.maven.plugins.annotations.LifecyclePhase;
40  import org.apache.maven.plugins.annotations.Mojo;
41  import org.apache.maven.plugins.annotations.Parameter;
42  import org.apache.maven.plugins.annotations.ResolutionScope;
43  import org.apache.maven.project.MavenProject;
44  import org.apache.maven.project.MavenProjectHelper;
45  import org.codehaus.mojo.natives.NativeBuildException;
46  import org.codehaus.mojo.natives.javah.Javah;
47  import org.codehaus.mojo.natives.javah.JavahConfiguration;
48  import org.codehaus.mojo.natives.manager.JavahManager;
49  import org.codehaus.mojo.natives.manager.NoSuchNativeProviderException;
50  import org.codehaus.plexus.archiver.util.DefaultFileSet;
51  import org.codehaus.plexus.archiver.zip.ZipArchiver;
52  import org.codehaus.plexus.util.FileUtils;
53  import org.codehaus.plexus.util.StringUtils;
54  
55  /**
56   * Generate JNI include files based on a set of class names
57   */
58  @Mojo(name = "javah", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE)
59  public class NativeJavahMojo
60      extends AbstractNativeMojo
61  {
62  
63      /**
64       * Javah Provider.
65       * @since 1.0-alpha-2
66       */
67      @Parameter(defaultValue = "default", required = true)
68      private String javahProvider;
69  
70      /**
71       * List of class names to generate native files. Additional JNI interface will automatically discovered from
72       * project's dependencies of <i>jar</i> type, when <i>javahSearchJNIFromDependencies</i> is true
73       * @since 1.0-alpha-4
74       */
75      @Parameter
76      private List javahClassNames = new ArrayList( 0 );
77  
78      /**
79       * Enable the search from project dependencies for JNI interfaces, in addition to <i>javahClassNames</i>
80       * @since 1.0-alpha-4
81       */
82      @Parameter(defaultValue = "false")
83      private boolean javahSearchJNIFromDependencies;
84  
85      /**
86       * Path to javah executable, if present, it will override the default one which bases on architecture type. See
87       * 'javahProvider' argument
88       * @since 1.0-alpha-2
89       */
90      @Parameter
91      private File javahPath;
92  
93      /**
94       * Where to place javah generated file
95       * @since 1.0-alpha-2
96       */
97      @Parameter(defaultValue = "${project.build.directory}/native/javah", required = true)
98      protected File javahOutputDirectory;
99  
100     /**
101      * if configured, this value will be combined with outputDirectory to pass into javah's -o option
102      * @since 1.0-alpha-4
103      */
104     @Parameter
105     private String javahOutputFileName;
106 
107     /**
108      * Additional javah classname and its corresponding header name. Use this option to create one class per header
109      * <p/>
110      *
111      * <pre>
112      * &lt;javahIncludes&gt;
113      *   &lt;javahInclude&gt;
114      *     &lt;className&gt;com.some.Class&lt;/className&gt;
115      *     &lt;headerName&gt;Class.h&lt;/headerName&gt;
116      *   &lt;javahInclude&gt;
117      * &lt;/javahIncludes&gt;
118      * </pre>
119      * @since 1.0-alpha-8
120      */
121     @Parameter
122     private List javahIncludes = new ArrayList();
123 
124     /**
125      * Enable javah verbose mode
126      * @since 1.0-alpha-2
127      */
128     @Parameter(defaultValue = "false")
129     private boolean javahVerbose;
130 
131     /**
132      * Archive all generated include files and deploy as an inczip
133      */
134     @Parameter(defaultValue = "false")
135     private boolean attach;
136 
137     /**
138      * Classifier name when install/deploy generated includes file. See ${attach} for details
139      */
140     @Parameter(defaultValue = "javah")
141     private String classifier;
142 
143     /**
144      * Archive file to bundle all generated include files if enable by ${attach}
145      * @since 1.0-alpha-8
146      */
147     @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.inczip", required = true)
148     private File incZipFile;
149 
150     /**
151      * Set CLASSPATH env variable instead of using -classpath command-line argument. Use this option to allow large
152      * number of jars in classpath due to command line size limit under Windows
153      * @since 1.0-alpha-9
154      */
155     @Parameter(defaultValue = "false")
156     private boolean useEnvClasspath;
157 
158     /**
159      * Internal: To look up javah implementation
160      * @since 1.0-alpha-2
161      */
162 
163     @Component
164     private JavahManager manager;
165 
166     /**
167      * Maven ProjectHelper.
168      * @since 1.0-alpha-8
169      */
170     @Component
171     private MavenProjectHelper projectHelper;
172 
173     /**
174      * For unit test only
175      */
176     private JavahConfiguration config;
177 
178     public void execute()
179         throws MojoExecutionException
180     {
181 
182         this.discoverAdditionalJNIClassName();
183 
184         if ( this.javahClassNames.size() == 0 && this.javahIncludes.size() == 0 )
185         {
186             return;
187         }
188 
189         try
190         {
191             if ( this.javahClassNames.size() != 0 )
192             {
193                 this.config =
194                     this.createProviderConfiguration( (String[]) javahClassNames.toArray( new String[javahClassNames.size()] ),
195                                                       this.javahOutputFileName );
196                 this.getJavah().compile( config );
197             }
198 
199             for ( int i = 0; i < this.javahIncludes.size(); ++i )
200             {
201                 JavahInclude javahInclude = (JavahInclude) this.javahIncludes.get( i );
202                 this.config =
203                     this.createProviderConfiguration( new String[] { javahInclude.getClassName() },
204                                                       javahInclude.getHeaderName() );
205                 this.getJavah().compile( config );
206             }
207 
208             if ( this.attach )
209             {
210                 attachGeneratedIncludeFilesAsIncZip();
211             }
212         }
213         catch ( NativeBuildException e )
214         {
215             throw new MojoExecutionException( "Error running javah command", e );
216         }
217 
218         this.project.addCompileSourceRoot( this.javahOutputDirectory.getAbsolutePath() );
219 
220     }
221 
222     private void attachGeneratedIncludeFilesAsIncZip()
223         throws MojoExecutionException
224     {
225         try
226         {
227             ZipArchiver archiver = new ZipArchiver();
228             DefaultFileSet fileSet = new DefaultFileSet();
229             fileSet.setUsingDefaultExcludes( true );
230             fileSet.setDirectory( javahOutputDirectory );
231             archiver.addFileSet( fileSet );
232             archiver.setDestFile( this.incZipFile );
233             archiver.createArchive();
234 
235             if ( StringUtils.isBlank( this.classifier ) )
236             {
237                 projectHelper.attachArtifact( this.project, INCZIP_TYPE, null, this.incZipFile );
238             }
239             else
240             {
241                 projectHelper.attachArtifact( this.project, INCZIP_TYPE, this.classifier, this.incZipFile );
242             }
243         }
244         catch ( Exception e )
245         {
246             throw new MojoExecutionException( "Unable to archive/deploy generated include files", e );
247         }
248     }
249 
250     private Javah getJavah()
251         throws MojoExecutionException
252     {
253         Javah javah;
254 
255         try
256         {
257             javah = this.manager.getJavah( this.javahProvider );
258 
259         }
260         catch ( NoSuchNativeProviderException pe )
261         {
262             throw new MojoExecutionException( pe.getMessage() );
263         }
264 
265         return javah;
266     }
267 
268     /**
269      * Get all jars in the pom excluding transitive, test, and provided scope dependencies.
270      *
271      * @return
272      */
273     private List getJavahArtifacts()
274     {
275         List list = new ArrayList();
276 
277         List artifacts = this.project.getCompileArtifacts();
278 
279         if ( artifacts != null )
280         {
281 
282             for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
283             {
284                 Artifact artifact = (Artifact) iter.next();
285 
286                 // pick up only jar files
287                 if ( !"jar".equals( artifact.getType() ) )
288                 {
289                     continue;
290                 }
291 
292                 // exclude some other scopes
293                 if ( Artifact.SCOPE_PROVIDED.equals( artifact.getScope() ) )
294                 {
295                     continue;
296                 }
297 
298                 list.add( artifact );
299 
300             }
301         }
302 
303         return list;
304     }
305 
306     /**
307      * Build classpaths from dependent jars including project output directory (i.e. classes directory )
308      *
309      * @return
310      */
311     private String[] getJavahClassPath()
312     {
313         List artifacts = this.getJavahArtifacts();
314 
315         String[] classPaths = new String[artifacts.size() + 1];
316 
317         classPaths[0] = this.project.getBuild().getOutputDirectory();
318 
319         Iterator iter = artifacts.iterator();
320 
321         for ( int i = 1; i < classPaths.length; ++i )
322         {
323             Artifact artifact = (Artifact) iter.next();
324 
325             classPaths[i] = artifact.getFile().getPath();
326         }
327 
328         return classPaths;
329     }
330 
331     /**
332      * Get applicable class names to be "javahed"
333      */
334 
335     private void discoverAdditionalJNIClassName()
336         throws MojoExecutionException
337     {
338         if ( !this.javahSearchJNIFromDependencies )
339         {
340             return;
341         }
342 
343         // scan the immediate dependency list for jni classes
344 
345         List artifacts = this.getJavahArtifacts();
346 
347         for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
348         {
349             Artifact artifact = (Artifact) iter.next();
350 
351             this.getLog().info( "Parsing " + artifact.getFile() + " for native classes." );
352 
353             try
354             {
355                 ZipFile zipFile = new ZipFile( artifact.getFile() );
356                 Enumeration zipEntries = zipFile.entries();
357 
358                 while ( zipEntries.hasMoreElements() )
359                 {
360                     ZipEntry zipEntry = (ZipEntry) zipEntries.nextElement();
361 
362                     if ( "class".equals( FileUtils.extension( zipEntry.getName() ) ) )
363                     {
364                         ClassParser parser = new ClassParser( artifact.getFile().getPath(), zipEntry.getName() );
365 
366                         JavaClass clazz = parser.parse();
367 
368                         Method[] methods = clazz.getMethods();
369 
370                         for ( int j = 0; j < methods.length; ++j )
371                         {
372                             if ( methods[j].isNative() )
373                             {
374                                 javahClassNames.add( clazz.getClassName() );
375 
376                                 this.getLog().info( "Found native class: " + clazz.getClassName() );
377 
378                                 break;
379                             }
380                         }
381                     }
382                 }// endwhile
383 
384                 // not full proof
385                 zipFile.close();
386             }
387             catch ( IOException ioe )
388             {
389                 throw new MojoExecutionException( "Error searching for native class in " + artifact.getFile(), ioe );
390             }
391         }
392 
393     }
394 
395     private JavahConfiguration createProviderConfiguration( String[] classNames, String javahOutputFileName )
396         throws MojoExecutionException
397     {
398         JavahConfiguration config = new JavahConfiguration();
399         config.setWorkingDirectory( this.workingDirectory );
400         config.setVerbose( this.javahVerbose );
401         config.setOutputDirectory( this.javahOutputDirectory );
402         config.setFileName( javahOutputFileName );
403         config.setClassPaths( this.getJavahClassPath() );
404         config.setUseEnvClasspath( useEnvClasspath );
405         config.setClassNames( classNames );
406         config.setJavahPath( this.javahPath );
407 
408         return config;
409     }
410 
411     /**
412      * Internal only for test harness purpose
413      *
414      * @return
415      */
416     protected JavahConfiguration getJavahConfiguration()
417     {
418         return this.config;
419     }
420 
421     /**
422      * Internal for unit test only
423      */
424 
425     protected MavenProject getProject()
426     {
427         return this.project;
428     }
429 }