View Javadoc

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.FileOutputStream;
23  import java.io.IOException;
24  import java.io.OutputStreamWriter;
25  import java.io.Writer;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugin.eclipse.BuildCommand;
36  import org.apache.maven.plugin.eclipse.Constants;
37  import org.apache.maven.plugin.eclipse.EclipseSourceDir;
38  import org.apache.maven.plugin.eclipse.Messages;
39  import org.apache.maven.plugin.ide.IdeDependency;
40  import org.apache.maven.plugin.ide.IdeUtils;
41  import org.codehaus.plexus.util.IOUtil;
42  import org.codehaus.plexus.util.StringUtils;
43  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
44  import org.codehaus.plexus.util.xml.XMLWriter;
45  
46  /**
47   * Writes eclipse .classpath file.
48   * 
49   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
50   * @author <a href="mailto:kenney@neonics.com">Kenney Westerhof</a>
51   * @author <a href="mailto:fgiust@apache.org">Fabrizio Giustina</a>
52   * @version $Id: EclipseClasspathWriter.java 636955 2008-03-14 02:10:42Z aheritier $
53   */
54  public class EclipseClasspathWriter
55      extends AbstractEclipseWriter
56  {
57  
58      /**
59       * Eclipse build path variable M2_REPO
60       */
61      private static final String M2_REPO = "M2_REPO"; //$NON-NLS-1$
62  
63      /**
64       * Attribute for sourcepath.
65       */
66      private static final String ATTR_SOURCEPATH = "sourcepath"; //$NON-NLS-1$
67  
68      /**
69       * Attribute for output.
70       */
71      private static final String ATTR_OUTPUT = "output"; //$NON-NLS-1$
72  
73      /**
74       * Attribute for path.
75       */
76      private static final String ATTR_PATH = "path"; //$NON-NLS-1$
77  
78      /**
79       * Attribute for kind - Container (con), Variable (var)..etc.
80       */
81      private static final String ATTR_KIND = "kind"; //$NON-NLS-1$
82  
83      /**
84       * Attribute value for kind: var
85       */
86      private static final String ATTR_VAR = "var"; //$NON-NLS-1$
87  
88      /**
89       * Attribute value for kind: lib
90       */
91      private static final String ATTR_LIB = "lib"; //$NON-NLS-1$
92  
93      /**
94       * Attribute value for kind: src
95       */
96      private static final String ATTR_SRC = "src"; //$NON-NLS-1$
97  
98      /**
99       * Attribute name for source file includes in a path.
100      */
101     private static final String ATTR_INCLUDING = "including";
102 
103     /**
104      * Attribute name for source file excludes in a path.
105      */
106     private static final String ATTR_EXCLUDING = "excluding";
107 
108     /**
109      * Element for classpathentry.
110      */
111     private static final String ELT_CLASSPATHENTRY = "classpathentry"; //$NON-NLS-1$
112 
113     /**
114      * Element for classpath.
115      */
116     private static final String ELT_CLASSPATH = "classpath"; //$NON-NLS-1$
117 
118     /**
119      * File name that stores project classpath settings.
120      */
121     private static final String FILE_DOT_CLASSPATH = ".classpath"; //$NON-NLS-1$
122 
123     /**
124      * @see org.apache.maven.plugin.eclipse.writers.EclipseWriter#write()
125      */
126     public void write()
127         throws MojoExecutionException
128     {
129 
130         Writer w;
131 
132         try
133         {
134             w =
135                 new OutputStreamWriter( new FileOutputStream( new File( config.getEclipseProjectDirectory(),
136                                                                         FILE_DOT_CLASSPATH ) ), "UTF-8" );
137         }
138         catch ( IOException ex )
139         {
140             throw new MojoExecutionException( Messages.getString( "EclipsePlugin.erroropeningfile" ), ex ); //$NON-NLS-1$
141         }
142 
143         XMLWriter writer = new PrettyPrintXMLWriter( w );
144 
145         writer.startElement( ELT_CLASSPATH );
146 
147         String defaultOutput =
148             IdeUtils.toRelativeAndFixSeparator( config.getProjectBaseDir(), config.getBuildOutputDirectory(), false );
149 
150         // ----------------------------------------------------------------------
151         // Source roots and resources
152         // ----------------------------------------------------------------------
153 
154         // List<EclipseSourceDir>
155         List specialSources = new ArrayList();
156 
157         // Map<String,List<EclipseSourceDir>>
158         Map byOutputDir = new HashMap();
159 
160         for ( int j = 0; j < config.getSourceDirs().length; j++ )
161         {
162             EclipseSourceDir dir = config.getSourceDirs()[j];
163 
164             // List<EclipseSourceDir>
165             List byOutputDirs = (List) byOutputDir.get( dir.getOutput() );
166             if ( byOutputDirs == null )
167             {
168                 // ArrayList<EclipseSourceDir>
169                 byOutputDir.put( dir.getOutput() == null ? defaultOutput : dir.getOutput(), byOutputDirs =
170                     new ArrayList() );
171             }
172             byOutputDirs.add( dir );
173         }
174 
175         for ( int j = 0; j < config.getSourceDirs().length; j++ )
176         {
177             EclipseSourceDir dir = config.getSourceDirs()[j];
178 
179             log.debug( "Processing " + ( dir.isResource() ? "re" : "" ) + "source " + dir.getPath() + ": output=" +
180                 dir.getOutput() + "; default output=" + defaultOutput );
181 
182             boolean isSpecial = false;
183 
184             // handle resource with nested output folders
185             if ( dir.isResource() )
186             {
187                 // Check if the output is a subdirectory of the default output,
188                 // and if the default output has any sources that copy there.
189 
190                 if ( dir.getOutput() != null // resource output dir is set
191                     &&
192                     !dir.getOutput().equals( defaultOutput ) // output dir is not default target/classes
193                     && dir.getOutput().startsWith( defaultOutput ) // ... but is nested
194                     && byOutputDir.get( defaultOutput ) != null // ???
195                     && !( (List) byOutputDir.get( defaultOutput ) ).isEmpty() // ???
196                 )
197                 {
198                     // do not specify as source since the output will be nested. Instead, mark
199                     // it as a todo, and handle it with a custom build.xml file later.
200 
201                     log.debug( "Marking as special to prevent output folder nesting: " + dir.getPath() + " (output=" +
202                         dir.getOutput() + ")" );
203 
204                     isSpecial = true;
205                     specialSources.add( dir );
206                 }
207             }
208 
209             writer.startElement( ELT_CLASSPATHENTRY );
210 
211             writer.addAttribute( ATTR_KIND, "src" ); //$NON-NLS-1$
212             writer.addAttribute( ATTR_PATH, dir.getPath() );
213 
214             if ( !isSpecial && dir.getOutput() != null && !defaultOutput.equals( dir.getOutput() ) )
215             {
216                 writer.addAttribute( ATTR_OUTPUT, dir.getOutput() );
217             }
218 
219             if ( StringUtils.isNotEmpty( dir.getInclude() ) )
220             {
221                 writer.addAttribute( ATTR_INCLUDING, dir.getInclude() );
222             }
223 
224             String excludes = dir.getExclude();
225 
226             if ( dir.isResource() )
227             {
228                 // automatically exclude java files: eclipse doesn't have the concept of resource directory so it will
229                 // try to compile any java file found in maven resource dirs
230                 excludes = StringUtils.isEmpty( excludes ) ? "**/*.java" : excludes + "|**/*.java";
231             }
232 
233             if ( StringUtils.isNotEmpty( excludes ) )
234             {
235                 writer.addAttribute( ATTR_EXCLUDING, excludes );
236             }
237 
238             writer.endElement();
239 
240         }
241 
242         // handle the special sources.
243         if ( !specialSources.isEmpty() )
244         {
245             log.info( "Creating maven-eclipse.xml Ant file to handle resources" );
246 
247             try
248             {
249                 Writer buildXmlWriter =
250                     new OutputStreamWriter( new FileOutputStream( new File( config.getEclipseProjectDirectory(),
251                                                                             "maven-eclipse.xml" ) ), "UTF-8" );
252                 PrettyPrintXMLWriter buildXmlPrinter = new PrettyPrintXMLWriter( buildXmlWriter );
253 
254                 buildXmlPrinter.startElement( "project" );
255                 buildXmlPrinter.addAttribute( "default", "copy-resources" );
256 
257                 buildXmlPrinter.startElement( "target" );
258                 buildXmlPrinter.addAttribute( "name", "init" );
259                 // initialize filtering tokens here
260                 buildXmlPrinter.endElement();
261 
262                 buildXmlPrinter.startElement( "target" );
263                 buildXmlPrinter.addAttribute( "name", "copy-resources" );
264                 buildXmlPrinter.addAttribute( "depends", "init" );
265 
266                 for ( Iterator it = specialSources.iterator(); it.hasNext(); )
267                 {
268                     // TODO: merge source dirs on output path+filtering to reduce
269                     // <copy> tags for speed.
270                     EclipseSourceDir dir = (EclipseSourceDir) it.next();
271                     buildXmlPrinter.startElement( "copy" );
272                     buildXmlPrinter.addAttribute( "todir", dir.getOutput() );
273                     buildXmlPrinter.addAttribute( "filtering", "" + dir.isFiltering() );
274 
275                     buildXmlPrinter.startElement( "fileset" );
276                     buildXmlPrinter.addAttribute( "dir", dir.getPath() );
277                     if ( dir.getInclude() != null )
278                     {
279                         buildXmlPrinter.addAttribute( "includes", dir.getInclude() );
280                     }
281                     if ( dir.getExclude() != null )
282                     {
283                         buildXmlPrinter.addAttribute( "excludes", dir.getExclude() );
284                     }
285                     buildXmlPrinter.endElement();
286 
287                     buildXmlPrinter.endElement();
288                 }
289 
290                 buildXmlPrinter.endElement();
291 
292                 buildXmlPrinter.endElement();
293 
294                 IOUtil.close( buildXmlWriter );
295             }
296             catch ( IOException e )
297             {
298                 throw new MojoExecutionException( "Cannot create " + config.getEclipseProjectDirectory() +
299                     "/maven-eclipse.xml", e );
300             }
301 
302             log.info( "Creating external launcher file" );
303             // now create the launcher
304             new EclipseAntExternalLaunchConfigurationWriter().init( log, config, "Maven_Ant_Builder.launch",
305                                                                     "maven-eclipse.xml" ).write();
306 
307             // finally add it to the project writer.
308 
309             config.getBuildCommands().add(
310                                            new BuildCommand(
311                                                              "org.eclipse.ui.externaltools.ExternalToolBuilder",
312                                                              "LaunchConfigHandle",
313                                                              "<project>/" +
314                                                                  EclipseLaunchConfigurationWriter.FILE_DOT_EXTERNAL_TOOL_BUILDERS +
315                                                                  "Maven_Ant_Builder.launch" ) );
316         }
317 
318         // ----------------------------------------------------------------------
319         // The default output
320         // ----------------------------------------------------------------------
321 
322         writer.startElement( ELT_CLASSPATHENTRY );
323         writer.addAttribute( ATTR_KIND, ATTR_OUTPUT );
324         writer.addAttribute( ATTR_PATH, defaultOutput );
325         writer.endElement();
326 
327         // ----------------------------------------------------------------------
328         // Container classpath entries
329         // ----------------------------------------------------------------------
330 
331         for ( Iterator it = config.getClasspathContainers().iterator(); it.hasNext(); )
332         {
333             writer.startElement( ELT_CLASSPATHENTRY );
334             writer.addAttribute( ATTR_KIND, "con" ); //$NON-NLS-1$
335             writer.addAttribute( ATTR_PATH, (String) it.next() );
336             writer.endElement(); // name
337         }
338 
339         // ----------------------------------------------------------------------
340         // The dependencies
341         // ----------------------------------------------------------------------
342         Set addedDependencies = new HashSet();
343         // TODO if (..magic property equals orderDependencies..)
344         IdeDependency[] depsToWrite = config.getDepsOrdered();
345         for ( int j = 0; j < depsToWrite.length; j++ )
346         {
347             IdeDependency dep = depsToWrite[j];
348 
349             if ( dep.isAddedToClasspath() )
350             {
351                 String depId =
352                     dep.getGroupId() + ":" + dep.getArtifactId() + ":" + dep.getClassifier() + ":" + dep.getVersion();
353                 /* avoid duplicates in the classpath for artifacts with different types (like ejbs) */
354                 if ( !addedDependencies.contains( depId ) )
355                 {
356                     addDependency( writer, dep );
357                     addedDependencies.add( depId );
358                 }
359             }
360         }
361 
362         writer.endElement();
363 
364         IOUtil.close( w );
365 
366     }
367 
368     protected void addDependency( XMLWriter writer, IdeDependency dep )
369         throws MojoExecutionException
370     {
371 
372         String path;
373         String kind;
374         String sourcepath = null;
375         String javadocpath = null;
376 
377         if ( dep.isReferencedProject() && !config.isPde() )
378         {
379             path = "/" + dep.getEclipseProjectName(); //$NON-NLS-1$
380             kind = ATTR_SRC;
381         }
382         else if ( dep.isReferencedProject() && config.isPde() )
383         {
384             // don't do anything, referenced projects are automatically handled by eclipse in PDE builds
385             return;
386         }
387         else
388         {
389             File artifactPath = dep.getFile();
390 
391             if ( artifactPath == null )
392             {
393                 log.error( Messages.getString( "EclipsePlugin.artifactpathisnull", dep.getId() ) ); //$NON-NLS-1$
394                 return;
395             }
396 
397             if ( dep.isSystemScoped() )
398             {
399                 path = IdeUtils.toRelativeAndFixSeparator( config.getEclipseProjectDirectory(), artifactPath, false );
400 
401                 if ( log.isDebugEnabled() )
402                 {
403                     log.debug( Messages.getString( "EclipsePlugin.artifactissystemscoped", //$NON-NLS-1$
404                                                    new Object[] { dep.getArtifactId(), path } ) );
405                 }
406 
407                 kind = ATTR_LIB;
408             }
409             else
410             {
411                 File localRepositoryFile = new File( config.getLocalRepository().getBasedir() );
412 
413                 // if the dependency is not provided and the plugin runs in "pde mode", the dependency is
414                 // added to the Bundle-Classpath:
415                 if ( config.isPde() && ( dep.isProvided() || dep.isOsgiBundle() ) )
416                 {
417                     return;
418                 }
419                 else if ( config.isPde() && !dep.isProvided() && !dep.isTestDependency() )
420                 {
421                     // path for link created in .project, not to the actual file
422                     path = dep.getFile().getName();
423 
424                     kind = ATTR_LIB;
425                 }
426                 // running in PDE mode and the dependency is provided means, that it is provided by
427                 // the target platform. This case is covered by adding the plugin container
428                 else
429                 {
430                     String fullPath = artifactPath.getPath();
431                     String relativePath =
432                         IdeUtils.toRelativeAndFixSeparator( localRepositoryFile, new File( fullPath ), false );
433 
434                     if ( !new File( relativePath ).isAbsolute() )
435                     {
436                         path = M2_REPO + "/" //$NON-NLS-1$
437                             + relativePath;
438                         kind = ATTR_VAR; //$NON-NLS-1$
439                     }
440                     else
441                     {
442                         path = relativePath;
443                         kind = ATTR_LIB;
444                     }
445                 }
446 
447                 if ( dep.getSourceAttachment() != null )
448                 {
449                     if ( ATTR_VAR.equals( kind ) )
450                     {
451                         sourcepath =
452                             M2_REPO +
453                                 "/" //$NON-NLS-1$
454                                 +
455                                 IdeUtils.toRelativeAndFixSeparator( localRepositoryFile, dep.getSourceAttachment(),
456                                                                     false );
457                     }
458                     else
459                     {
460                         // source archive must be referenced with the full path, we can't mix a lib with a variable
461                         sourcepath = IdeUtils.getCanonicalPath( dep.getSourceAttachment() );
462                     }
463                 }
464 
465                 if ( dep.getJavadocAttachment() != null )
466                 {
467                     // NB eclipse (3.1) doesn't support variables in javadoc paths, so we need to add the
468                     // full path for the maven repo
469                     javadocpath =
470                         StringUtils.replace( IdeUtils.getCanonicalPath( dep.getJavadocAttachment() ), "\\", "/" ); //$NON-NLS-1$ //$NON-NLS-2$
471                 }
472 
473             }
474 
475         }
476 
477         writer.startElement( ELT_CLASSPATHENTRY );
478         writer.addAttribute( ATTR_KIND, kind );
479         writer.addAttribute( ATTR_PATH, path );
480 
481         if ( sourcepath != null )
482         {
483             writer.addAttribute( ATTR_SOURCEPATH, sourcepath );
484         }
485 
486         boolean attributeElemOpen = false;
487 
488         if ( javadocpath != null )
489         {
490             if ( !attributeElemOpen )
491             {
492                 writer.startElement( "attributes" ); //$NON-NLS-1$
493                 attributeElemOpen = true;
494             }
495 
496             writer.startElement( "attribute" ); //$NON-NLS-1$
497             writer.addAttribute( "value", "jar:" + new File( javadocpath ).toURI() + "!/" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
498             writer.addAttribute( "name", "javadoc_location" ); //$NON-NLS-1$ //$NON-NLS-2$
499             writer.endElement();
500 
501         }
502 
503         if ( Constants.PROJECT_PACKAGING_WAR.equals( this.config.getPackaging() ) && config.getWtpapplicationxml() &&
504             kind.equals( ATTR_VAR ) && !dep.isTestDependency() && !dep.isProvided() &&
505             !dep.isSystemScopedOutsideProject( this.config.getProject() ) )
506         {
507             if ( !attributeElemOpen )
508             {
509                 writer.startElement( "attributes" ); //$NON-NLS-1$
510                 attributeElemOpen = true;
511             }
512 
513             writer.startElement( "attribute" ); //$NON-NLS-1$
514             writer.addAttribute( "value", "/WEB-INF/lib" ); //$NON-NLS-1$ //$NON-NLS-2$
515             writer.addAttribute( "name", "org.eclipse.jst.component.dependency" ); //$NON-NLS-1$ //$NON-NLS-2$
516             writer.endElement();
517 
518         }
519 
520         if ( attributeElemOpen )
521         {
522             writer.endElement();
523         }
524         writer.endElement();
525 
526     }
527 }