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  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.Enumeration;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.jar.JarFile;
30  import java.util.regex.Matcher;
31  import java.util.regex.Pattern;
32  import java.util.zip.ZipEntry;
33  
34  import org.apache.maven.artifact.Artifact;
35  
36  import aQute.bnd.osgi.Analyzer;
37  
38  
39  /**
40   * Default implementation of {@link Maven2OsgiConverter}
41   * 
42   * @plexus.component
43   * 
44   * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
45   * @version $Id: DefaultMaven2OsgiConverter.java 661727 2008-05-30 14:21:49Z bentmann $
46   */
47  public class DefaultMaven2OsgiConverter implements Maven2OsgiConverter
48  {
49  
50      private static final String FILE_SEPARATOR = System.getProperty( "file.separator" );
51  
52  
53      private String getBundleSymbolicName( String groupId, String artifactId )
54      {
55          return groupId + "." + artifactId;
56      }
57  
58  
59      /**
60       * Get the symbolic name as groupId + "." + artifactId, with the following exceptions
61       * <ul>
62       * <li>if artifact.getFile is not null and the jar contains a OSGi Manifest with
63       * Bundle-SymbolicName property then that value is returned</li>
64       * <li>if groupId has only one section (no dots) and artifact.getFile is not null then the
65       * first package name with classes is returned. eg. commons-logging:commons-logging ->
66       * org.apache.commons.logging</li>
67       * <li>if artifactId is equal to last section of groupId then groupId is returned. eg.
68       * org.apache.maven:maven -> org.apache.maven</li>
69       * <li>if artifactId starts with last section of groupId that portion is removed. eg.
70       * org.apache.maven:maven-core -> org.apache.maven.core</li>
71       * <li>if artifactId starts with groupId then the artifactId is removed. eg.
72       * org.apache:org.apache.maven.core -> org.apache.maven.core</li>
73       * </ul>
74       */
75      public String getBundleSymbolicName( Artifact artifact )
76      {
77          if ( ( artifact.getFile() != null ) && artifact.getFile().isFile() )
78          {
79              Analyzer analyzer = new Analyzer();
80  
81              JarFile jar = null;
82              try
83              {
84                  jar = new JarFile( artifact.getFile(), false );
85  
86                  if ( jar.getManifest() != null )
87                  {
88                      String symbolicNameAttribute = jar.getManifest().getMainAttributes()
89                          .getValue( Analyzer.BUNDLE_SYMBOLICNAME );
90                      Map bundleSymbolicNameHeader = analyzer.parseHeader( symbolicNameAttribute );
91  
92                      Iterator it = bundleSymbolicNameHeader.keySet().iterator();
93                      if ( it.hasNext() )
94                      {
95                          return ( String ) it.next();
96                      }
97                  }
98              }
99              catch ( IOException e )
100             {
101                 throw new ManifestReadingException( "Error reading manifest in jar "
102                     + artifact.getFile().getAbsolutePath(), e );
103             }
104             finally
105             {
106                 if ( jar != null )
107                 {
108                     try
109                     {
110                         jar.close();
111                     }
112                     catch ( IOException e )
113                     {
114                     }
115                 }
116             }
117         }
118 
119         int i = artifact.getGroupId().lastIndexOf( '.' );
120         if ( ( i < 0 ) && ( artifact.getFile() != null ) && artifact.getFile().isFile() )
121         {
122             String groupIdFromPackage = getGroupIdFromPackage( artifact.getFile() );
123             if ( groupIdFromPackage != null )
124             {
125                 return groupIdFromPackage;
126             }
127         }
128         String lastSection = artifact.getGroupId().substring( ++i );
129         if ( artifact.getArtifactId().equals( lastSection ) )
130         {
131             return artifact.getGroupId();
132         }
133         if ( artifact.getArtifactId().equals( artifact.getGroupId() )
134             || artifact.getArtifactId().startsWith( artifact.getGroupId() + "." ) )
135         {
136             return artifact.getArtifactId();
137         }
138         if ( artifact.getArtifactId().startsWith( lastSection ) )
139         {
140             String artifactId = artifact.getArtifactId().substring( lastSection.length() );
141             if ( Character.isLetterOrDigit( artifactId.charAt( 0 ) ) )
142             {
143                 return getBundleSymbolicName( artifact.getGroupId(), artifactId );
144             }
145             else
146             {
147                 return getBundleSymbolicName( artifact.getGroupId(), artifactId.substring( 1 ) );
148             }
149         }
150         return getBundleSymbolicName( artifact.getGroupId(), artifact.getArtifactId() );
151     }
152 
153 
154     private String getGroupIdFromPackage( File artifactFile )
155     {
156         try
157         {
158             /* get package names from jar */
159             Set packageNames = new HashSet();
160             JarFile jar = new JarFile( artifactFile, false );
161             Enumeration entries = jar.entries();
162             while ( entries.hasMoreElements() )
163             {
164                 ZipEntry entry = ( ZipEntry ) entries.nextElement();
165                 if ( entry.getName().endsWith( ".class" ) )
166                 {
167                     File f = new File( entry.getName() );
168                     String packageName = f.getParent();
169                     if ( packageName != null )
170                     {
171                         packageNames.add( packageName );
172                     }
173                 }
174             }
175             jar.close();
176 
177             /* find the top package */
178             String[] groupIdSections = null;
179             for ( Iterator it = packageNames.iterator(); it.hasNext(); )
180             {
181                 String packageName = ( String ) it.next();
182 
183                 String[] packageNameSections = packageName.split( "\\" + FILE_SEPARATOR );
184                 if ( groupIdSections == null )
185                 {
186                     /* first candidate */
187                     groupIdSections = packageNameSections;
188                 }
189                 else
190                 // if ( packageNameSections.length < groupIdSections.length )
191                 {
192                     /*
193                      * find the common portion of current package and previous selected groupId
194                      */
195                     int i;
196                     for ( i = 0; ( i < packageNameSections.length ) && ( i < groupIdSections.length ); i++ )
197                     {
198                         if ( !packageNameSections[i].equals( groupIdSections[i] ) )
199                         {
200                             break;
201                         }
202                     }
203                     groupIdSections = new String[i];
204                     System.arraycopy( packageNameSections, 0, groupIdSections, 0, i );
205                 }
206             }
207 
208             if ( ( groupIdSections == null ) || ( groupIdSections.length == 0 ) )
209             {
210                 return null;
211             }
212 
213             /* only one section as id doesn't seem enough, so ignore it */
214             if ( groupIdSections.length == 1 )
215             {
216                 return null;
217             }
218 
219             StringBuffer sb = new StringBuffer();
220             for ( int i = 0; i < groupIdSections.length; i++ )
221             {
222                 sb.append( groupIdSections[i] );
223                 if ( i < groupIdSections.length - 1 )
224                 {
225                     sb.append( '.' );
226                 }
227             }
228             return sb.toString();
229         }
230         catch ( IOException e )
231         {
232             /* we took all the precautions to avoid this */
233             throw new RuntimeException( e );
234         }
235     }
236 
237 
238     public String getBundleFileName( Artifact artifact )
239     {
240         return getBundleSymbolicName( artifact ) + "_" + getVersion( artifact.getVersion() ) + ".jar";
241     }
242 
243 
244     public String getVersion( Artifact artifact )
245     {
246         return getVersion( artifact.getVersion() );
247     }
248 
249 
250     public String getVersion( String version )
251     {
252         return cleanupVersion( version );
253     }
254 
255     /**
256      * Clean up version parameters. Other builders use more fuzzy definitions of
257      * the version syntax. This method cleans up such a version to match an OSGi
258      * version.
259      *
260      * @param VERSION_STRING
261      * @return
262      */
263     static final Pattern FUZZY_VERSION = Pattern.compile( "(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
264         Pattern.DOTALL );
265 
266 
267     static public String cleanupVersion( String version )
268     {
269         StringBuffer result = new StringBuffer();
270         Matcher m = FUZZY_VERSION.matcher( version );
271         if ( m.matches() )
272         {
273             String major = m.group( 1 );
274             String minor = m.group( 3 );
275             String micro = m.group( 5 );
276             String qualifier = m.group( 7 );
277 
278             if ( major != null )
279             {
280                 result.append( major );
281                 if ( minor != null )
282                 {
283                     result.append( "." );
284                     result.append( minor );
285                     if ( micro != null )
286                     {
287                         result.append( "." );
288                         result.append( micro );
289                         if ( qualifier != null )
290                         {
291                             result.append( "." );
292                             cleanupModifier( result, qualifier );
293                         }
294                     }
295                     else if ( qualifier != null )
296                     {
297                         result.append( ".0." );
298                         cleanupModifier( result, qualifier );
299                     }
300                     else
301                     {
302                         result.append( ".0" );
303                     }
304                 }
305                 else if ( qualifier != null )
306                 {
307                     result.append( ".0.0." );
308                     cleanupModifier( result, qualifier );
309                 }
310                 else
311                 {
312                     result.append( ".0.0" );
313                 }
314             }
315         }
316         else
317         {
318             result.append( "0.0.0." );
319             cleanupModifier( result, version );
320         }
321         return result.toString();
322     }
323 
324 
325     static void cleanupModifier( StringBuffer result, String modifier )
326     {
327         for ( int i = 0; i < modifier.length(); i++ )
328         {
329             char c = modifier.charAt( i );
330             if ( ( c >= '0' && c <= '9' ) || ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_'
331                 || c == '-' )
332                 result.append( c );
333             else
334                 result.append( '_' );
335         }
336     }
337 
338 }