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.shared.osgi;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.Enumeration;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.jar.JarFile;
29  import java.util.regex.Matcher;
30  import java.util.regex.Pattern;
31  import java.util.zip.ZipEntry;
32  
33  import org.apache.maven.artifact.Artifact;
34  
35  import aQute.lib.osgi.Analyzer;
36  
37  /**
38   * Default implementation of {@link Maven2OsgiConverter}
39   * 
40   * @plexus.component
41   * 
42   * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
43   * @version $Id: DefaultMaven2OsgiConverter.java 573759 2007-09-07 23:45:40Z carlos $
44   */
45  public class DefaultMaven2OsgiConverter
46      implements Maven2OsgiConverter
47  {
48  
49      /** Bundle-Version must match this pattern */
50      private static final Pattern OSGI_VERSION_PATTERN = Pattern
51          .compile( "[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9A-Za-z_-]+)?" );
52  
53      /** pattern used to change - to . */
54      // private static final Pattern P_VERSION = Pattern.compile("([0-9]+(\\.[0-9])*)-(.*)");
55      /** pattern that matches strings that contain only numbers */
56      private static final Pattern ONLY_NUMBERS = Pattern.compile( "[0-9]+" );
57  
58      private static final String FILE_SEPARATOR = System.getProperty( "file.separator" );
59  
60      private String getBundleSymbolicName( String groupId, String artifactId )
61      {
62          return groupId + "." + artifactId;
63      }
64  
65      /**
66       * Get the symbolic name as groupId + "." + artifactId, with the following exceptions
67       * <ul>
68       * <li>if artifact.getFile is not null and the jar contains a OSGi Manifest with
69       * Bundle-SymbolicName property then that value is returned</li>
70       * <li>if groupId has only one section (no dots) and artifact.getFile is not null then the
71       * first package name with classes is returned. eg. commons-logging:commons-logging ->
72       * org.apache.commons.logging</li>
73       * <li>if artifactId is equal to last section of groupId then groupId is returned. eg.
74       * org.apache.maven:maven -> org.apache.maven</li>
75       * <li>if artifactId starts with last section of groupId that portion is removed. eg.
76       * org.apache.maven:maven-core -> org.apache.maven.core</li>
77       * </ul>
78       */
79      public String getBundleSymbolicName( Artifact artifact )
80      {
81          if ( ( artifact.getFile() != null ) && artifact.getFile().exists() )
82          {
83              Analyzer analyzer = new Analyzer();
84  
85              try
86              {
87                  JarFile jar = new JarFile( artifact.getFile(), false );
88  
89                  if ( jar.getManifest() != null )
90                  {
91                      String symbolicNameAttribute = jar.getManifest().getMainAttributes()
92                          .getValue( Analyzer.BUNDLE_SYMBOLICNAME );
93                      Map bundleSymbolicNameHeader = analyzer.parseHeader( symbolicNameAttribute );
94  
95                      Iterator it = bundleSymbolicNameHeader.keySet().iterator();
96                      if ( it.hasNext() )
97                      {
98                          return (String) it.next();
99                      }
100                 }
101             }
102             catch ( IOException e )
103             {
104                 throw new ManifestReadingException( "Error reading manifest in jar "
105                     + artifact.getFile().getAbsolutePath(), e );
106             }
107         }
108 
109         int i = artifact.getGroupId().lastIndexOf( '.' );
110         if ( ( i < 0 ) && ( artifact.getFile() != null ) && artifact.getFile().exists() )
111         {
112             String groupIdFromPackage = getGroupIdFromPackage( artifact.getFile() );
113             if ( groupIdFromPackage != null )
114             {
115                 return groupIdFromPackage;
116             }
117         }
118         String lastSection = artifact.getGroupId().substring( ++i );
119         if ( artifact.getArtifactId().equals( lastSection ) )
120         {
121             return artifact.getGroupId();
122         }
123         if ( artifact.getArtifactId().startsWith( lastSection ) )
124         {
125             String artifactId = artifact.getArtifactId().substring( lastSection.length() );
126             if ( Character.isLetterOrDigit( artifactId.charAt( 0 ) ) )
127             {
128                 return getBundleSymbolicName( artifact.getGroupId(), artifactId );
129             }
130             else
131             {
132                 return getBundleSymbolicName( artifact.getGroupId(), artifactId.substring( 1 ) );
133             }
134         }
135         return getBundleSymbolicName( artifact.getGroupId(), artifact.getArtifactId() );
136     }
137 
138     private String getGroupIdFromPackage( File artifactFile )
139     {
140         try
141         {
142             /* get package names from jar */
143             Set packageNames = new HashSet();
144             JarFile jar = new JarFile( artifactFile, false );
145             Enumeration entries = jar.entries();
146             while ( entries.hasMoreElements() )
147             {
148                 ZipEntry entry = (ZipEntry) entries.nextElement();
149                 if ( entry.getName().endsWith( ".class" ) )
150                 {
151                     File f = new File( entry.getName() );
152                     String packageName = f.getParent();
153                     if ( packageName != null )
154                     {
155                         packageNames.add( packageName );
156                     }
157                 }
158             }
159 
160             /* find the top package */
161             String[] groupIdSections = null;
162             for ( Iterator it = packageNames.iterator(); it.hasNext(); )
163             {
164                 String packageName = (String) it.next();
165 
166                 String[] packageNameSections = packageName.split( "\\" + FILE_SEPARATOR );
167                 if ( groupIdSections == null )
168                 {
169                     /* first candidate */
170                     groupIdSections = packageNameSections;
171                 }
172                 else
173                 // if ( packageNameSections.length < groupIdSections.length )
174                 {
175                     /*
176                      * find the common portion of current package and previous selected groupId
177                      */
178                     int i;
179                     for ( i = 0; ( i < packageNameSections.length ) && ( i < groupIdSections.length ); i++ )
180                     {
181                         if ( !packageNameSections[i].equals( groupIdSections[i] ) )
182                         {
183                             break;
184                         }
185                     }
186                     groupIdSections = new String[i];
187                     System.arraycopy( packageNameSections, 0, groupIdSections, 0, i );
188                 }
189             }
190 
191             if ( ( groupIdSections == null ) || ( groupIdSections.length == 0 ) )
192             {
193                 return null;
194             }
195 
196             /* only one section as id doesn't seem enough, so ignore it */
197             if ( groupIdSections.length == 1 )
198             {
199                 return null;
200             }
201 
202             StringBuffer sb = new StringBuffer();
203             for ( int i = 0; i < groupIdSections.length; i++ )
204             {
205                 sb.append( groupIdSections[i] );
206                 if ( i < groupIdSections.length - 1 )
207                 {
208                     sb.append( '.' );
209                 }
210             }
211             return sb.toString();
212         }
213         catch ( IOException e )
214         {
215             /* we took all the precautions to avoid this */
216             throw new RuntimeException( e );
217         }
218     }
219 
220     public String getBundleFileName( Artifact artifact )
221     {
222         return getBundleSymbolicName( artifact ) + "_" + getVersion( artifact.getVersion() ) + ".jar";
223     }
224 
225     public String getVersion( Artifact artifact )
226     {
227         return getVersion( artifact.getVersion() );
228     }
229 
230     public String getVersion( String version )
231     {
232         String osgiVersion;
233 
234         // Matcher m = P_VERSION.matcher(version);
235         // if (m.matches()) {
236         // osgiVersion = m.group(1) + "." + m.group(3);
237         // }
238 
239         /* TODO need a regexp guru here */
240 
241         Matcher m;
242 
243         /* if it's already OSGi compliant don't touch it */
244         m = OSGI_VERSION_PATTERN.matcher( version );
245         if ( m.matches() )
246         {
247             return version;
248         }
249 
250         osgiVersion = version;
251 
252         /* check for dated snapshot versions with only major or major and minor */
253         Pattern DATED_SNAPSHOT = Pattern.compile( "([0-9])(\\.([0-9]))?(\\.([0-9]))?\\-([0-9]{8}\\.[0-9]{6}\\-[0-9]*)" );
254         m = DATED_SNAPSHOT.matcher( osgiVersion );
255         if ( m.matches() )
256         {
257             String major = m.group( 1 );
258             String minor = ( m.group( 3 ) != null ) ? m.group( 3 ) : "0";
259             String service = ( m.group( 5 ) != null ) ? m.group( 5 ) : "0";
260             String qualifier = m.group( 6 ).replaceAll( "-", "_" ).replaceAll( "\\.", "_" );
261             osgiVersion = major + "." + minor + "." + service + "." + qualifier;
262         }
263 
264         /* else transform first - to . and others to _ */
265         osgiVersion = osgiVersion.replaceFirst( "-", "\\." );
266         osgiVersion = osgiVersion.replaceAll( "-", "_" );
267         m = OSGI_VERSION_PATTERN.matcher( osgiVersion );
268         if ( m.matches() )
269         {
270             return osgiVersion;
271         }
272 
273         /* remove dots in the middle of the qualifier */
274         Pattern DOTS_IN_QUALIFIER = Pattern.compile( "([0-9])(\\.[0-9])?\\.([0-9A-Za-z_-]+)\\.([0-9A-Za-z_-]+)" );
275         m = DOTS_IN_QUALIFIER.matcher( osgiVersion );
276         if ( m.matches() )
277         {
278             String s1 = m.group( 1 );
279             String s2 = m.group( 2 );
280             String s3 = m.group( 3 );
281             String s4 = m.group( 4 );
282 
283             Matcher qualifierMatcher = ONLY_NUMBERS.matcher( s3 );
284             /*
285              * if last portion before dot is only numbers then it's not in the middle of the
286              * qualifier
287              */
288             if ( !qualifierMatcher.matches() )
289             {
290                 osgiVersion = s1 + s2 + "." + s3 + "_" + s4;
291             }
292         }
293 
294         /* convert
295          * 1.string   -> 1.0.0.string
296          * 1.2.string -> 1.2.0.string
297          * 1          -> 1.0.0
298          * 1.1        -> 1.1.0
299          */
300         //Pattern NEED_TO_FILL_ZEROS = Pattern.compile( "([0-9])(\\.([0-9]))?\\.([0-9A-Za-z_-]+)" );
301         Pattern NEED_TO_FILL_ZEROS = Pattern.compile( "([0-9])(\\.([0-9]))?(\\.([0-9A-Za-z_-]+))?" );
302         m = NEED_TO_FILL_ZEROS.matcher( osgiVersion );
303         if ( m.matches() )
304         {
305             String major = m.group( 1 );
306             String minor = m.group( 3 );
307             String service = null;
308             String qualifier = m.group( 5 );
309 
310             /* if there's no qualifier just fill with 0s */
311             if ( qualifier == null )
312             {
313                 osgiVersion = getVersion( major, minor, service, qualifier );
314             }
315             else
316             {
317                 /* if last portion is only numbers then it's not a qualifier */
318                 Matcher qualifierMatcher = ONLY_NUMBERS.matcher( qualifier );
319                 if ( qualifierMatcher.matches() )
320                 {
321                     if ( minor == null )
322                     {
323                         minor = qualifier;
324                     }
325                     else
326                     {
327                         service = qualifier;
328                     }
329                     osgiVersion = getVersion( major, minor, service, null );
330                 }
331                 else
332                 {
333                     osgiVersion = getVersion( major, minor, service, qualifier );
334                 }
335             }
336         }
337 
338         m = OSGI_VERSION_PATTERN.matcher( osgiVersion );
339         /* if still its not OSGi version then add everything as qualifier */
340         if ( !m.matches() )
341         {
342             String major = "0";
343             String minor = "0";
344             String service = "0";
345             String qualifier = osgiVersion.replaceAll( "\\.", "_" );
346             osgiVersion = major + "." + minor + "." + service + "." + qualifier;
347         }
348 
349         return osgiVersion;
350     }
351 
352     private String getVersion( String major, String minor, String service, String qualifier )
353     {
354         StringBuffer sb = new StringBuffer();
355         sb.append( major != null ? major : "0" );
356         sb.append( '.' );
357         sb.append( minor != null ? minor : "0" );
358         sb.append( '.' );
359         sb.append( service != null ? service : "0" );
360         if ( qualifier != null )
361         {
362             sb.append( '.' );
363             sb.append( qualifier );
364         }
365         return sb.toString();
366     }
367 }