1 package org.apache.maven.plugins.shade;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
53
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
125
126
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
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
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
201
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
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
278 String mappedName = remapper.map( name.substring( 0, name.indexOf( '.' ) ) );
279
280 try
281 {
282
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 }