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