View Javadoc
1   package org.andromda.maven.plugin.andromdapp.eclipse;
2   
3   import java.io.File;
4   import java.io.FileWriter;
5   import java.util.ArrayList;
6   import java.util.Collections;
7   import java.util.Iterator;
8   import java.util.LinkedHashSet;
9   import java.util.List;
10  import java.util.Set;
11  import org.andromda.core.common.ResourceUtils;
12  import org.apache.commons.lang.StringUtils;
13  import org.apache.maven.artifact.Artifact;
14  import org.apache.maven.artifact.factory.ArtifactFactory;
15  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
16  import org.apache.maven.artifact.repository.ArtifactRepository;
17  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
18  import org.apache.maven.artifact.resolver.ArtifactResolver;
19  import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
20  import org.apache.maven.plugin.logging.Log;
21  import org.apache.maven.project.MavenProject;
22  import org.codehaus.plexus.util.IOUtil;
23  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
24  import org.codehaus.plexus.util.xml.XMLWriter;
25  
26  /**
27   * Writes the Eclipse .classpath files.
28   *
29   * @author Chad Brandon
30   */
31  public class ClasspathWriter
32      extends EclipseWriter
33  {
34      /**
35       * @param project
36       * @param logger
37       */
38      public ClasspathWriter(
39          final MavenProject project,
40          final Log logger)
41      {
42          super(project, logger);
43      }
44  
45      /**
46       * Writes the .classpath file for eclipse.
47       *
48       * @param projects the list of projects from which the .classpath will get its dependencies.
49       * @param repositoryVariableName the name of the maven repository variable.
50       * @param artifactFactory the factory for constructing artifacts.
51       * @param artifactResolver the artifact resolver.
52       * @param localRepository the local repository instance.
53       * @param artifactMetadataSource
54       * @param classpathArtifactTypes the artifacts types that are allowed in the classpath file.
55       * @param remoteRepositories the list of remote repository instances.
56       * @param resolveTransitiveDependencies whether or not dependencies shall be transitively resolved.
57       * @param variables Variables replaced within the path
58       * @param merge anything extra (not auto-generated), that should be "merged" into the generated .classpath
59       * @throws Exception
60       */
61      public void write(
62          final List<MavenProject> projects,
63          final String repositoryVariableName,
64          final ArtifactFactory artifactFactory,
65          final ArtifactResolver artifactResolver,
66          final ArtifactRepository localRepository,
67          final ArtifactMetadataSource artifactMetadataSource,
68          final Set<String> classpathArtifactTypes,
69          final List<ArtifactRepository> remoteRepositories,
70          final boolean resolveTransitiveDependencies,
71          final Variable[] variables,
72          final String merge)
73          throws Exception
74      {
75          final String rootDirectory = ResourceUtils.normalizePath(this.project.getBasedir().toString());
76          final File classpathFile = new File(rootDirectory, ".classpath");
77          final FileWriter fileWriter = new FileWriter(classpathFile);
78          final XMLWriter writer = new PrettyPrintXMLWriter(fileWriter);
79  
80          writer.startElement("classpath");
81  
82          final Set<String> projectArtifactIds = new LinkedHashSet<String>();
83          for (final MavenProject project : projects)
84          {
85              final Artifact projectArtifact =
86                  artifactFactory.createArtifact(
87                      project.getGroupId(),
88                      project.getArtifactId(),
89                      project.getVersion(),
90                      null,
91                      project.getPackaging());
92              projectArtifactIds.add(projectArtifact.getId());
93          }
94  
95          // - write the source roots for the root project (if they are any)
96          this.writeSourceRoots(this.project, rootDirectory, writer);
97  
98          final Set<Artifact> allArtifacts = new LinkedHashSet<Artifact>(this.project.createArtifacts(
99              artifactFactory,
100             null,
101             null));
102         for (final MavenProject project : projects)
103         {
104             this.writeSourceRoots(project, rootDirectory, writer);
105             final Set<Artifact> artifacts = project.createArtifacts(
106                     artifactFactory,
107                     null,
108                     null);
109 
110             // - get the direct dependencies
111             for (final Artifact artifact : artifacts)
112             {
113                 // - don't attempt to resolve the artifact if its part of the project (we
114                 //   infer this if it has the same id has one of the projects or is in
115                 //   the same groupId).
116                 if (!projectArtifactIds.contains(artifact.getId()) &&
117                     !project.getGroupId().equals(artifact.getGroupId()))
118                 {
119                     artifactResolver.resolve(
120                         artifact,
121                         project.getRemoteArtifactRepositories(),
122                         localRepository);
123                     allArtifacts.add(artifact);
124                 }
125                 else
126                 {
127                     allArtifacts.add(artifact);
128                 }
129             }
130         }
131 
132         // - remove the project artifacts
133         for (final MavenProject project : projects)
134         {
135             final Artifact projectArtifact = project.getArtifact();
136             if (projectArtifact != null)
137             {
138                 for (final Iterator<Artifact> artifactIterator = allArtifacts.iterator(); artifactIterator.hasNext();)
139                 {
140                     final Artifact artifact = artifactIterator.next();
141                     final String projectId = projectArtifact.getArtifactId();
142                     final String projectGroupId = projectArtifact.getGroupId();
143                     final String artifactId = artifact.getArtifactId();
144                     final String groupId = artifact.getGroupId();
145                     if (artifactId.equals(projectId) && groupId.equals(projectGroupId))
146                     {
147                         artifactIterator.remove();
148                     }
149                 }
150             }
151         }
152 
153         // - now we resolve transitively, if we have the flag on
154         if (resolveTransitiveDependencies)
155         {
156             final Artifact rootProjectArtifact =
157                 artifactFactory.createArtifact(
158                     this.project.getGroupId(),
159                     this.project.getArtifactId(),
160                     this.project.getVersion(),
161                     null,
162                     this.project.getPackaging());
163 
164             final OrArtifactFilter filter = new OrArtifactFilter();
165             filter.add(new ScopeArtifactFilter(Artifact.SCOPE_COMPILE));
166             filter.add(new ScopeArtifactFilter(Artifact.SCOPE_PROVIDED));
167             filter.add(new ScopeArtifactFilter(Artifact.SCOPE_TEST));
168             final ArtifactResolutionResult result =
169                 artifactResolver.resolveTransitively(
170                     allArtifacts,
171                     rootProjectArtifact,
172                     localRepository,
173                     remoteRepositories,
174                     artifactMetadataSource,
175                     filter);
176 
177             allArtifacts.clear();
178             allArtifacts.addAll(result.getArtifacts());
179         }
180 
181         final List<String> artifactPathList = new ArrayList<String>();
182         for (final Artifact artifact : allArtifacts)
183         {
184             if (classpathArtifactTypes.contains(artifact.getType()))
185             {
186                 final File artifactFile = artifact.getFile();
187                 final String artifactPath = ResourceUtils.normalizePath(artifactFile.toString());
188                 String path =
189                     StringUtils.replace(
190                         artifactPath,
191                         ResourceUtils.normalizePath(localRepository.getBasedir()),
192                         VAR_PREFIX + repositoryVariableName);
193                 if (path.equals(artifactPath))
194                 {
195                     // - replace any variables if present
196                     if (variables != null)
197                     {
198                         for (final Variable variable : variables)
199                         {
200                             final String name = StringUtils.trimToEmpty(variable.getName());
201                             final String value = StringUtils.trimToEmpty(variable.getValue());
202                             path = StringUtils.replace(path, value, VAR_PREFIX + name);
203                         }
204                     }
205                 }
206                 artifactPathList.add(path);
207             }
208         }
209 
210         // - sort the paths
211         Collections.sort(artifactPathList);
212 
213         // - get rid of any duplicates
214         final Set<String> artifactPaths = new LinkedHashSet<String>(artifactPathList);
215 
216         for (String path : artifactPaths)
217         {
218             if (path.startsWith(VAR_PREFIX))
219             {
220                 this.writeClasspathEntry(
221                     writer,
222                     "var",
223                     path.split(VAR_PREFIX)[1]);
224             }
225             else
226             {
227                 if (path.startsWith(rootDirectory))
228                 {
229                     path = StringUtils.replace(path, rootDirectory + '/', "");
230                 }
231                 this.writeClasspathEntry(
232                     writer,
233                     "lib",
234                     path);
235             }
236         }
237 
238         this.writeClasspathEntry(
239             writer,
240             "con",
241             "org.eclipse.jdt.launching.JRE_CONTAINER");
242 
243         String outputPath =
244             StringUtils.replace(
245                 ResourceUtils.normalizePath(this.project.getBuild().getOutputDirectory()),
246                 rootDirectory,
247                 "");
248         if (outputPath.startsWith("/"))
249         {
250             outputPath = outputPath.substring(
251                     1,
252                     outputPath.length());
253         }
254         this.writeClasspathEntry(
255             writer,
256             "output",
257             outputPath);
258 
259         if (StringUtils.isNotBlank(merge))
260         {
261             writer.writeMarkup(merge);
262         }
263         writer.endElement();
264 
265         logger.info("Classpath file written --> '" + classpathFile + '\'');
266         IOUtil.close(fileWriter);
267     }
268 
269     private static final String VAR_PREFIX = "var:";
270 
271     private static final String DRIVE_PATTERN = ".*:";
272 
273     /**
274      * Writes the source roots for the given project.
275      *
276      * @param project the project for which to write the source roots.
277      * @param rootDirectory the root project's base directory
278      * @param writer the XMLWriter used to write the source roots.
279      */
280     private void writeSourceRoots(final MavenProject project, String rootDirectory, final XMLWriter writer)
281     {
282         // - strip the drive prefix (so we don't have to worry about replacement dependent on case)
283         rootDirectory = rootDirectory.replaceFirst(DRIVE_PATTERN, "");
284         for (final Iterator<String> sourceIterator = project.getCompileSourceRoots().iterator(); sourceIterator.hasNext();)
285         {
286             final String sourceRoot = ResourceUtils.normalizePath(sourceIterator.next()).replaceFirst(DRIVE_PATTERN, "");
287             if (new File(sourceRoot).isDirectory())
288             {
289                 String sourceRootPath = StringUtils.replace(
290                         sourceRoot,
291                         rootDirectory,
292                         "");
293                 if (sourceRootPath.startsWith("/"))
294                 {
295                     sourceRootPath = sourceRootPath.substring(
296                             1,
297                             sourceRootPath.length());
298                     this.writeClasspathEntry(
299                         writer,
300                         "src",
301                         sourceRootPath);
302                 }
303             }
304         }
305     }
306 
307     /**
308      * Writes a classpathentry with the given <code>kind</code> and <code>path</code> values.
309      *
310      * @param writer the XML writer with which to write.
311      * @param kind the kind of the classpath entry.
312      * @param path the path of the classpath entry.
313      */
314     private void writeClasspathEntry(
315         final XMLWriter writer,
316         final String kind,
317         final String path)
318     {
319         writer.startElement("classpathentry");
320         writer.addAttribute(
321             "kind",
322             kind);
323         writer.addAttribute(
324             "path",
325             path);
326         writer.endElement();
327     }
328 }