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