View Javadoc

1   /* ==========================================================================
2    * Copyright 2003-2007 Mevenide Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   * =========================================================================
16   */
17  package org.codehaus.mojo.nbm;
18  
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.IOException;
22  import java.io.InputStreamReader;
23  import java.io.PrintWriter;
24  import java.io.Reader;
25  import java.net.URL;
26  import java.text.BreakIterator;
27  import java.text.SimpleDateFormat;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.Date;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  import java.util.TimeZone;
38  import java.util.regex.Pattern;
39  import org.apache.maven.artifact.Artifact;
40  import org.apache.maven.artifact.factory.ArtifactFactory;
41  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
42  import org.apache.maven.artifact.repository.ArtifactRepository;
43  import org.apache.maven.artifact.resolver.ArtifactCollector;
44  import org.apache.maven.plugin.MojoExecutionException;
45  import org.apache.maven.plugin.MojoFailureException;
46  import org.apache.maven.plugins.annotations.Component;
47  import org.apache.maven.plugins.annotations.LifecyclePhase;
48  import org.apache.maven.plugins.annotations.Mojo;
49  import org.apache.maven.plugins.annotations.Parameter;
50  import org.apache.maven.plugins.annotations.ResolutionScope;
51  import org.codehaus.mojo.nbm.model.Dependency;
52  import org.codehaus.mojo.nbm.model.NetBeansModule;
53  import org.apache.maven.project.MavenProject;
54  import org.apache.maven.shared.dependency.analyzer.DefaultClassAnalyzer;
55  import org.apache.maven.shared.dependency.analyzer.asm.ASMDependencyAnalyzer;
56  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
57  import org.apache.maven.shared.dependency.graph.DependencyNode;
58  import org.apache.tools.ant.taskdefs.Manifest;
59  import org.apache.tools.ant.taskdefs.ManifestException;
60  import org.codehaus.mojo.nbm.utils.ExamineManifest;
61  import org.codehaus.plexus.util.IOUtil;
62  
63  /**
64   * Goal for generating NetBeans module system specific manifest entries, part of <code>nbm</code> lifecycle/packaging.
65   *
66   * In order to have the generated manifest picked up by the maven-jar-plugin,
67   * one shall add the following configuration snippet to maven-jar-plugin.
68   * <p/>
69   * <pre>
70      &lt;plugin&gt;
71          &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
72          &lt;artifactId&gt;maven-jar-plugin&lt;/artifactId&gt;
73          &lt;version&gt;2.2&lt;/version&gt;
74          &lt;configuration&gt;
75              &lt;useDefaultManifestFile&gt;true&lt;/useDefaultManifestFile&gt;
76          &lt;/configuration&gt;
77      &lt;/plugin&gt;
78   * </pre>
79   *
80   * @author <a href="mailto:mkleint@codehaus.org">Milos Kleint</a>
81   */
82  @Mojo(name="manifest", 
83          defaultPhase= LifecyclePhase.PROCESS_CLASSES, 
84          requiresProject=true, 
85          threadSafe = true,
86          requiresDependencyResolution= ResolutionScope.RUNTIME )
87  public class NetBeansManifestUpdateMojo
88      extends AbstractNbmMojo
89  {
90  
91      /**
92       * NetBeans module assembly build directory.
93       * directory where the the NetBeans jar and nbm file get constructed.
94       */
95      @Parameter(defaultValue="${project.build.directory}/nbm", property="maven.nbm.buildDir")
96      protected File nbmBuildDir;
97  
98      /**
99       * a NetBeans module descriptor containing dependency information and more
100      * @deprecated all content from the module descriptor can be defined as plugin configuration now, will be removed in 4.0 entirely
101      */
102     @Parameter(defaultValue="${basedir}/src/main/nbm/module.xml")
103     protected File descriptor;
104 
105     /**
106      * maven project
107      */
108     @Parameter(required=true, readonly=true, property="project")
109     private MavenProject project;
110 
111     /**
112      * The location of JavaHelp sources for the project. The documentation
113      * itself is expected to be in the directory structure based on codenamebase of the module.
114      * eg. if your codenamebase is "org.netbeans.modules.apisupport", then the actual docs
115      * files shall go to ${basedir}/src/main/javahelp/org/netbeans/modules/apisupport/docs.
116      * Obsolete as of NetBeans 7.0 with &#64;HelpSetRegistration.
117      * @since 2.7
118      */
119     @Parameter(defaultValue="${basedir}/src/main/javahelp")
120     protected File nbmJavahelpSource;
121 
122     /**
123      * Path to manifest file that will be used as base and enhanced with generated content. Any entry specified in the original file
124      * will not be overwritten
125      * @since 3.0
126      */
127     @Parameter(required=true, defaultValue="${basedir}/src/main/nbm/manifest.mf")
128     private File sourceManifestFile;
129 
130     /**
131      * Path to the generated MANIFEST file to use. It will be used by jar:jar plugin.
132      * @since 3.0
133      */
134     @Parameter(required=true, readonly=true, defaultValue="${project.build.outputDirectory}/META-INF/MANIFEST.MF")
135     private File targetManifestFile;
136 
137     /**
138      * Verify the runtime NetBeans module dependencies and Class-Path items
139      * generated from Maven dependencies. The check is done by matching classes used
140      * in current project. Allowed values for the parameter are <code>fail</code>, <code>warn</code> and <code>skip</code>.
141      * The default is <code>fail</code> in which case the validation failure results in a failed build,
142      * in the vast majority of cases the module would fail at runtime anyway.
143      *
144      * @since 3.0
145      */
146     @Parameter(property="maven.nbm.verify", defaultValue="fail")
147     private String verifyRuntime;
148     
149     private static final String FAIL = "fail";
150     private static final String WARN = "warn";
151     private static final String SKIP = "skip";
152 
153     /**
154      * A list of module's public packages. If not defined, no packages are exported as public.
155      * Allowed values are single package names
156      * or package names ending with .* which represent the package and all subpackages.
157      * <p/>
158      * Eg. "org.kleint.milos.api" designates just the one package, while "org.kleint.milos.spi.*"
159      * denotes the spi package an all it's subpackages.
160      * @since 3.0
161      */
162     @Parameter
163     private List<String> publicPackages;
164 
165     /**
166      * When encountering an OSGi bundle among dependencies, the plugin will generate a direct dependency
167      * on the bundle and will not include the bundle's jar into the nbm. Will only work with NetBeans 6.9+ runtime.
168      * Therefore it is off by default.
169      * WARNING: Additionally existing applications/modules need to check modules wrapping
170      * external libraries for library jars that are also OSGi bundles. Such modules will no longer include the OSGi bundles
171      * as part of the module but will include a modular dependency on the bundle. Modules depending on these old wrappers
172      * shall depend directly on the bundle, eventually rendering the old library wrapper module obsolete.
173      *
174      * @since 3.2
175      */
176     @Parameter(defaultValue="false")
177     private boolean useOSGiDependencies;
178     
179     /**
180      * codename base of the module, uniquely identifying the module within the NetBeans runtime. usually the package name equivalent.
181      * Can include the major release version.
182      * See <a href="http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/api.html#how-manifest"> NetBeans Module system docs</a>
183      * @since 3.8
184      */
185     @Parameter(defaultValue="${project.groupId}.${project.artifactId}")
186     private String codeNameBase;
187     
188     /**
189      * List of explicit module dependency declarations overriding the default specification dependency. Useful when depending on a range of major versions,
190      * depending on implementation version etc.
191      * <p>The format is:
192      * <pre>
193      * &lt;dependency&gt;
194      *    &lt;id&gt;groupId:artifactId&lt;/id&gt;
195      *    &lt;type&gt;spec|impl|loose&lt;/type&gt;
196      *    &lt;explicitValue&gt;the entire dependency token&lt;/explicitValue&gt;
197      * &lt;/dependency&gt;
198      * </pre>
199      * </p>
200      * <p>
201      * where <code>id</code> is composed of grouId and artifactId of a dependency defined in effective pom, separated by double colon. This is mandatory.</p>
202      * <p>
203      * Then there are 2 exclusively optional fields <code>type</code> and <code>explicitValue</code>, if both are defined <code>explicitValue</code> gets applied.
204      * </p>
205      * <p><code>type</code> values: <code>spec</code> means specification dependency.That's the default. 
206      * <code>impl</code> means implementation dependency, only the exact version match will satisfy the constraint. 
207      * <code>loose</code> means loose dependency, no requirement on version, the module just has to be present. Not very common option.
208      * 
209      * @since 3.8
210      */
211     @Parameter
212     private Dependency[] moduleDependencies;
213     
214 /**
215      * Deployment type of the module, allowed values are <code>normal</code>,<code>eager</code>,<code>autoload</code>,
216      * <code>disabled</code>.
217      * <p>
218      * <code>autoload</code> - Such a module is
219      * automatically enabled when some other module requires it and
220      * automatically disabled otherwise.</p>
221      *                     <p><code>eager</code> - This module type gets
222      * automatically enabled when all it's dependencies are
223      * satisfied. Disabled otherwise.</p>
224      *                     <p><code>normal</code> - This is the default
225      * value. This kind of module is enabled/disabled manually by
226      * the user. It installs enabled.</p>
227      *                     <p><code>disabled</code> - This kind of module is enabled/disabled manually by
228      * the user. It installs disabled. Since 3.11</p>
229      *
230      * For details, see <a href="http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/api.html#enablement">Netbeans Module system docs</a>
231      * 
232      * Since 3.14, for autoload and eager modules, we automatically set AutoUpdate-Show-In-Client manifest entry to false, if not defined already otherwise in the manifest.
233      * See issue <a href="http://jira.codehaus.org/browse/MNBMODULE-194">MNBMODULE-194</a>
234      * 
235      * @since 3.8 (3.14 in manifest goal)
236      */ 
237     @Parameter(defaultValue="normal")
238     protected String moduleType;    
239 
240     // <editor-fold defaultstate="collapsed" desc="Component parameters">
241 
242     /**
243      * The artifact repository to use.
244 
245      */
246     @Parameter(required=true, readonly=true, defaultValue="${localRepository}")
247     private ArtifactRepository localRepository;
248 
249     /**
250      * The artifact factory to use.
251      */
252     @Component
253     private ArtifactFactory artifactFactory;
254 
255     /**
256      * The artifact metadata source to use.
257      */
258     @Component
259     private ArtifactMetadataSource artifactMetadataSource;
260 
261     /**
262      * The artifact collector to use.
263      */
264     @Component
265     private ArtifactCollector artifactCollector;
266 
267     /**
268      * The dependency tree builder to use.
269      */
270     @Component( hint = "default" )
271     private DependencyGraphBuilder dependencyGraphBuilder;
272 
273 // end of component params custom code folding
274 // </editor-fold> 
275 
276     /**
277      * execute plugin
278      * @throws org.apache.maven.plugin.MojoExecutionException
279      * @throws org.apache.maven.plugin.MojoFailureException
280      */
281     public void execute()
282         throws MojoExecutionException, MojoFailureException
283 
284     {
285         //need to do this to chekc for javahelp on CP.
286         super.registerNbmAntTasks();
287         NetBeansModule module;
288         if ( descriptor != null && descriptor.exists() )
289         {
290             module = readModuleDescriptor( descriptor );
291             getLog().warn( "descriptor parameter is deprecated, use equivalent mojo parameters instead.");
292         }
293         else
294         {
295             module = createDefaultDescriptor( project, false );
296         }
297         
298         String mtype = moduleType;
299         //same moduleType related code in CreateNetBeansFileStructure.java
300         if ("normal".equals(mtype) && module.getModuleType() != null) {
301             mtype = module.getModuleType();
302             getLog().warn( "moduleType in module descriptor is deprecated, use the plugin's parameter moduleType");
303         }
304         if (!"normal".equals(mtype) && !"autoload".equals(mtype) && !"eager".equals(mtype) && !"disabled".equals(mtype)) {
305             getLog().error( "Only 'normal,autoload,eager,disabled' are allowed values in the moduleType parameter");
306         }
307         boolean autoload = "autoload".equals( mtype );
308         boolean eager = "eager".equals( mtype );
309         
310 
311         String moduleName = codeNameBase;
312         if (module.getCodeNameBase() != null) {
313             moduleName = module.getCodeNameBase();
314             getLog().warn( "codeNameBase in module descriptor is deprecated, use the plugin's parameter codeNameBase");
315         }
316         moduleName = moduleName.replaceAll( "-", "." );
317 //<!-- if a NetBeans specific manifest is defined, examine this one, otherwise the already included one.
318 // ignoring the case when some of the NetBeans attributes are already defined in the jar and more is included.
319         File specialManifest = sourceManifestFile;
320         File nbmManifest = ( module.getManifest() != null ? new File(
321             project.getBasedir(), module.getManifest() ) : null );
322         if ( nbmManifest != null && nbmManifest.exists() )
323         {
324             //deprecated, but if actually defined, will use it.
325             specialManifest = nbmManifest;
326         }
327         ExamineManifest examinator = new ExamineManifest( getLog() );
328         if ( specialManifest != null && specialManifest.exists() )
329         {
330             examinator.setManifestFile( specialManifest );
331             examinator.checkFile();
332         }
333         else
334         {
335 //            examinator.setJarFile( jarFile );
336         }
337 
338         getLog().info( "NBM Plugin generates manifest" );
339 
340         Manifest manifest = null;
341         if ( specialManifest != null && specialManifest.exists() )
342         {
343             Reader reader = null;
344             try
345             {
346                 reader = new InputStreamReader( new FileInputStream( specialManifest ) );
347                 manifest = new Manifest( reader );
348             }
349             catch ( IOException exc )
350             {
351                 manifest = new Manifest();
352                 getLog().warn( "Error reading manifest at " + specialManifest, exc );
353             }
354             catch ( ManifestException ex )
355             {
356                 getLog().warn( "Error reading manifest at " + specialManifest, ex );
357                 manifest = new Manifest();
358             }
359             finally
360             {
361                 IOUtil.close( reader );
362             }
363         }
364         else
365         {
366             manifest = new Manifest();
367         }
368         Date date = new Date();
369         String specVersion = AdaptNbVersion.adaptVersion( project.getVersion(),
370             AdaptNbVersion.TYPE_SPECIFICATION, date );
371         String implVersion = AdaptNbVersion.adaptVersion( project.getVersion(),
372             AdaptNbVersion.TYPE_IMPLEMENTATION, date );
373         Manifest.Section mainSection = manifest.getMainSection();
374         conditionallyAddAttribute( mainSection,
375             "OpenIDE-Module-Specification-Version", specVersion );
376         conditionallyAddAttribute( mainSection,
377             "OpenIDE-Module-Implementation-Version", implVersion );
378         if (autoload || eager) { //MNBMODULE-194
379             conditionallyAddAttribute( mainSection, "AutoUpdate-Show-In-Client", "false");
380         }
381         final String timestamp = createTimestamp( date );
382         conditionallyAddAttribute( mainSection, "OpenIDE-Module-Build-Version",
383             timestamp );
384         String projectCNB = conditionallyAddAttribute( mainSection, "OpenIDE-Module", moduleName );
385         String packagesValue;
386         if ( publicPackages != null && publicPackages.size() > 0 )
387         {
388             StringBuilder sb = new StringBuilder();
389             for ( String pub : publicPackages )
390             {
391                 if (pub == null) { //#MNBMODULE-237
392                     continue;
393                 }
394                 if ( pub.endsWith( ".**" ) )
395                 {
396                     // well, just sort of wrong value but accept
397                     sb.append( pub );
398                 }
399                 else if ( pub.endsWith( ".*" ) )
400                 {
401                     //multipackage value
402                     sb.append( pub ).append( "*" );
403                 }
404                 else
405                 {
406                     sb.append( pub ).append( ".*" );
407                 }
408                 sb.append( ", " );
409             }
410             if (sb.length() > 1) { //if only item is null, we have empty builder
411                 sb.setLength( sb.length() - 2 ); //cut the last 2 ", " characters
412                 packagesValue = sb.toString();
413             } else {
414                 // no packages available
415                 packagesValue = "-";
416             }
417         }
418         else
419         {
420             // no packages available
421             packagesValue = "-";
422         }
423         conditionallyAddAttribute( mainSection, "OpenIDE-Module-Public-Packages", packagesValue );
424 
425         //See http://www.netbeans.org/download/dev/javadoc/org-openide-modules/apichanges.html#split-of-openide-jar
426         conditionallyAddAttribute( mainSection, "OpenIDE-Module-Requires",
427             "org.openide.modules.ModuleFormat1" );
428 //        conditionallyAddAttribute(mainSection, "OpenIDE-Module-IDE-Dependencies", "IDE/1 > 3.40");
429         // localization items
430         if ( !examinator.isLocalized() )
431         {
432             conditionallyAddAttribute( mainSection,
433                 "OpenIDE-Module-Display-Category", project.getGroupId() );
434             conditionallyAddAttribute( mainSection, "OpenIDE-Module-Name",
435                 project.getName() );
436             conditionallyAddAttribute( mainSection,
437                 "OpenIDE-Module-Short-Description", shorten( project.getDescription() ) );
438             conditionallyAddAttribute( mainSection,
439                 "OpenIDE-Module-Long-Description", project.getDescription() );
440         }
441         getLog().debug( "module =" + module );
442         
443             DependencyNode treeroot = createDependencyTree( project, dependencyGraphBuilder, "compile" );
444             Map<Artifact, ExamineManifest> examinerCache = new HashMap<Artifact, ExamineManifest>();
445             @SuppressWarnings( "unchecked" )
446             List<Artifact> libArtifacts = getLibraryArtifacts( treeroot, module, project.getRuntimeArtifacts(),
447                 examinerCache, getLog(), useOSGiDependencies );
448             List<ModuleWrapper> moduleArtifacts = getModuleDependencyArtifacts( treeroot, module, moduleDependencies, project, examinerCache,
449                 libArtifacts, getLog(), useOSGiDependencies );
450             StringBuilder classPath = new StringBuilder();
451             StringBuilder mavenClassPath = new StringBuilder();
452             String dependencies = "";
453             String depSeparator = " ";
454 
455             for ( Artifact a : libArtifacts )
456             {
457                 if (classPath.length() > 0)
458                 {
459                     classPath.append(' ');
460                 }
461                 classPath.append(artifactToClassPathEntry( a, codeNameBase ));
462                 if ( mavenClassPath.length() > 0 )
463                 {
464                     mavenClassPath.append( ' ' );
465                 }
466                 mavenClassPath.append( a.getGroupId() ).append( ':' ).append( a.getArtifactId() ).append( ':' ).append( a.getBaseVersion() );
467                 if (a.getClassifier() != null) 
468                 {
469                     mavenClassPath.append(":").append(a.getClassifier());
470             }
471             }
472 
473             for ( ModuleWrapper wr : moduleArtifacts )
474             {
475                 if ( wr.transitive )
476                 {
477                     continue;
478                 }
479                 Dependency dep = wr.dependency;
480                 Artifact artifact = wr.artifact;
481                 ExamineManifest depExaminator = examinerCache.get( artifact );
482                 String type = dep.getType();
483                 String depToken = dep.getExplicitValue();
484                 if ( depToken == null )
485                 {
486                     if ( "loose".equals( type ) )
487                     {
488                         depToken = depExaminator.getModuleWithRelease();
489                     }
490                     else if ( "spec".equals( type ) )
491                     {
492                         depToken =
493                             depExaminator.getModuleWithRelease()
494                                 + " > "
495                                 + ( depExaminator.isNetBeansModule() ? depExaminator.getSpecVersion()
496                                                 : AdaptNbVersion.adaptVersion( depExaminator.getSpecVersion(),
497                                                                                AdaptNbVersion.TYPE_SPECIFICATION, date ) );
498                     }
499                     else if ( "impl".equals( type ) )
500                     {
501                         depToken =
502                             depExaminator.getModuleWithRelease()
503                                 + " = "
504                                 + ( depExaminator.isNetBeansModule() ? depExaminator.getImplVersion()
505                                                 : AdaptNbVersion.adaptVersion( depExaminator.getImplVersion(),
506                                                                                AdaptNbVersion.TYPE_IMPLEMENTATION, date ) );
507                     }
508                     else
509                     {
510                         throw new MojoExecutionException(
511                             "Wrong type of NetBeans dependency: " + type + " Allowed values are: loose, spec, impl." );
512                     }
513                 }
514                 if ( depToken == null )
515                 {
516                     //TODO report
517                     getLog().error(
518                         "Cannot properly resolve the NetBeans dependency for " + dep.getId() );
519                 }
520                 else
521                 {
522                     dependencies = dependencies + depSeparator + depToken;
523                     depSeparator = ", ";
524                 }
525             }
526             if ( !verifyRuntime.equalsIgnoreCase( SKIP ) )
527             {
528                 try
529                 {
530                     checkModuleClassPath( treeroot, libArtifacts, examinerCache, moduleArtifacts, projectCNB );
531                 }
532                 catch ( IOException ex )
533                 {
534                     throw new MojoExecutionException( "Error while checking runtime dependencies", ex );
535                 }
536             }
537 
538             if ( nbmJavahelpSource.exists() )
539             {
540                 String moduleJarName = stripVersionFromCodebaseName( moduleName ).replace( ".", "-" );
541                 classPath.append( " docs/").append( moduleJarName ).append( ".jar" );
542             }
543 
544             if ( classPath.length() > 0 )
545             {
546                 conditionallyAddAttribute( mainSection, "X-Class-Path", classPath.toString().trim() );
547             }
548             if ( mavenClassPath.length() > 0)
549             {
550                 conditionallyAddAttribute( mainSection, "Maven-Class-Path", mavenClassPath.toString() );
551             }
552             if ( dependencies.length() > 0 )
553             {
554                 conditionallyAddAttribute( mainSection, "OpenIDE-Module-Module-Dependencies", dependencies );
555             }
556 //            if ( librList.size() > 0 )
557 //            {
558 //                String list = "";
559 //                for ( int i = 0; i < librList.size(); i++ )
560 //                {
561 //                    list = list + " " + librList.get( i );
562 //                }
563 //                getLog().warn(
564 //                        "Some libraries could not be found in the dependency chain: " + list );
565 //            }
566         PrintWriter writer = null;
567         try
568         {
569             if ( !targetManifestFile.exists() )
570             {
571                 targetManifestFile.getParentFile().mkdirs();
572                 targetManifestFile.createNewFile();
573             }
574             writer = new PrintWriter( targetManifestFile, "UTF-8" ); //TODO really UTF-8??
575             manifest.write( writer );
576         }
577         catch ( IOException ex )
578         {
579             throw new MojoExecutionException( ex.getMessage(), ex );
580         }
581         finally
582         {
583             IOUtil.close( writer );
584         }
585     }
586 
587     //MNBMODULE-137
588     static String artifactToClassPathEntry( Artifact a, String codenamebase )
589     {
590         return "ext/" + codenamebase + "/" + a.getGroupId().replace( '.', '-') + "/" + a.getArtifactId() + ( a.getClassifier() != null ? "-" + a.getClassifier() : "" ) + "." + a.getArtifactHandler().getExtension();
591     }
592 
593     /**
594      * Create a timestamp for <code>OpenIDE-Module-Build-Version</code> manifest
595      * entry.
596      *
597      * It's created from the current time and formatted using a UTC timezone
598      * explicitly which makes created timestamp timezone-independent.
599      *
600      * @return timestamp represented as <code>201012292045</code>
601      */
602     private static String createTimestamp( Date date )
603     {
604         final SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyyMMddHHmm" );
605         dateFormat.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
606         final String timestamp = dateFormat.format( date );
607         return timestamp;
608     }
609 
610     static String stripVersionFromCodebaseName( String cnb )
611     {
612         // it can happen the moduleName is in format org.milos/1
613         String base = cnb;
614         int index = base.indexOf( '/' );
615         if ( index > -1 )
616         {
617             base = base.substring( 0, index ).trim();
618         }
619         return base;
620     }
621 
622     String conditionallyAddAttribute( Manifest.Section section, String key, String value )
623     {
624         Manifest.Attribute attr = section.getAttribute( key );
625         if ( attr == null )
626         {
627             attr = new Manifest.Attribute();
628             attr.setName( key );
629             attr.setValue( value != null ? value.replaceAll("\\s+", " ").trim() : "<undefined>" );
630             try
631             {
632                 section.addConfiguredAttribute( attr );
633             }
634             catch ( ManifestException ex )
635             {
636                 getLog().error( "Cannot update manifest (key=" + key + ")" );
637                 ex.printStackTrace();
638             }
639         }
640         return attr.getValue();
641     }
642 
643     /**
644      * Pick out the first sentence of a paragraph.
645      * @param paragraph some text (may be null)
646      * @return the first sentence (may be null)
647      */
648     static String shorten( String paragraph )
649     {
650         if ( paragraph == null || paragraph.length() == 0 )
651         {
652             return null;
653         }
654         BreakIterator breaker = BreakIterator.getSentenceInstance();
655         breaker.setText( paragraph );
656         return paragraph.substring( 0, breaker.following( 0 ) ).trim();
657     }
658 
659 //----------------------------------------------------------------------------------
660 // classpat checking related.
661 //----------------------------------------------------------------------------------
662     private void checkModuleClassPath( DependencyNode treeroot,
663         List<Artifact> libArtifacts,
664         Map<Artifact, ExamineManifest> examinerCache, List<ModuleWrapper> moduleArtifacts, String projectCodeNameBase )
665         throws IOException, MojoExecutionException, MojoFailureException
666     {
667         Set<String> deps = buildProjectDependencyClasses( project, libArtifacts );
668         deps.retainAll( allProjectClasses( project ) );
669 
670         Set<String> own = projectModuleOwnClasses( project, libArtifacts );
671         deps.removeAll( own );
672         CollectModuleLibrariesNodeVisitor visitor = new CollectModuleLibrariesNodeVisitor(
673             project.getRuntimeArtifacts(), examinerCache, getLog(), treeroot, useOSGiDependencies );
674         treeroot.accept( visitor );
675         Map<String, List<Artifact>> modules = visitor.getDeclaredArtifacts();
676         Map<Artifact, Set<String>> moduleAllClasses = new HashMap<Artifact, Set<String>>();
677 
678         for ( ModuleWrapper wr : moduleArtifacts )
679         {
680             if ( modules.containsKey( wr.artifact.getDependencyConflictId() ) )
681             {
682                 ExamineManifest man = examinerCache.get( wr.artifact );
683                 List<Artifact> arts = modules.get( wr.artifact.getDependencyConflictId() );
684                 Set<String>[] classes = visibleModuleClasses( arts, man, wr.dependency, projectCodeNameBase );
685                 deps.removeAll( classes[0] );
686                 moduleAllClasses.put( wr.artifact, classes[1] );
687             }
688         }
689 
690         //now we have the classes that are not in public packages of declared modules,
691         //but are being used
692         if ( !deps.isEmpty() )
693         {
694             Map<String, List<Artifact>> transmodules = visitor.getTransitiveArtifacts();
695             for ( ModuleWrapper wr : moduleArtifacts )
696             {
697                 if ( transmodules.containsKey( wr.artifact.getDependencyConflictId() ) )
698                 {
699                     ExamineManifest man = examinerCache.get( wr.artifact );
700                     List<Artifact> arts = transmodules.get( wr.artifact.getDependencyConflictId() );
701                     Set<String>[] classes = visibleModuleClasses( arts, man, wr.dependency, projectCodeNameBase );
702                     classes[0].retainAll( deps );
703                     if ( classes[0].size() > 0 )
704                     {
705                         String module = wr.osgi ? "OSGi bundle" : "module";
706                         getLog().error(
707                             "Project uses classes from transitive " + module + " " + wr.artifact.getId() + " which will not be accessible at runtime." );
708                         getLog().info( "    To fix the problem, add this module as direct dependency. For OSGi bundles that are supposed to be wrapped in NetBeans modules, use the useOSGiDependencies=false parameter");
709                         deps.removeAll( classes[0] );
710                     }
711                     classes[1].retainAll( deps );
712                     if ( classes[1].size() > 0 )
713                     {
714                         getLog().info( "Private classes referenced in transitive module: " + Arrays.toString( classes[1].toArray() ) );
715                         getLog().error(
716                             "Project depends on packages not accessible at runtime in transitive module " + wr.artifact.getId() + " which will not be accessible at runtime." );
717                         deps.removeAll( classes[1] );
718                     }
719                 }
720             }
721             for ( Map.Entry<Artifact, Set<String>> e : moduleAllClasses.entrySet() )
722             {
723                 List<String> strs = new ArrayList<String>( deps );
724                 if ( deps.removeAll( e.getValue() ) )
725                 {
726                     strs.retainAll( e.getValue() );
727                     getLog().info( "Private classes referenced in module: " + Arrays.toString( strs.toArray() ) );
728                     getLog().error( "Project depends on packages not accessible at runtime in module " + e.getKey().getId() );
729                 }
730             }
731             if ( verifyRuntime.equalsIgnoreCase( FAIL ) )
732             {
733                 if ( !deps.isEmpty() )
734                 {
735                     throw new MojoFailureException( "Uncategorized problems with NetBeans dependency verification (maybe MNBMODULE-102 or wrong maven dependency metadata). Supposedly external classes are used in the project's binaries but the classes are not found on classpath. Class usages: " + deps );
736                 }
737                 else
738                 {
739                     throw new MojoFailureException( "See above for failures in runtime NetBeans dependencies verification." );
740                 }
741             }
742         }
743     }
744 
745     /**
746      * The current projects's dependencies, includes classes used in teh module itself
747      * and the classpath libraries as well.
748      * @param project
749      * @param libraries
750      * @return
751      * @throws java.io.IOException
752      */
753     private Set<String> buildProjectDependencyClasses( MavenProject project, List<Artifact> libraries )
754         throws IOException
755     {
756         Set<String> dependencyClasses = new HashSet<String>();
757 
758         String outputDirectory = project.getBuild().getOutputDirectory();
759         dependencyClasses.addAll( buildDependencyClasses( outputDirectory ) );
760 
761         for ( Artifact lib : libraries )
762         {
763             dependencyClasses.addAll( buildDependencyClasses( lib.getFile().getAbsolutePath() ) );
764         }
765         return dependencyClasses;
766     }
767 
768     @SuppressWarnings( "unchecked" )
769     private Set<String> projectModuleOwnClasses( MavenProject project, List<Artifact> libraries )
770         throws IOException
771     {
772         Set<String> projectClasses = new HashSet<String>();
773         DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer();
774 
775         String outputDirectory = project.getBuild().getOutputDirectory();
776         URL fl = new File( outputDirectory ).toURI().toURL();
777         projectClasses.addAll( analyzer.analyze( fl ) );
778 
779         for ( Artifact lib : libraries )
780         {
781             URL url = lib.getFile().toURI().toURL();
782             projectClasses.addAll( analyzer.analyze( url ) );
783         }
784 
785         return projectClasses;
786     }
787 
788     /**
789      * complete list of classes on project runtime classpath (excluding
790      * jdk bit)
791      * @param project
792      * @return
793      * @throws java.io.IOException
794      */
795     @SuppressWarnings( "unchecked" )
796     private Set<String> allProjectClasses( MavenProject project )
797         throws IOException
798     {
799         Set<String> projectClasses = new HashSet<String>();
800         DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer();
801 
802         String outputDirectory = project.getBuild().getOutputDirectory();
803         URL fl = new File( outputDirectory ).toURI().toURL();
804         projectClasses.addAll( analyzer.analyze( fl ) );
805 
806         List<Artifact> libs = project.getRuntimeArtifacts();
807 
808         for ( Artifact lib : libs )
809         {
810             URL url = lib.getFile().toURI().toURL();
811             projectClasses.addAll( analyzer.analyze( url ) );
812         }
813 
814         return projectClasses;
815     }
816 
817     private Set<String>[] visibleModuleClasses( List<Artifact> moduleLibraries,
818         ExamineManifest manifest, Dependency dep, String projectCodeNameBase )
819         throws IOException, MojoFailureException
820     {
821         Set<String> moduleClasses = new HashSet<String>();
822         Set<String> visibleModuleClasses = new HashSet<String>();
823         DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer();
824         String type = dep.getType();
825         if ( dep.getExplicitValue() != null )
826         {
827             if ( dep.getExplicitValue().contains( "=" ) )
828             {
829                 type = "impl";
830             }
831         }
832         if ( type == null || "loose".equals( type ) )
833         {
834             type = "spec";
835         }
836 
837         for ( Artifact lib : moduleLibraries )
838         {
839             URL url = lib.getFile().toURI().toURL();
840             moduleClasses.addAll( analyzer.analyze( url ) );
841         }
842 
843         if ( "spec".equals( type ) )
844         {
845             String cnb = stripVersionFromCodebaseName( projectCodeNameBase );
846             if ( manifest.hasFriendPackages() && !manifest.getFriends().contains( cnb ) )
847             {
848                 String message = "Module has friend dependency on " + manifest.getModule() + " but is not listed as a friend.";
849                 if ( verifyRuntime.equalsIgnoreCase( FAIL ) )
850                 {
851                     throw new MojoFailureException( message );
852                 }
853                 else
854                 {
855                     getLog().warn( message );
856                 }
857             }
858             List<Pattern> compiled = createCompiledPatternList( manifest.getPackages() );
859             if ( useOSGiDependencies && manifest.isOsgiBundle() )
860             {
861                 // TODO how to extract the public packages in osgi bundles easily..
862                 compiled = Collections.singletonList( Pattern.compile( "(.+)" ) );
863             }
864             for ( String clazz : moduleClasses )
865             {
866                 for ( Pattern patt : compiled )
867                 {
868                     if ( patt.matcher( clazz ).matches() ) 
869                     {
870                         visibleModuleClasses.add( clazz );
871                         break;
872                     }
873                 }
874             }
875 
876         }
877         else if ( "impl".equals( type ) )
878         {
879             visibleModuleClasses.addAll( moduleClasses );
880         }
881         else
882         {
883             //HUH?
884             throw new MojoFailureException( "Wrong type of module dependency " + type );
885         }
886 
887         return new Set[]
888             {
889                 visibleModuleClasses,
890                 moduleClasses
891             };
892     }
893 
894     static List<Pattern> createCompiledPatternList( List<String> packages )
895     {
896         List<Pattern> toRet = new ArrayList<Pattern>();
897         for ( String token : packages )
898         {
899             if ( token.endsWith( ".**" ) )
900             {
901                 String patt = "^" + Pattern.quote( token.substring( 0, token.length() - 2 ) ) + "(.+)";
902                 toRet.add( 0, Pattern.compile( patt ) );
903             }
904             else
905             {
906                 String patt = "^" + Pattern.quote( token.substring( 0, token.length() - 1 ) ) + "([^\\.]+)";
907                 toRet.add( Pattern.compile( patt ) );
908             }
909         }
910         return toRet;
911     }
912 
913     @SuppressWarnings( "unchecked" )
914     private Set<String> buildDependencyClasses( String path )
915         throws IOException
916     {
917         URL url = new File( path ).toURI().toURL();
918         ASMDependencyAnalyzer dependencyAnalyzer = new ASMDependencyAnalyzer();
919         return dependencyAnalyzer.analyze( url );
920     }
921 }