diff options
author | rfeng <rfeng@13f79535-47bb-0310-9956-ffa450edef68> | 2008-11-15 00:33:33 +0000 |
---|---|---|
committer | rfeng <rfeng@13f79535-47bb-0310-9956-ffa450edef68> | 2008-11-15 00:33:33 +0000 |
commit | b8f72835fcc629d3fbcaf37d66172610dd818266 (patch) | |
tree | ff6adf0301c1a888caebec084930dbf551c6915e /branches/sca-equinox/tools | |
parent | 6720e5f8ad276be5a9c6719150d9c40588652b6e (diff) |
Leverage code from maven eclipse plugin to handle .classpath/.project generation for tuscany modules
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@714199 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'branches/sca-equinox/tools')
9 files changed, 3914 insertions, 682 deletions
diff --git a/branches/sca-equinox/tools/maven/maven-bundle-plugin/pom.xml b/branches/sca-equinox/tools/maven/maven-bundle-plugin/pom.xml index 559c7da443..8b8bd996af 100644 --- a/branches/sca-equinox/tools/maven/maven-bundle-plugin/pom.xml +++ b/branches/sca-equinox/tools/maven/maven-bundle-plugin/pom.xml @@ -33,31 +33,31 @@ <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> - <version>2.0.7</version> + <version>2.0.8</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-project</artifactId> - <version>2.0.7</version> + <version>2.0.8</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-settings</artifactId> - <version>2.0.7</version> + <version>2.0.8</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-artifact</artifactId> - <version>2.0.7</version> + <version>2.0.8</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-model</artifactId> - <version>2.0.7</version> + <version>2.0.8</version> </dependency> <dependency> @@ -70,8 +70,12 @@ <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>1.4.3</version> - </dependency> - + </dependency> + <dependency> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-eclipse-plugin</artifactId> + <version>2.5.1</version> + </dependency> </dependencies> </project> diff --git a/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/AbstractIdeSupportMojo.java b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/AbstractIdeSupportMojo.java new file mode 100644 index 0000000000..d0961ccd9c --- /dev/null +++ b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/AbstractIdeSupportMojo.java @@ -0,0 +1,1114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.maven.plugin.eclipse; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.zip.ZipFile; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.metadata.ArtifactMetadataSource; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactCollector; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolutionResult; +import org.apache.maven.artifact.resolver.ArtifactResolver; +import org.apache.maven.artifact.resolver.DebugResolutionListener; +import org.apache.maven.artifact.resolver.ResolutionNode; +import org.apache.maven.artifact.resolver.WarningResolutionListener; +import org.apache.maven.artifact.resolver.filter.ArtifactFilter; +import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.DependencyManagement; +import org.apache.maven.model.Exclusion; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.eclipse.Constants; +import org.apache.maven.plugin.ide.IdeDependency; +import org.apache.maven.plugin.ide.IdeUtils; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.logging.LogEnabled; +import org.codehaus.plexus.logging.Logger; +import org.codehaus.plexus.util.IOUtil; + +/** + * Abstract base plugin which takes care of the common stuff usually needed by maven IDE plugins. A plugin extending + * AbstractIdeSupportMojo should implement the <code>setup()</code> and <code>writeConfiguration()</code> methods, + * plus the getters needed to get the various configuration flags and required components. The lifecycle: + * + * <pre> + * *** calls setup() where you can configure your specific stuff and stop the mojo from execute if appropriate *** + * - manually resolve project dependencies, NOT failing if a dependency is missing + * - compute project references (reactor projects) if the getUseProjectReferences() flag is set + * - download sources/javadocs if the getDownloadSources() flag is set + * *** calls writeConfiguration(), passing the list of resolved referenced dependencies *** + * - report the list of missing sources or just tell how to turn this feature on if the flag was disabled + * </pre> + * + * @author Fabrizio Giustina + * @version $Id: AbstractIdeSupportMojo.java 628794 2008-02-18 16:09:11Z aheritier $ + */ +public abstract class AbstractIdeSupportMojo + extends AbstractMojo + implements LogEnabled +{ + /** + * Is it an PDE project? If yes, the plugin adds the necessary natures and build commands to the .project file. + * Additionally it copies all libraries to a project local directory and references them instead of referencing the + * files in the local Maven repository. It also ensured that the "Bundle-Classpath" in META-INF/MANIFEST.MF is + * synchronized. + * + * @parameter expression="${eclipse.pde}" default-value="true" + */ + protected boolean pde; + + /** + * The project whose project files to create. + * + * @parameter expression="${project}" + * @required + * @readonly + */ + protected MavenProject project; + + // [rfeng] Change it to use the current project + /** + * The currently executed project (can be a reactor project). + * + * @parameter expression="${project}" + * @readonly + */ + protected MavenProject executedProject; + + /** + * The project packaging. + * + * @parameter expression="${project.packaging}" + */ + protected String packaging; + + /** + * Artifact factory, needed to download source jars for inclusion in classpath. + * + * @component role="org.apache.maven.artifact.factory.ArtifactFactory" + * @required + * @readonly + */ + protected ArtifactFactory artifactFactory; + + /** + * Artifact resolver, needed to download source jars for inclusion in classpath. + * + * @component role="org.apache.maven.artifact.resolver.ArtifactResolver" + * @required + * @readonly + */ + protected ArtifactResolver artifactResolver; + + /** + * Artifact collector, needed to resolve dependencies. + * + * @component role="org.apache.maven.artifact.resolver.ArtifactCollector" + * @required + * @readonly + */ + protected ArtifactCollector artifactCollector; + + /** + * @component role="org.apache.maven.artifact.metadata.ArtifactMetadataSource" hint="maven" + */ + protected ArtifactMetadataSource artifactMetadataSource; + + /** + * Remote repositories which will be searched for source attachments. + * + * @parameter expression="${project.remoteArtifactRepositories}" + * @required + * @readonly + */ + protected List remoteArtifactRepositories; + + /** + * Local maven repository. + * + * @parameter expression="${localRepository}" + * @required + * @readonly + */ + protected ArtifactRepository localRepository; + + /** + * If the executed project is a reactor project, this will contains the full list of projects in the reactor. + * + * @parameter expression="${reactorProjects}" + * @required + * @readonly + */ + protected List reactorProjects; + + /** + * Skip the operation when true. + * + * @parameter expression="${eclipse.skip}" default-value="false" + */ + private boolean skip; + + /** + * Enables/disables the downloading of source attachments. Defaults to false. When this flag is <code>true</code> + * remote repositories are checked for sources: in order to avoid repeated check for unavailable source archives, a + * status cache is mantained into the target dir of the root project. Run <code>mvn:clean</code> or delete the + * file <code>mvn-eclipse-cache.properties</code> in order to reset this cache. + * + * @parameter expression="${downloadSources}" + */ + protected boolean downloadSources; + + /** + * Enables/disables the downloading of javadoc attachments. Defaults to false. When this flag is <code>true</code> + * remote repositories are checked for javadocs: in order to avoid repeated check for unavailable javadoc archives, + * a status cache is mantained into the target dir of the root project. Run <code>mvn:clean</code> or delete the + * file <code>mvn-eclipse-cache.properties</code> in order to reset this cache. + * + * @parameter expression="${downloadJavadocs}" + */ + protected boolean downloadJavadocs; + + /** + * Plexus logger needed for debugging manual artifact resolution. + */ + protected Logger logger; + + /** + * Getter for <code>artifactMetadataSource</code>. + * + * @return Returns the artifactMetadataSource. + */ + public ArtifactMetadataSource getArtifactMetadataSource() + { + return artifactMetadataSource; + } + + /** + * Setter for <code>artifactMetadataSource</code>. + * + * @param artifactMetadataSource The artifactMetadataSource to set. + */ + public void setArtifactMetadataSource( ArtifactMetadataSource artifactMetadataSource ) + { + this.artifactMetadataSource = artifactMetadataSource; + } + + /** + * Getter for <code>project</code>. + * + * @return Returns the project. + */ + public MavenProject getProject() + { + return project; + } + + /** + * Setter for <code>project</code>. + * + * @param project The project to set. + */ + public void setProject( MavenProject project ) + { + this.project = project; + } + + /** + * Getter for <code>reactorProjects</code>. + * + * @return Returns the reactorProjects. + */ + public List getReactorProjects() + { + return reactorProjects; + } + + /** + * Setter for <code>reactorProjects</code>. + * + * @param reactorProjects The reactorProjects to set. + */ + public void setReactorProjects( List reactorProjects ) + { + this.reactorProjects = reactorProjects; + } + + /** + * Getter for <code>remoteArtifactRepositories</code>. + * + * @return Returns the remoteArtifactRepositories. + */ + public List getRemoteArtifactRepositories() + { + return remoteArtifactRepositories; + } + + /** + * Setter for <code>remoteArtifactRepositories</code>. + * + * @param remoteArtifactRepositories The remoteArtifactRepositories to set. + */ + public void setRemoteArtifactRepositories( List remoteArtifactRepositories ) + { + this.remoteArtifactRepositories = remoteArtifactRepositories; + } + + /** + * Getter for <code>artifactFactory</code>. + * + * @return Returns the artifactFactory. + */ + public ArtifactFactory getArtifactFactory() + { + return artifactFactory; + } + + /** + * Setter for <code>artifactFactory</code>. + * + * @param artifactFactory The artifactFactory to set. + */ + public void setArtifactFactory( ArtifactFactory artifactFactory ) + { + this.artifactFactory = artifactFactory; + } + + /** + * Getter for <code>artifactResolver</code>. + * + * @return Returns the artifactResolver. + */ + public ArtifactResolver getArtifactResolver() + { + return artifactResolver; + } + + /** + * Setter for <code>artifactResolver</code>. + * + * @param artifactResolver The artifactResolver to set. + */ + public void setArtifactResolver( ArtifactResolver artifactResolver ) + { + this.artifactResolver = artifactResolver; + } + + /** + * Getter for <code>executedProject</code>. + * + * @return Returns the executedProject. + */ + public MavenProject getExecutedProject() + { + return executedProject; + } + + /** + * Setter for <code>executedProject</code>. + * + * @param executedProject The executedProject to set. + */ + public void setExecutedProject( MavenProject executedProject ) + { + this.executedProject = executedProject; + } + + /** + * Getter for <code>localRepository</code>. + * + * @return Returns the localRepository. + */ + public ArtifactRepository getLocalRepository() + { + return localRepository; + } + + /** + * Setter for <code>localRepository</code>. + * + * @param localRepository The localRepository to set. + */ + public void setLocalRepository( ArtifactRepository localRepository ) + { + this.localRepository = localRepository; + } + + /** + * Getter for <code>downloadJavadocs</code>. + * + * @return Returns the downloadJavadocs. + */ + public boolean getDownloadJavadocs() + { + return downloadJavadocs; + } + + /** + * Setter for <code>downloadJavadocs</code>. + * + * @param downloadJavadocs The downloadJavadocs to set. + */ + public void setDownloadJavadocs( boolean downloadJavadoc ) + { + downloadJavadocs = downloadJavadoc; + } + + /** + * Getter for <code>downloadSources</code>. + * + * @return Returns the downloadSources. + */ + public boolean getDownloadSources() + { + return downloadSources; + } + + /** + * Setter for <code>downloadSources</code>. + * + * @param downloadSources The downloadSources to set. + */ + public void setDownloadSources( boolean downloadSources ) + { + this.downloadSources = downloadSources; + } + + protected void setResolveDependencies( boolean resolveDependencies ) + { + this.resolveDependencies = resolveDependencies; + } + + protected boolean isResolveDependencies() + { + return resolveDependencies; + } + + /** + * return <code>false</code> if projects available in a reactor build should be considered normal dependencies, + * <code>true</code> if referenced project will be linked and not need artifact resolution. + * + * @return <code>true</code> if referenced project will be linked and not need artifact resolution + */ + protected abstract boolean getUseProjectReferences(); + + /** + * Hook for preparation steps before the actual plugin execution. + * + * @return <code>true</code> if execution should continue or <code>false</code> if not. + * @throws MojoExecutionException generic mojo exception + */ + protected abstract boolean setup() + throws MojoExecutionException; + + /** + * Main plugin method where dependencies should be processed in order to generate IDE configuration files. + * + * @param deps list of <code>IdeDependency</code> objects, with artifacts, sources and javadocs already resolved + * @throws MojoExecutionException generic mojo exception + */ + protected abstract void writeConfiguration( IdeDependency[] deps ) + throws MojoExecutionException; + + /** + * Not a plugin parameter. Collect the list of dependencies with a missing source artifact for the final report. + */ + private List missingSourceDependencies = new ArrayList(); + + /** + * Not a plugin parameter. Collect the list of dependencies with a missing javadoc artifact for the final report. + */ + // TODO merge this with the missingSourceDependencies in a classifier based map? + private List missingJavadocDependencies = new ArrayList(); + + /** + * Cached array of resolved dependencies. + */ + private IdeDependency[] ideDeps; + + /** + * Flag for mojo implementations to control whether normal maven dependencies should be resolved. Default value is + * true. + */ + private boolean resolveDependencies = true; + + /** + * @see org.codehaus.plexus.logging.LogEnabled#enableLogging(org.codehaus.plexus.logging.Logger) + */ + public void enableLogging( Logger logger ) + { + this.logger = logger; + } + + /** + * @see org.apache.maven.plugin.Mojo#execute() + */ + public final void execute() + throws MojoExecutionException, MojoFailureException + { + if ( skip ) + { + return; + } + + boolean processProject = setup(); + if ( !processProject ) + { + return; + } + + // resolve artifacts + IdeDependency[] deps = doDependencyResolution(); + + resolveSourceAndJavadocArtifacts( deps ); + + writeConfiguration( deps ); + + reportMissingArtifacts(); + + } + + /** + * Resolve project dependencies. Manual resolution is needed in order to avoid resolution of multiproject artifacts + * (if projects will be linked each other an installed jar is not needed) and to avoid a failure when a jar is + * missing. + * + * @throws MojoExecutionException if dependencies can't be resolved + * @return resolved IDE dependencies, with attached jars for non-reactor dependencies + */ + protected IdeDependency[] doDependencyResolution() + throws MojoExecutionException + { + if ( ideDeps == null ) + { + if ( resolveDependencies ) + { + MavenProject project = getProject(); + ArtifactRepository localRepo = getLocalRepository(); + + List deps = getProject().getDependencies(); + + // Collect the list of resolved IdeDependencies. + List dependencies = new ArrayList(); + + if ( deps != null ) + { + Map managedVersions = + createManagedVersionMap( getArtifactFactory(), project.getId(), + project.getDependencyManagement() ); + + ArtifactResolutionResult artifactResolutionResult = null; + + try + { + + List listeners = new ArrayList(); + + if ( logger.isDebugEnabled() ) + { + listeners.add( new DebugResolutionListener( logger ) ); + } + + listeners.add( new WarningResolutionListener( logger ) ); + + artifactResolutionResult = + artifactCollector.collect( getProjectArtifacts(), project.getArtifact(), managedVersions, + localRepo, project.getRemoteArtifactRepositories(), + getArtifactMetadataSource(), null, listeners ); + } + catch ( ArtifactResolutionException e ) + { + getLog().debug( e.getMessage(), e ); + getLog().error( + Messages.getString( "artifactresolution", new Object[] { //$NON-NLS-1$ + e.getGroupId(), e.getArtifactId(), e.getVersion(), + e.getMessage() } ) ); + + // if we are here artifactResolutionResult is null, create a project without dependencies but + // don't fail + // (this could be a reactor projects, we don't want to fail everything) + // Causes MECLIPSE-185. Not sure if it should be handled this way?? + return new IdeDependency[0]; + } + + // keep track of added reactor projects in order to avoid duplicates + Set emittedReactorProjectId = new HashSet(); + + for ( Iterator i = artifactResolutionResult.getArtifactResolutionNodes().iterator(); i.hasNext(); ) + { + + ResolutionNode node = (ResolutionNode) i.next(); + int dependencyDepth = node.getDepth(); + Artifact art = node.getArtifact(); + // don't resolve jars for reactor projects + if ( hasToResolveJar( art ) ) + { + try + { + artifactResolver.resolve( art, node.getRemoteRepositories(), localRepository ); + } + catch ( ArtifactNotFoundException e ) + { + getLog().debug( e.getMessage(), e ); + getLog().warn( + Messages.getString( "artifactdownload", new Object[] { //$NON-NLS-1$ + e.getGroupId(), e.getArtifactId(), e.getVersion(), + e.getMessage() } ) ); + } + catch ( ArtifactResolutionException e ) + { + getLog().debug( e.getMessage(), e ); + getLog().warn( + Messages.getString( "artifactresolution", new Object[] { //$NON-NLS-1$ + e.getGroupId(), e.getArtifactId(), e.getVersion(), + e.getMessage() } ) ); + } + } + + boolean includeArtifact = true; + if ( getExcludes() != null ) + { + String artifactFullId = art.getGroupId() + ":" + art.getArtifactId(); + if ( getExcludes().contains( artifactFullId ) ) + { + getLog().info( "excluded: " + artifactFullId ); + includeArtifact = false; + } + } + + if ( includeArtifact && + ( !( getUseProjectReferences() && isAvailableAsAReactorProject( art ) ) || emittedReactorProjectId.add( art.getGroupId() + + '-' + art.getArtifactId() ) ) ) + { + + // the following doesn't work: art.getArtifactHandler().getPackaging() always returns "jar" + // also + // if the packaging specified in pom.xml is different. + + // osgi-bundle packaging is provided by the felix osgi plugin + // eclipse-plugin packaging is provided by this eclipse plugin + // String packaging = art.getArtifactHandler().getPackaging(); + // boolean isOsgiBundle = "osgi-bundle".equals( packaging ) || "eclipse-plugin".equals( + // packaging ); + + // we need to check the manifest, if "Bundle-SymbolicName" is there the artifact can be + // considered + // an osgi bundle + boolean isOsgiBundle = false; + String osgiSymbolicName = null; + if ( art.getFile() != null ) + { + JarFile jarFile = null; + try + { + jarFile = new JarFile( art.getFile(), false, ZipFile.OPEN_READ ); + + Manifest manifest = jarFile.getManifest(); + if ( manifest != null ) + { + osgiSymbolicName = + manifest.getMainAttributes().getValue( + new Attributes.Name( + "Bundle-SymbolicName" ) ); + } + } + catch ( IOException e ) + { + getLog().info( "Unable to read jar manifest from " + art.getFile() ); + } + finally + { + if ( jarFile != null ) + { + try + { + jarFile.close(); + } + catch ( IOException e ) + { + // ignore + } + } + } + } + + isOsgiBundle = osgiSymbolicName != null; + + IdeDependency dep = + new IdeDependency( art.getGroupId(), art.getArtifactId(), art.getVersion(), + art.getClassifier(), useProjectReference( art ), + Artifact.SCOPE_TEST.equals( art.getScope() ), + Artifact.SCOPE_SYSTEM.equals( art.getScope() ), + Artifact.SCOPE_PROVIDED.equals( art.getScope() ), + art.getArtifactHandler().isAddedToClasspath(), art.getFile(), + art.getType(), isOsgiBundle, osgiSymbolicName, dependencyDepth, + getProjectNameForArifact( art ) ); + // no duplicate entries allowed. System paths can cause this problem. + if ( !dependencies.contains( dep ) ) + { + // [rfeng] Do not add compile/provided dependencies + if (!(pde && (Artifact.SCOPE_COMPILE.equals(art.getScope()) || Artifact.SCOPE_PROVIDED + .equals(art.getScope())))) { + dependencies.add( dep ); + } + } + } + + } + + // @todo a final report with the list of + // missingArtifacts? + + } + + ideDeps = (IdeDependency[]) dependencies.toArray( new IdeDependency[dependencies.size()] ); + } + else + { + ideDeps = new IdeDependency[0]; + } + } + + return ideDeps; + } + + /** + * Find the name of the project as used in eclipse. + * + * @param artifact The artifact to find the eclipse name for. + * @return The name os the eclipse project. + */ + abstract public String getProjectNameForArifact( Artifact artifact ); + + /** + * Returns the list of project artifacts. Also artifacts generated from referenced projects will be added, but with + * the <code>resolved</code> property set to true. + * + * @return list of projects artifacts + * @throws MojoExecutionException if unable to parse dependency versions + */ + private Set getProjectArtifacts() + throws MojoExecutionException + { + // keep it sorted, this should avoid random classpath order in tests + Set artifacts = new TreeSet(); + + for ( Iterator dependencies = getProject().getDependencies().iterator(); dependencies.hasNext(); ) + { + Dependency dependency = (Dependency) dependencies.next(); + + String groupId = dependency.getGroupId(); + String artifactId = dependency.getArtifactId(); + VersionRange versionRange; + try + { + versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new MojoExecutionException( + Messages.getString( + "unabletoparseversion", new Object[] { //$NON-NLS-1$ + dependency.getArtifactId(), + dependency.getVersion(), + dependency.getManagementKey(), e.getMessage() } ), + e ); + } + + String type = dependency.getType(); + if ( type == null ) + { + type = Constants.PROJECT_PACKAGING_JAR; + } + String classifier = dependency.getClassifier(); + boolean optional = dependency.isOptional(); + String scope = dependency.getScope(); + if ( scope == null ) + { + scope = Artifact.SCOPE_COMPILE; + } + + Artifact art = + getArtifactFactory().createDependencyArtifact( groupId, artifactId, versionRange, type, classifier, + scope, optional ); + + if ( scope.equalsIgnoreCase( Artifact.SCOPE_SYSTEM ) ) + { + art.setFile( new File( dependency.getSystemPath() ) ); + } + + List exclusions = new ArrayList(); + for ( Iterator j = dependency.getExclusions().iterator(); j.hasNext(); ) + { + Exclusion e = (Exclusion) j.next(); + exclusions.add( e.getGroupId() + ":" + e.getArtifactId() ); //$NON-NLS-1$ + } + + ArtifactFilter newFilter = new ExcludesArtifactFilter( exclusions ); + + art.setDependencyFilter( newFilter ); + + artifacts.add( art ); + } + + return artifacts; + } + + /** + * Utility method that locates a project producing the given artifact. + * + * @param artifact the artifact a project should produce. + * @return <code>true</code> if the artifact is produced by a reactor projectart. + */ + protected boolean isAvailableAsAReactorProject( Artifact artifact ) + { + if ( reactorProjects != null ) + { + for ( Iterator iter = reactorProjects.iterator(); iter.hasNext(); ) + { + MavenProject reactorProject = (MavenProject) iter.next(); + + if ( reactorProject.getGroupId().equals( artifact.getGroupId() ) && + reactorProject.getArtifactId().equals( artifact.getArtifactId() ) ) + { + if ( reactorProject.getVersion().equals( artifact.getVersion() ) ) + { + return true; + } + else + { + getLog().info( + "Artifact " + + artifact.getId() + + " already available as a reactor project, but with different version. Expected: " + + artifact.getVersion() + ", found: " + reactorProject.getVersion() ); + } + } + } + } + return false; + } + + /** + * @return an array with all dependencies avalaible in the workspace, to be implemented by the subclasses. + */ + protected IdeDependency[] getWorkspaceArtefacts() + { + return new IdeDependency[0]; + } + + private Map createManagedVersionMap( ArtifactFactory artifactFactory, String projectId, + DependencyManagement dependencyManagement ) + throws MojoExecutionException + { + Map map; + if ( dependencyManagement != null && dependencyManagement.getDependencies() != null ) + { + map = new HashMap(); + for ( Iterator i = dependencyManagement.getDependencies().iterator(); i.hasNext(); ) + { + Dependency d = (Dependency) i.next(); + + try + { + VersionRange versionRange = VersionRange.createFromVersionSpec( d.getVersion() ); + Artifact artifact = + artifactFactory.createDependencyArtifact( d.getGroupId(), d.getArtifactId(), versionRange, + d.getType(), d.getClassifier(), d.getScope(), + d.isOptional() ); + map.put( d.getManagementKey(), artifact ); + } + catch ( InvalidVersionSpecificationException e ) + { + throw new MojoExecutionException( Messages.getString( "unabletoparseversion", new Object[] { //$NON-NLS-1$ + projectId, d.getVersion(), + d.getManagementKey(), e.getMessage() } ), + e ); + } + } + } + else + { + map = Collections.EMPTY_MAP; + } + return map; + } + + /** + * Find the reactor target dir. executedProject doesn't have the multiproject root dir set, and the only way to + * extract it is iterating on parent projects. + * + * @param prj current project + * @return the parent target dir. + */ + private File getReactorTargetDir( MavenProject prj ) + { + if ( prj.getParent() != null ) + { + if ( prj.getParent().getBasedir() != null && prj.getParent().getBasedir().exists() ) + { + return getReactorTargetDir( prj.getParent() ); + } + } + return new File( prj.getBuild().getDirectory() ); + } + + /** + * Resolve source artifacts and download them if <code>downloadSources</code> is <code>true</code>. Source and + * javadocs artifacts will be attached to the <code>IdeDependency</code> Resolve source and javadoc artifacts. The + * resolved artifacts will be downloaded based on the <code>downloadSources</code> and + * <code>downloadJavadocs</code> attributes. Source and + * + * @param deps resolved dependencies + */ + private void resolveSourceAndJavadocArtifacts( IdeDependency[] deps ) + { + + File reactorTargetDir = getReactorTargetDir( project ); + File unavailableArtifactsTmpFile = new File( reactorTargetDir, "mvn-eclipse-cache.properties" ); + + getLog().info( "Using source status cache: " + unavailableArtifactsTmpFile.getAbsolutePath() ); + + // create target dir if missing + if ( !unavailableArtifactsTmpFile.getParentFile().exists() ) + { + unavailableArtifactsTmpFile.getParentFile().mkdirs(); + } + + Properties unavailableArtifactsCache = new Properties(); + if ( unavailableArtifactsTmpFile.exists() ) + { + InputStream is = null; + try + { + is = new FileInputStream( unavailableArtifactsTmpFile ); + unavailableArtifactsCache.load( is ); + } + catch ( IOException e ) + { + getLog().warn( "Unable to read source status for reactor projects" ); + } + finally + { + IOUtil.close( is ); + } + + } + + final List missingSources = + resolveDependenciesWithClassifier( deps, "sources", getDownloadSources(), unavailableArtifactsCache ); + missingSourceDependencies.addAll( missingSources ); + + final List missingJavadocs = + resolveDependenciesWithClassifier( deps, "javadoc", getDownloadJavadocs(), unavailableArtifactsCache ); + missingJavadocDependencies.addAll( missingJavadocs ); + + FileOutputStream fos = null; + try + { + fos = new FileOutputStream( unavailableArtifactsTmpFile ); + unavailableArtifactsCache.store( fos, "Temporary index for unavailable sources and javadocs" ); + } + catch ( IOException e ) + { + getLog().warn( "Unable to cache source status for reactor projects" ); + } + finally + { + IOUtil.close( fos ); + } + + } + + /** + * Resolve the required artifacts for each of the dependency. <code>sources</code> or <code>javadoc</code> + * artifacts (depending on the <code>classifier</code>) are attached to the dependency. + * + * @param deps resolved dependencies + * @param inClassifier the classifier we are looking for (either <code>sources</code> or <code>javadoc</code>) + * @param includeRemoteRepositories flag whether we should search remote repositories for the artifacts or not + * @param unavailableArtifactsCache cache of unavailable artifacts + * @return the list of dependencies for which the required artifact was not found + */ + private List resolveDependenciesWithClassifier( IdeDependency[] deps, String inClassifier, + boolean includeRemoteRepositories, + Properties unavailableArtifactsCache ) + { + List missingClassifierDependencies = new ArrayList(); + + // if downloadSources is off, just check + // local repository for reporting missing source jars + List remoteRepos = includeRemoteRepositories ? getRemoteArtifactRepositories() : Collections.EMPTY_LIST; + + for ( int j = 0; j < deps.length; j++ ) + { + IdeDependency dependency = deps[j]; + + if ( dependency.isReferencedProject() || dependency.isSystemScoped() ) + { + // artifact not needed + continue; + } + + if ( getLog().isDebugEnabled() ) + { + getLog().debug( + "Searching for sources for " + dependency.getId() + ":" + dependency.getClassifier() + + " at " + dependency.getId() + ":" + inClassifier ); + } + + String key = + dependency.getClassifier() == null ? dependency.getId() + ":" + inClassifier : dependency.getId() + + ":" + inClassifier + ":" + dependency.getClassifier(); + + if ( !unavailableArtifactsCache.containsKey( key ) ) + { + Artifact artifact = + IdeUtils.resolveArtifactWithClassifier( dependency.getGroupId(), dependency.getArtifactId(), + dependency.getVersion(), dependency.getClassifier(), + inClassifier, localRepository, artifactResolver, + artifactFactory, remoteRepos, getLog() ); + if ( artifact.isResolved() ) + { + if ( "sources".equals( inClassifier ) ) + { + dependency.setSourceAttachment( artifact.getFile() ); + } + else if ( "javadoc".equals( inClassifier ) ) + { + dependency.setJavadocAttachment( artifact.getFile() ); + } + } + else + { + unavailableArtifactsCache.put( key, Boolean.TRUE.toString() ); + // add the dependencies to the list + // of those lacking the required + // artifact + missingClassifierDependencies.add( dependency ); + } + } + } + + // return the list of dependencies missing the + // required artifact + return missingClassifierDependencies; + + } + + /** + * Output a message with the list of missing dependencies and info on how turn download on if it was disabled. + */ + private void reportMissingArtifacts() + { + StringBuffer msg = new StringBuffer(); + + if ( !missingSourceDependencies.isEmpty() ) + { + if ( getDownloadSources() ) + { + msg.append( Messages.getString( "sourcesnotavailable" ) ); //$NON-NLS-1$ + } + else + { + msg.append( Messages.getString( "sourcesnotdownloaded" ) ); //$NON-NLS-1$ + } + + for ( Iterator it = missingSourceDependencies.iterator(); it.hasNext(); ) + { + IdeDependency art = (IdeDependency) it.next(); + msg.append( Messages.getString( "sourcesmissingitem", art.getId() ) ); //$NON-NLS-1$ + } + msg.append( "\n" ); //$NON-NLS-1$ + } + + if ( !missingJavadocDependencies.isEmpty() ) + { + if ( getDownloadJavadocs() ) + { + msg.append( Messages.getString( "javadocnotavailable" ) ); //$NON-NLS-1$ + } + else + { + msg.append( Messages.getString( "javadocnotdownloaded" ) ); //$NON-NLS-1$ + } + + for ( Iterator it = missingJavadocDependencies.iterator(); it.hasNext(); ) + { + IdeDependency art = (IdeDependency) it.next(); + msg.append( Messages.getString( "javadocmissingitem", art.getId() ) ); //$NON-NLS-1$ + } + msg.append( "\n" ); //$NON-NLS-1$ + } + getLog().info( msg ); + } + + /** + * @return List of dependencies to exclude from eclipse classpath. + * @since 2.5 + */ + public abstract List getExcludes(); + + /** + * Checks if jar has to be resolved for the given artifact + * + * @param art the artifact to check + * @return true if resolution should happen + */ + protected boolean hasToResolveJar( Artifact art ) + { + return !( getUseProjectReferences() && isAvailableAsAReactorProject( art ) ); + } + + /** + * Checks if a projects reference has to be used for the given artifact + * + * @param art the artifact to check + * @return true if a project reference has to be used. + */ + protected boolean useProjectReference( Artifact art ) + { + return getUseProjectReferences() && isAvailableAsAReactorProject( art ); + } +} diff --git a/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipseClasspathWriter.java b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipseClasspathWriter.java new file mode 100644 index 0000000000..2813eb5388 --- /dev/null +++ b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipseClasspathWriter.java @@ -0,0 +1,531 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.maven.plugin.eclipse; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.eclipse.BuildCommand; +import org.apache.maven.plugin.eclipse.Constants; +import org.apache.maven.plugin.eclipse.EclipseSourceDir; +import org.apache.maven.plugin.eclipse.writers.AbstractEclipseWriter; +import org.apache.maven.plugin.eclipse.writers.EclipseAntExternalLaunchConfigurationWriter; +import org.apache.maven.plugin.eclipse.writers.EclipseLaunchConfigurationWriter; +import org.apache.maven.plugin.ide.IdeDependency; +import org.apache.maven.plugin.ide.IdeUtils; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; +import org.codehaus.plexus.util.xml.XMLWriter; + +/** + * Writes eclipse .classpath file. + * + * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a> + * @author <a href="mailto:kenney@neonics.com">Kenney Westerhof</a> + * @author <a href="mailto:fgiust@apache.org">Fabrizio Giustina</a> + * @version $Id: EclipseClasspathWriter.java 636955 2008-03-14 02:10:42Z aheritier $ + */ +public class EclipseClasspathWriter + extends AbstractEclipseWriter +{ + + /** + * Eclipse build path variable M2_REPO + */ + private static final String M2_REPO = "M2_REPO"; //$NON-NLS-1$ + + /** + * Attribute for sourcepath. + */ + private static final String ATTR_SOURCEPATH = "sourcepath"; //$NON-NLS-1$ + + /** + * Attribute for output. + */ + private static final String ATTR_OUTPUT = "output"; //$NON-NLS-1$ + + /** + * Attribute for path. + */ + private static final String ATTR_PATH = "path"; //$NON-NLS-1$ + + /** + * Attribute for kind - Container (con), Variable (var)..etc. + */ + private static final String ATTR_KIND = "kind"; //$NON-NLS-1$ + + /** + * Attribute value for kind: var + */ + private static final String ATTR_VAR = "var"; //$NON-NLS-1$ + + /** + * Attribute value for kind: lib + */ + private static final String ATTR_LIB = "lib"; //$NON-NLS-1$ + + /** + * Attribute value for kind: src + */ + private static final String ATTR_SRC = "src"; //$NON-NLS-1$ + + /** + * Attribute name for source file includes in a path. + */ + private static final String ATTR_INCLUDING = "including"; + + /** + * Attribute name for source file excludes in a path. + */ + private static final String ATTR_EXCLUDING = "excluding"; + + /** + * Element for classpathentry. + */ + private static final String ELT_CLASSPATHENTRY = "classpathentry"; //$NON-NLS-1$ + + /** + * Element for classpath. + */ + private static final String ELT_CLASSPATH = "classpath"; //$NON-NLS-1$ + + /** + * File name that stores project classpath settings. + */ + private static final String FILE_DOT_CLASSPATH = ".classpath"; //$NON-NLS-1$ + + /** + * @see org.apache.tuscany.sca.maven.plugin.eclipse.writers.EclipseWriter#write() + */ + public void write() + throws MojoExecutionException + { + + Writer w; + + try + { + w = + new OutputStreamWriter( new FileOutputStream( new File( config.getEclipseProjectDirectory(), + FILE_DOT_CLASSPATH ) ), "UTF-8" ); + } + catch ( IOException ex ) + { + throw new MojoExecutionException( Messages.getString( "EclipsePlugin.erroropeningfile" ), ex ); //$NON-NLS-1$ + } + + XMLWriter writer = new PrettyPrintXMLWriter( w ); + + writer.startElement( ELT_CLASSPATH ); + + String defaultOutput = + IdeUtils.toRelativeAndFixSeparator( config.getProjectBaseDir(), config.getBuildOutputDirectory(), false ); + + // ---------------------------------------------------------------------- + // Source roots and resources + // ---------------------------------------------------------------------- + + // List<EclipseSourceDir> + List specialSources = new ArrayList(); + + // Map<String,List<EclipseSourceDir>> + Map byOutputDir = new HashMap(); + + for ( int j = 0; j < config.getSourceDirs().length; j++ ) + { + EclipseSourceDir dir = config.getSourceDirs()[j]; + + // List<EclipseSourceDir> + List byOutputDirs = (List) byOutputDir.get( dir.getOutput() ); + if ( byOutputDirs == null ) + { + // ArrayList<EclipseSourceDir> + byOutputDir.put( dir.getOutput() == null ? defaultOutput : dir.getOutput(), byOutputDirs = + new ArrayList() ); + } + byOutputDirs.add( dir ); + } + + for ( int j = 0; j < config.getSourceDirs().length; j++ ) + { + EclipseSourceDir dir = config.getSourceDirs()[j]; + + log.debug( "Processing " + ( dir.isResource() ? "re" : "" ) + "source " + dir.getPath() + ": output=" + + dir.getOutput() + "; default output=" + defaultOutput ); + + boolean isSpecial = false; + + // handle resource with nested output folders + if ( dir.isResource() ) + { + // Check if the output is a subdirectory of the default output, + // and if the default output has any sources that copy there. + + if ( dir.getOutput() != null // resource output dir is set + && + !dir.getOutput().equals( defaultOutput ) // output dir is not default target/classes + && dir.getOutput().startsWith( defaultOutput ) // ... but is nested + && byOutputDir.get( defaultOutput ) != null // ??? + && !( (List) byOutputDir.get( defaultOutput ) ).isEmpty() // ??? + ) + { + // do not specify as source since the output will be nested. Instead, mark + // it as a todo, and handle it with a custom build.xml file later. + + log.debug( "Marking as special to prevent output folder nesting: " + dir.getPath() + " (output=" + + dir.getOutput() + ")" ); + + isSpecial = true; + specialSources.add( dir ); + } + } + + writer.startElement( ELT_CLASSPATHENTRY ); + + writer.addAttribute( ATTR_KIND, "src" ); //$NON-NLS-1$ + writer.addAttribute( ATTR_PATH, dir.getPath() ); + + if ( !isSpecial && dir.getOutput() != null && !defaultOutput.equals( dir.getOutput() ) ) + { + writer.addAttribute( ATTR_OUTPUT, dir.getOutput() ); + } + + if ( StringUtils.isNotEmpty( dir.getInclude() ) ) + { + writer.addAttribute( ATTR_INCLUDING, dir.getInclude() ); + } + + String excludes = dir.getExclude(); + + if ( dir.isResource() ) + { + // automatically exclude java files: eclipse doesn't have the concept of resource directory so it will + // try to compile any java file found in maven resource dirs + excludes = StringUtils.isEmpty( excludes ) ? "**/*.java" : excludes + "|**/*.java"; + } + + if ( StringUtils.isNotEmpty( excludes ) ) + { + writer.addAttribute( ATTR_EXCLUDING, excludes ); + } + + writer.endElement(); + + } + + // handle the special sources. + if ( !specialSources.isEmpty() ) + { + log.info( "Creating maven-eclipse.xml Ant file to handle resources" ); + + try + { + Writer buildXmlWriter = + new OutputStreamWriter( new FileOutputStream( new File( config.getEclipseProjectDirectory(), + "maven-eclipse.xml" ) ), "UTF-8" ); + PrettyPrintXMLWriter buildXmlPrinter = new PrettyPrintXMLWriter( buildXmlWriter ); + + buildXmlPrinter.startElement( "project" ); + buildXmlPrinter.addAttribute( "default", "copy-resources" ); + + buildXmlPrinter.startElement( "target" ); + buildXmlPrinter.addAttribute( "name", "init" ); + // initialize filtering tokens here + buildXmlPrinter.endElement(); + + buildXmlPrinter.startElement( "target" ); + buildXmlPrinter.addAttribute( "name", "copy-resources" ); + buildXmlPrinter.addAttribute( "depends", "init" ); + + for ( Iterator it = specialSources.iterator(); it.hasNext(); ) + { + // TODO: merge source dirs on output path+filtering to reduce + // <copy> tags for speed. + EclipseSourceDir dir = (EclipseSourceDir) it.next(); + buildXmlPrinter.startElement( "copy" ); + buildXmlPrinter.addAttribute( "todir", dir.getOutput() ); + buildXmlPrinter.addAttribute( "filtering", "" + dir.isFiltering() ); + + buildXmlPrinter.startElement( "fileset" ); + buildXmlPrinter.addAttribute( "dir", dir.getPath() ); + if ( dir.getInclude() != null ) + { + buildXmlPrinter.addAttribute( "includes", dir.getInclude() ); + } + if ( dir.getExclude() != null ) + { + buildXmlPrinter.addAttribute( "excludes", dir.getExclude() ); + } + buildXmlPrinter.endElement(); + + buildXmlPrinter.endElement(); + } + + buildXmlPrinter.endElement(); + + buildXmlPrinter.endElement(); + + IOUtil.close( buildXmlWriter ); + } + catch ( IOException e ) + { + throw new MojoExecutionException( "Cannot create " + config.getEclipseProjectDirectory() + + "/maven-eclipse.xml", e ); + } + + log.info( "Creating external launcher file" ); + // now create the launcher + new EclipseAntExternalLaunchConfigurationWriter().init( log, config, "Maven_Ant_Builder.launch", + "maven-eclipse.xml" ).write(); + + // finally add it to the project writer. + + config.getBuildCommands().add( + new BuildCommand( + "org.eclipse.ui.externaltools.ExternalToolBuilder", + "LaunchConfigHandle", + "<project>/" + + EclipseLaunchConfigurationWriter.FILE_DOT_EXTERNAL_TOOL_BUILDERS + + "Maven_Ant_Builder.launch" ) ); + } + + // ---------------------------------------------------------------------- + // The default output + // ---------------------------------------------------------------------- + + writer.startElement( ELT_CLASSPATHENTRY ); + writer.addAttribute( ATTR_KIND, ATTR_OUTPUT ); + writer.addAttribute( ATTR_PATH, defaultOutput ); + writer.endElement(); + + // ---------------------------------------------------------------------- + // Container classpath entries + // ---------------------------------------------------------------------- + + for ( Iterator it = config.getClasspathContainers().iterator(); it.hasNext(); ) + { + writer.startElement( ELT_CLASSPATHENTRY ); + writer.addAttribute( ATTR_KIND, "con" ); //$NON-NLS-1$ + writer.addAttribute( ATTR_PATH, (String) it.next() ); + writer.endElement(); // name + } + + // ---------------------------------------------------------------------- + // The dependencies + // ---------------------------------------------------------------------- + Set addedDependencies = new HashSet(); + // TODO if (..magic property equals orderDependencies..) + IdeDependency[] depsToWrite = config.getDepsOrdered(); + for ( int j = 0; j < depsToWrite.length; j++ ) + { + IdeDependency dep = depsToWrite[j]; + + if ( dep.isAddedToClasspath() ) + { + String depId = + dep.getGroupId() + ":" + dep.getArtifactId() + ":" + dep.getClassifier() + ":" + dep.getVersion(); + /* avoid duplicates in the classpath for artifacts with different types (like ejbs) */ + if ( !addedDependencies.contains( depId ) ) + { + addDependency( writer, dep ); + addedDependencies.add( depId ); + } + } + } + + writer.endElement(); + + IOUtil.close( w ); + + } + + protected void addDependency( XMLWriter writer, IdeDependency dep ) + throws MojoExecutionException + { + + String path; + String kind; + String sourcepath = null; + String javadocpath = null; + // [rfeng] Force to non-PDE mode + boolean pdeMode = false; + + if ( dep.isReferencedProject() && !pdeMode ) + { + path = "/" + dep.getEclipseProjectName(); //$NON-NLS-1$ + kind = ATTR_SRC; + } + else if ( dep.isReferencedProject() && pdeMode ) + { + // don't do anything, referenced projects are automatically handled by eclipse in PDE builds + return; + } + else + { + File artifactPath = dep.getFile(); + + if ( artifactPath == null ) + { + log.error( Messages.getString( "EclipsePlugin.artifactpathisnull", dep.getId() ) ); //$NON-NLS-1$ + return; + } + + if ( dep.isSystemScoped() ) + { + path = IdeUtils.toRelativeAndFixSeparator( config.getEclipseProjectDirectory(), artifactPath, false ); + + if ( log.isDebugEnabled() ) + { + log.debug( Messages.getString( "EclipsePlugin.artifactissystemscoped", //$NON-NLS-1$ + new Object[] { dep.getArtifactId(), path } ) ); + } + + kind = ATTR_LIB; + } + else + { + File localRepositoryFile = new File( config.getLocalRepository().getBasedir() ); + + // if the dependency is not provided and the plugin runs in "pde mode", the dependency is + // added to the Bundle-Classpath: + if ( pdeMode && ( dep.isProvided() || dep.isOsgiBundle() ) ) + { + return; + } + else if ( pdeMode && !dep.isProvided() && !dep.isTestDependency() ) + { + // path for link created in .project, not to the actual file + path = dep.getFile().getName(); + + kind = ATTR_LIB; + } + // running in PDE mode and the dependency is provided means, that it is provided by + // the target platform. This case is covered by adding the plugin container + else + { + String fullPath = artifactPath.getPath(); + String relativePath = + IdeUtils.toRelativeAndFixSeparator( localRepositoryFile, new File( fullPath ), false ); + + if ( !new File( relativePath ).isAbsolute() ) + { + path = M2_REPO + "/" //$NON-NLS-1$ + + relativePath; + kind = ATTR_VAR; //$NON-NLS-1$ + } + else + { + path = relativePath; + kind = ATTR_LIB; + } + } + + if ( dep.getSourceAttachment() != null ) + { + if ( ATTR_VAR.equals( kind ) ) + { + sourcepath = + M2_REPO + + "/" //$NON-NLS-1$ + + + IdeUtils.toRelativeAndFixSeparator( localRepositoryFile, dep.getSourceAttachment(), + false ); + } + else + { + // source archive must be referenced with the full path, we can't mix a lib with a variable + sourcepath = IdeUtils.getCanonicalPath( dep.getSourceAttachment() ); + } + } + + if ( dep.getJavadocAttachment() != null ) + { + // NB eclipse (3.1) doesn't support variables in javadoc paths, so we need to add the + // full path for the maven repo + javadocpath = + StringUtils.replace( IdeUtils.getCanonicalPath( dep.getJavadocAttachment() ), "\\", "/" ); //$NON-NLS-1$ //$NON-NLS-2$ + } + + } + + } + + writer.startElement( ELT_CLASSPATHENTRY ); + writer.addAttribute( ATTR_KIND, kind ); + writer.addAttribute( ATTR_PATH, path ); + + if ( sourcepath != null ) + { + writer.addAttribute( ATTR_SOURCEPATH, sourcepath ); + } + + boolean attributeElemOpen = false; + + if ( javadocpath != null ) + { + if ( !attributeElemOpen ) + { + writer.startElement( "attributes" ); //$NON-NLS-1$ + attributeElemOpen = true; + } + + writer.startElement( "attribute" ); //$NON-NLS-1$ + writer.addAttribute( "value", "jar:" + new File( javadocpath ).toURI() + "!/" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + writer.addAttribute( "name", "javadoc_location" ); //$NON-NLS-1$ //$NON-NLS-2$ + writer.endElement(); + + } + + if ( Constants.PROJECT_PACKAGING_WAR.equals( this.config.getPackaging() ) && config.getWtpapplicationxml() && + kind.equals( ATTR_VAR ) && !dep.isTestDependency() && !dep.isProvided() && + !dep.isSystemScopedOutsideProject( this.config.getProject() ) ) + { + if ( !attributeElemOpen ) + { + writer.startElement( "attributes" ); //$NON-NLS-1$ + attributeElemOpen = true; + } + + writer.startElement( "attribute" ); //$NON-NLS-1$ + writer.addAttribute( "value", "/WEB-INF/lib" ); //$NON-NLS-1$ //$NON-NLS-2$ + writer.addAttribute( "name", "org.eclipse.jst.component.dependency" ); //$NON-NLS-1$ //$NON-NLS-2$ + writer.endElement(); + + } + + if ( attributeElemOpen ) + { + writer.endElement(); + } + writer.endElement(); + + } +} diff --git a/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipseCleanMojo.java b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipseCleanMojo.java new file mode 100644 index 0000000000..ac770c743c --- /dev/null +++ b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipseCleanMojo.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.maven.plugin.eclipse; + +import java.io.File; +import java.io.IOException; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.eclipse.Constants; +import org.apache.maven.plugin.eclipse.EclipseConfigFile; +import org.codehaus.plexus.util.FileUtils; + +/** + * Deletes the .project, .classpath, .wtpmodules files and .settings folder used by Eclipse. + * + * @goal clean + */ +public class EclipseCleanMojo + extends AbstractMojo +{ + + /** + * Definition file for Eclipse Web Tools project. + */ + private static final String FILE_DOT_WTPMODULES = ".wtpmodules"; //$NON-NLS-1$ + + /** + * Classpath definition file for an Eclipse Java project. + */ + private static final String FILE_DOT_CLASSPATH = ".classpath"; //$NON-NLS-1$ + + /** + * Project definition file for an Eclipse Project. + */ + private static final String FILE_DOT_PROJECT = ".project"; //$NON-NLS-1$ + + /** + * Web Project definition file for Eclipse Web Tools Project (Release 1.0x). + */ + private static final String DIR_DOT_SETTINGS = ".settings"; //$NON-NLS-1$ + + /** + * File name where the WTP component settings will be stored - WTP 1.0 name. + */ + private static final String FILE_DOT_COMPONENT = ".settings/.component"; //$NON-NLS-1$ + + /** + * File name where the WTP component settings will be stored - WTP 1.5 name. + */ + private static final String FILE_DOT_COMPONENT_15 = ".settings/org.eclipse.wst.common.component"; //$NON-NLS-1$ + + /** + * File name where Eclipse Project's Facet configuration will be stored. + */ + private static final String FILE_FACET_CORE_XML = ".settings/org.eclipse.wst.common.project.facet.core.xml"; //$NON-NLS-1$ + + /** + * General project preferences. + */ + private static final String FILE_ECLIPSE_JDT_CORE_PREFS = ".settings/org.eclipse.jdt.core.prefs"; //$NON-NLS-1$ + + /** + * Packaging for the current project. + * + * @parameter expression="${project.packaging}" + */ + private String packaging; + + /** + * The root directory of the project + * + * @parameter expression="${basedir}" + */ + private File basedir; + + /** + * Skip the operation when true. + * + * @parameter expression="${eclipse.skip}" default-value="false" + */ + private boolean skip; + + /** + * additional generic configuration files for eclipse + * + * @parameter + */ + private EclipseConfigFile[] additionalConfig; + + /** + * @see org.apache.maven.plugin.AbstractMojo#execute() + */ + public void execute() + throws MojoExecutionException + { + if ( skip ) + { + return; + } + + if ( Constants.PROJECT_PACKAGING_POM.equals( this.packaging ) ) + { + return; + } + + delete( new File( basedir, FILE_DOT_PROJECT ) ); + delete( new File( basedir, FILE_DOT_CLASSPATH ) ); + delete( new File( basedir, FILE_DOT_WTPMODULES ) ); + + delete( new File( basedir, FILE_DOT_COMPONENT ) ); + delete( new File( basedir, FILE_DOT_COMPONENT_15 ) ); + delete( new File( basedir, FILE_FACET_CORE_XML ) ); + delete( new File( basedir, FILE_ECLIPSE_JDT_CORE_PREFS ) ); + + File settingsDir = new File( basedir, DIR_DOT_SETTINGS ); + if ( settingsDir.exists() && settingsDir.isDirectory() && settingsDir.list().length == 0 ) + { + delete( settingsDir ); + } + + if ( additionalConfig != null ) + { + for ( int i = 0; i < additionalConfig.length; i++ ) + { + delete( new File( basedir, additionalConfig[i].getName() ) ); + } + } + + cleanExtras(); + } + + protected void cleanExtras() + throws MojoExecutionException + { + // extension point. + } + + /** + * Delete a file, handling log messages and exceptions + * + * @param f File to be deleted + * @throws MojoExecutionException only if a file exists and can't be deleted + */ + protected void delete( File f ) + throws MojoExecutionException + { + if ( f.isDirectory() ) + { + getLog().info( Messages.getString( "EclipseCleanMojo.deletingDirectory", f.getName() ) ); //$NON-NLS-1$ + } + else + { + getLog().info( Messages.getString( "EclipseCleanMojo.deletingFile", f.getName() ) ); //$NON-NLS-1$ + } + + if ( f.exists() ) + { + if ( !f.delete() ) + { + try + { + FileUtils.forceDelete( f ); + } + catch ( IOException e ) + { + throw new MojoExecutionException( Messages.getString( "EclipseCleanMojo.failedtodelete", //$NON-NLS-1$ + new Object[] { f.getName(), + f.getAbsolutePath() } ) ); + } + } + } + else + { + getLog().debug( Messages.getString( "EclipseCleanMojo.nofilefound", f.getName() ) ); //$NON-NLS-1$ + } + } + + /** + * Getter for <code>basedir</code>. + * + * @return Returns the basedir. + */ + public File getBasedir() + { + return this.basedir; + } + + /** + * Setter for <code>basedir</code>. + * + * @param basedir The basedir to set. + */ + public void setBasedir( File basedir ) + { + this.basedir = basedir; + } + + /** + * @return the packaging + */ + public String getPackaging() + { + return this.packaging; + } + + /** + * @param packaging the packaging to set + */ + public void setPackaging( String packaging ) + { + this.packaging = packaging; + } + +} diff --git a/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipsePlugin.java b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipsePlugin.java new file mode 100644 index 0000000000..f2049c2cde --- /dev/null +++ b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipsePlugin.java @@ -0,0 +1,1550 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.maven.plugin.eclipse; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.handler.ArtifactHandler; +import org.apache.maven.model.Resource; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.eclipse.BuildCommand; +import org.apache.maven.plugin.eclipse.Constants; +import org.apache.maven.plugin.eclipse.EclipseConfigFile; +import org.apache.maven.plugin.eclipse.EclipseSourceDir; +import org.apache.maven.plugin.eclipse.WorkspaceConfiguration; +import org.apache.maven.plugin.eclipse.reader.ReadWorkspaceLocations; +import org.apache.maven.plugin.eclipse.writers.EclipseManifestWriter; +import org.apache.maven.plugin.eclipse.writers.EclipseSettingsWriter; +import org.apache.maven.plugin.eclipse.writers.EclipseWriterConfig; +import org.apache.maven.plugin.eclipse.writers.wtp.EclipseWtpApplicationXMLWriter; +import org.apache.maven.plugin.eclipse.writers.wtp.EclipseWtpComponent15Writer; +import org.apache.maven.plugin.eclipse.writers.wtp.EclipseWtpComponentWriter; +import org.apache.maven.plugin.eclipse.writers.wtp.EclipseWtpFacetsWriter; +import org.apache.maven.plugin.eclipse.writers.wtp.EclipseWtpmodulesWriter; +import org.apache.maven.plugin.ide.IdeDependency; +import org.apache.maven.plugin.ide.IdeUtils; +import org.apache.maven.plugin.ide.JeeUtils; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.resource.ResourceManager; +import org.codehaus.plexus.resource.loader.FileResourceLoader; +import org.codehaus.plexus.resource.loader.ResourceNotFoundException; +import org.codehaus.plexus.util.FileUtils; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Generates the following eclipse configuration files: + * <ul> + * <li><code>.project</code> and <code>.classpath</code> files</li> + * <li><code>.setting/org.eclipse.jdt.core.prefs</code> with project specific compiler settings</li> + * <li>various configuration files for WTP (Web Tools Project), if the parameter <code>wtpversion</code> is set to a + * valid version (WTP configuration is not generated by default)</li> + * </ul> + * If this goal is run on a multiproject root, dependencies between modules will be configured as direct project + * dependencies in Eclipse (unless <code>useProjectReferences</code> is set to <code>false</code>). + * + * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a> + * @author <a href="mailto:fgiust@apache.org">Fabrizio Giustina</a> + * @version $Id: EclipsePlugin.java 641616 2008-03-26 22:42:42Z aheritier $ + * @goal eclipse + * @phase generate-resources + */ +public class EclipsePlugin + extends AbstractIdeSupportMojo +{ + + private static final String NATURE_WST_FACET_CORE_NATURE = "org.eclipse.wst.common.project.facet.core.nature"; //$NON-NLS-1$ + + private static final String BUILDER_WST_COMPONENT_STRUCTURAL_DEPENDENCY_RESOLVER = + "org.eclipse.wst.common.modulecore.ComponentStructuralBuilderDependencyResolver"; //$NON-NLS-1$ + + protected static final String BUILDER_WST_VALIDATION = "org.eclipse.wst.validation.validationbuilder"; //$NON-NLS-1$ + + private static final String BUILDER_JDT_CORE_JAVA = "org.eclipse.jdt.core.javabuilder"; //$NON-NLS-1$ + + private static final String BUILDER_WST_COMPONENT_STRUCTURAL = + "org.eclipse.wst.common.modulecore.ComponentStructuralBuilder"; //$NON-NLS-1$ + + private static final String BUILDER_WST_FACET = "org.eclipse.wst.common.project.facet.core.builder"; //$NON-NLS-1$ + + private static final String BUILDER_PDE_MANIFEST = "org.eclipse.pde.ManifestBuilder"; //$NON-NLS-1$ + + private static final String BUILDER_PDE_SCHEMA = "org.eclipse.pde.SchemaBuilder"; //$NON-NLS-1$ + + private static final String NATURE_WST_MODULE_CORE_NATURE = "org.eclipse.wst.common.modulecore.ModuleCoreNature"; //$NON-NLS-1$ + + private static final String NATURE_JDT_CORE_JAVA = "org.eclipse.jdt.core.javanature"; //$NON-NLS-1$ + + private static final String NATURE_JEM_WORKBENCH_JAVA_EMF = "org.eclipse.jem.workbench.JavaEMFNature"; //$NON-NLS-1$ + + private static final String NATURE_PDE_PLUGIN = "org.eclipse.pde.PluginNature"; //$NON-NLS-1$ + + protected static final String COMMON_PATH_JDT_LAUNCHING_JRE_CONTAINER = "org.eclipse.jdt.launching.JRE_CONTAINER"; //$NON-NLS-1$ + + protected static final String REQUIRED_PLUGINS_CONTAINER = "org.eclipse.pde.core.requiredPlugins"; //$NON-NLS-1$ + + // warning, order is important for binary search + public static final String[] WTP_SUPPORTED_VERSIONS = new String[] { "1.0", "1.5", "2.0", "R7", "none" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + /** + * Constant for 'artifactId' element in POM.xml. + */ + private static final String POM_ELT_ARTIFACT_ID = "artifactId"; //$NON-NLS-1$ + + /** + * Constant for 'groupId' element in POM.xml. + */ + private static final String POM_ELT_GROUP_ID = "groupId"; //$NON-NLS-1$ + + /** + * List of eclipse project natures. By default the <code>org.eclipse.jdt.core.javanature</code> nature plus the + * needed WTP natures are added. Natures added using this property <strong>replace</strong> the default list. + * + * <pre> + * <projectnatures> + * <projectnature>org.eclipse.jdt.core.javanature</projectnature> + * <projectnature>org.eclipse.wst.common.modulecore.ModuleCoreNature</projectnature> + * </projectnatures> + * </pre> + * + * @parameter + */ + private List projectnatures; + + /** + * List of artifact to exclude from eclipse classpath, beeing provided by some eclipse classPathContainer + * [MECLIPSE-79] + * + * @since 2.5 + * @parameter + */ + private List excludes; + + /** + * List of eclipse project natures to be added to the default ones. + * + * <pre> + * <additionalProjectnatures> + * <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature> + * </additionalProjectnatures> + * </pre> + * + * @parameter + */ + private List additionalProjectnatures; + + /** + * List of eclipse project facets to be added to the default ones. + * + * <pre> + * <additionalProjectFacets> + * <jst.jsf>1.1<jst.jsf/> + * </additionalProjectFacets> + * </pre> + * + * @parameter + */ + private Map additionalProjectFacets; + + /** + * List of eclipse build commands. By default the <code>org.eclipse.jdt.core.javabuilder</code> builder plus the + * needed WTP builders are added. If you specify any configuration for this parameter, only those buildcommands + * specified will be used; the defaults won't be added. Use the <code>additionalBuildCommands</code> parameter for + * that. Configuration example: Old style: + * + * <pre> + * <buildcommands> + * <buildcommand>org.eclipse.wst.common.modulecore.ComponentStructuralBuilder</buildcommand> + * <buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand> + * <buildcommand>org.eclipse.wst.common.modulecore.ComponentStructuralBuilderDependencyResolver</buildcommand> + * </buildcommands> + * </pre> + * + * For new style, see <code>additionalBuildCommands</code>. + * + * @parameter + */ + private List buildcommands; + + /** + * List of eclipse build commands to be added to the default ones. Old style: + * + * <pre> + * <additionalBuildcommands> + * <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand> + * </additionalBuildcommands> + * </pre> + * + * New style: + * + * <pre> + * <additionalBuildcommands> + * <buildCommand> + * <name>org.ui.externaltools.ExternalToolBuilder</name> + * <triggers>auto,full,incremental,</triggers> + * <arguments> + * <LaunchConfigHandle>&lt;project&gt;./externalToolBuilders/MavenBuilder.launch</LaunchConfighandle> + * </arguments> + * </buildCommand> + * </additionalBuildcommands> + * </pre> + * + * Note the difference between <code>build<strong>c</strong>ommand</code> and + * <code>build<strong>C</strong>ommand</code>. You can mix and match old and new-style configuration entries. + * + * @parameter + */ + private List additionalBuildcommands; + + /** + * List of container classpath entries. By default the <code>org.eclipse.jdt.launching.JRE_CONTAINER</code> + * classpath container is added. Configuration example: + * + * <pre> + * <classpathContainers> + * <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer> + * <classpathContainer>org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v5.5</classpathContainer> + * <classpathContainer>org.eclipse.jst.j2ee.internal.web.container/artifact</classpathContainer> + * </classpathContainers> + * </pre> + * + * @parameter + */ + private List classpathContainers; + + /** + * Enables/disables the downloading of source attachments. Defaults to false. DEPRECATED - use downloadSources + * + * @parameter expression="${eclipse.downloadSources}" + * @deprecated use downloadSources + */ + private boolean eclipseDownloadSources; + + /** + * Eclipse workspace directory. + * + * @parameter expression="${eclipse.projectDir}" alias="outputDir" + */ + private File eclipseProjectDir; + + /** + * When set to false, the plugin will not create sub-projects and instead reference those sub-projects using the + * installed package in the local repository + * + * @parameter expression="${eclipse.useProjectReferences}" default-value="true" + * @required + */ + private boolean useProjectReferences; + + /** + * The default output directory + * + * @parameter expression="${outputDirectory}" alias="outputDirectory" + * default-value="${project.build.outputDirectory}" + * @required + */ + private File buildOutputDirectory; + + /** + * The version of WTP for which configuration files will be generated. The default value is "none" (don't generate + * WTP configuration), supported versions are "R7", "1.0", and "1.5" + * + * @parameter expression="${wtpversion}" default-value="none" + */ + private String wtpversion; + + /** + * JEE context name of the WTP module. ( ex. WEB context name ). + * + * @parameter expression="${wtpContextName}" + */ + private String wtpContextName; + + /** + * The relative path of the manifest file + * + * @parameter expression="${eclipse.manifest}" default-value="${basedir}/META-INF/MANIFEST.MF" + */ + private File manifest; + + /** + * Allow to configure additional generic configuration files for eclipse that will be written out to disk when + * running eclipse:eclipse. FOr each file you can specify the name and the text content. + * + * <pre> + * <plugin> + * <groupId>org.apache.maven.plugins</groupId> + * <artifactId>maven-eclipse-plugin</artifactId> + * <configuration> + * <additionalConfig> + * <file> + * <name>.checkstyle</name> + * <content> + * <![CDATA[<fileset-config file-format-version="1.2.0" simple-config="true"> + * <fileset name="all" enabled="true" check-config-name="acme corporate style" local="false"> + * <file-match-pattern match-pattern="." include-pattern="true"/> + * </fileset> + * <filter name="NonSrcDirs" enabled="true"/> + * </fileset-config>]]> + * </content> + * </file> + * </additionalConfig> + * </configuration> + * </plugin> + * </pre> + * + * Instead of the content you can also define (from version 2.5) an url to download the file : + * + * <pre> + * <plugin> + * <groupId>org.apache.maven.plugins</groupId> + * <artifactId>maven-eclipse-plugin</artifactId> + * <configuration> + * <additionalConfig> + * <file> + * <name>.checkstyle</name> + * <url>http://some.place.org/path/to/file</url> + * </file> + * </additionalConfig> + * </configuration> + * </pre> + * + * or a location : + * + * <pre> + * <plugin> + * <groupId>org.apache.maven.plugins</groupId> + * <artifactId>maven-eclipse-plugin</artifactId> + * <configuration> + * <additionalConfig> + * <file> + * <name>.checkstyle</name> + * <location>/checkstyle-config.xml</location> + * </file> + * </additionalConfig> + * </configuration> + * <dependencies> + * <!-- The file defined in the location is stored in this dependency --> + * <dependency> + * <groupId>eclipsetest</groupId> + * <artifactId>checkstyle-config</artifactId> + * <version>1.0</version> + * </dependency> + * </dependencies> + * </plugin> + * </pre> + * + * @parameter + */ + private EclipseConfigFile[] additionalConfig; + + /** + * If set to <code>true</code>, the version number of the artifact is appended to the name of the generated + * Eclipse project. See projectNameTemplate for other options. + * + * @parameter expression="${eclipse.addVersionToProjectName}" default-value="false" + */ + private boolean addVersionToProjectName; + + /** + * If set to <code>true</code>, the groupId of the artifact is appended to the name of the generated Eclipse + * project. See projectNameTemplate for other options. + * + * @parameter expression="${eclipse.addGroupIdToProjectName}" default-value="false" + */ + private boolean addGroupIdToProjectName; + + /** + * Allows configuring the name of the eclipse projects. This property if set wins over addVersionToProjectName and + * addGroupIdToProjectName You can use <code>[groupId]</code>, <code>[artifactId]</code> and + * <code>[version]</code> variables. eg. <code>[groupId].[artifactId]-[version]</code> + * + * @parameter expression="${eclipse.projectNameTemplate}" + */ + private String projectNameTemplate; + + /** + * Parsed wtp version. + */ + private float wtpVersionFloat; + + /** + * Not a plugin parameter. Is this a java project? + */ + private boolean isJavaProject; + + /** + * Must the manifest files be written for java projects so that that the jee classpath for wtp is correct. + * + * @parameter expression="${eclipse.wtpmanifest}" default-value="false" + */ + private boolean wtpmanifest; + + /** + * Must the application files be written for ear projects in a separate directory. + * + * @parameter expression="${eclipse.wtpapplicationxml}" default-value="false" + */ + private boolean wtpapplicationxml; + + /** + * What WTP defined server to use for deployment informations. + * + * @parameter expression="${eclipse.wtpdefaultserver}" + */ + private String wtpdefaultserver; + + private WorkspaceConfiguration workspaceConfiguration; + + /** + * ResourceManager for getting additonalConfig files from resources + * + * @component + * @required + * @readonly + */ + private ResourceManager locator; + + /** + * This eclipse workspace is read and all artifacts detected there will be connected as eclipse projects and will + * not be linked to the jars in the local repository. Requirement is that it was created with the similar wtp + * settings as the reactor projects, but the project name template my differ. The pom's in the workspace projects + * may not contain variables in the artefactId, groupId and version tags. + * + * @since 2.5 + * @parameter expression="${eclipse.workspace}" + */ + protected String workspace; + + /** + * Limit the use of project references to the current workspace. No project references will be created to projects + * in the reactor when they are not available in the workspace. + * + * @parameter expression="${eclipse.limitProjectReferencesToWorkspace}" default-value="false" + */ + protected boolean limitProjectReferencesToWorkspace; + + protected boolean isJavaProject() + { + return isJavaProject; + } + + protected boolean isPdeProject() + { + return pde; + } + + /** + * Getter for <code>buildcommands</code>. + * + * @return Returns the buildcommands. + */ + public List getBuildcommands() + { + return buildcommands; + } + + /** + * Setter for <code>buildcommands</code>. + * + * @param buildcommands The buildcommands to set. + */ + public void setBuildcommands( List buildcommands ) + { + this.buildcommands = buildcommands; + } + + /** + * Getter for <code>buildOutputDirectory</code>. + * + * @return Returns the buildOutputDirectory. + */ + public File getBuildOutputDirectory() + { + return buildOutputDirectory; + } + + /** + * Setter for <code>buildOutputDirectory</code>. + * + * @param buildOutputDirectory The buildOutputDirectory to set. + */ + public void setBuildOutputDirectory( File buildOutputDirectory ) + { + this.buildOutputDirectory = buildOutputDirectory; + } + + /** + * Getter for <code>classpathContainers</code>. + * + * @return Returns the classpathContainers. + */ + public List getClasspathContainers() + { + return classpathContainers; + } + + /** + * Setter for <code>classpathContainers</code>. + * + * @param classpathContainers The classpathContainers to set. + */ + public void setClasspathContainers( List classpathContainers ) + { + this.classpathContainers = classpathContainers; + } + + /** + * Getter for <code>eclipseProjectDir</code>. + * + * @return Returns the eclipseProjectDir. + */ + public File getEclipseProjectDir() + { + return eclipseProjectDir; + } + + /** + * Setter for <code>eclipseProjectDir</code>. + * + * @param eclipseProjectDir The eclipseProjectDir to set. + */ + public void setEclipseProjectDir( File eclipseProjectDir ) + { + this.eclipseProjectDir = eclipseProjectDir; + } + + /** + * Getter for <code>projectnatures</code>. + * + * @return Returns the projectnatures. + */ + public List getProjectnatures() + { + return projectnatures; + } + + /** + * Setter for <code>projectnatures</code>. + * + * @param projectnatures The projectnatures to set. + */ + public void setProjectnatures( List projectnatures ) + { + this.projectnatures = projectnatures; + } + + /** + * Getter for <code>useProjectReferences</code>. + * + * @return Returns the useProjectReferences. + */ + public boolean getUseProjectReferences() + { + return useProjectReferences; + } + + /** + * Setter for <code>useProjectReferences</code>. + * + * @param useProjectReferences The useProjectReferences to set. + */ + public void setUseProjectReferences( boolean useProjectReferences ) + { + this.useProjectReferences = useProjectReferences; + } + + /** + * Getter for <code>wtpversion</code>. + * + * @return Returns the wtpversion. + */ + public String getWtpversion() + { + return wtpversion; + } + + /** + * Setter for <code>wtpversion</code>. + * + * @param wtpversion The wtpversion to set. + */ + public void setWtpversion( String wtpversion ) + { + this.wtpversion = wtpversion; + } + + /** + * Getter for <code>additionalBuildcommands</code>. + * + * @return Returns the additionalBuildcommands. + */ + public List getAdditionalBuildcommands() + { + return additionalBuildcommands; + } + + /** + * Setter for <code>additionalBuildcommands</code>. + * + * @param additionalBuildcommands The additionalBuildcommands to set. + */ + public void setAdditionalBuildcommands( List additionalBuildcommands ) + { + this.additionalBuildcommands = additionalBuildcommands; + } + + /** + * Getter for <code>additionalProjectnatures</code>. + * + * @return Returns the additionalProjectnatures. + */ + public List getAdditionalProjectnatures() + { + return additionalProjectnatures; + } + + /** + * Setter for <code>additionalProjectnatures</code>. + * + * @param additionalProjectnatures The additionalProjectnatures to set. + */ + public void setAdditionalProjectnatures( List additionalProjectnatures ) + { + this.additionalProjectnatures = additionalProjectnatures; + } + + /** + * Getter for <code>addVersionToProjectName</code>. + */ + public boolean isAddVersionToProjectName() + { + return addVersionToProjectName; + } + + /** + * Setter for <code>addVersionToProjectName</code>. + */ + public void setAddVersionToProjectName( boolean addVersionToProjectName ) + { + this.addVersionToProjectName = addVersionToProjectName; + } + + /** + * Getter for <code>addGroupIdToProjectName</code>. + */ + public boolean isAddGroupIdToProjectName() + { + return addGroupIdToProjectName; + } + + /** + * Setter for <code>addGroupIdToProjectName</code>. + */ + public void setAddGroupIdToProjectName( boolean addGroupIdToProjectName ) + { + this.addGroupIdToProjectName = addGroupIdToProjectName; + } + + public String getProjectNameTemplate() + { + return projectNameTemplate; + } + + public void setProjectNameTemplate( String projectNameTemplate ) + { + this.projectNameTemplate = projectNameTemplate; + } + + /** + * @see org.apache.maven.plugin.Mojo#execute() + */ + public boolean setup() + throws MojoExecutionException + { + boolean ready = true; + + checkDeprecations(); + + ready = validate(); + + // TODO: Why are we using project in some places, and executedProject in others?? + ArtifactHandler artifactHandler = project.getArtifact().getArtifactHandler(); + + // ear projects don't contain java sources + // pde projects are always java projects + isJavaProject = + pde || + ( Constants.LANGUAGE_JAVA.equals( artifactHandler.getLanguage() ) && !Constants.PROJECT_PACKAGING_EAR.equals( packaging ) ); + + setupExtras(); + + parseConfigurationOptions(); + + // defaults + if ( projectnatures == null ) + { + fillDefaultNatures( packaging ); + } + + if ( additionalProjectnatures != null ) + { + projectnatures.addAll( additionalProjectnatures ); + } + + if ( buildcommands == null ) + { + fillDefaultBuilders( packaging ); + } + else + { + convertBuildCommandList( buildcommands ); + } + + if ( additionalBuildcommands != null ) + { + convertBuildCommandList( additionalBuildcommands ); + buildcommands.addAll( additionalBuildcommands ); + } + + if ( classpathContainers == null ) + { + fillDefaultClasspathContainers( packaging ); + } + else + { + verifyClasspathContainerListIsComplete(); + } + locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() ); + locator.setOutputDirectory( new File( project.getBuild().getDirectory() ) ); + + // ready to start + return ready; + } + + protected void convertBuildCommandList( List commands ) + { + if ( commands != null ) + { + for ( ListIterator i = commands.listIterator(); i.hasNext(); ) + { + Object command = i.next(); + + if ( command instanceof String ) + { + command = new BuildCommand( (String) command ); + i.set( command ); + } + } + } + } + + private void parseConfigurationOptions() + { + if ( "R7".equalsIgnoreCase( wtpversion ) ) //$NON-NLS-1$ + { + wtpVersionFloat = 0.7f; + } + else if ( "1.0".equalsIgnoreCase( wtpversion ) ) //$NON-NLS-1$ + { + wtpVersionFloat = 1.0f; + } + else if ( "1.5".equalsIgnoreCase( wtpversion ) ) //$NON-NLS-1$ + { + wtpVersionFloat = 1.5f; + } + else if ( "2.0".equalsIgnoreCase( wtpversion ) ) //$NON-NLS-1$ + { + wtpVersionFloat = 2.0f; + } + if ( !"none".equalsIgnoreCase( wtpversion ) ) + { + getLog().info( Messages.getString( "EclipsePlugin.wtpversion", wtpversion ) ); + } + } + + protected void setupExtras() + throws MojoExecutionException + { + // extension point. + } + + protected void verifyClasspathContainerListIsComplete() + { + boolean containsJREContainer = false; + // Check if classpathContainer contains a JRE (default, alternate or + // Execution Environment) + for ( Iterator iter = classpathContainers.iterator(); iter.hasNext(); ) + { + Object classPathContainer = iter.next(); + if ( classPathContainer != null && + classPathContainer.toString().startsWith( COMMON_PATH_JDT_LAUNCHING_JRE_CONTAINER ) ) + { + containsJREContainer = true; + break; + } + } + if ( !containsJREContainer ) + { + getLog().warn( Messages.getString( "EclipsePlugin.missingjrecontainer" ) ); //$NON-NLS-1$ + classpathContainers.add( 0, COMMON_PATH_JDT_LAUNCHING_JRE_CONTAINER ); + } + } + + private boolean validate() + throws MojoExecutionException + { + // validate sanity of the current m2 project + if ( Arrays.binarySearch( WTP_SUPPORTED_VERSIONS, wtpversion ) < 0 ) + { + throw new MojoExecutionException( + Messages.getString( "EclipsePlugin.unsupportedwtp", new Object[] { //$NON-NLS-1$ + wtpversion, + StringUtils.join( WTP_SUPPORTED_VERSIONS, " " ) } ) ); //$NON-NLS-1$ + } + + assertNotEmpty( executedProject.getGroupId(), POM_ELT_GROUP_ID ); + assertNotEmpty( executedProject.getArtifactId(), POM_ELT_ARTIFACT_ID ); + + if ( executedProject.getFile() == null || !executedProject.getFile().exists() ) + { + throw new MojoExecutionException( Messages.getString( "EclipsePlugin.missingpom" ) ); //$NON-NLS-1$ + } + + if ( "pom".equals( packaging ) && eclipseProjectDir == null ) //$NON-NLS-1$ + { + getLog().info( Messages.getString( "EclipsePlugin.pompackaging" ) ); //$NON-NLS-1$ + return false; + } + + if ( "eclipse-plugin".equals( packaging ) ) + { + pde = true; + } + + if ( eclipseProjectDir == null ) + { + eclipseProjectDir = executedProject.getFile().getParentFile(); + } + + if ( !eclipseProjectDir.exists() && !eclipseProjectDir.mkdirs() ) + { + throw new MojoExecutionException( Messages.getString( "EclipsePlugin.cantcreatedir", eclipseProjectDir ) ); //$NON-NLS-1$ + } + + if ( !eclipseProjectDir.equals( executedProject.getFile().getParentFile() ) ) + { + if ( !eclipseProjectDir.isDirectory() ) + { + throw new MojoExecutionException( Messages.getString( "EclipsePlugin.notadir", eclipseProjectDir ) ); //$NON-NLS-1$ + } + eclipseProjectDir = new File( eclipseProjectDir, executedProject.getArtifactId() ); + if ( !eclipseProjectDir.isDirectory() && !eclipseProjectDir.mkdirs() ) + { + throw new MojoExecutionException( Messages.getString( "EclipsePlugin.cantcreatedir", eclipseProjectDir ) ); //$NON-NLS-1$ + } + } + + validateExtras(); + + return true; + } + + protected void validateExtras() + { + // provided for extension. + } + + private void checkDeprecations() + { + if ( eclipseDownloadSources ) + { + // deprecated warning + getLog().warn( Messages.getString( "EclipsePlugin.deprecatedpar", new Object[] { //$NON-NLS-1$ + "eclipse.downloadSources", //$NON-NLS-1$ + "downloadSources" } ) ); //$NON-NLS-1$ + downloadSources = true; + } + + checkExtraDeprecations(); + } + + protected void checkExtraDeprecations() + { + // provided for extension. + } + + public void writeConfiguration( IdeDependency[] deps ) + throws MojoExecutionException + { + EclipseWriterConfig config = createEclipseWriterConfig( deps ); + + if ( wtpmanifest && isJavaProject() ) + { + EclipseManifestWriter.addManifestResource( getLog(), config ); + } + // NOTE: This could change the config! + writeExtraConfiguration( config ); + + if ( wtpVersionFloat == 0.7f ) + { + new EclipseWtpmodulesWriter().init( getLog(), config ).write(); + } + + if ( wtpVersionFloat >= 1.0f ) + { + new EclipseWtpFacetsWriter().init( getLog(), config ).write(); + } + if ( wtpVersionFloat == 1.0f ) + { + new EclipseWtpComponentWriter().init( getLog(), config ).write(); + } + if ( wtpVersionFloat >= 1.5 ) + { + new EclipseWtpComponent15Writer().init( getLog(), config ).write(); + } + + new EclipseSettingsWriter().init( getLog(), config ).write(); + + if ( isJavaProject ) + { + new EclipseClasspathWriter().init( getLog(), config ).write(); + } + + if ( wtpapplicationxml ) + { + new EclipseWtpApplicationXMLWriter().init( getLog(), config ).write(); + } + + // [rfeng] + /* + if ( pde ) + { + this.getLog().info( "The Maven Eclipse plugin runs in 'pde'-mode." ); + new EclipseOSGiManifestWriter().init( getLog(), config ).write(); + } + */ + // [rfeng] + + // NOTE: This one MUST be after EclipseClasspathwriter, and possibly others, + // since currently EclipseClasspathWriter does some magic to detect nested + // output folders and modifies the configuration by adding new (Ant) builders. + // So the .project file must be written AFTER those have run! + new EclipseProjectWriter().init( getLog(), config ).write(); + + writeAdditionalConfig(); + + getLog().info( Messages.getString( "EclipsePlugin.wrote", new Object[] { //$NON-NLS-1$ + config.getEclipseProjectName(), eclipseProjectDir.getAbsolutePath() } ) ); + } + + protected void writeAdditionalConfig() + throws MojoExecutionException + { + if ( additionalConfig != null ) + { + for ( int j = 0; j < additionalConfig.length; j++ ) + { + EclipseConfigFile file = additionalConfig[j]; + File projectRelativeFile = new File( eclipseProjectDir, file.getName() ); + if ( projectRelativeFile.isDirectory() ) + { + // just ignore? + getLog().warn( Messages.getString( "EclipsePlugin.foundadir", //$NON-NLS-1$ + projectRelativeFile.getAbsolutePath() ) ); + } + + try + { + projectRelativeFile.getParentFile().mkdirs(); + if ( file.getContent() == null ) + { + InputStream inStream; + if ( file.getLocation() != null ) + { + inStream = locator.getResourceAsInputStream( file.getLocation() ); + } + else + { + inStream = file.getURL().openConnection().getInputStream(); + } + OutputStream outStream = new FileOutputStream( projectRelativeFile ); + try + { + IOUtil.copy( inStream, outStream ); + } + finally + { + inStream.close(); + outStream.close(); + } + } + else + { + FileUtils.fileWrite( projectRelativeFile.getAbsolutePath(), file.getContent() ); + } + } + catch ( IOException e ) + { + throw new MojoExecutionException( Messages.getString( "EclipsePlugin.cantwritetofile", //$NON-NLS-1$ + projectRelativeFile.getAbsolutePath() ) ); + } + catch ( ResourceNotFoundException e ) + { + throw new MojoExecutionException( Messages.getString( "EclipsePlugin.cantfindresource", //$NON-NLS-1$ + file.getLocation() ) ); + } + + } + } + } + + protected EclipseWriterConfig createEclipseWriterConfig( IdeDependency[] deps ) + throws MojoExecutionException + { + File projectBaseDir = executedProject.getFile().getParentFile(); + + // build a list of UNIQUE source dirs (both src and resources) to be + // used in classpath and wtpmodules + EclipseSourceDir[] sourceDirs = buildDirectoryList( executedProject, eclipseProjectDir, buildOutputDirectory ); + + EclipseWriterConfig config = new EclipseWriterConfig(); + + config.setWorkspaceConfiguration( getWorkspaceConfiguration() ); + + config.setProjectNameTemplate( calculateProjectNameTemplate() ); + + String projectName = IdeUtils.getProjectName( config.getProjectNameTemplate(), project ); + + config.setEclipseProjectName( projectName ); + + config.setWtpapplicationxml( wtpapplicationxml ); + + config.setWtpVersion( wtpVersionFloat ); + + Set convertedBuildCommands = new LinkedHashSet(); + + if ( buildcommands != null ) + { + for ( Iterator it = buildcommands.iterator(); it.hasNext(); ) + { + Object cmd = it.next(); + + if ( cmd instanceof BuildCommand ) + { + convertedBuildCommands.add( cmd ); + } + else + { + convertedBuildCommands.add( new BuildCommand( (String) cmd ) ); + } + } + } + + config.setBuildCommands( new LinkedList( convertedBuildCommands ) ); + + config.setBuildOutputDirectory( buildOutputDirectory ); + config.setClasspathContainers( classpathContainers ); + config.setDeps( deps ); + config.setEclipseProjectDirectory( eclipseProjectDir ); + config.setLocalRepository( localRepository ); + config.setOSGIManifestFile( manifest ); + config.setPde( pde ); + config.setProject( project ); + config.setProjectBaseDir( projectBaseDir ); + config.setProjectnatures( projectnatures ); + config.setProjectFacets( additionalProjectFacets ); + config.setSourceDirs( sourceDirs ); + config.setAddVersionToProjectName( isAddVersionToProjectName() ); + config.setPackaging( packaging ); + + collectWarContextRootsFromReactorEarConfiguration( config ); + + return config; + } + + /** + * If this is a war module peek into the reactor an search for an ear module that defines the context root of this + * module. + * + * @param config config to save the context root. + */ + private void collectWarContextRootsFromReactorEarConfiguration( EclipseWriterConfig config ) + { + if ( reactorProjects != null && wtpContextName == null && + Constants.PROJECT_PACKAGING_WAR.equals( project.getPackaging() ) ) + { + for ( Iterator iter = reactorProjects.iterator(); iter.hasNext(); ) + { + MavenProject reactorProject = (MavenProject) iter.next(); + + if ( Constants.PROJECT_PACKAGING_EAR.equals( reactorProject.getPackaging() ) ) + { + Xpp3Dom[] warDefinitions = + IdeUtils.getPluginConfigurationDom( reactorProject, JeeUtils.ARTIFACT_MAVEN_EAR_PLUGIN, + new String[] { "modules", "webModule" } ); + for ( int index = 0; index < warDefinitions.length; index++ ) + { + Xpp3Dom groupId = warDefinitions[index].getChild( "groupId" ); + Xpp3Dom artifactId = warDefinitions[index].getChild( "artifactId" ); + Xpp3Dom contextRoot = warDefinitions[index].getChild( "contextRoot" ); + if ( groupId != null && artifactId != null && contextRoot != null && + groupId.getValue() != null && artifactId.getValue() != null && + contextRoot.getValue() != null ) + { + getLog().info( + "Found context root definition for " + groupId.getValue() + ":" + + artifactId.getValue() + " " + contextRoot.getValue() ); + if ( project.getArtifactId().equals( artifactId.getValue() ) && + project.getGroupId().equals( groupId.getValue() ) ) + { + config.setContextName( contextRoot.getValue() ); + } + } + else + { + getLog().info( + "Found incomplete ear configuration in " + reactorProject.getGroupId() + + ":" + reactorProject.getGroupId() + " found " + + warDefinitions[index].toString() ); + } + } + } + } + } + if ( config.getContextName() == null && Constants.PROJECT_PACKAGING_WAR.equals( project.getPackaging() ) ) + { + if ( wtpContextName == null ) + { + config.setContextName( project.getArtifactId() ); + } + else + { + config.setContextName( wtpContextName ); + } + } + } + + /** + * Write any extra configuration information for the Eclipse project. This is an extension point, called before the + * main configurations are written. <br/> <b> NOTE: This could change the config! </b> + * + * @param config + * @throws MojoExecutionException + */ + protected void writeExtraConfiguration( EclipseWriterConfig config ) + throws MojoExecutionException + { + // extension point. + } + + private void assertNotEmpty( String string, String elementName ) + throws MojoExecutionException + { + if ( string == null ) + { + throw new MojoExecutionException( Messages.getString( "EclipsePlugin.missingelement", elementName ) ); //$NON-NLS-1$ + } + } + + protected void fillDefaultNatures( String packaging ) + { + projectnatures = new ArrayList(); + + if ( wtpVersionFloat >= 1.0f ) + { + projectnatures.add( NATURE_WST_FACET_CORE_NATURE ); // WTP 1.0 nature + } + + if ( isJavaProject ) + { + projectnatures.add( NATURE_JDT_CORE_JAVA ); + } + + if ( wtpVersionFloat >= 0.7f ) + { + projectnatures.add( NATURE_WST_MODULE_CORE_NATURE ); // WTP 0.7/1.0 nature + + if ( isJavaProject ) + { + projectnatures.add( NATURE_JEM_WORKBENCH_JAVA_EMF ); // WTP 0.7/1.0 nature + } + } + + if ( pde ) + { + projectnatures.add( NATURE_PDE_PLUGIN ); + } + + } + + protected void fillDefaultClasspathContainers( String packaging ) + { + classpathContainers = new ArrayList(); + + if ( getWorkspaceConfiguration().getDefaultClasspathContainer() != null ) + { + getLog().info( + "Adding default classpath contaigner: " + + getWorkspaceConfiguration().getDefaultClasspathContainer() ); + classpathContainers.add( getWorkspaceConfiguration().getDefaultClasspathContainer() ); + } + if ( pde ) + { + classpathContainers.add( REQUIRED_PLUGINS_CONTAINER ); + } + } + + protected void fillDefaultBuilders( String packaging ) + { + buildcommands = new ArrayList(); + + if ( wtpVersionFloat == 0.7f ) + { + buildcommands.add( new BuildCommand( BUILDER_WST_COMPONENT_STRUCTURAL ) ); // WTP 0.7 builder + } + + if ( isJavaProject ) + { + buildcommands.add( new BuildCommand( BUILDER_JDT_CORE_JAVA ) ); + } + + if ( wtpVersionFloat >= 1.5f ) + { + buildcommands.add( new BuildCommand( BUILDER_WST_FACET ) ); // WTP 1.5 builder + } + + if ( wtpVersionFloat >= 0.7f ) + { + buildcommands.add( new BuildCommand( BUILDER_WST_VALIDATION ) ); // WTP 0.7/1.0 builder + } + + if ( wtpVersionFloat == 0.7f ) + { + // WTP 0.7 builder + buildcommands.add( new BuildCommand( BUILDER_WST_COMPONENT_STRUCTURAL_DEPENDENCY_RESOLVER ) ); + } + + if ( pde ) + { + buildcommands.add( new BuildCommand( BUILDER_PDE_MANIFEST ) ); + buildcommands.add( new BuildCommand( BUILDER_PDE_SCHEMA ) ); + } + } + + public EclipseSourceDir[] buildDirectoryList( MavenProject project, File basedir, File buildOutputDirectory ) + throws MojoExecutionException + { + File projectBaseDir = project.getFile().getParentFile(); + + // avoid duplicated entries + Set directories = new TreeSet(); + + extractSourceDirs( directories, project.getCompileSourceRoots(), basedir, projectBaseDir, false, null ); + + String relativeOutput = IdeUtils.toRelativeAndFixSeparator( projectBaseDir, buildOutputDirectory, false ); + + extractResourceDirs( directories, project.getBuild().getResources(), project, basedir, projectBaseDir, false, + relativeOutput ); + + // If using the standard output location, don't mix the test output into it. + String testOutput = null; + boolean useStandardOutputDir = + buildOutputDirectory.equals( new File( project.getBuild().getOutputDirectory() ) ); + if ( useStandardOutputDir ) + { + getLog().debug( + "testOutput toRelativeAndFixSeparator " + projectBaseDir + " , " + + project.getBuild().getTestOutputDirectory() ); + testOutput = + IdeUtils.toRelativeAndFixSeparator( projectBaseDir, + new File( project.getBuild().getTestOutputDirectory() ), false ); + getLog().debug( "testOutput after toRelative : " + testOutput ); + } + + extractSourceDirs( directories, project.getTestCompileSourceRoots(), basedir, projectBaseDir, true, testOutput ); + + extractResourceDirs( directories, project.getBuild().getTestResources(), project, basedir, projectBaseDir, + true, testOutput ); + + return (EclipseSourceDir[]) directories.toArray( new EclipseSourceDir[directories.size()] ); + } + + private void extractSourceDirs( Set directories, List sourceRoots, File basedir, File projectBaseDir, boolean test, + String output ) + throws MojoExecutionException + { + for ( Iterator it = sourceRoots.iterator(); it.hasNext(); ) + { + + File sourceRootFile = new File( (String) it.next() ); + + if ( sourceRootFile.isDirectory() ) + { + String sourceRoot = + IdeUtils.toRelativeAndFixSeparator( projectBaseDir, sourceRootFile, + !projectBaseDir.equals( basedir ) ); + + directories.add( new EclipseSourceDir( sourceRoot, output, false, test, null, null, false ) ); + } + } + } + + void extractResourceDirs( Set directories, List resources, MavenProject project, File basedir, + File workspaceProjectBaseDir, boolean test, final String output ) + throws MojoExecutionException + { + for ( Iterator it = resources.iterator(); it.hasNext(); ) + { + Resource resource = (Resource) it.next(); + + getLog().debug( "Processing resource dir: " + resource.getDirectory() ); + + String includePattern = null; + String excludePattern = null; + + if ( resource.getIncludes().size() != 0 ) + { + includePattern = StringUtils.join( resource.getIncludes().iterator(), "|" ); + } + + if ( resource.getExcludes().size() != 0 ) + { + excludePattern = StringUtils.join( resource.getExcludes().iterator(), "|" ); + } + + // TODO: figure out how to merge if the same dir is specified twice + // with different in/exclude patterns. + + File resourceDirectory = new File( /* basedir, */resource.getDirectory() ); + + if ( !resourceDirectory.exists() || !resourceDirectory.isDirectory() ) + { + getLog().debug( "Resource dir: " + resourceDirectory + " either missing or not a directory." ); + continue; + } + + String resourceDir = + IdeUtils.toRelativeAndFixSeparator( workspaceProjectBaseDir, resourceDirectory, + !workspaceProjectBaseDir.equals( basedir ) ); + String thisOutput = output; + if ( thisOutput != null ) + { + // sometimes thisOutput is already an absolute path + File outputFile = new File( thisOutput ); + if ( !outputFile.isAbsolute() ) + { + outputFile = new File( workspaceProjectBaseDir, thisOutput ); + } + // create output dir if it doesn't exist + outputFile.mkdirs(); + + if ( !StringUtils.isEmpty( resource.getTargetPath() ) ) + { + outputFile = new File( outputFile, resource.getTargetPath() ); + // create output dir if it doesn't exist + outputFile.mkdirs(); + } + + getLog().debug( + "Making relative and fixing separator: { " + workspaceProjectBaseDir + ", " + + outputFile + ", false }." ); + thisOutput = IdeUtils.toRelativeAndFixSeparator( workspaceProjectBaseDir, outputFile, false ); + } + + getLog().debug( + "Adding eclipse source dir: { " + resourceDir + ", " + thisOutput + ", true, " + test + + ", " + includePattern + ", " + excludePattern + " }." ); + + directories.add( new EclipseSourceDir( resourceDir, thisOutput, true, test, includePattern, excludePattern, + resource.isFiltering() ) ); + } + } + + /** + * Calculate the project name template from the fields {@link #projectNameTemplate}, + * {@link #addVersionToProjectName} and {@link #addGroupIdToProjectName} + * + * @return the project name template that should be used after considering the plugin configuration + */ + private String calculateProjectNameTemplate() + { + if ( getProjectNameTemplate() != null ) + { + if ( isAddVersionToProjectName() || isAddGroupIdToProjectName() ) + { + getLog().warn( + "projectNameTemplate definition overrides " + + "addVersionToProjectName or addGroupIdToProjectName" ); + } + return getProjectNameTemplate(); + } + else if ( isAddVersionToProjectName() && isAddGroupIdToProjectName() ) + { + return IdeUtils.PROJECT_NAME_WITH_GROUP_AND_VERSION_TEMPLATE; + } + else if ( isAddVersionToProjectName() ) + { + return IdeUtils.PROJECT_NAME_WITH_VERSION_TEMPLATE; + } + else if ( isAddGroupIdToProjectName() ) + { + return IdeUtils.PROJECT_NAME_WITH_GROUP_TEMPLATE; + } + return IdeUtils.PROJECT_NAME_DEFAULT_TEMPLATE; + } + + /** + * {@inheritDoc} + */ + public String getProjectNameForArifact( Artifact artifact ) + { + IdeDependency[] workspaceArtefacts = getWorkspaceArtefacts(); + for ( int index = 0; workspaceArtefacts != null && index < workspaceArtefacts.length; index++ ) + { + IdeDependency workspaceArtefact = workspaceArtefacts[index]; + if ( workspaceArtefact.isAddedToClasspath() && + workspaceArtefact.getGroupId().equals( artifact.getGroupId() ) && + workspaceArtefact.getArtifactId().equals( artifact.getArtifactId() ) ) + { + if ( workspaceArtefact.getVersion().equals( artifact.getVersion() ) ) + { + return workspaceArtefact.getEclipseProjectName(); + } + } + } + return IdeUtils.getProjectName( calculateProjectNameTemplate(), artifact ); + } + + /** + * {@inheritDoc} + */ + protected IdeDependency[] getWorkspaceArtefacts() + { + return getWorkspaceConfiguration().getWorkspaceArtefacts(); + } + + public WorkspaceConfiguration getWorkspaceConfiguration() + { + if ( workspaceConfiguration == null ) + { + workspaceConfiguration = new WorkspaceConfiguration(); + if ( workspace != null ) + { + workspaceConfiguration.setWorkspaceDirectory( new File( workspace ) ); + } + new ReadWorkspaceLocations().init( getLog(), workspaceConfiguration, project, wtpdefaultserver ); + } + return workspaceConfiguration; + } + + public List getExcludes() + { + return excludes; + } + + /** + * Utility method that locates a project in the workspace for the given artifact. + * + * @param artifact the artifact a project should produce. + * @return <code>true</code> if the artifact is produced by a reactor projectart. + */ + private boolean isAvailableAsAWorkspaceProject( Artifact artifact ) + { + IdeDependency[] workspaceArtefacts = getWorkspaceArtefacts(); + for ( int index = 0; workspaceArtefacts != null && index < workspaceArtefacts.length; index++ ) + { + IdeDependency workspaceArtefact = workspaceArtefacts[index]; + if ( workspaceArtefact.getGroupId().equals( artifact.getGroupId() ) && + workspaceArtefact.getArtifactId().equals( artifact.getArtifactId() ) ) + { + if ( workspaceArtefact.getVersion().equals( artifact.getVersion() ) ) + { + workspaceArtefact.setAddedToClasspath( true ); + getLog().debug( "Using workspace project: " + workspaceArtefact.getEclipseProjectName() ); + return true; + } + else + { + getLog().info( + "Artifact " + + artifact.getId() + + " already available as a workspace project, but with different version. Expected: " + + artifact.getVersion() + ", found: " + workspaceArtefact.getVersion() ); + } + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @see org.apache.maven.plugin.ide.AbstractIdeSupportMojo#doDependencyResolution() + */ + protected IdeDependency[] doDependencyResolution() + throws MojoExecutionException + { + + return super.doDependencyResolution(); + } + + /** + * Checks if jar has to be resolved for the given artifact + * + * @param art the artifact to check + * @return true if resolution should happen + */ + protected boolean hasToResolveJar( Artifact art ) + { + return !( getUseProjectReferences() && isAvailableAsAReactorProject( art ) ) || + ( limitProjectReferencesToWorkspace && !( getUseProjectReferences() && isAvailableAsAWorkspaceProject( art ) ) ); + } + + /** + * Checks if a projects reference has to be used for the given artifact + * + * @param art the artifact to check + * @return true if a project reference has to be used. + */ + protected boolean useProjectReference( Artifact art ) + { + boolean isReactorProject = getUseProjectReferences() && isAvailableAsAReactorProject( art ); + boolean isWorkspaceProject = getUseProjectReferences() && isAvailableAsAWorkspaceProject( art ); + return ( isReactorProject && !limitProjectReferencesToWorkspace ) || // default + ( limitProjectReferencesToWorkspace && isWorkspaceProject ) || // limitProjectReferencesToWorkspace + ( !isReactorProject && isWorkspaceProject ); // default + workspace projects + } +} diff --git a/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipseProjectWriter.java b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipseProjectWriter.java new file mode 100644 index 0000000000..070b9b3e8b --- /dev/null +++ b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/EclipseProjectWriter.java @@ -0,0 +1,351 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.maven.plugin.eclipse; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.maven.model.Resource; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.eclipse.BuildCommand; +import org.apache.maven.plugin.eclipse.writers.AbstractEclipseWriter; +import org.apache.maven.plugin.ide.IdeDependency; +import org.apache.maven.plugin.ide.IdeUtils; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; +import org.codehaus.plexus.util.xml.XMLWriter; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.codehaus.plexus.util.xml.Xpp3DomBuilder; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Writes eclipse .project file. + * + * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a> + * @author <a href="mailto:kenney@neonics.com">Kenney Westerhof</a> + * @author <a href="mailto:fgiust@apache.org">Fabrizio Giustina</a> + * @version $Id: EclipseProjectWriter.java 616816 2008-01-30 17:23:08Z aheritier $ + */ +public class EclipseProjectWriter + extends AbstractEclipseWriter +{ + private static final String ELT_NAME = "name"; //$NON-NLS-1$ + + private static final String ELT_BUILD_COMMAND = "buildCommand"; //$NON-NLS-1$ + + private static final String ELT_BUILD_SPEC = "buildSpec"; //$NON-NLS-1$ + + private static final String ELT_NATURE = "nature"; //$NON-NLS-1$ + + private static final String ELT_NATURES = "natures"; //$NON-NLS-1$ + + private static final String FILE_DOT_PROJECT = ".project"; //$NON-NLS-1$ + + /** + * Constant for links to files. + */ + private static final int LINK_TYPE_FILE = 1; + + /** + * Constant for links to directories. + */ + private static final int LINK_TYPE_DIRECTORY = 2; + + /** + * @see org.apache.tuscany.sca.maven.plugin.eclipse.writers.EclipseWriter#write() + */ + public void write() + throws MojoExecutionException + { + + Set projectnatures = new LinkedHashSet(); + Set buildCommands = new LinkedHashSet(); + + File dotProject = new File( config.getEclipseProjectDirectory(), FILE_DOT_PROJECT ); + + if ( dotProject.exists() ) + { + + log.info( Messages.getString( "EclipsePlugin.keepexisting", dotProject.getAbsolutePath() ) ); //$NON-NLS-1$ + + // parse existing file in order to keep manually-added entries + Reader reader = null; + try + { + reader = new InputStreamReader( new FileInputStream( dotProject ), "UTF-8" ); + Xpp3Dom dom = Xpp3DomBuilder.build( reader ); + + Xpp3Dom naturesElement = dom.getChild( ELT_NATURES ); + if ( naturesElement != null ) + { + Xpp3Dom[] existingNatures = naturesElement.getChildren( ELT_NATURE ); + for ( int j = 0; j < existingNatures.length; j++ ) + { + // adds all the existing natures + projectnatures.add( existingNatures[j].getValue() ); + } + } + + Xpp3Dom buildSpec = dom.getChild( ELT_BUILD_SPEC ); + if ( buildSpec != null ) + { + Xpp3Dom[] existingBuildCommands = buildSpec.getChildren( ELT_BUILD_COMMAND ); + for ( int j = 0; j < existingBuildCommands.length; j++ ) + { + Xpp3Dom buildCommandName = existingBuildCommands[j].getChild( ELT_NAME ); + if ( buildCommandName != null ) + { + buildCommands.add( new BuildCommand( existingBuildCommands[j] ) ); + } + } + } + } + catch ( XmlPullParserException e ) + { + log.warn( Messages.getString( "EclipsePlugin.cantparseexisting", dotProject.getAbsolutePath() ) ); //$NON-NLS-1$ + } + catch ( IOException e ) + { + log.warn( Messages.getString( "EclipsePlugin.cantparseexisting", dotProject.getAbsolutePath() ) ); //$NON-NLS-1$ + } + finally + { + IOUtil.close( reader ); + } + } + + // adds new entries after the existing ones + for ( Iterator iter = config.getProjectnatures().iterator(); iter.hasNext(); ) + { + projectnatures.add( iter.next() ); + } + + for ( Iterator iter = config.getBuildCommands().iterator(); iter.hasNext(); ) + { + buildCommands.add( (BuildCommand) iter.next() ); + } + + Writer w; + + try + { + w = new OutputStreamWriter( new FileOutputStream( dotProject ), "UTF-8" ); + } + catch ( IOException ex ) + { + throw new MojoExecutionException( Messages.getString( "EclipsePlugin.erroropeningfile" ), ex ); //$NON-NLS-1$ + } + + XMLWriter writer = new PrettyPrintXMLWriter( w ); + + writer.startElement( "projectDescription" ); //$NON-NLS-1$ + + writer.startElement( ELT_NAME ); + writer.writeText( config.getEclipseProjectName() ); + writer.endElement(); + + // TODO: this entire element might be dropped if the comment is null. + // but as the maven1 eclipse plugin does it, it's better to be safe than sorry + // A eclipse developer might want to look at this. + writer.startElement( "comment" ); //$NON-NLS-1$ + + if ( config.getProject().getDescription() != null ) + { + writer.writeText( config.getProject().getDescription() ); + } + + writer.endElement(); + + writer.startElement( "projects" ); //$NON-NLS-1$ + + // referenced projects should not be added for plugins + if ( !config.isPde() ) + { + for ( int j = 0; j < config.getDepsOrdered().length; j++ ) + { + IdeDependency dep = config.getDepsOrdered()[j]; + if ( dep.isReferencedProject() ) + { + writer.startElement( "project" ); //$NON-NLS-1$ + writer.writeText( dep.getEclipseProjectName() ); + writer.endElement(); + } + } + } + + writer.endElement(); // projects + + writer.startElement( ELT_BUILD_SPEC ); + + for ( Iterator it = buildCommands.iterator(); it.hasNext(); ) + { + ( (BuildCommand) it.next() ).print( writer ); + } + + writer.endElement(); // buildSpec + + writer.startElement( ELT_NATURES ); + + for ( Iterator it = projectnatures.iterator(); it.hasNext(); ) + { + writer.startElement( ELT_NATURE ); + writer.writeText( (String) it.next() ); + writer.endElement(); // name + } + + writer.endElement(); // natures + + /* + boolean addLinks = !config.getProjectBaseDir().equals( config.getEclipseProjectDirectory() ); + + if ( addLinks || ( config.isPde() && config.getDepsOrdered().length > 0 ) ) + { + writer.startElement( "linkedResources" ); //$NON-NLS-1$ + + if ( addLinks ) + { + + addFileLink( writer, config.getProjectBaseDir(), config.getEclipseProjectDirectory(), + config.getProject().getFile() ); + + addSourceLinks( writer, config.getProjectBaseDir(), config.getEclipseProjectDirectory(), + config.getProject().getCompileSourceRoots() ); + addResourceLinks( writer, config.getProjectBaseDir(), config.getEclipseProjectDirectory(), + config.getProject().getBuild().getResources() ); + + addSourceLinks( writer, config.getProjectBaseDir(), config.getEclipseProjectDirectory(), + config.getProject().getTestCompileSourceRoots() ); + addResourceLinks( writer, config.getProjectBaseDir(), config.getEclipseProjectDirectory(), + config.getProject().getBuild().getTestResources() ); + + } + + if ( config.isPde() ) + { + for ( int j = 0; j < config.getDepsOrdered().length; j++ ) + { + IdeDependency dep = config.getDepsOrdered()[j]; + + if ( dep.isAddedToClasspath() && !dep.isProvided() && !dep.isReferencedProject() && + !dep.isTestDependency() && !dep.isOsgiBundle() ) + { + String name = dep.getFile().getName(); + addLink( writer, name, StringUtils.replace( IdeUtils.getCanonicalPath( dep.getFile() ), "\\", + "/" ), LINK_TYPE_FILE ); + } + } + } + + writer.endElement(); // linkedResources + } + */ + + writer.endElement(); // projectDescription + + IOUtil.close( w ); + } + + private void addFileLink( XMLWriter writer, File projectBaseDir, File basedir, File file ) + throws MojoExecutionException + { + if ( file.isFile() ) + { + String name = IdeUtils.toRelativeAndFixSeparator( projectBaseDir, file, true ); + String location = IdeUtils.getCanonicalPath( file ).replaceAll( "\\\\", "/" ); //$NON-NLS-1$ //$NON-NLS-2$ + + addLink( writer, name, location, LINK_TYPE_FILE ); + } + else + { + log.warn( Messages.getString( "EclipseProjectWriter.notafile", file ) ); //$NON-NLS-1$ + } + } + + private void addSourceLinks( XMLWriter writer, File projectBaseDir, File basedir, List sourceRoots ) + throws MojoExecutionException + { + for ( Iterator it = sourceRoots.iterator(); it.hasNext(); ) + { + String sourceRootString = (String) it.next(); + File sourceRoot = new File( sourceRootString ); + + if ( sourceRoot.isDirectory() ) + { + String name = IdeUtils.toRelativeAndFixSeparator( projectBaseDir, sourceRoot, true ); + String location = IdeUtils.getCanonicalPath( sourceRoot ).replaceAll( "\\\\", "/" ); //$NON-NLS-1$ //$NON-NLS-2$ + + addLink( writer, name, location, LINK_TYPE_DIRECTORY ); + } + } + } + + private void addResourceLinks( XMLWriter writer, File projectBaseDir, File basedir, List sourceRoots ) + throws MojoExecutionException + { + for ( Iterator it = sourceRoots.iterator(); it.hasNext(); ) + { + String resourceDirString = ( (Resource) it.next() ).getDirectory(); + File resourceDir = new File( resourceDirString ); + + if ( resourceDir.isDirectory() ) + { + String name = IdeUtils.toRelativeAndFixSeparator( projectBaseDir, resourceDir, true ); + String location = IdeUtils.getCanonicalPath( resourceDir ).replaceAll( "\\\\", "/" ); //$NON-NLS-1$ //$NON-NLS-2$ + + addLink( writer, name, location, LINK_TYPE_DIRECTORY ); + } + } + } + + /** + * @param writer + * @param name + * @param location + */ + private void addLink( XMLWriter writer, String name, String location, int type ) + { + writer.startElement( "link" ); //$NON-NLS-1$ + + writer.startElement( ELT_NAME ); + writer.writeText( name ); + writer.endElement(); // name + + writer.startElement( "type" ); //$NON-NLS-1$ + writer.writeText( Integer.toString( type ) ); + writer.endElement(); // type + + writer.startElement( "location" ); //$NON-NLS-1$ + + writer.writeText( location ); + + writer.endElement(); // location + + writer.endElement(); // link + } +} diff --git a/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/Messages.java b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/Messages.java new file mode 100644 index 0000000000..553cae6256 --- /dev/null +++ b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/maven/plugin/eclipse/Messages.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.maven.plugin.eclipse; + +import java.text.MessageFormat; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * @author <a href="mailto:fgiust@users.sourceforge.net">Fabrizio Giustina</a> + * @version $Id: Messages.java 485864 2006-12-11 20:41:36Z fgiust $ + */ +public class Messages +{ + + private static final String BUNDLE_NAME = "org.apache.tuscany.sca.maven.plugin.eclipse.messages"; //$NON-NLS-1$ + + private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle( BUNDLE_NAME ); + + private Messages() + { + } + + public static String getString( String key ) + { + try + { + return RESOURCE_BUNDLE.getString( key ); + } + catch ( MissingResourceException e ) + { + return '!' + key + '!'; + } + } + + public static String getString( String key, Object[] params ) + { + try + { + return MessageFormat.format( RESOURCE_BUNDLE.getString( key ), params ); + } + catch ( MissingResourceException e ) + { + return '!' + key + '!'; + } + } + + public static String getString( String key, Object param ) + { + return getString( key, new Object[] { param } ); + } +} diff --git a/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/tools/bundle/plugin/EclipsePluginMojo.java b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/tools/bundle/plugin/EclipsePluginMojo.java deleted file mode 100644 index c40020aee8..0000000000 --- a/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/java/org/apache/tuscany/sca/tools/bundle/plugin/EclipsePluginMojo.java +++ /dev/null @@ -1,675 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.tuscany.sca.tools.bundle.plugin; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -import org.apache.maven.artifact.Artifact; -import org.apache.maven.artifact.repository.ArtifactRepository; -import org.apache.maven.model.Resource; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.util.StringUtils; - -/** - * A maven plugin that generates Generate .classpath and .project - * - * @version $Rev$ $Date$ - * @goal generate-pde - * @phase generate-resources - * @requiresDependencyResolution test - * @description Generate .classpath and .project - */ -public class EclipsePluginMojo extends AbstractMojo { - - public static class EclipseSourceDir implements Comparable { - private String exclude; - - private boolean filtering; - - private String include; - - private boolean isResource; - - private String output; - - private String path; - - private boolean test; - - public EclipseSourceDir(String path, - String output, - boolean isResource, - boolean test, - String include, - String exclude, - boolean filtering) { - this.path = path; - this.output = output; - this.isResource = isResource; - this.test = test; - this.include = include; - this.exclude = exclude; - this.filtering = filtering; - } - - /** - * @see java.lang.Comparable#compareTo(java.lang.Object) - */ - public int compareTo(Object obj) { - return this.path.compareTo(((EclipseSourceDir)obj).path); - } - - /** - * @see java.lang.Object#equals(java.lang.Object) - */ - public boolean equals(Object obj) { - return (obj != null) && (obj instanceof EclipseSourceDir) && this.path.equals(((EclipseSourceDir)obj).path); - } - - /** - * Getter for <code>exclude</code>. - * - * @return Returns the exclude. - */ - public String getExclude() { - return this.exclude; - } - - /** - * Getter for <code>include</code>. - * - * @return Returns the include. - */ - public String getInclude() { - return this.include; - } - - /** - * Getter for <code>output</code>. - * - * @return Returns the output. - */ - public String getOutput() { - return this.output; - } - - /** - * Getter for <code>path</code>. - * - * @return Returns the path. - */ - public String getPath() { - return this.path; - } - - /** - * @see java.lang.Object#hashCode() - */ - public int hashCode() { - return this.path.hashCode(); - } - - /** - * Wheter this resource should be copied with filtering. - */ - public boolean isFiltering() { - return filtering; - } - - /** - * Getter for <code>isResource</code>. - * - * @return Returns the isResource. - */ - public boolean isResource() { - return this.isResource; - } - - /** - * Getter for <code>test</code>. - * - * @return Returns the test. - */ - public boolean isTest() { - return this.test; - } - - /** - * Setter for <code>exclude</code>. - * - * @param exclude The exclude to set. - */ - public void setExclude(String exclude) { - this.exclude = exclude; - } - - /** - * Setter for <code>include</code>. - * - * @param include The include to set. - */ - public void setInclude(String include) { - this.include = include; - } - - /** - * Setter for <code>output</code>. - * - * @param output The output to set. - */ - public void setOutput(String output) { - this.output = output; - } - - /** - * Setter for <code>path</code>. - * - * @param path The path to set. - */ - public void setPath(String path) { - this.path = path; - } - - /** - * Setter for <code>test</code>. - * - * @param test The test to set. - */ - public void setTest(boolean test) { - this.test = test; - } - } - - /** - * Attribute name for source file excludes in a path. - */ - private static final String ATTR_EXCLUDING = "excluding"; - - /** - * Attribute name for source file includes in a path. - */ - private static final String ATTR_INCLUDING = "including"; - - /** - * Attribute for kind - Container (con), Variable (var)..etc. - */ - private static final String ATTR_KIND = "kind"; - - /** - * Attribute for output. - */ - private static final String ATTR_OUTPUT = "output"; - - /** - * Attribute for path. - */ - private static final String ATTR_PATH = "path"; - - /** - * Attribute value for kind: src - */ - private static final String ATTR_SRC = "src"; - - /** - * Attribute value for kind: var - */ - private static final String ATTR_VAR = "var"; - - /** - * Attribute value for kind: lib - */ - private static final String ATTR_LIB = "lib"; - - /** - * Eclipse build path variable M2_REPO - */ - private static final String M2_REPO = "M2_REPO"; - - /** - * Element for classpathentry. - */ - private static final String ELT_CLASSPATHENTRY = "classpathentry"; - private static final String ELT_CLASSPATH = "classpath"; - - /** - * @parameter expression="${localRepository}" - * @required - * @readonly - */ - private ArtifactRepository localRepository; - - /** - * If the executed project is a reactor project, this will contains the full list of projects in the reactor. - * - * @parameter expression="${reactorProjects}" - * @required - * @readonly - */ - protected List reactorProjects; - - /** - * Utility method that locates a project producing the given artifact. - * - * @param artifact the artifact a project should produce. - * @return <code>true</code> if the artifact is produced by a reactor project. - */ - private boolean isAvailableAsAReactorProject(Artifact artifact) { - if (reactorProjects != null) { - for (Iterator iter = reactorProjects.iterator(); iter.hasNext();) { - MavenProject reactorProject = (MavenProject)iter.next(); - - if (reactorProject.getGroupId().equals(artifact.getGroupId()) && reactorProject.getArtifactId() - .equals(artifact.getArtifactId())) { - if (reactorProject.getVersion().equals(artifact.getVersion())) { - return true; - } else { - getLog().info("Artifact " + artifact.getId() - + " already available as a reactor project, but with different version. Expected: " - + artifact.getVersion() - + ", found: " - + reactorProject.getVersion()); - } - } - } - } - return false; - } - - private static String getCanonicalPath(File file) throws MojoExecutionException { - try { - return file.getCanonicalPath(); - } catch (IOException e) { - throw new MojoExecutionException(e.getMessage(), e); - } - } - - private static String toRelativeAndFixSeparator(File basedir, File fileToAdd, boolean replaceSlashesWithDashes) - throws MojoExecutionException { - if (!fileToAdd.isAbsolute()) { - fileToAdd = new File(basedir, fileToAdd.getPath()); - } - - String basedirpath; - String absolutePath; - - basedirpath = getCanonicalPath(basedir); - absolutePath = getCanonicalPath(fileToAdd); - - String relative; - - if (absolutePath.equals(basedirpath)) { - relative = "."; - } else if (absolutePath.startsWith(basedirpath)) { - relative = absolutePath.substring(basedirpath.length() + 1); - } else { - relative = absolutePath; - } - - relative = StringUtils.replace(relative, '\\', '/'); - - if (replaceSlashesWithDashes) { - relative = StringUtils.replace(relative, '/', '-'); - relative = StringUtils.replace(relative, ':', '-'); // remove ":" for absolute paths in windows - } - - return relative; - } - - /** - * The project to create a distribution for. - * - * @parameter expression="${project}" - * @required - * @readonly - */ - private MavenProject project; - - /** - * Skip the operation when true. - * - * @parameter expression="${eclipse.skip}" default-value="false" - */ - private boolean skip; - - private EclipseSourceDir[] buildDirectoryList() throws MojoExecutionException { - File buildOutputDirectory = new File(project.getBuild().getOutputDirectory()); - File basedir = project.getBasedir(); - File projectBaseDir = project.getFile().getParentFile(); - - // avoid duplicated entries - Set<EclipseSourceDir> directories = new TreeSet<EclipseSourceDir>(); - - extractSourceDirs(directories, project.getCompileSourceRoots(), basedir, projectBaseDir, false, null); - - String relativeOutput = toRelativeAndFixSeparator(projectBaseDir, buildOutputDirectory, false); - - extractResourceDirs(directories, - project.getBuild().getResources(), - project, - basedir, - projectBaseDir, - false, - relativeOutput); - - // If using the standard output location, don't mix the test output into it. - String testOutput = null; - boolean useStandardOutputDir = buildOutputDirectory.equals(new File(project.getBuild().getOutputDirectory())); - if (useStandardOutputDir) { - getLog().debug("testOutput toRelativeAndFixSeparator " + projectBaseDir - + " , " - + project.getBuild().getTestOutputDirectory()); - testOutput = - toRelativeAndFixSeparator(projectBaseDir, new File(project.getBuild().getTestOutputDirectory()), false); - getLog().debug("testOutput after toRelative : " + testOutput); - } - - extractSourceDirs(directories, project.getTestCompileSourceRoots(), basedir, projectBaseDir, true, testOutput); - - extractResourceDirs(directories, - project.getBuild().getTestResources(), - project, - basedir, - projectBaseDir, - true, - testOutput); - - return (EclipseSourceDir[])directories.toArray(new EclipseSourceDir[directories.size()]); - } - - public void execute() throws MojoExecutionException { - - try { - if (skip || "pom".equals(project.getPackaging())) { - return; - } - - EclipseSourceDir[] dirs = buildDirectoryList(); - File classPathFile = new File(project.getBasedir(), ".classpath"); - writeClassPath(new PrintWriter(classPathFile, "UTF-8"), dirs); - - File projectFile = new File(project.getBasedir(), ".project"); - writeProject(new PrintWriter(projectFile, "UTF-8")); - - } catch (Exception e) { - throw new MojoExecutionException(e.getMessage(), e); - } - - } - - void extractResourceDirs(Set<EclipseSourceDir> directories, - List<Resource> resources, - MavenProject project, - File basedir, - File workspaceProjectBaseDir, - boolean test, - final String output) throws MojoExecutionException { - for (Iterator<Resource> it = resources.iterator(); it.hasNext();) { - Resource resource = it.next(); - - getLog().debug("Processing resource dir: " + resource.getDirectory()); - - String includePattern = null; - String excludePattern = null; - - if (resource.getIncludes().size() != 0) { - includePattern = StringUtils.join(resource.getIncludes().iterator(), "|"); - } - - if (resource.getExcludes().size() != 0) { - excludePattern = StringUtils.join(resource.getExcludes().iterator(), "|"); - } - - // TODO: figure out how to merge if the same dir is specified twice - // with different in/exclude patterns. - - File resourceDirectory = new File( /* basedir, */resource.getDirectory()); - - if (!resourceDirectory.exists() || !resourceDirectory.isDirectory()) { - getLog().debug("Resource dir: " + resourceDirectory + " either missing or not a directory."); - continue; - } - - String resourceDir = - toRelativeAndFixSeparator(workspaceProjectBaseDir, resourceDirectory, !workspaceProjectBaseDir - .equals(basedir)); - String thisOutput = output; - if (thisOutput != null) { - // sometimes thisOutput is already an absolute path - File outputFile = new File(thisOutput); - if (!outputFile.isAbsolute()) { - outputFile = new File(workspaceProjectBaseDir, thisOutput); - } - // create output dir if it doesn't exist - outputFile.mkdirs(); - - if (!StringUtils.isEmpty(resource.getTargetPath())) { - outputFile = new File(outputFile, resource.getTargetPath()); - // create output dir if it doesn't exist - outputFile.mkdirs(); - } - - getLog().debug("Making relative and fixing separator: { " + workspaceProjectBaseDir - + ", " - + outputFile - + ", false }."); - thisOutput = toRelativeAndFixSeparator(workspaceProjectBaseDir, outputFile, false); - } - - getLog().debug("Adding eclipse source dir: { " + resourceDir - + ", " - + thisOutput - + ", true, " - + test - + ", " - + includePattern - + ", " - + excludePattern - + " }."); - - directories.add(new EclipseSourceDir(resourceDir, thisOutput, true, test, includePattern, excludePattern, - resource.isFiltering())); - } - } - - private void extractSourceDirs(Set<EclipseSourceDir> directories, - List<String> sourceRoots, - File basedir, - File projectBaseDir, - boolean test, - String output) throws MojoExecutionException { - for (Iterator<String> it = sourceRoots.iterator(); it.hasNext();) { - - File sourceRootFile = new File(it.next()); - - if (sourceRootFile.isDirectory()) { - String sourceRoot = - toRelativeAndFixSeparator(projectBaseDir, sourceRootFile, !projectBaseDir.equals(basedir)); - - directories.add(new EclipseSourceDir(sourceRoot, output, false, test, null, null, false)); - } - } - } - - private void writeClassPath(PrintWriter writer, EclipseSourceDir[] dirs) throws MojoExecutionException { - String defaultOutput = - toRelativeAndFixSeparator(project.getBasedir(), new File(project.getBuild().getOutputDirectory()), false); - - // ---------------------------------------------------------------------- - // Source roots and resources - // ---------------------------------------------------------------------- - - // List<EclipseSourceDir> - List<EclipseSourceDir> specialSources = new ArrayList<EclipseSourceDir>(); - - // Map<String,List<EclipseSourceDir>> - Map<String, List<EclipseSourceDir>> byOutputDir = new HashMap<String, List<EclipseSourceDir>>(); - - for (int j = 0; j < dirs.length; j++) { - EclipseSourceDir dir = dirs[j]; - - // List<EclipseSourceDir> - List<EclipseSourceDir> byOutputDirs = byOutputDir.get(dir.getOutput()); - if (byOutputDirs == null) { - // ArrayList<EclipseSourceDir> - byOutputDir.put(dir.getOutput() == null ? defaultOutput : dir.getOutput(), byOutputDirs = - new ArrayList<EclipseSourceDir>()); - } - byOutputDirs.add(dir); - } - - writer.println("<" + ELT_CLASSPATH + ">"); - writer.println(" <classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER\"/>"); - writer.println(" <classpathentry kind=\"con\" path=\"org.eclipse.pde.core.requiredPlugins\"/>"); - for (int j = 0; j < dirs.length; j++) { - EclipseSourceDir dir = dirs[j]; - - getLog().debug("Processing " + (dir.isResource() ? "re" : "") - + "source " - + dir.getPath() - + ": output=" - + dir.getOutput() - + "; default output=" - + defaultOutput); - - boolean isSpecial = false; - - // handle resource with nested output folders - if (dir.isResource()) { - // Check if the output is a subdirectory of the default output, - // and if the default output has any sources that copy there. - - if (dir.getOutput() != null // resource output dir is set - && !dir.getOutput().equals(defaultOutput) // output dir is not default target/classes - && dir.getOutput().startsWith(defaultOutput) // ... but is nested - && byOutputDir.get(defaultOutput) != null // ??? - && !(byOutputDir.get(defaultOutput)).isEmpty() // ??? - ) { - // do not specify as source since the output will be nested. Instead, mark - // it as a todo, and handle it with a custom build.xml file later. - - getLog().debug("Marking as special to prevent output folder nesting: " + dir.getPath() - + " (output=" - + dir.getOutput() - + ")"); - - isSpecial = true; - specialSources.add(dir); - } - } - - writer.print(" <" + ELT_CLASSPATHENTRY); - - writer.print(" " + ATTR_KIND + "=\"" + ATTR_SRC + "\""); - writer.print(" " + ATTR_PATH + "=\"" + dir.getPath() + "\""); - - if (!isSpecial && dir.getOutput() != null && !defaultOutput.equals(dir.getOutput())) { - writer.print(" " + ATTR_OUTPUT + "=\"" + dir.getOutput() + "\""); - } - - if (StringUtils.isNotEmpty(dir.getInclude())) { - writer.print(" " + ATTR_INCLUDING + "=\"" + dir.getInclude() + "\""); - } - - String excludes = dir.getExclude(); - - if (dir.isResource()) { - // automatically exclude java files: eclipse doesn't have the concept of resource directory so it will - // try to compile any java file found in maven resource dirs - excludes = StringUtils.isEmpty(excludes) ? "**/*.java" : excludes + "|**/*.java"; - } - - if (StringUtils.isNotEmpty(excludes)) { - writer.print(" " + ATTR_EXCLUDING + "=\"" + excludes + "\""); - } - - writer.println("/>"); - } - - for (Object a : project.getArtifacts()) { - Artifact artifact = (Artifact)a; - if (Artifact.SCOPE_TEST.equals(artifact.getScope()) || Artifact.SCOPE_RUNTIME.equals(artifact.getScope())) { - if (getLog().isDebugEnabled()) { - getLog().debug("Adding explict classpath entry: " + artifact.toString()); - } - - String path = "", kind = ""; - if (isAvailableAsAReactorProject(artifact)) { - path = "/" + artifact.getArtifactId(); - kind = ATTR_SRC; - } else { - String fullPath = artifact.getFile().getPath(); - String relativePath = - toRelativeAndFixSeparator(new File(localRepository.getBasedir()), new File(fullPath), false); - - if (!new File(relativePath).isAbsolute()) { - path = M2_REPO + "/" + relativePath; - kind = ATTR_VAR; - } else { - path = relativePath; - kind = ATTR_LIB; - } - } - writer.print(" <" + ELT_CLASSPATHENTRY); - - writer.print(" " + ATTR_KIND + "=\"" + kind + "\""); - writer.print(" " + ATTR_PATH + "=\"" + path + "\""); - writer.println("/>"); - } - } - writer.println(" <classpathentry kind=\"" + ATTR_OUTPUT + "\" path=\"" + defaultOutput + "\"/>"); - writer.println("</" + ELT_CLASSPATH + ">"); - writer.close(); - } - - private void writeProject(PrintWriter ps) { - ps.println("<projectDescription>"); - ps.println(" <name>" + project.getArtifactId() + "</name>"); - ps.println(" <projects/>"); - ps.println(" <buildSpec>"); - ps.println(" <buildCommand>"); - ps.println(" <name>org.eclipse.jdt.core.javabuilder</name>"); - ps.println(" </buildCommand>"); - ps.println(" <buildCommand>"); - ps.println(" <name>org.eclipse.pde.ManifestBuilder</name>"); - ps.println(" </buildCommand>"); - ps.println(" <buildCommand>"); - ps.println(" <name>org.eclipse.pde.SchemaBuilder</name>"); - ps.println(" </buildCommand>"); - ps.println(" </buildSpec>"); - ps.println(" <natures>"); - ps.println(" <nature>org.eclipse.jdt.core.javanature</nature>"); - ps.println(" <nature>org.eclipse.pde.PluginNature</nature>"); - ps.println(" </natures>"); - ps.println(" <linkedResources/>"); - ps.println("</projectDescription>"); - ps.close(); - } - -} diff --git a/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/resources/org/apache/tuscany/sca/maven/plugin/eclipse/messages.properties b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/resources/org/apache/tuscany/sca/maven/plugin/eclipse/messages.properties new file mode 100644 index 0000000000..11de16d7a8 --- /dev/null +++ b/branches/sca-equinox/tools/maven/maven-bundle-plugin/src/main/resources/org/apache/tuscany/sca/maven/plugin/eclipse/messages.properties @@ -0,0 +1,58 @@ +EclipsePlugin.missingpom=There must be a POM in the current working directory for the Eclipse plugin to work. +EclipsePlugin.pompackaging=Not running eclipse plugin goal for pom project +EclipsePlugin.notadir=Not a directory: "{0}" +EclipsePlugin.cantcreatedir=Can''t create directory "{0}" +EclipsePlugin.erroropeningfile=Exception while opening file. +EclipsePlugin.cantwritetofile=Unable to write to file: {0} +EclipsePlugin.cantfindresource=Unable to resolve resource location: {0} +EclipsePlugin.cantreadfile=Unable to read file: {0} +EclipsePlugin.keepexisting=File {0} already exists.\n Additional settings will be preserved, run mvn eclipse:clean if you want old settings to be removed. +EclipsePlugin.cantparseexisting=Unable to parse existing file: {0}. Settings will not be preserved. +EclipsePlugin.cantresolvesources=Cannot resolve source artifact. Artifact id: {0} (Message: {1}) +EclipsePlugin.wrote=Wrote Eclipse project for "{0}" to {1}. +EclipsePlugin.missingelement=Missing element from the project descriptor: "{0}" +EclipsePlugin.includenotsupported=This plugin currently doesn't support include patterns for resources. Adding the entire directory. +EclipsePlugin.excludenotsupported=This plugin currently doesn't support exclude patterns for resources. Adding the entire directory. +EclipsePlugin.artifactpathisnull=The artifact path was null. Artifact id: {0} +EclipsePlugin.artifactissystemscoped=The artifact has scope ''system''. Artifact id: {0}. System path: {1} +EclipsePlugin.unsupportedwtp=Unsupported WTP version: {0}. This plugin currently supports only the following versions: {1}. +EclipsePlugin.wtpversion=Adding support for WTP version {0}. +EclipsePlugin.missingjrecontainer=You did specify a list of classpath containers without the base org.eclipse.jdt.launching.JRE_CONTAINER.\n If you specify custom classpath containers you should also add org.eclipse.jdt.launching.JRE_CONTAINER to the list +EclipsePlugin.deprecatedpar=Plugin parameter "{0}" is deprecated, please use "{1}" +EclipsePlugin.cantcopyartifact=Can''t copy artifact "{0}". +EclipsePlugin.foundadir={0} is a directory, ignoring. + +EclipseSettingsWriter.wrotesettings=Wrote settings to {0} +EclipseSettingsWriter.cannotcreatesettings=Cannot create settings file +EclipseSettingsWriter.errorwritingsettings=Error writing settings file +EclipseSettingsWriter.usingdefaults=Not writing settings - defaults suffice + +EclipseClasspathWriter.lookingforsources=Looking for source archive for artifact {0} +EclipseClasspathWriter.sourcesavailable=Sources attachment for artifact {0} set to {1} + +EclipseProjectWriter.notafile=Not adding a file link to {0}; it is not a file + +EclipseCleanMojo.failedtodelete=Failed to delete {0} file: {0} +EclipseCleanMojo.nofilefound=No {0} file found +EclipseCleanMojo.deletingFile=Deleting file: {0} +EclipseCleanMojo.deletingDirectory=Deleting directory: {0} + +EclipseOSGiManifestWriter.nomanifestfile=The references manifest file doesn''t exist, plugin dependencies will not be updated: {0} + +IdeDependency.cantreadfile=Unable to read file: {0} + +Rad6LibCopier.cantdeletefile=Failed to delete file: {0} + +MyEclipseSpringBeansWriter.springVersionNotFound=Spring must be declared in the project's dependencies to generate .springBeans file. + +AbstractIdeSupportMojo.sourcesnotavailable=\n Sources for some artifacts are not available.\n List of artifacts without a source archive: +AbstractIdeSupportMojo.sourcesnotdownloaded=\n Sources for some artifacts are not available.\n Please run the same goal with the -DdownloadSources=true parameter in order to check remote repositories for sources.\n List of artifacts without a source archive: +AbstractIdeSupportMojo.sourcesmissingitem=\n o {0} +AbstractIdeSupportMojo.javadocnotavailable=\n Javadoc for some artifacts is not available.\n List of artifacts without a javadoc archive: +AbstractIdeSupportMojo.javadocnotdownloaded=\n Javadoc for some artifacts is not available.\n Please run the same goal with the -DdownloadJavadocs=true parameter in order to check remote repositories for javadoc.\n List of artifacts without a javadoc archive: +AbstractIdeSupportMojo.javadocmissingitem=\n o {0} +AbstractIdeSupportMojo.errorresolving=Error resolving {0} artifact. Artifact id: {1} (Message: {2}) +AbstractIdeSupportMojo.artifactresolution=An error occurred during dependency resolution of the following artifact:\n {0}:{1}:{2}\nCaused by: {3} +AbstractIdeSupportMojo.artifactdownload=An error occurred during dependency resolution.\n Failed to retrieve {0}:{1}-{2}\nCaused by: {3} +AbstractIdeSupportMojo.cantcanonicalize=Can''t canonicalize system path: {0} +AbstractIdeSupportMojo.unabletoparseversion={0}: unable to parse version ''{1}'' for dependency ''{2}'': {3} |