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