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