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 org.apache.maven.plugin.MojoExecutionException;
23 import org.apache.maven.plugins.shade.filter.Filter;
24 import org.apache.maven.plugins.shade.relocation.Relocator;
25 import org.apache.maven.plugins.shade.resource.ManifestResourceTransformer;
26 import org.apache.maven.plugins.shade.resource.ResourceTransformer;
27 import org.codehaus.plexus.component.annotations.Component;
28 import org.codehaus.plexus.logging.AbstractLogEnabled;
29 import org.codehaus.plexus.util.IOUtil;
30 import org.objectweb.asm.ClassReader;
31 import org.objectweb.asm.ClassVisitor;
32 import org.objectweb.asm.ClassWriter;
33 import org.objectweb.asm.commons.Remapper;
34 import org.objectweb.asm.commons.RemappingClassAdapter;
35
36 import java.io.File;
37 import java.io.FileOutputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.util.ArrayList;
41 import java.util.Enumeration;
42 import java.util.HashSet;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Set;
46 import java.util.jar.JarEntry;
47 import java.util.jar.JarFile;
48 import java.util.jar.JarOutputStream;
49 import java.util.regex.Matcher;
50 import java.util.regex.Pattern;
51 import java.util.zip.ZipException;
52
53
54
55
56 @Component( role = Shader.class, hint = "default" )
57 public class DefaultShader
58 extends AbstractLogEnabled
59 implements Shader
60 {
61
62 public void shade( Set<File> jars, File uberJar, List<Filter> filters, List<Relocator> relocators,
63 List<ResourceTransformer> resourceTransformers )
64 throws IOException, MojoExecutionException
65 {
66 Set resources = new HashSet();
67
68 ResourceTransformer manifestTransformer = null;
69 List transformers = new ArrayList( resourceTransformers );
70 for ( Iterator<ResourceTransformer> it = transformers.iterator(); it.hasNext(); )
71 {
72 ResourceTransformer transformer = it.next();
73 if ( transformer instanceof ManifestResourceTransformer )
74 {
75 manifestTransformer = transformer;
76 it.remove();
77 }
78 }
79
80 RelocatorRemapper remapper = new RelocatorRemapper( relocators );
81
82 uberJar.getParentFile().mkdirs();
83 JarOutputStream jos = new JarOutputStream( new FileOutputStream( uberJar ) );
84
85 if ( manifestTransformer != null )
86 {
87 for ( File jar : jars )
88 {
89 JarFile jarFile = newJarFile( jar );
90 for ( Enumeration en = jarFile.entries(); en.hasMoreElements(); )
91 {
92 JarEntry entry = (JarEntry) en.nextElement();
93 String resource = entry.getName();
94 if ( manifestTransformer.canTransformResource( resource ) )
95 {
96 resources.add( resource );
97 manifestTransformer.processResource( resource, jarFile.getInputStream( entry ), relocators );
98 break;
99 }
100 }
101 }
102 if ( manifestTransformer.hasTransformedResource() )
103 {
104 manifestTransformer.modifyOutputStream( jos );
105 }
106 }
107
108 for ( File jar : jars )
109 {
110
111 getLogger().debug( "Processing JAR " + jar );
112
113 List jarFilters = getFilters( jar, filters );
114
115 JarFile jarFile = newJarFile( jar );
116
117 for ( Enumeration<JarEntry> j = jarFile.entries(); j.hasMoreElements(); )
118 {
119 JarEntry entry = j.nextElement();
120
121 String name = entry.getName();
122
123 if ( "META-INF/INDEX.LIST".equals( name ) )
124 {
125
126
127
128 continue;
129 }
130
131 if ( !entry.isDirectory() && !isFiltered( jarFilters, name ) )
132 {
133 InputStream is = jarFile.getInputStream( entry );
134
135 String mappedName = remapper.map( name );
136
137 int idx = mappedName.lastIndexOf( '/' );
138 if ( idx != -1 )
139 {
140
141 String dir = mappedName.substring( 0, idx );
142 if ( !resources.contains( dir ) )
143 {
144 addDirectory( resources, jos, dir );
145 }
146 }
147
148 if ( name.endsWith( ".class" ) )
149 {
150 addRemappedClass( remapper, jos, jar, name, is );
151 }
152 else
153 {
154 if ( !resourceTransformed( transformers, mappedName, is, relocators ) )
155 {
156
157 if ( resources.contains( mappedName ) )
158 {
159 continue;
160 }
161
162 addResource( resources, jos, mappedName, is );
163 }
164 }
165
166 IOUtil.close( is );
167 }
168 }
169
170 jarFile.close();
171 }
172
173 for ( Iterator i = transformers.iterator(); i.hasNext(); )
174 {
175 ResourceTransformer transformer = (ResourceTransformer) i.next();
176
177 if ( transformer.hasTransformedResource() )
178 {
179 transformer.modifyOutputStream( jos );
180 }
181 }
182
183 IOUtil.close( jos );
184
185 for ( Filter filter : filters )
186 {
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<Filter> getFilters( File jar, List<Filter> filters )
207 {
208 List<Filter> list = new ArrayList<Filter>();
209
210 for ( Filter filter : filters )
211 {
212 if ( filter.canFilter( jar ) )
213 {
214 list.add( filter );
215 }
216
217 }
218
219 return list;
220 }
221
222 private void addDirectory( Set resources, JarOutputStream jos, String name )
223 throws IOException
224 {
225 if ( name.lastIndexOf( '/' ) > 0 )
226 {
227 String parent = name.substring( 0, name.lastIndexOf( '/' ) );
228 if ( !resources.contains( parent ) )
229 {
230 addDirectory( resources, jos, parent );
231 }
232 }
233
234
235 JarEntry entry = new JarEntry( name + "/" );
236 jos.putNextEntry( entry );
237
238 resources.add( name );
239 }
240
241 private void addRemappedClass( RelocatorRemapper remapper, JarOutputStream jos, File jar, String name,
242 InputStream is )
243 throws IOException, MojoExecutionException
244 {
245 if ( !remapper.hasRelocators() )
246 {
247 try
248 {
249 jos.putNextEntry( new JarEntry( name ) );
250 IOUtil.copy( is, jos );
251 }
252 catch ( ZipException e )
253 {
254 getLogger().warn( "We have a duplicate " + name + " in " + jar );
255 }
256
257 return;
258 }
259
260 ClassReader cr = new ClassReader( is );
261
262
263
264
265
266
267 ClassWriter cw = new ClassWriter( 0 );
268
269 ClassVisitor cv = new RemappingClassAdapter( cw, remapper );
270
271 try
272 {
273 cr.accept( cv, ClassReader.EXPAND_FRAMES );
274 }
275 catch ( Throwable ise )
276 {
277 throw new MojoExecutionException( "Error in ASM processing class " + name, ise );
278 }
279
280 byte[] renamedClass = cw.toByteArray();
281
282
283 String mappedName = remapper.map( name.substring( 0, name.indexOf( '.' ) ) );
284
285 try
286 {
287
288 jos.putNextEntry( new JarEntry( mappedName + ".class" ) );
289
290 IOUtil.copy( renamedClass, jos );
291 }
292 catch ( ZipException e )
293 {
294 getLogger().warn( "We have a duplicate " + mappedName + " in " + jar );
295 }
296 }
297
298 private boolean isFiltered( List<Filter> filters, String name )
299 {
300 for ( Filter filter : filters )
301 {
302 if ( filter.isFiltered( name ) )
303 {
304 return true;
305 }
306 }
307
308 return false;
309 }
310
311 private boolean resourceTransformed( List<ResourceTransformer> resourceTransformers, String name, InputStream is,
312 List<Relocator> relocators )
313 throws IOException
314 {
315 boolean resourceTransformed = false;
316
317 for ( ResourceTransformer transformer : resourceTransformers )
318 {
319 if ( transformer.canTransformResource( name ) )
320 {
321 getLogger().debug( "Transforming " + name + " using " + transformer.getClass().getName() );
322
323 transformer.processResource( name, is, relocators );
324
325 resourceTransformed = true;
326
327 break;
328 }
329 }
330 return resourceTransformed;
331 }
332
333 private void addResource( Set resources, JarOutputStream jos, String name, InputStream is )
334 throws IOException
335 {
336 jos.putNextEntry( new JarEntry( name ) );
337
338 IOUtil.copy( is, jos );
339
340 resources.add( name );
341 }
342
343 class RelocatorRemapper
344 extends Remapper
345 {
346
347 private final Pattern classPattern = Pattern.compile( "(\\[*)?L(.+);" );
348
349 List<Relocator> relocators;
350
351 public RelocatorRemapper( List<Relocator> relocators )
352 {
353 this.relocators = relocators;
354 }
355
356 public boolean hasRelocators()
357 {
358 return !relocators.isEmpty();
359 }
360
361 public Object mapValue( Object object )
362 {
363 if ( object instanceof String )
364 {
365 String name = (String) object;
366 String value = name;
367
368 String prefix = "";
369 String suffix = "";
370
371 Matcher m = classPattern.matcher( name );
372 if ( m.matches() )
373 {
374 prefix = m.group( 1 ) + "L";
375 suffix = ";";
376 name = m.group( 2 );
377 }
378
379 for ( Relocator r : relocators )
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 ( Relocator r : relocators )
415 {
416 if ( r.canRelocatePath( name ) )
417 {
418 value = prefix + r.relocatePath( name ) + suffix;
419 break;
420 }
421 }
422
423 return value;
424 }
425
426 }
427
428 }