001package org.andromda.maven.plugin.andromdapp.eclipse;
002
003import java.io.File;
004import java.io.FileWriter;
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.Iterator;
008import java.util.LinkedHashSet;
009import java.util.List;
010import java.util.Set;
011import org.andromda.core.common.ResourceUtils;
012import org.apache.commons.lang.StringUtils;
013import org.apache.maven.artifact.Artifact;
014import org.apache.maven.artifact.factory.ArtifactFactory;
015import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
016import org.apache.maven.artifact.repository.ArtifactRepository;
017import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
018import org.apache.maven.artifact.resolver.ArtifactResolver;
019import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
020import org.apache.maven.plugin.logging.Log;
021import org.apache.maven.project.MavenProject;
022import org.codehaus.plexus.util.IOUtil;
023import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
024import org.codehaus.plexus.util.xml.XMLWriter;
025
026/**
027 * Writes the Eclipse .classpath files.
028 *
029 * @author Chad Brandon
030 */
031public class ClasspathWriter
032    extends EclipseWriter
033{
034    /**
035     * @param project
036     * @param logger
037     */
038    public ClasspathWriter(
039        final MavenProject project,
040        final Log logger)
041    {
042        super(project, logger);
043    }
044
045    /**
046     * Writes the .classpath file for eclipse.
047     *
048     * @param projects the list of projects from which the .classpath will get its dependencies.
049     * @param repositoryVariableName the name of the maven repository variable.
050     * @param artifactFactory the factory for constructing artifacts.
051     * @param artifactResolver the artifact resolver.
052     * @param localRepository the local repository instance.
053     * @param artifactMetadataSource
054     * @param classpathArtifactTypes the artifacts types that are allowed in the classpath file.
055     * @param remoteRepositories the list of remote repository instances.
056     * @param resolveTransitiveDependencies whether or not dependencies shall be transitively resolved.
057     * @param variables Variables replaced within the path
058     * @param merge anything extra (not auto-generated), that should be "merged" into the generated .classpath
059     * @throws Exception
060     */
061    public void write(
062        final List<MavenProject> projects,
063        final String repositoryVariableName,
064        final ArtifactFactory artifactFactory,
065        final ArtifactResolver artifactResolver,
066        final ArtifactRepository localRepository,
067        final ArtifactMetadataSource artifactMetadataSource,
068        final Set<String> classpathArtifactTypes,
069        final List<ArtifactRepository> remoteRepositories,
070        final boolean resolveTransitiveDependencies,
071        final Variable[] variables,
072        final String merge)
073        throws Exception
074    {
075        final String rootDirectory = ResourceUtils.normalizePath(this.project.getBasedir().toString());
076        final File classpathFile = new File(rootDirectory, ".classpath");
077        final FileWriter fileWriter = new FileWriter(classpathFile);
078        final XMLWriter writer = new PrettyPrintXMLWriter(fileWriter);
079
080        writer.startElement("classpath");
081
082        final Set<String> projectArtifactIds = new LinkedHashSet<String>();
083        for (final MavenProject project : projects)
084        {
085            final Artifact projectArtifact =
086                artifactFactory.createArtifact(
087                    project.getGroupId(),
088                    project.getArtifactId(),
089                    project.getVersion(),
090                    null,
091                    project.getPackaging());
092            projectArtifactIds.add(projectArtifact.getId());
093        }
094
095        // - write the source roots for the root project (if they are any)
096        this.writeSourceRoots(this.project, rootDirectory, writer);
097
098        final Set<Artifact> allArtifacts = new LinkedHashSet<Artifact>(this.project.createArtifacts(
099            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}