View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.eclipse;
20  
21  import aQute.lib.osgi.Analyzer;
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.deployer.ArtifactDeployer;
24  import org.apache.maven.artifact.deployer.ArtifactDeploymentException;
25  import org.apache.maven.artifact.factory.ArtifactFactory;
26  import org.apache.maven.artifact.installer.ArtifactInstallationException;
27  import org.apache.maven.artifact.installer.ArtifactInstaller;
28  import org.apache.maven.artifact.metadata.ArtifactMetadata;
29  import org.apache.maven.artifact.repository.ArtifactRepository;
30  import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
31  import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
32  import org.apache.maven.model.Dependency;
33  import org.apache.maven.model.License;
34  import org.apache.maven.model.Model;
35  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
36  import org.apache.maven.plugin.AbstractMojo;
37  import org.apache.maven.plugin.MojoExecutionException;
38  import org.apache.maven.plugin.MojoFailureException;
39  import org.apache.maven.plugin.eclipse.osgiplugin.EclipseOsgiPlugin;
40  import org.apache.maven.plugin.eclipse.osgiplugin.ExplodedPlugin;
41  import org.apache.maven.plugin.eclipse.osgiplugin.PackagedPlugin;
42  import org.apache.maven.plugins.annotations.Component;
43  import org.apache.maven.plugins.annotations.Mojo;
44  import org.apache.maven.plugins.annotations.Parameter;
45  import org.apache.maven.project.artifact.ProjectArtifactMetadata;
46  import org.codehaus.plexus.PlexusConstants;
47  import org.codehaus.plexus.PlexusContainer;
48  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
49  import org.codehaus.plexus.components.interactivity.InputHandler;
50  import org.codehaus.plexus.context.Context;
51  import org.codehaus.plexus.context.ContextException;
52  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
53  import org.codehaus.plexus.util.IOUtil;
54  import org.codehaus.plexus.util.StringUtils;
55  
56  import java.io.File;
57  import java.io.FileOutputStream;
58  import java.io.IOException;
59  import java.io.OutputStreamWriter;
60  import java.io.Writer;
61  import java.util.ArrayList;
62  import java.util.HashMap;
63  import java.util.List;
64  import java.util.Map;
65  import java.util.regex.Matcher;
66  import java.util.regex.Pattern;
67  
68  /**
69   * Add eclipse artifacts from an eclipse installation to the local repo. This mojo automatically analize the eclipse
70   * directory, copy plugins jars to the local maven repo, and generates appropriate poms. This is the official central
71   * repository builder for Eclipse plugins, so it has the necessary default values. For customized repositories see
72   * {@link MakeArtifactsMojo} Typical usage:
73   * <code>mvn eclipse:to-maven -DdeployTo=maven.org::default::scpexe://repo1.maven.org/home/maven/repository-staging/to-ibiblio/eclipse-staging -DeclipseDir=.</code>
74   *
75   * @author Fabrizio Giustina
76   * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
77   * @version $Id: EclipseToMavenMojo.java 1672305 2015-04-09 12:10:45Z khmarbaise $
78   */
79  @Mojo( name = "to-maven", requiresProject = false )
80  public class EclipseToMavenMojo
81      extends AbstractMojo
82      implements Contextualizable
83  {
84  
85      /**
86       * A pattern the <code>deployTo</code> param should match.
87       */
88      private static final Pattern DEPLOYTO_PATTERN = Pattern.compile( "(.+)::(.+)::(.+)" ); //$NON-NLS-1$
89  
90      /**
91       * A pattern for a 4 digit osgi version number.
92       */
93      private static final Pattern VERSION_PATTERN = Pattern.compile( "(([0-9]+\\.)+[0-9]+)" ); //$NON-NLS-1$
94  
95      /**
96       * Plexus container, needed to manually lookup components for deploy of artifacts.
97       */
98      private PlexusContainer container;
99  
100     /**
101      * Local maven repository.
102      */
103     @Parameter( property = "localRepository", required = true, readonly = true )
104     private ArtifactRepository localRepository;
105 
106     /**
107      * ArtifactRepositoryFactory component.
108      */
109     @Component
110     private ArtifactRepositoryFactory artifactRepositoryFactory;
111 
112     /**
113      * ArtifactFactory component.
114      */
115     @Component
116     private ArtifactFactory artifactFactory;
117 
118     /**
119      * ArtifactInstaller component.
120      */
121     @Component
122     protected ArtifactInstaller installer;
123 
124     /**
125      * ArtifactDeployer component.
126      */
127     @Component
128     private ArtifactDeployer deployer;
129 
130     /**
131      * Eclipse installation dir. If not set, a value for this parameter will be asked on the command line.
132      */
133     @Parameter( property = "eclipseDir" )
134     private File eclipseDir;
135 
136     /**
137      * Input handler, needed for comand line handling.
138      */
139     @Component
140     protected InputHandler inputHandler;
141 
142     /**
143      * Strip qualifier (fourth token) from the plugin version. Qualifiers are for eclipse plugin the equivalent of
144      * timestamped snapshot versions for Maven, but the date is maintained also for released version (e.g. a jar for the
145      * release <code>3.2</code> can be named <code>org.eclipse.core.filesystem_1.0.0.v20060603.jar</code>. It's usually
146      * handy to not to include this qualifier when generating maven artifacts for major releases, while it's needed when
147      * working with eclipse integration/nightly builds.
148      */
149     @Parameter( property = "stripQualifier", defaultValue = "false" )
150     private boolean stripQualifier;
151 
152     /**
153      * Specifies a remote repository to which generated artifacts should be deployed to. If this property is specified,
154      * artifacts are also deployed to the remote repo. The format for this parameter is <code>id::layout::url</code>
155      */
156     @Parameter( property = "deployTo" )
157     private String deployTo;
158 
159     /**
160      * @see org.apache.maven.plugin.Mojo#execute()
161      */
162     public void execute()
163         throws MojoExecutionException, MojoFailureException
164     {
165         if ( eclipseDir == null )
166         {
167             getLog().info( Messages.getString( "EclipseToMavenMojo.eclipseDirectoryPrompt" ) ); //$NON-NLS-1$
168 
169             String eclipseDirString;
170             try
171             {
172                 eclipseDirString = inputHandler.readLine();
173             }
174             catch ( IOException e )
175             {
176                 throw new MojoFailureException(
177                     Messages.getString( "EclipseToMavenMojo.errorreadingfromstandardinput" ) ); //$NON-NLS-1$
178             }
179             eclipseDir = new File( eclipseDirString );
180         }
181 
182         if ( !eclipseDir.isDirectory() )
183         {
184             throw new MojoFailureException( Messages.getString( "EclipseToMavenMojo.directoydoesnotexist",
185                                                                 eclipseDir.getAbsolutePath() ) ); //$NON-NLS-1$
186         }
187 
188         File pluginDir = new File( eclipseDir, "plugins" ); //$NON-NLS-1$
189 
190         if ( !pluginDir.isDirectory() )
191         {
192             throw new MojoFailureException( Messages.getString( "EclipseToMavenMojo.plugindirectorydoesnotexist",
193                                                                 pluginDir.getAbsolutePath() ) ); //$NON-NLS-1$
194         }
195 
196         File[] files = pluginDir.listFiles();
197 
198         ArtifactRepository remoteRepo = resolveRemoteRepo();
199 
200         if ( remoteRepo != null )
201         {
202             getLog().info(
203                 Messages.getString( "EclipseToMavenMojo.remoterepositorydeployto", deployTo ) ); //$NON-NLS-1$
204         }
205 
206         Map plugins = new HashMap();
207         Map models = new HashMap();
208 
209         for (File file : files) {
210             getLog().info(
211                     Messages.getString("EclipseToMavenMojo.processingfile", file.getAbsolutePath())); //$NON-NLS-1$
212 
213             processFile(file, plugins, models);
214         }
215 
216         int i = 1;
217         for (Object o : plugins.keySet()) {
218             getLog().info(Messages.getString("EclipseToMavenMojo.processingplugin",
219                     new Object[]{i++,
220                             plugins.keySet().size()})); //$NON-NLS-1$
221             String key = (String) o;
222             EclipseOsgiPlugin plugin = (EclipseOsgiPlugin) plugins.get(key);
223             Model model = (Model) models.get(key);
224             writeArtifact(plugin, model, remoteRepo);
225         }
226     }
227 
228     protected void processFile( File file, Map plugins, Map models )
229         throws MojoExecutionException, MojoFailureException
230     {
231         EclipseOsgiPlugin plugin = getEclipsePlugin( file );
232 
233         if ( plugin == null )
234         {
235             getLog().warn(
236                 Messages.getString( "EclipseToMavenMojo.skippingfile", file.getAbsolutePath() ) ); //$NON-NLS-1$
237             return;
238         }
239 
240         Model model = createModel( plugin );
241 
242         if ( model == null )
243         {
244             return;
245         }
246 
247         processPlugin( plugin, model, plugins, models );
248     }
249 
250     protected void processPlugin( EclipseOsgiPlugin plugin, Model model, Map plugins, Map models )
251         throws MojoExecutionException, MojoFailureException
252     {
253         plugins.put( getKey( model ), plugin );
254         models.put( getKey( model ), model );
255     }
256 
257     protected String getKey( Model model )
258     {
259         return model.getGroupId() + "." + model.getArtifactId(); //$NON-NLS-1$
260     }
261 
262     private String getKey( Dependency dependency )
263     {
264         return dependency.getGroupId() + "." + dependency.getArtifactId(); //$NON-NLS-1$
265     }
266 
267     /**
268      * Resolve version ranges in the model provided, overriding version ranges with versions from the dependency in the
269      * provided map of models. TODO doesn't check if the version is in range, it just overwrites it
270      *
271      * @param model
272      * @param models
273      * @throws MojoFailureException
274      */
275     protected void resolveVersionRanges( Model model, Map models )
276         throws MojoFailureException
277     {
278         for (Dependency dep : model.getDependencies()) {
279             if (dep.getVersion().contains("[")
280                     || dep.getVersion().contains("(")) //$NON-NLS-1$ //$NON-NLS-2$
281             {
282                 String key = getKey(model);
283                 Model dependencyModel = (Model) models.get(getKey(dep));
284                 if (dependencyModel != null) {
285                     dep.setVersion(dependencyModel.getVersion());
286                 } else {
287                     throw new MojoFailureException(
288                             Messages.getString("EclipseToMavenMojo.unabletoresolveversionrange",
289                                     new Object[]{dep //$NON-NLS-1$
290                                             , key})); //$NON-NLS-1$
291                 }
292             }
293         }
294     }
295 
296     /**
297      * Get a {@link EclipseOsgiPlugin} object from a plugin jar/dir found in the target dir.
298      *
299      * @param file plugin jar or dir
300      * @throws MojoExecutionException if anything bad happens while parsing files
301      */
302     private EclipseOsgiPlugin getEclipsePlugin( File file )
303         throws MojoExecutionException
304     {
305         if ( file.isDirectory() )
306         {
307             return new ExplodedPlugin( file );
308         }
309         else if ( file.getName().endsWith( ".jar" ) ) //$NON-NLS-1$
310         {
311             try
312             {
313                 return new PackagedPlugin( file );
314             }
315             catch ( IOException e )
316             {
317                 throw new MojoExecutionException(
318                     Messages.getString( "EclipseToMavenMojo.unabletoaccessjar", file.getAbsolutePath() ),
319                     e ); //$NON-NLS-1$
320             }
321         }
322 
323         return null;
324     }
325 
326     /**
327      * Create the {@link Model} from a plugin manifest
328      *
329      * @param plugin Eclipse plugin jar or dir
330      * @throws MojoExecutionException if anything bad happens while parsing files
331      */
332     private Model createModel( EclipseOsgiPlugin plugin )
333         throws MojoExecutionException
334     {
335 
336         String name, bundleName, version, groupId, artifactId, requireBundle;
337 
338         try
339         {
340             if ( !plugin.hasManifest() )
341             {
342                 getLog().warn(
343                     Messages.getString( "EclipseToMavenMojo.plugindoesnothavemanifest", plugin ) ); //$NON-NLS-1$
344                 return null;
345             }
346 
347             Analyzer analyzer = new Analyzer();
348 
349             Map bundleSymbolicNameHeader =
350                 analyzer.parseHeader( plugin.getManifestAttribute( Analyzer.BUNDLE_SYMBOLICNAME ) );
351             bundleName = (String) bundleSymbolicNameHeader.keySet().iterator().next();
352             version = plugin.getManifestAttribute( Analyzer.BUNDLE_VERSION );
353 
354             if ( bundleName == null || version == null )
355             {
356                 getLog().error(
357                     Messages.getString( "EclipseToMavenMojo.unabletoreadbundlefrommanifest" ) ); //$NON-NLS-1$
358                 return null;
359             }
360 
361             version = osgiVersionToMavenVersion( version );
362 
363             name = plugin.getManifestAttribute( Analyzer.BUNDLE_NAME );
364 
365             requireBundle = plugin.getManifestAttribute( Analyzer.REQUIRE_BUNDLE );
366 
367         }
368         catch ( IOException e )
369         {
370             throw new MojoExecutionException( Messages.getString( "EclipseToMavenMojo.errorprocessingplugin", plugin ),
371                                               e ); //$NON-NLS-1$
372         }
373 
374         Dependency[] deps = parseDependencies( requireBundle );
375 
376         groupId = createGroupId( bundleName );
377         artifactId = createArtifactId( bundleName );
378 
379         Model model = new Model();
380         model.setModelVersion( "4.0.0" ); //$NON-NLS-1$
381         model.setGroupId( groupId );
382         model.setArtifactId( artifactId );
383         model.setName( name );
384         model.setVersion( version );
385 
386         model.setProperties( plugin.getPomProperties() );
387 
388         if ( groupId.startsWith( "org.eclipse" ) ) //$NON-NLS-1$
389         {
390             // why do we need a parent?
391 
392             // Parent parent = new Parent();
393             // parent.setGroupId( "org.eclipse" );
394             // parent.setArtifactId( "eclipse" );
395             // parent.setVersion( "1" );
396             // model.setParent( parent );
397 
398             // infer license for know projects, everything at eclipse is licensed under EPL
399             // maybe too simplicistic, but better than nothing
400             License license = new License();
401             license.setName( "Eclipse Public License - v 1.0" ); //$NON-NLS-1$
402             license.setUrl( "http://www.eclipse.org/org/documents/epl-v10.html" ); //$NON-NLS-1$
403             model.addLicense( license );
404         }
405 
406         if ( deps.length > 0 )
407         {
408             for (Dependency dep : deps) {
409                 model.getDependencies().add(dep);
410             }
411 
412         }
413 
414         return model;
415     }
416 
417     /**
418      * Writes the artifact to the repo
419      *
420      * @param model
421      * @param remoteRepo remote repository (if set)
422      * @throws MojoExecutionException
423      */
424     private void writeArtifact( EclipseOsgiPlugin plugin, Model model, ArtifactRepository remoteRepo )
425         throws MojoExecutionException
426     {
427         Writer fw = null;
428         ArtifactMetadata metadata;
429         File pomFile = null;
430         Artifact pomArtifact =
431             artifactFactory.createArtifact( model.getGroupId(), model.getArtifactId(), model.getVersion(), null,
432                                             "pom" ); //$NON-NLS-1$
433         Artifact artifact =
434             artifactFactory.createArtifact( model.getGroupId(), model.getArtifactId(), model.getVersion(), null,
435                                             Constants.PROJECT_PACKAGING_JAR );
436         try
437         {
438             pomFile = File.createTempFile( "pom-", ".xml" ); //$NON-NLS-1$ //$NON-NLS-2$
439 
440             // TODO use WriterFactory.newXmlWriter() when plexus-utils is upgraded to 1.4.5+
441             fw = new OutputStreamWriter( new FileOutputStream( pomFile ), "UTF-8" ); //$NON-NLS-1$
442             model.setModelEncoding(
443                 "UTF-8" ); // to be removed when encoding is detected instead of forced to UTF-8 //$NON-NLS-1$
444             pomFile.deleteOnExit();
445             new MavenXpp3Writer().write( fw, model );
446             metadata = new ProjectArtifactMetadata( pomArtifact, pomFile );
447             pomArtifact.addMetadata( metadata );
448         }
449         catch ( IOException e )
450         {
451             throw new MojoExecutionException(
452                 Messages.getString( "EclipseToMavenMojo.errorwritingtemporarypom", e.getMessage() ), e ); //$NON-NLS-1$
453         }
454         finally
455         {
456             IOUtil.close( fw );
457         }
458 
459         try
460         {
461             File jarFile = plugin.getJarFile();
462 
463             if ( remoteRepo != null )
464             {
465                 deployer.deploy( pomFile, pomArtifact, remoteRepo, localRepository );
466                 deployer.deploy( jarFile, artifact, remoteRepo, localRepository );
467             }
468             else
469             {
470                 installer.install( pomFile, pomArtifact, localRepository );
471                 installer.install( jarFile, artifact, localRepository );
472             }
473         }
474         catch ( ArtifactDeploymentException e )
475         {
476             throw new MojoExecutionException(
477                 Messages.getString( "EclipseToMavenMojo.errordeployartifacttorepository" ), e ); //$NON-NLS-1$
478         }
479         catch ( ArtifactInstallationException e )
480         {
481             throw new MojoExecutionException(
482                 Messages.getString( "EclipseToMavenMojo.errorinstallartifacttorepository" ), e ); //$NON-NLS-1$
483         }
484         catch ( IOException e )
485         {
486             throw new MojoExecutionException(
487                 Messages.getString( "EclipseToMavenMojo.errorgettingjarfileforplugin", plugin ), e ); //$NON-NLS-1$
488         }
489         finally
490         {
491             pomFile.delete();
492         }
493 
494     }
495 
496     protected String osgiVersionToMavenVersion( String version )
497     {
498         return osgiVersionToMavenVersion( version, null, stripQualifier );
499     }
500 
501     /**
502      * The 4th (build) token MUST be separed with "-" and not with "." in maven. A version with 4 dots is not parsed,
503      * and the whole string is considered a qualifier. See tests in DefaultArtifactVersion for reference.
504      *
505      * @param version         initial version
506      * @param forcedQualifier build number
507      * @param stripQualifier  always remove 4th token in version
508      * @return converted version
509      */
510     protected String osgiVersionToMavenVersion( String version, String forcedQualifier, boolean stripQualifier )
511     {
512         if ( stripQualifier && StringUtils.countMatches( version, "." ) > 2 ) //$NON-NLS-1$
513         {
514             version = StringUtils.substring( version, 0, version.lastIndexOf( '.' ) ); //$NON-NLS-1$
515         }
516         else if ( StringUtils.countMatches( version, "." ) > 2 ) //$NON-NLS-1$
517         {
518             int lastDot = version.lastIndexOf( '.' ); //$NON-NLS-1$
519             if ( StringUtils.isNotEmpty( forcedQualifier ) )
520             {
521                 version = StringUtils.substring( version, 0, lastDot ) + "-" + forcedQualifier; //$NON-NLS-1$
522             }
523             else
524             {
525                 version = StringUtils.substring( version, 0, lastDot ) + "-" //$NON-NLS-1$
526                     + StringUtils.substring( version, lastDot + 1, version.length() );
527             }
528         }
529         return version;
530     }
531 
532     /**
533      * Resolves the deploy<code>deployTo</code> parameter to an <code>ArtifactRepository</code> instance (if set).
534      *
535      * @return ArtifactRepository instance of null if <code>deployTo</code> is not set.
536      * @throws MojoFailureException
537      * @throws MojoExecutionException
538      */
539     private ArtifactRepository resolveRemoteRepo()
540         throws MojoFailureException, MojoExecutionException
541     {
542         if ( deployTo != null )
543         {
544             Matcher matcher = DEPLOYTO_PATTERN.matcher( deployTo );
545 
546             if ( !matcher.matches() )
547             {
548                 throw new MojoFailureException( deployTo,
549                                                 Messages.getString( "EclipseToMavenMojo.invalidsyntaxforrepository" ),
550                                                 //$NON-NLS-1$
551                                                 Messages.getString(
552                                                     "EclipseToMavenMojo.invalidremoterepositorysyntax" ) ); //$NON-NLS-1$
553             }
554             else
555             {
556                 String id = matcher.group( 1 ).trim();
557                 String layout = matcher.group( 2 ).trim();
558                 String url = matcher.group( 3 ).trim();
559 
560                 ArtifactRepositoryLayout repoLayout;
561                 try
562                 {
563                     repoLayout = (ArtifactRepositoryLayout) container.lookup( ArtifactRepositoryLayout.ROLE, layout );
564                 }
565                 catch ( ComponentLookupException e )
566                 {
567                     throw new MojoExecutionException(
568                         Messages.getString( "EclipseToMavenMojo.cannotfindrepositorylayout", layout ),
569                         e ); //$NON-NLS-1$
570                 }
571 
572                 return artifactRepositoryFactory.createDeploymentArtifactRepository( id, url, repoLayout, true );
573             }
574         }
575         return null;
576     }
577 
578     /**
579      * {@inheritDoc}
580      */
581     public void contextualize( Context context )
582         throws ContextException
583     {
584         this.container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
585     }
586 
587     /**
588      * Get the group id as the tokens until last dot e.g. <code>org.eclipse.jdt</code> -> <code>org.eclipse</code>
589      *
590      * @param bundleName bundle name
591      * @return group id
592      */
593     protected String createGroupId( String bundleName )
594     {
595         int i = bundleName.lastIndexOf( '.' ); //$NON-NLS-1$
596         if ( i > 0 )
597         {
598             return bundleName.substring( 0, i );
599         }
600         else
601         {
602             return bundleName;
603         }
604     }
605 
606     /**
607      * Get the artifact id as the tokens after last dot e.g. <code>org.eclipse.jdt</code> -> <code>jdt</code>
608      *
609      * @param bundleName bundle name
610      * @return artifact id
611      */
612     protected String createArtifactId( String bundleName )
613     {
614         int i = bundleName.lastIndexOf( '.' ); //$NON-NLS-1$
615         if ( i > 0 )
616         {
617             return bundleName.substring( i + 1 );
618         }
619         else
620         {
621             return bundleName;
622         }
623     }
624 
625     /**
626      * Parses the "Require-Bundle" and convert it to a list of dependencies.
627      *
628      * @param requireBundle "Require-Bundle" entry
629      * @return an array of <code>Dependency</code>
630      */
631     protected Dependency[] parseDependencies( String requireBundle )
632     {
633         if ( requireBundle == null )
634         {
635             return new Dependency[0];
636         }
637 
638         List dependencies = new ArrayList();
639 
640         Analyzer analyzer = new Analyzer();
641 
642         Map requireBundleHeader = analyzer.parseHeader( requireBundle );
643 
644         // now iterates on bundles and extract dependencies
645         for (Object o : requireBundleHeader.entrySet()) {
646             Map.Entry entry = (Map.Entry) o;
647             String bundleName = (String) entry.getKey();
648             Map attributes = (Map) entry.getValue();
649 
650             String version = (String) attributes.get(Analyzer.BUNDLE_VERSION.toLowerCase());
651             boolean optional = "optional".equals(attributes.get("resolution:")); //$NON-NLS-1$ //$NON-NLS-2$
652 
653             if (version == null) {
654                 getLog().info(
655                         Messages.getString("EclipseToMavenMojo.missingversionforbundle", bundleName)); //$NON-NLS-1$
656                 version = "[0,)"; //$NON-NLS-1$
657             }
658 
659             version = fixBuildNumberSeparator(version);
660 
661             Dependency dep = new Dependency();
662             dep.setGroupId(createGroupId(bundleName));
663             dep.setArtifactId(createArtifactId(bundleName));
664             dep.setVersion(version);
665             dep.setOptional(optional);
666 
667             dependencies.add(dep);
668 
669         }
670 
671         return (Dependency[]) dependencies.toArray( new Dependency[dependencies.size()] );
672 
673     }
674 
675     /**
676      * Fix the separator for the 4th token in a versions. In maven this must be "-", in OSGI it's "."
677      *
678      * @param versionRange input range
679      * @return modified version range
680      */
681     protected String fixBuildNumberSeparator( String versionRange )
682     {
683         // should not be called with a null versionRange, but a check doesn't hurt...
684         if ( versionRange == null )
685         {
686             return null;
687         }
688 
689         StringBuffer newVersionRange = new StringBuffer();
690 
691         Matcher matcher = VERSION_PATTERN.matcher( versionRange );
692 
693         while ( matcher.find() )
694         {
695             String group = matcher.group();
696 
697             if ( StringUtils.countMatches( group, "." ) > 2 ) //$NON-NLS-1$
698             {
699                 // build number found, fix it
700                 int lastDot = group.lastIndexOf( '.' ); //$NON-NLS-1$
701                 group = StringUtils.substring( group, 0, lastDot ) + "-" //$NON-NLS-1$
702                     + StringUtils.substring( group, lastDot + 1, group.length() );
703             }
704             matcher.appendReplacement( newVersionRange, group );
705         }
706 
707         matcher.appendTail( newVersionRange );
708 
709         return newVersionRange.toString();
710     }
711 
712 }