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}