Coverage Report - org.codehaus.mojo.nbm.NetBeansManifestUpdateMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
NetBeansManifestUpdateMojo
0%
0/293
0%
0/166
8.385
 
 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  0
 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  0
         super.registerNbmAntTasks();
 287  
         NetBeansModule module;
 288  0
         if ( descriptor != null && descriptor.exists() )
 289  
         {
 290  0
             module = readModuleDescriptor( descriptor );
 291  0
             getLog().warn( "descriptor parameter is deprecated, use equivalent mojo parameters instead.");
 292  
         }
 293  
         else
 294  
         {
 295  0
             module = createDefaultDescriptor( project, false );
 296  
         }
 297  
         
 298  0
         String mtype = moduleType;
 299  
         //same moduleType related code in CreateNetBeansFileStructure.java
 300  0
         if ("normal".equals(mtype) && module.getModuleType() != null) {
 301  0
             mtype = module.getModuleType();
 302  0
             getLog().warn( "moduleType in module descriptor is deprecated, use the plugin's parameter moduleType");
 303  
         }
 304  0
         if (!"normal".equals(mtype) && !"autoload".equals(mtype) && !"eager".equals(mtype) && !"disabled".equals(mtype)) {
 305  0
             getLog().error( "Only 'normal,autoload,eager,disabled' are allowed values in the moduleType parameter");
 306  
         }
 307  0
         boolean autoload = "autoload".equals( mtype );
 308  0
         boolean eager = "eager".equals( mtype );
 309  
         
 310  
 
 311  0
         String moduleName = codeNameBase;
 312  0
         if (module.getCodeNameBase() != null) {
 313  0
             moduleName = module.getCodeNameBase();
 314  0
             getLog().warn( "codeNameBase in module descriptor is deprecated, use the plugin's parameter codeNameBase");
 315  
         }
 316  0
         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  0
         File specialManifest = sourceManifestFile;
 320  0
         File nbmManifest = ( module.getManifest() != null ? new File(
 321  
             project.getBasedir(), module.getManifest() ) : null );
 322  0
         if ( nbmManifest != null && nbmManifest.exists() )
 323  
         {
 324  
             //deprecated, but if actually defined, will use it.
 325  0
             specialManifest = nbmManifest;
 326  
         }
 327  0
         ExamineManifest examinator = new ExamineManifest( getLog() );
 328  0
         if ( specialManifest != null && specialManifest.exists() )
 329  
         {
 330  0
             examinator.setManifestFile( specialManifest );
 331  0
             examinator.checkFile();
 332  
         }
 333  
         else
 334  
         {
 335  
 //            examinator.setJarFile( jarFile );
 336  
         }
 337  
 
 338  0
         getLog().info( "NBM Plugin generates manifest" );
 339  
 
 340  0
         Manifest manifest = null;
 341  0
         if ( specialManifest != null && specialManifest.exists() )
 342  
         {
 343  0
             Reader reader = null;
 344  
             try
 345  
             {
 346  0
                 reader = new InputStreamReader( new FileInputStream( specialManifest ) );
 347  0
                 manifest = new Manifest( reader );
 348  
             }
 349  0
             catch ( IOException exc )
 350  
             {
 351  0
                 manifest = new Manifest();
 352  0
                 getLog().warn( "Error reading manifest at " + specialManifest, exc );
 353  
             }
 354  0
             catch ( ManifestException ex )
 355  
             {
 356  0
                 getLog().warn( "Error reading manifest at " + specialManifest, ex );
 357  0
                 manifest = new Manifest();
 358  
             }
 359  
             finally
 360  
             {
 361  0
                 IOUtil.close( reader );
 362  0
             }
 363  0
         }
 364  
         else
 365  
         {
 366  0
             manifest = new Manifest();
 367  
         }
 368  0
         Date date = new Date();
 369  0
         String specVersion = AdaptNbVersion.adaptVersion( project.getVersion(),
 370  
             AdaptNbVersion.TYPE_SPECIFICATION, date );
 371  0
         String implVersion = AdaptNbVersion.adaptVersion( project.getVersion(),
 372  
             AdaptNbVersion.TYPE_IMPLEMENTATION, date );
 373  0
         Manifest.Section mainSection = manifest.getMainSection();
 374  0
         conditionallyAddAttribute( mainSection,
 375  
             "OpenIDE-Module-Specification-Version", specVersion );
 376  0
         conditionallyAddAttribute( mainSection,
 377  
             "OpenIDE-Module-Implementation-Version", implVersion );
 378  0
         if (autoload || eager) { //MNBMODULE-194
 379  0
             conditionallyAddAttribute( mainSection, "AutoUpdate-Show-In-Client", "false");
 380  
         }
 381  0
         final String timestamp = createTimestamp( date );
 382  0
         conditionallyAddAttribute( mainSection, "OpenIDE-Module-Build-Version",
 383  
             timestamp );
 384  0
         String projectCNB = conditionallyAddAttribute( mainSection, "OpenIDE-Module", moduleName );
 385  
         String packagesValue;
 386  0
         if ( publicPackages != null && publicPackages.size() > 0 )
 387  
         {
 388  0
             StringBuilder sb = new StringBuilder();
 389  0
             for ( String pub : publicPackages )
 390  
             {
 391  0
                 if (pub == null) { //#MNBMODULE-237
 392  0
                     continue;
 393  
                 }
 394  0
                 if ( pub.endsWith( ".**" ) )
 395  
                 {
 396  
                     // well, just sort of wrong value but accept
 397  0
                     sb.append( pub );
 398  
                 }
 399  0
                 else if ( pub.endsWith( ".*" ) )
 400  
                 {
 401  
                     //multipackage value
 402  0
                     sb.append( pub ).append( "*" );
 403  
                 }
 404  
                 else
 405  
                 {
 406  0
                     sb.append( pub ).append( ".*" );
 407  
                 }
 408  0
                 sb.append( ", " );
 409  0
             }
 410  0
             if (sb.length() > 1) { //if only item is null, we have empty builder
 411  0
                 sb.setLength( sb.length() - 2 ); //cut the last 2 ", " characters
 412  0
                 packagesValue = sb.toString();
 413  
             } else {
 414  
                 // no packages available
 415  0
                 packagesValue = "-";
 416  
             }
 417  0
         }
 418  
         else
 419  
         {
 420  
             // no packages available
 421  0
             packagesValue = "-";
 422  
         }
 423  0
         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  0
         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  0
         if ( !examinator.isLocalized() )
 431  
         {
 432  0
             conditionallyAddAttribute( mainSection,
 433  
                 "OpenIDE-Module-Display-Category", project.getGroupId() );
 434  0
             conditionallyAddAttribute( mainSection, "OpenIDE-Module-Name",
 435  
                 project.getName() );
 436  0
             conditionallyAddAttribute( mainSection,
 437  
                 "OpenIDE-Module-Short-Description", shorten( project.getDescription() ) );
 438  0
             conditionallyAddAttribute( mainSection,
 439  
                 "OpenIDE-Module-Long-Description", project.getDescription() );
 440  
         }
 441  0
         getLog().debug( "module =" + module );
 442  
         
 443  0
             DependencyNode treeroot = createDependencyTree( project, dependencyGraphBuilder, "compile" );
 444  0
             Map<Artifact, ExamineManifest> examinerCache = new HashMap<Artifact, ExamineManifest>();
 445  
             @SuppressWarnings( "unchecked" )
 446  0
             List<Artifact> libArtifacts = getLibraryArtifacts( treeroot, module, project.getRuntimeArtifacts(),
 447  
                 examinerCache, getLog(), useOSGiDependencies );
 448  0
             List<ModuleWrapper> moduleArtifacts = getModuleDependencyArtifacts( treeroot, module, moduleDependencies, project, examinerCache,
 449  
                 libArtifacts, getLog(), useOSGiDependencies );
 450  0
             StringBuilder classPath = new StringBuilder();
 451  0
             StringBuilder mavenClassPath = new StringBuilder();
 452  0
             String dependencies = "";
 453  0
             String depSeparator = " ";
 454  
 
 455  0
             for ( Artifact a : libArtifacts )
 456  
             {
 457  0
                 if (classPath.length() > 0)
 458  
                 {
 459  0
                     classPath.append(' ');
 460  
                 }
 461  0
                 classPath.append(artifactToClassPathEntry( a, codeNameBase ));
 462  0
                 if ( mavenClassPath.length() > 0 )
 463  
                 {
 464  0
                     mavenClassPath.append( ' ' );
 465  
                 }
 466  0
                 mavenClassPath.append( a.getGroupId() ).append( ':' ).append( a.getArtifactId() ).append( ':' ).append( a.getBaseVersion() );
 467  0
                 if (a.getClassifier() != null) 
 468  
                 {
 469  0
                     mavenClassPath.append(":").append(a.getClassifier());
 470  
             }
 471  0
             }
 472  
 
 473  0
             for ( ModuleWrapper wr : moduleArtifacts )
 474  
             {
 475  0
                 if ( wr.transitive )
 476  
                 {
 477  0
                     continue;
 478  
                 }
 479  0
                 Dependency dep = wr.dependency;
 480  0
                 Artifact artifact = wr.artifact;
 481  0
                 ExamineManifest depExaminator = examinerCache.get( artifact );
 482  0
                 String type = dep.getType();
 483  0
                 String depToken = dep.getExplicitValue();
 484  0
                 if ( depToken == null )
 485  
                 {
 486  0
                     if ( "loose".equals( type ) )
 487  
                     {
 488  0
                         depToken = depExaminator.getModuleWithRelease();
 489  
                     }
 490  0
                     else if ( "spec".equals( type ) )
 491  
                     {
 492  0
                         depToken =
 493  
                             depExaminator.getModuleWithRelease()
 494  
                                 + " > "
 495  
                                 + ( depExaminator.isNetBeansModule() ? depExaminator.getSpecVersion()
 496  
                                                 : AdaptNbVersion.adaptVersion( depExaminator.getSpecVersion(),
 497  
                                                                                AdaptNbVersion.TYPE_SPECIFICATION, date ) );
 498  
                     }
 499  0
                     else if ( "impl".equals( type ) )
 500  
                     {
 501  0
                         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  0
                         throw new MojoExecutionException(
 511  
                             "Wrong type of NetBeans dependency: " + type + " Allowed values are: loose, spec, impl." );
 512  
                     }
 513  
                 }
 514  0
                 if ( depToken == null )
 515  
                 {
 516  
                     //TODO report
 517  0
                     getLog().error(
 518  
                         "Cannot properly resolve the NetBeans dependency for " + dep.getId() );
 519  
                 }
 520  
                 else
 521  
                 {
 522  0
                     dependencies = dependencies + depSeparator + depToken;
 523  0
                     depSeparator = ", ";
 524  
                 }
 525  0
             }
 526  0
             if ( !verifyRuntime.equalsIgnoreCase( SKIP ) )
 527  
             {
 528  
                 try
 529  
                 {
 530  0
                     checkModuleClassPath( treeroot, libArtifacts, examinerCache, moduleArtifacts, projectCNB );
 531  
                 }
 532  0
                 catch ( IOException ex )
 533  
                 {
 534  0
                     throw new MojoExecutionException( "Error while checking runtime dependencies", ex );
 535  0
                 }
 536  
             }
 537  
 
 538  0
             if ( nbmJavahelpSource.exists() )
 539  
             {
 540  0
                 String moduleJarName = stripVersionFromCodebaseName( moduleName ).replace( ".", "-" );
 541  0
                 classPath.append( " docs/").append( moduleJarName ).append( ".jar" );
 542  
             }
 543  
 
 544  0
             if ( classPath.length() > 0 )
 545  
             {
 546  0
                 conditionallyAddAttribute( mainSection, "X-Class-Path", classPath.toString().trim() );
 547  
             }
 548  0
             if ( mavenClassPath.length() > 0)
 549  
             {
 550  0
                 conditionallyAddAttribute( mainSection, "Maven-Class-Path", mavenClassPath.toString() );
 551  
             }
 552  0
             if ( dependencies.length() > 0 )
 553  
             {
 554  0
                 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  0
         PrintWriter writer = null;
 567  
         try
 568  
         {
 569  0
             if ( !targetManifestFile.exists() )
 570  
             {
 571  0
                 targetManifestFile.getParentFile().mkdirs();
 572  0
                 targetManifestFile.createNewFile();
 573  
             }
 574  0
             writer = new PrintWriter( targetManifestFile, "UTF-8" ); //TODO really UTF-8??
 575  0
             manifest.write( writer );
 576  
         }
 577  0
         catch ( IOException ex )
 578  
         {
 579  0
             throw new MojoExecutionException( ex.getMessage(), ex );
 580  
         }
 581  
         finally
 582  
         {
 583  0
             IOUtil.close( writer );
 584  0
         }
 585  0
     }
 586  
 
 587  
     //MNBMODULE-137
 588  
     static String artifactToClassPathEntry( Artifact a, String codenamebase )
 589  
     {
 590  0
         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  0
         final SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyyMMddHHmm" );
 605  0
         dateFormat.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
 606  0
         final String timestamp = dateFormat.format( date );
 607  0
         return timestamp;
 608  
     }
 609  
 
 610  
     static String stripVersionFromCodebaseName( String cnb )
 611  
     {
 612  
         // it can happen the moduleName is in format org.milos/1
 613  0
         String base = cnb;
 614  0
         int index = base.indexOf( '/' );
 615  0
         if ( index > -1 )
 616  
         {
 617  0
             base = base.substring( 0, index ).trim();
 618  
         }
 619  0
         return base;
 620  
     }
 621  
 
 622  
     String conditionallyAddAttribute( Manifest.Section section, String key, String value )
 623  
     {
 624  0
         Manifest.Attribute attr = section.getAttribute( key );
 625  0
         if ( attr == null )
 626  
         {
 627  0
             attr = new Manifest.Attribute();
 628  0
             attr.setName( key );
 629  0
             attr.setValue( value != null ? value.replaceAll("\\s+", " ").trim() : "<undefined>" );
 630  
             try
 631  
             {
 632  0
                 section.addConfiguredAttribute( attr );
 633  
             }
 634  0
             catch ( ManifestException ex )
 635  
             {
 636  0
                 getLog().error( "Cannot update manifest (key=" + key + ")" );
 637  0
                 ex.printStackTrace();
 638  0
             }
 639  
         }
 640  0
         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  0
         if ( paragraph == null || paragraph.length() == 0 )
 651  
         {
 652  0
             return null;
 653  
         }
 654  0
         BreakIterator breaker = BreakIterator.getSentenceInstance();
 655  0
         breaker.setText( paragraph );
 656  0
         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  0
         Set<String> deps = buildProjectDependencyClasses( project, libArtifacts );
 668  0
         deps.retainAll( allProjectClasses( project ) );
 669  
 
 670  0
         Set<String> own = projectModuleOwnClasses( project, libArtifacts );
 671  0
         deps.removeAll( own );
 672  0
         CollectModuleLibrariesNodeVisitor visitor = new CollectModuleLibrariesNodeVisitor(
 673  
             project.getRuntimeArtifacts(), examinerCache, getLog(), treeroot, useOSGiDependencies );
 674  0
         treeroot.accept( visitor );
 675  0
         Map<String, List<Artifact>> modules = visitor.getDeclaredArtifacts();
 676  0
         Map<Artifact, Set<String>> moduleAllClasses = new HashMap<Artifact, Set<String>>();
 677  
 
 678  0
         for ( ModuleWrapper wr : moduleArtifacts )
 679  
         {
 680  0
             if ( modules.containsKey( wr.artifact.getDependencyConflictId() ) )
 681  
             {
 682  0
                 ExamineManifest man = examinerCache.get( wr.artifact );
 683  0
                 List<Artifact> arts = modules.get( wr.artifact.getDependencyConflictId() );
 684  0
                 Set<String>[] classes = visibleModuleClasses( arts, man, wr.dependency, projectCodeNameBase );
 685  0
                 deps.removeAll( classes[0] );
 686  0
                 moduleAllClasses.put( wr.artifact, classes[1] );
 687  
             }
 688  0
         }
 689  
 
 690  
         //now we have the classes that are not in public packages of declared modules,
 691  
         //but are being used
 692  0
         if ( !deps.isEmpty() )
 693  
         {
 694  0
             Map<String, List<Artifact>> transmodules = visitor.getTransitiveArtifacts();
 695  0
             for ( ModuleWrapper wr : moduleArtifacts )
 696  
             {
 697  0
                 if ( transmodules.containsKey( wr.artifact.getDependencyConflictId() ) )
 698  
                 {
 699  0
                     ExamineManifest man = examinerCache.get( wr.artifact );
 700  0
                     List<Artifact> arts = transmodules.get( wr.artifact.getDependencyConflictId() );
 701  0
                     Set<String>[] classes = visibleModuleClasses( arts, man, wr.dependency, projectCodeNameBase );
 702  0
                     classes[0].retainAll( deps );
 703  0
                     if ( classes[0].size() > 0 )
 704  
                     {
 705  0
                         String module = wr.osgi ? "OSGi bundle" : "module";
 706  0
                         getLog().error(
 707  
                             "Project uses classes from transitive " + module + " " + wr.artifact.getId() + " which will not be accessible at runtime." );
 708  0
                         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  0
                         deps.removeAll( classes[0] );
 710  
                     }
 711  0
                     classes[1].retainAll( deps );
 712  0
                     if ( classes[1].size() > 0 )
 713  
                     {
 714  0
                         getLog().info( "Private classes referenced in transitive module: " + Arrays.toString( classes[1].toArray() ) );
 715  0
                         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  0
                         deps.removeAll( classes[1] );
 718  
                     }
 719  
                 }
 720  0
             }
 721  0
             for ( Map.Entry<Artifact, Set<String>> e : moduleAllClasses.entrySet() )
 722  
             {
 723  0
                 List<String> strs = new ArrayList<String>( deps );
 724  0
                 if ( deps.removeAll( e.getValue() ) )
 725  
                 {
 726  0
                     strs.retainAll( e.getValue() );
 727  0
                     getLog().info( "Private classes referenced in module: " + Arrays.toString( strs.toArray() ) );
 728  0
                     getLog().error( "Project depends on packages not accessible at runtime in module " + e.getKey().getId() );
 729  
                 }
 730  0
             }
 731  0
             if ( verifyRuntime.equalsIgnoreCase( FAIL ) )
 732  
             {
 733  0
                 if ( !deps.isEmpty() )
 734  
                 {
 735  0
                     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  0
                     throw new MojoFailureException( "See above for failures in runtime NetBeans dependencies verification." );
 740  
                 }
 741  
             }
 742  
         }
 743  0
     }
 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  0
         Set<String> dependencyClasses = new HashSet<String>();
 757  
 
 758  0
         String outputDirectory = project.getBuild().getOutputDirectory();
 759  0
         dependencyClasses.addAll( buildDependencyClasses( outputDirectory ) );
 760  
 
 761  0
         for ( Artifact lib : libraries )
 762  
         {
 763  0
             dependencyClasses.addAll( buildDependencyClasses( lib.getFile().getAbsolutePath() ) );
 764  0
         }
 765  0
         return dependencyClasses;
 766  
     }
 767  
 
 768  
     @SuppressWarnings( "unchecked" )
 769  
     private Set<String> projectModuleOwnClasses( MavenProject project, List<Artifact> libraries )
 770  
         throws IOException
 771  
     {
 772  0
         Set<String> projectClasses = new HashSet<String>();
 773  0
         DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer();
 774  
 
 775  0
         String outputDirectory = project.getBuild().getOutputDirectory();
 776  0
         URL fl = new File( outputDirectory ).toURI().toURL();
 777  0
         projectClasses.addAll( analyzer.analyze( fl ) );
 778  
 
 779  0
         for ( Artifact lib : libraries )
 780  
         {
 781  0
             URL url = lib.getFile().toURI().toURL();
 782  0
             projectClasses.addAll( analyzer.analyze( url ) );
 783  0
         }
 784  
 
 785  0
         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  0
         Set<String> projectClasses = new HashSet<String>();
 800  0
         DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer();
 801  
 
 802  0
         String outputDirectory = project.getBuild().getOutputDirectory();
 803  0
         URL fl = new File( outputDirectory ).toURI().toURL();
 804  0
         projectClasses.addAll( analyzer.analyze( fl ) );
 805  
 
 806  0
         List<Artifact> libs = project.getRuntimeArtifacts();
 807  
 
 808  0
         for ( Artifact lib : libs )
 809  
         {
 810  0
             URL url = lib.getFile().toURI().toURL();
 811  0
             projectClasses.addAll( analyzer.analyze( url ) );
 812  0
         }
 813  
 
 814  0
         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  0
         Set<String> moduleClasses = new HashSet<String>();
 822  0
         Set<String> visibleModuleClasses = new HashSet<String>();
 823  0
         DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer();
 824  0
         String type = dep.getType();
 825  0
         if ( dep.getExplicitValue() != null )
 826  
         {
 827  0
             if ( dep.getExplicitValue().contains( "=" ) )
 828  
             {
 829  0
                 type = "impl";
 830  
             }
 831  
         }
 832  0
         if ( type == null || "loose".equals( type ) )
 833  
         {
 834  0
             type = "spec";
 835  
         }
 836  
 
 837  0
         for ( Artifact lib : moduleLibraries )
 838  
         {
 839  0
             URL url = lib.getFile().toURI().toURL();
 840  0
             moduleClasses.addAll( analyzer.analyze( url ) );
 841  0
         }
 842  
 
 843  0
         if ( "spec".equals( type ) )
 844  
         {
 845  0
             String cnb = stripVersionFromCodebaseName( projectCodeNameBase );
 846  0
             if ( manifest.hasFriendPackages() && !manifest.getFriends().contains( cnb ) )
 847  
             {
 848  0
                 String message = "Module has friend dependency on " + manifest.getModule() + " but is not listed as a friend.";
 849  0
                 if ( verifyRuntime.equalsIgnoreCase( FAIL ) )
 850  
                 {
 851  0
                     throw new MojoFailureException( message );
 852  
                 }
 853  
                 else
 854  
                 {
 855  0
                     getLog().warn( message );
 856  
                 }
 857  
             }
 858  0
             List<Pattern> compiled = createCompiledPatternList( manifest.getPackages() );
 859  0
             if ( useOSGiDependencies && manifest.isOsgiBundle() )
 860  
             {
 861  
                 // TODO how to extract the public packages in osgi bundles easily..
 862  0
                 compiled = Collections.singletonList( Pattern.compile( "(.+)" ) );
 863  
             }
 864  0
             for ( String clazz : moduleClasses )
 865  
             {
 866  0
                 for ( Pattern patt : compiled )
 867  
                 {
 868  0
                     if ( patt.matcher( clazz ).matches() ) 
 869  
                     {
 870  0
                         visibleModuleClasses.add( clazz );
 871  0
                         break;
 872  
                     }
 873  0
                 }
 874  0
             }
 875  
 
 876  0
         }
 877  0
         else if ( "impl".equals( type ) )
 878  
         {
 879  0
             visibleModuleClasses.addAll( moduleClasses );
 880  
         }
 881  
         else
 882  
         {
 883  
             //HUH?
 884  0
             throw new MojoFailureException( "Wrong type of module dependency " + type );
 885  
         }
 886  
 
 887  0
         return new Set[]
 888  
             {
 889  
                 visibleModuleClasses,
 890  
                 moduleClasses
 891  
             };
 892  
     }
 893  
 
 894  
     static List<Pattern> createCompiledPatternList( List<String> packages )
 895  
     {
 896  0
         List<Pattern> toRet = new ArrayList<Pattern>();
 897  0
         for ( String token : packages )
 898  
         {
 899  0
             if ( token.endsWith( ".**" ) )
 900  
             {
 901  0
                 String patt = "^" + Pattern.quote( token.substring( 0, token.length() - 2 ) ) + "(.+)";
 902  0
                 toRet.add( 0, Pattern.compile( patt ) );
 903  0
             }
 904  
             else
 905  
             {
 906  0
                 String patt = "^" + Pattern.quote( token.substring( 0, token.length() - 1 ) ) + "([^\\.]+)";
 907  0
                 toRet.add( Pattern.compile( patt ) );
 908  
             }
 909  0
         }
 910  0
         return toRet;
 911  
     }
 912  
 
 913  
     @SuppressWarnings( "unchecked" )
 914  
     private Set<String> buildDependencyClasses( String path )
 915  
         throws IOException
 916  
     {
 917  0
         URL url = new File( path ).toURI().toURL();
 918  0
         ASMDependencyAnalyzer dependencyAnalyzer = new ASMDependencyAnalyzer();
 919  0
         return dependencyAnalyzer.analyze( url );
 920  
     }
 921  
 }