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.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
52
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
124
125
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
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
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
200
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
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
272 String mappedName = remapper.map( name.substring( 0, name.indexOf( '.' ) ) );
273
274 try
275 {
276
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 }