View Javadoc

1   package org.apache.maven.plugins.shade;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.util.Enumeration;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Set;
31  import java.util.ArrayList;
32  import java.util.jar.JarEntry;
33  import java.util.jar.JarFile;
34  import java.util.jar.JarOutputStream;
35  import java.util.regex.Matcher;
36  import java.util.regex.Pattern;
37  import java.util.zip.ZipException;
38  
39  import org.apache.maven.plugin.MojoExecutionException;
40  import org.apache.maven.plugins.shade.relocation.Relocator;
41  import org.apache.maven.plugins.shade.resource.ManifestResourceTransformer;
42  import org.apache.maven.plugins.shade.resource.ResourceTransformer;
43  import org.apache.maven.plugins.shade.filter.Filter;
44  import org.codehaus.plexus.logging.AbstractLogEnabled;
45  import org.codehaus.plexus.util.IOUtil;
46  import org.objectweb.asm.ClassReader;
47  import org.objectweb.asm.ClassVisitor;
48  import org.objectweb.asm.ClassWriter;
49  import org.objectweb.asm.commons.Remapper;
50  
51  /**
52   * @author Jason van Zyl
53   * @plexus.component instantiation-strategy="per-lookup"
54   */
55  public class DefaultShader
56      extends AbstractLogEnabled
57      implements Shader
58  {
59  
60      public void shade( Set jars, File uberJar, List filters, List relocators, List resourceTransformers )
61          throws IOException, MojoExecutionException
62      {
63          Set resources = new HashSet();
64  
65          ResourceTransformer manifestTransformer = null;
66          List transformers = new ArrayList( resourceTransformers );
67          for ( Iterator it = transformers.iterator(); it.hasNext(); )
68          {
69              ResourceTransformer transformer = (ResourceTransformer) it.next();
70              if ( transformer instanceof ManifestResourceTransformer )
71              {
72                  manifestTransformer = transformer;
73                  it.remove();
74              }
75          }
76  
77          RelocatorRemapper remapper = new RelocatorRemapper( relocators );
78  
79          uberJar.getParentFile().mkdirs();
80          JarOutputStream jos = new JarOutputStream( new FileOutputStream( uberJar ) );
81  
82          if ( manifestTransformer != null )
83          {
84              for ( Iterator it = jars.iterator(); it.hasNext(); )
85              {
86                  File jar = (File) it.next();
87                  JarFile jarFile = newJarFile( jar );
88                  for ( Enumeration en = jarFile.entries(); en.hasMoreElements(); )
89                  {
90                      JarEntry entry = (JarEntry) en.nextElement();
91                      String resource = entry.getName();
92                      if ( manifestTransformer.canTransformResource( resource ) )
93                      {
94                          resources.add( resource );
95                          manifestTransformer.processResource( resource, jarFile.getInputStream( entry ), relocators );
96                          break;
97                      }
98                  }
99              }
100             if ( manifestTransformer.hasTransformedResource() )
101             {
102                 manifestTransformer.modifyOutputStream( jos );
103             }
104         }
105 
106         for ( Iterator i = jars.iterator(); i.hasNext(); )
107         {
108             File jar = (File) i.next();
109 
110             getLogger().debug( "Processing JAR " + jar );
111 
112             List jarFilters = getFilters( jar, filters );
113 
114             JarFile jarFile = newJarFile( jar );
115 
116             for ( Enumeration j = jarFile.entries(); j.hasMoreElements(); )
117             {
118                 JarEntry entry = (JarEntry) j.nextElement();
119 
120                 String name = entry.getName();
121 
122                 if ( "META-INF/INDEX.LIST".equals( name ) )
123                 {
124                     // we cannot allow the jar indexes to be copied over or the
125                     // jar is useless. Ideally, we could create a new one
126                     // later
127                     continue;
128                 }
129 
130                 if ( !entry.isDirectory() && !isFiltered( jarFilters, name ) )
131                 {
132                     InputStream is = jarFile.getInputStream( entry );
133 
134                     String mappedName = remapper.map( name );
135 
136                     int idx = mappedName.lastIndexOf( '/' );
137                     if ( idx != -1 )
138                     {
139                         // make sure dirs are created
140                         String dir = mappedName.substring( 0, idx );
141                         if ( !resources.contains( dir ) )
142                         {
143                             addDirectory( resources, jos, dir );
144                         }
145                     }
146 
147                     if ( name.endsWith( ".class" ) )
148                     {
149                         addRemappedClass( remapper, jos, jar, name, is );
150                     }
151                     else
152                     {
153                         if ( !resourceTransformed( transformers, mappedName, is, relocators ) )
154                         {
155                             // Avoid duplicates that aren't accounted for by the resource transformers
156                             if ( resources.contains( mappedName ) )
157                             {
158                                 continue;
159                             }
160 
161                             addResource( resources, jos, mappedName, is );
162                         }
163                     }
164 
165                     IOUtil.close( is );
166                 }
167             }
168 
169             jarFile.close();
170         }
171 
172         for ( Iterator i = transformers.iterator(); i.hasNext(); )
173         {
174             ResourceTransformer transformer = (ResourceTransformer) i.next();
175 
176             if ( transformer.hasTransformedResource() )
177             {
178                 transformer.modifyOutputStream( jos );
179             }
180         }
181 
182         IOUtil.close( jos );
183 
184         for ( Iterator it = filters.iterator(); it.hasNext(); )
185         {
186             Filter filter = (Filter) it.next();
187             filter.finished();
188         }
189     }
190 
191     private JarFile newJarFile( File jar )
192         throws IOException
193     {
194         try
195         {
196             return new JarFile( jar );
197         }
198         catch ( ZipException zex )
199         {
200             // JarFile is not very verbose and doesn't tell the user which file it was
201             // so we will create a new Exception instead
202             throw new ZipException( "error in opening zip file " + jar );
203         }
204     }
205 
206     private List getFilters( File jar, List filters )
207     {
208         List list = new ArrayList();
209 
210         for ( int i = 0; i < filters.size(); i++ )
211         {
212             Filter filter = (Filter) filters.get( i );
213 
214             if ( filter.canFilter( jar ) )
215             {
216                 list.add( filter );
217             }
218 
219         }
220 
221         return list;
222     }
223 
224     private void addDirectory( Set resources, JarOutputStream jos, String name )
225         throws IOException
226     {
227         if ( name.lastIndexOf( '/' ) > 0 )
228         {
229             String parent = name.substring( 0, name.lastIndexOf( '/' ) );
230             if ( !resources.contains( parent ) )
231             {
232                 addDirectory( resources, jos, parent );
233             }
234         }
235 
236         // directory entries must end in "/"
237         JarEntry entry = new JarEntry( name + "/" );
238         jos.putNextEntry( entry );
239 
240         resources.add( name );
241     }
242 
243     private void addRemappedClass( RelocatorRemapper remapper, JarOutputStream jos, File jar, String name,
244                                    InputStream is )
245         throws IOException, MojoExecutionException
246     {
247         if ( !remapper.hasRelocators() )
248         {
249             try
250             {
251                 jos.putNextEntry( new JarEntry( name ) );
252                 IOUtil.copy( is, jos );
253             }
254             catch ( ZipException e )
255             {
256                 getLogger().warn( "We have a duplicate " + name + " in " + jar );
257             }
258 
259             return;
260         }
261 
262         ClassReader cr = new ClassReader( is );
263 
264         ClassWriter cw = new ClassWriter( cr, 0 );
265 
266         ClassVisitor cv = new TempRemappingClassAdapter( cw, remapper );
267 
268         try {
269         	cr.accept( cv, ClassReader.EXPAND_FRAMES );
270         } catch ( Throwable ise ) {
271         	throw new MojoExecutionException ("Error in ASM processing class "
272         			+ name, ise );
273         }
274 
275         byte[] renamedClass = cw.toByteArray();
276 
277         // Need to take the .class off for remapping evaluation
278         String mappedName = remapper.map( name.substring( 0, name.indexOf( '.' ) ) );
279 
280         try
281         {
282             // Now we put it back on so the class file is written out with the right extension.
283             jos.putNextEntry( new JarEntry( mappedName + ".class" ) );
284 
285             IOUtil.copy( renamedClass, jos );
286         }
287         catch ( ZipException e )
288         {
289             getLogger().warn( "We have a duplicate " + mappedName + " in " + jar );
290         }
291     }
292 
293     private boolean isFiltered( List filters, String name )
294     {
295         for ( int i = 0; i < filters.size(); i++ )
296         {
297             Filter filter = (Filter) filters.get( i );
298 
299             if ( filter.isFiltered( name ) )
300             {
301                 return true;
302             }
303         }
304 
305         return false;
306     }
307 
308     private boolean resourceTransformed( List resourceTransformers, String name, InputStream is, List relocators )
309         throws IOException
310     {
311         boolean resourceTransformed = false;
312 
313         for ( Iterator k = resourceTransformers.iterator(); k.hasNext(); )
314         {
315             ResourceTransformer transformer = (ResourceTransformer) k.next();
316 
317             if ( transformer.canTransformResource( name ) )
318             {
319                 getLogger().debug( "Transforming " + name + " using " + transformer.getClass().getName() );
320 
321                 transformer.processResource( name, is, relocators );
322 
323                 resourceTransformed = true;
324 
325                 break;
326             }
327         }
328         return resourceTransformed;
329     }
330 
331     private void addResource( Set resources, JarOutputStream jos, String name, InputStream is )
332         throws IOException
333     {
334         jos.putNextEntry( new JarEntry( name ) );
335 
336         IOUtil.copy( is, jos );
337 
338         resources.add( name );
339     }
340 
341     class RelocatorRemapper
342         extends Remapper
343     {
344 
345         private final Pattern classPattern = Pattern.compile( "(\\[*)?L(.+);" );
346 
347         List relocators;
348 
349         public RelocatorRemapper( List relocators )
350         {
351             this.relocators = relocators;
352         }
353 
354         public boolean hasRelocators()
355         {
356             return !relocators.isEmpty();
357         }
358 
359         public Object mapValue( Object object )
360         {
361             if ( object instanceof String )
362             {
363                 String name = (String) object;
364                 String value = name;
365 
366                 String prefix = "";
367                 String suffix = "";
368 
369                 Matcher m = classPattern.matcher( name );
370                 if ( m.matches() )
371                 {
372                     prefix = m.group( 1 ) + "L";
373                     suffix = ";";
374                     name = m.group( 2 );
375                 }
376 
377                 for ( Iterator i = relocators.iterator(); i.hasNext(); )
378                 {
379                     Relocator r = (Relocator) i.next();
380 
381                     if ( r.canRelocateClass( name ) )
382                     {
383                         value = prefix + r.relocateClass( name ) + suffix;
384                         break;
385                     }
386                     else if ( r.canRelocatePath( name ) )
387                     {
388                         value = prefix + r.relocatePath( name ) + suffix;
389                         break;
390                     }
391                 }
392 
393                 return value;
394             }
395 
396             return super.mapValue( object );
397         }
398 
399         public String map( String name )
400         {
401             String value = name;
402 
403             String prefix = "";
404             String suffix = "";
405 
406             Matcher m = classPattern.matcher( name );
407             if ( m.matches() )
408             {
409                 prefix = m.group( 1 ) + "L";
410                 suffix = ";";
411                 name = m.group( 2 );
412             }
413 
414             for ( Iterator i = relocators.iterator(); i.hasNext(); )
415             {
416                 Relocator r = (Relocator) i.next();
417 
418                 if ( r.canRelocatePath( name ) )
419                 {
420                     value = prefix + r.relocatePath( name ) + suffix;
421                     break;
422                 }
423             }
424 
425             return value;
426         }
427 
428     }
429 
430 }