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