1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.maven.plugin.eclipse.writers;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.util.Arrays;
26 import java.util.HashSet;
27 import java.util.Set;
28 import java.util.jar.Attributes;
29 import java.util.jar.Manifest;
30
31 import org.apache.maven.artifact.repository.ArtifactRepository;
32 import org.apache.maven.plugin.MojoExecutionException;
33 import org.apache.maven.plugin.eclipse.EclipseSourceDir;
34 import org.apache.maven.plugin.eclipse.Messages;
35 import org.apache.maven.plugin.eclipse.writers.wtp.AbstractWtpResourceWriter;
36 import org.apache.maven.plugin.ide.IdeDependency;
37 import org.apache.maven.project.MavenProject;
38 import org.codehaus.plexus.util.IOUtil;
39
40 /**
41 * Common behaviours for creating or adapting the manifest files for eclipse environments.
42 *
43 * @author <a href="mailto:nir@cfc.at">Richard van Nieuwenhoven </a>
44 */
45 public abstract class AbstractEclipseManifestWriter
46 extends AbstractEclipseWriter
47 {
48
49 protected static final String MANIFEST_MF_FILENAME = "MANIFEST.MF";
50
51 protected static final String META_INF_DIRECTORY = "META-INF";
52
53 public AbstractEclipseManifestWriter()
54 {
55 super();
56 }
57
58 /**
59 * Aphabeticaly sort the classpath. Do this by splitting it up, sort the entries and gleue them together again.
60 *
61 * @param newValue classpath to sort
62 * @return the sorted classpath
63 */
64 protected String orderClasspath( String newValue )
65 {
66 if ( newValue == null )
67 {
68 return null;
69 }
70 String[] entries = newValue.split( " " );
71 Arrays.sort( entries );
72 StringBuilder buffer = new StringBuilder( newValue.length() );
73 for (String entry : entries) {
74 buffer.append(entry);
75 buffer.append(' ');
76 }
77 return buffer.toString();
78 }
79
80 /**
81 * Read and parse the existing manifest file.
82 *
83 * @param manifestFile file
84 * @return the read manifest
85 * @throws IOException if the file could not be read
86 */
87 protected Manifest readExistingManifest( File manifestFile )
88 throws IOException
89 {
90 if ( !manifestFile.exists() )
91 {
92 return null;
93 }
94
95 Manifest existingManifest = new Manifest();
96 FileInputStream inputStream = new FileInputStream( manifestFile );
97 try
98 {
99 existingManifest.read( inputStream );
100 }
101 finally
102 {
103 IOUtil.close( inputStream );
104 }
105 return existingManifest;
106 }
107
108 /**
109 * Add one dependency to the blank separated classpath StringBuilder. When the project is available in the reactor
110 * (current build) then the project is used else the jar representing the artifact. System dependencies will only be
111 * included if they are in this project.
112 *
113 * @param classpath existing classpath to append
114 * @param dependency dependency to append as jar or as project
115 */
116 protected void addDependencyToClassPath( StringBuilder classpath, IdeDependency dependency )
117 {
118 if ( !dependency.isTestDependency() && !dependency.isProvided()
119 && !dependency.isSystemScopedOutsideProject( this.config.getProject() ) )
120 {
121
122 // blank is the separator in manifest classpath's
123 if ( classpath.length() != 0 )
124 {
125 classpath.append( ' ' );
126 }
127 // if the dependency is a workspace project add the project and not
128 // the jar
129 if ( !dependency.isReferencedProject() )
130 {
131 classpath.append( dependency.getFile().getName() );
132 }
133 else
134 {
135 classpath.append( dependency.getEclipseProjectName() ).append( ".jar" );
136 }
137 }
138 }
139
140 /**
141 * Check if the two manifests are equal. Manifest.equal can not be used because of the special case the Classpath
142 * entr, witch must be comaired sorted so that a different oder in the classpath does not result in "not equal".
143 * This not not realy correct but in this case it is more important to reduce the number of version-controll files.
144 *
145 * @param manifest the new manifest
146 * @param existingManifest to compaire the new one with
147 * @return are the manifests equal
148 */
149 protected boolean areManifestsEqual( Manifest manifest, Manifest existingManifest )
150 {
151 if ( existingManifest == null )
152 {
153 log.info( "@@@ FALSE - Manifest are not equal because existingManifest is null" );
154 return false;
155 }
156
157 Set keys = new HashSet();
158 Attributes existingMap = existingManifest.getMainAttributes();
159 Attributes newMap = manifest.getMainAttributes();
160 keys.addAll( existingMap.keySet() );
161 keys.addAll( newMap.keySet() );
162 for (Object key1 : keys) {
163 Attributes.Name key = (Attributes.Name) key1;
164 String newValue = (String) newMap.get(key);
165 String existingValue = (String) existingMap.get(key);
166 // special case classpath... they are equal when there entries
167 // are equal
168 if (Attributes.Name.CLASS_PATH.equals(key)) {
169 newValue = orderClasspath(newValue);
170 existingValue = orderClasspath(existingValue);
171 }
172 if ((newValue == null || !newValue.equals(existingValue))
173 && (existingValue == null || !existingValue.equals(newValue))) {
174 log.info("@@@ FALSE - Manifest are not equal because key = " + key + " has existing value = "
175 + existingValue + " and new value = " + newValue + " are different");
176 return false;
177 }
178 }
179 log.info( "@@@ TRUE - Manifests are equal" );
180 return true;
181 }
182
183 /**
184 * Convert all dependencies in a blank seperated list of jars and projects representing the classpath.
185 *
186 * @return the blank separeted classpath string
187 */
188 protected String constructManifestClasspath()
189 {
190 StringBuilder StringBuilder = new StringBuilder();
191 IdeDependency[] deps = this.config.getDeps();
192
193 for (IdeDependency dep : deps) {
194 addDependencyToClassPath(StringBuilder, dep);
195 }
196
197 return StringBuilder.toString();
198 }
199
200 /**
201 * Create a manifest contaigning the required classpath.
202 *
203 * @return the newly created manifest
204 */
205 protected Manifest createNewManifest()
206 {
207 Manifest manifest = new Manifest();
208 manifest.getMainAttributes().put( Attributes.Name.MANIFEST_VERSION, "1.0" );
209 manifest.getMainAttributes().put( Attributes.Name.CLASS_PATH, constructManifestClasspath() );
210 return manifest;
211 }
212
213 /**
214 * Verify is the manifest sould be overwritten this sould take in account that the manifest should only be written
215 * if the contents of the classpath was changed not the order. The classpath sorting oder should be ignored.
216 *
217 * @param manifest the newly created classpath
218 * @param manifestFile the file where the manifest
219 * @return if the new manifest file must be written
220 * @throws MojoExecutionException
221 */
222 protected boolean shouldNewManifestFileBeWritten( Manifest manifest, File manifestFile )
223 throws MojoExecutionException
224 {
225 try
226 {
227 Manifest existingManifest = readExistingManifest( manifestFile );
228 if ( areManifestsEqual( manifest, existingManifest ) )
229 {
230 this.log.info( Messages.getString( "EclipsePlugin.unchangedmanifest", manifestFile.getAbsolutePath() ) );
231 return false;
232 }
233 }
234 catch ( Exception e )
235 {
236 throw new MojoExecutionException( Messages.getString( "EclipseCleanMojo.nofilefound",
237 manifestFile.getAbsolutePath() ), e );
238 }
239 return true;
240 }
241
242 /**
243 * Search the project for the existing META-INF directory where the manifest should be located.
244 *
245 * @return the absolute path to the META-INF directory
246 * @throws MojoExecutionException
247 */
248 protected abstract String getMetaInfBaseDirectory( MavenProject project )
249 throws MojoExecutionException;
250
251 /**
252 * If the existing manifest file located in <code>getMetaInfBaseDirectory()</code> already has a correct
253 * MANIFEST_VERSION and CLASS_PATH value then do nothing.
254 * <p>
255 * Otherwise generate a <b>NEW</b> (i.e the old one is overwritten) which only contains values for MANIFEST_VERSION
256 * and CLASS_PATH, all other previous entries are not kept.
257 *
258 * @see AbstractWtpResourceWriter#write(EclipseSourceDir[], ArtifactRepository, File)
259 * @param sourceDirs all eclipse source directorys
260 * @param localRepository the local reposetory
261 * @param buildOutputDirectory build output directory (target)
262 * @throws MojoExecutionException when writing the config files was not possible
263 */
264 public void write()
265 throws MojoExecutionException
266 {
267 String metaInfBaseDirectory = getMetaInfBaseDirectory( this.config.getProject() );
268
269 if ( metaInfBaseDirectory == null )
270 {
271 // TODO: if this really is an error, shouldn't we stop the build??
272 throw new MojoExecutionException(
273 Messages.getString(
274 "EclipseCleanMojo.nofilefound",
275 new Object[] { EclipseManifestWriter.META_INF_DIRECTORY } ) );
276 }
277 File manifestFile =
278 new File( metaInfBaseDirectory + File.separatorChar + EclipseManifestWriter.META_INF_DIRECTORY
279 + File.separatorChar + EclipseManifestWriter.MANIFEST_MF_FILENAME );
280 Manifest manifest = createNewManifest();
281
282 if ( shouldNewManifestFileBeWritten( manifest, manifestFile ) )
283 {
284 log.info( "Writing manifest..." );
285
286 manifestFile.getParentFile().mkdirs();
287
288 try
289 {
290 FileOutputStream stream = new FileOutputStream( manifestFile );
291
292 manifest.write( stream );
293
294 stream.close();
295
296 }
297 catch ( Exception e )
298 {
299 this.log.error( Messages.getString( "EclipsePlugin.cantwritetofile",
300 new Object[] { manifestFile.getAbsolutePath() } ) );
301 }
302 }
303 }
304
305 }