org.apache.maven.plugin.eclipse.writers.AbstractEclipseManifestWriter Maven / Gradle / Ivy
package org.apache.maven.plugin.eclipse.writers;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.eclipse.Messages;
import org.apache.maven.plugin.ide.IdeDependency;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.IOUtil;
/**
* Common behaviours for creating or adapting the manifest files for eclipse environments.
*
* @author Richard van Nieuwenhoven
*/
public abstract class AbstractEclipseManifestWriter
extends AbstractEclipseWriter
{
protected static final String MANIFEST_MF_FILENAME = "MANIFEST.MF";
protected static final String META_INF_DIRECTORY = "META-INF";
public AbstractEclipseManifestWriter()
{
super();
}
/**
* Aphabeticaly sort the classpath. Do this by splitting it up, sort the entries and gleue them together again.
*
* @param newValue classpath to sort
* @return the sorted classpath
*/
protected String orderClasspath( String newValue )
{
if ( newValue == null )
{
return null;
}
String[] entries = newValue.split( " " );
Arrays.sort( entries );
StringBuilder buffer = new StringBuilder( newValue.length() );
for ( String entry : entries )
{
buffer.append( entry );
buffer.append( ' ' );
}
return buffer.toString();
}
/**
* Read and parse the existing manifest file.
*
* @param manifestFile file
* @return the read manifest
* @throws IOException if the file could not be read
*/
protected Manifest readExistingManifest( File manifestFile )
throws IOException
{
if ( !manifestFile.exists() )
{
return null;
}
Manifest existingManifest = new Manifest();
FileInputStream inputStream = new FileInputStream( manifestFile );
try
{
existingManifest.read( inputStream );
}
finally
{
IOUtil.close( inputStream );
}
return existingManifest;
}
/**
* Add one dependency to the blank separated classpath StringBuilder. When the project is available in the reactor
* (current build) then the project is used else the jar representing the artifact. System dependencies will only be
* included if they are in this project.
*
* @param classpath existing classpath to append
* @param dependency dependency to append as jar or as project
*/
protected void addDependencyToClassPath( StringBuilder classpath, IdeDependency dependency )
{
if ( !dependency.isTestDependency() && !dependency.isProvided()
&& !dependency.isSystemScopedOutsideProject( this.config.getProject() ) )
{
// blank is the separator in manifest classpath's
if ( classpath.length() != 0 )
{
classpath.append( ' ' );
}
// if the dependency is a workspace project add the project and not
// the jar
if ( !dependency.isReferencedProject() )
{
classpath.append( dependency.getFile().getName() );
}
else
{
classpath.append( dependency.getEclipseProjectName() ).append( ".jar" );
}
}
}
/**
* Check if the two manifests are equal. Manifest.equal can not be used because of the special case the Classpath
* entr, witch must be comaired sorted so that a different oder in the classpath does not result in "not equal".
* This not not realy correct but in this case it is more important to reduce the number of version-controll files.
*
* @param manifest the new manifest
* @param existingManifest to compaire the new one with
* @return are the manifests equal
*/
protected boolean areManifestsEqual( Manifest manifest, Manifest existingManifest )
{
if ( existingManifest == null )
{
log.info( "@@@ FALSE - Manifest are not equal because existingManifest is null" );
return false;
}
Set keys = new HashSet();
Attributes existingMap = existingManifest.getMainAttributes();
Attributes newMap = manifest.getMainAttributes();
keys.addAll( existingMap.keySet() );
keys.addAll( newMap.keySet() );
for ( Object key1 : keys )
{
Attributes.Name key = (Attributes.Name) key1;
String newValue = (String) newMap.get( key );
String existingValue = (String) existingMap.get( key );
// special case classpath... they are equal when there entries
// are equal
if ( Attributes.Name.CLASS_PATH.equals( key ) )
{
newValue = orderClasspath( newValue );
existingValue = orderClasspath( existingValue );
}
if ( ( newValue == null || !newValue.equals( existingValue ) )
&& ( existingValue == null || !existingValue.equals( newValue ) ) )
{
log.info( "@@@ FALSE - Manifest are not equal because key = " + key + " has existing value = "
+ existingValue + " and new value = " + newValue + " are different" );
return false;
}
}
log.info( "@@@ TRUE - Manifests are equal" );
return true;
}
/**
* Convert all dependencies in a blank seperated list of jars and projects representing the classpath.
*
* @return the blank separeted classpath string
*/
protected String constructManifestClasspath()
{
StringBuilder stringBuilder = new StringBuilder();
IdeDependency[] deps = this.config.getDeps();
for ( IdeDependency dep : deps )
{
addDependencyToClassPath( stringBuilder, dep );
}
return stringBuilder.toString();
}
/**
* Create a manifest contaigning the required classpath.
*
* @return the newly created manifest
*/
protected Manifest createNewManifest()
{
Manifest manifest = new Manifest();
manifest.getMainAttributes().put( Attributes.Name.MANIFEST_VERSION, "1.0" );
manifest.getMainAttributes().put( Attributes.Name.CLASS_PATH, constructManifestClasspath() );
return manifest;
}
/**
* Verify is the manifest sould be overwritten this sould take in account that the manifest should only be written
* if the contents of the classpath was changed not the order. The classpath sorting oder should be ignored.
*
* @param manifest the newly created classpath
* @param manifestFile the file where the manifest
* @return if the new manifest file must be written
* @throws MojoExecutionException
*/
protected boolean shouldNewManifestFileBeWritten( Manifest manifest, File manifestFile )
throws MojoExecutionException
{
try
{
Manifest existingManifest = readExistingManifest( manifestFile );
if ( areManifestsEqual( manifest, existingManifest ) )
{
log.info( Messages.getString( "EclipsePlugin.unchangedmanifest", manifestFile.getAbsolutePath() ) );
return false;
}
}
catch ( Exception e )
{
throw new MojoExecutionException( Messages.getString( "EclipseCleanMojo.nofilefound",
manifestFile.getAbsolutePath() ), e );
}
return true;
}
/**
* Search the project for the existing META-INF directory where the manifest should be located.
*
* @return the absolute path to the META-INF directory
* @throws MojoExecutionException
*/
protected abstract String getMetaInfBaseDirectory( MavenProject project )
throws MojoExecutionException;
/**
* If the existing manifest file located in getMetaInfBaseDirectory()
already has a correct
* MANIFEST_VERSION and CLASS_PATH value then do nothing.
*
* Otherwise generate a NEW (i.e the old one is overwritten) which only contains values for MANIFEST_VERSION
* and CLASS_PATH, all other previous entries are not kept.
*
* @param sourceDirs all eclipse source directorys
* @param localRepository the local reposetory
* @param buildOutputDirectory build output directory (target)
* @throws MojoExecutionException when writing the config files was not possible
*/
public void write()
throws MojoExecutionException
{
String metaInfBaseDirectory = getMetaInfBaseDirectory( this.config.getProject() );
if ( metaInfBaseDirectory == null )
{
// TODO: if this really is an error, shouldn't we stop the build??
throw new MojoExecutionException(
Messages.getString( "EclipseCleanMojo.nofilefound",
new Object[] { EclipseManifestWriter.META_INF_DIRECTORY } ) );
}
File manifestFile =
new File( metaInfBaseDirectory + File.separatorChar + EclipseManifestWriter.META_INF_DIRECTORY
+ File.separatorChar + EclipseManifestWriter.MANIFEST_MF_FILENAME );
Manifest manifest = createNewManifest();
if ( shouldNewManifestFileBeWritten( manifest, manifestFile ) )
{
log.info( "Writing manifest..." );
manifestFile.getParentFile().mkdirs();
try
{
FileOutputStream stream = new FileOutputStream( manifestFile );
manifest.write( stream );
stream.close();
}
catch ( Exception e )
{
this.log.error( Messages.getString( "EclipsePlugin.cantwritetofile",
new Object[] { manifestFile.getAbsolutePath() } ) );
}
}
}
}