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.io.InputStreamReader;
41 import java.io.OutputStreamWriter;
42 import java.util.ArrayList;
43 import java.util.Enumeration;
44 import java.util.HashSet;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.Set;
48 import java.util.jar.JarEntry;
49 import java.util.jar.JarFile;
50 import java.util.jar.JarOutputStream;
51 import java.util.regex.Matcher;
52 import java.util.regex.Pattern;
53 import java.util.zip.ZipException;
54
55
56
57
58 @Component( role = Shader.class, hint = "default" )
59 public class DefaultShader
60 extends AbstractLogEnabled
61 implements Shader
62 {
63
64 public void shade( ShadeRequest shadeRequest )
65 throws IOException, MojoExecutionException
66 {
67 Set<String> resources = new HashSet<String>();
68
69 ResourceTransformer manifestTransformer = null;
70 List<ResourceTransformer> transformers = new ArrayList<ResourceTransformer>( shadeRequest.getResourceTransformers() );
71 for ( Iterator<ResourceTransformer> it = transformers.iterator(); it.hasNext(); )
72 {
73 ResourceTransformer transformer = it.next();
74 if ( transformer instanceof ManifestResourceTransformer )
75 {
76 manifestTransformer = transformer;
77 it.remove();
78 }
79 }
80
81 RelocatorRemapper remapper = new RelocatorRemapper( shadeRequest.getRelocators() );
82
83 shadeRequest.getUberJar().getParentFile().mkdirs();
84 JarOutputStream jos = new JarOutputStream( new FileOutputStream( shadeRequest.getUberJar() ) );
85
86 if ( manifestTransformer != null )
87 {
88 for ( File jar : shadeRequest.getJars() )
89 {
90 JarFile jarFile = newJarFile( jar );
91 for ( Enumeration<JarEntry> en = jarFile.entries(); en.hasMoreElements(); )
92 {
93 JarEntry entry = en.nextElement();
94 String resource = entry.getName();
95 if ( manifestTransformer.canTransformResource( resource ) )
96 {
97 resources.add( resource );
98 manifestTransformer.processResource( resource, jarFile.getInputStream( entry ), shadeRequest.getRelocators() );
99 break;
100 }
101 }
102 }
103 if ( manifestTransformer.hasTransformedResource() )
104 {
105 manifestTransformer.modifyOutputStream( jos );
106 }
107 }
108
109 for ( File jar : shadeRequest.getJars() )
110 {
111
112 getLogger().debug( "Processing JAR " + jar );
113
114 List<Filter> jarFilters = getFilters( jar, shadeRequest.getFilters() );
115
116 JarFile jarFile = newJarFile( jar );
117
118 for ( Enumeration<JarEntry> j = jarFile.entries(); j.hasMoreElements(); )
119 {
120 JarEntry entry = j.nextElement();
121
122 String name = entry.getName();
123
124 if ( "META-INF/INDEX.LIST".equals( name ) )
125 {
126
127
128
129 continue;
130 }
131
132 if ( !entry.isDirectory() && !isFiltered( jarFilters, name ) )
133 {
134 InputStream is = jarFile.getInputStream( entry );
135
136 String mappedName = remapper.map( name );
137
138 int idx = mappedName.lastIndexOf( '/' );
139 if ( idx != -1 )
140 {
141
142 String dir = mappedName.substring( 0, idx );
143 if ( !resources.contains( dir ) )
144 {
145 addDirectory( resources, jos, dir );
146 }
147 }
148
149 if ( name.endsWith( ".class" ) )
150 {
151 addRemappedClass( remapper, jos, jar, name, is );
152 }
153 else if ( shadeRequest.isShadeSourcesContent() && name.endsWith( ".java" ) )
154 {
155
156 if ( resources.contains( mappedName ) )
157 {
158 continue;
159 }
160
161 addJavaSource( resources, jos, mappedName, is, shadeRequest.getRelocators() );
162 }
163 else
164 {
165 if ( !resourceTransformed( transformers, mappedName, is, shadeRequest.getRelocators() ) )
166 {
167
168 if ( resources.contains( mappedName ) )
169 {
170 continue;
171 }
172
173 addResource( resources, jos, mappedName, is );
174 }
175 }
176
177 IOUtil.close( is );
178 }
179 }
180
181 jarFile.close();
182 }
183
184 for ( ResourceTransformer transformer : transformers )
185 {
186 if ( transformer.hasTransformedResource() )
187 {
188 transformer.modifyOutputStream( jos );
189 }
190 }
191
192 IOUtil.close( jos );
193
194 for ( Filter filter : shadeRequest.getFilters() )
195 {
196 filter.finished();
197 }
198 }
199
200 private JarFile newJarFile( File jar )
201 throws IOException
202 {
203 try
204 {
205 return new JarFile( jar );
206 }
207 catch ( ZipException zex )
208 {
209
210
211 throw new ZipException( "error in opening zip file " + jar );
212 }
213 }
214
215 private List<Filter> getFilters( File jar, List<Filter> filters )
216 {
217 List<Filter> list = new ArrayList<Filter>();
218
219 for ( Filter filter : filters )
220 {
221 if ( filter.canFilter( jar ) )
222 {
223 list.add( filter );
224 }
225
226 }
227
228 return list;
229 }
230
231 private void addDirectory( Set<String> resources, JarOutputStream jos, String name )
232 throws IOException
233 {
234 if ( name.lastIndexOf( '/' ) > 0 )
235 {
236 String parent = name.substring( 0, name.lastIndexOf( '/' ) );
237 if ( !resources.contains( parent ) )
238 {
239 addDirectory( resources, jos, parent );
240 }
241 }
242
243
244 JarEntry entry = new JarEntry( name + "/" );
245 jos.putNextEntry( entry );
246
247 resources.add( name );
248 }
249
250 private void addRemappedClass( RelocatorRemapper remapper, JarOutputStream jos, File jar, String name,
251 InputStream is )
252 throws IOException, MojoExecutionException
253 {
254 if ( !remapper.hasRelocators() )
255 {
256 try
257 {
258 jos.putNextEntry( new JarEntry( name ) );
259 IOUtil.copy( is, jos );
260 }
261 catch ( ZipException e )
262 {
263 getLogger().warn( "We have a duplicate " + name + " in " + jar );
264 }
265
266 return;
267 }
268
269 ClassReader cr = new ClassReader( is );
270
271
272
273
274
275
276 ClassWriter cw = new ClassWriter( 0 );
277
278 ClassVisitor cv = new RemappingClassAdapter( cw, remapper );
279
280 try
281 {
282 cr.accept( cv, ClassReader.EXPAND_FRAMES );
283 }
284 catch ( Throwable ise )
285 {
286 throw new MojoExecutionException( "Error in ASM processing class " + name, ise );
287 }
288
289 byte[] renamedClass = cw.toByteArray();
290
291
292 String mappedName = remapper.map( name.substring( 0, name.indexOf( '.' ) ) );
293
294 try
295 {
296
297 jos.putNextEntry( new JarEntry( mappedName + ".class" ) );
298
299 IOUtil.copy( renamedClass, jos );
300 }
301 catch ( ZipException e )
302 {
303 getLogger().warn( "We have a duplicate " + mappedName + " in " + jar );
304 }
305 }
306
307 private boolean isFiltered( List<Filter> filters, String name )
308 {
309 for ( Filter filter : filters )
310 {
311 if ( filter.isFiltered( name ) )
312 {
313 return true;
314 }
315 }
316
317 return false;
318 }
319
320 private boolean resourceTransformed( List<ResourceTransformer> resourceTransformers, String name, InputStream is,
321 List<Relocator> relocators )
322 throws IOException
323 {
324 boolean resourceTransformed = false;
325
326 for ( ResourceTransformer transformer : resourceTransformers )
327 {
328 if ( transformer.canTransformResource( name ) )
329 {
330 getLogger().debug( "Transforming " + name + " using " + transformer.getClass().getName() );
331
332 transformer.processResource( name, is, relocators );
333
334 resourceTransformed = true;
335
336 break;
337 }
338 }
339 return resourceTransformed;
340 }
341
342 private void addJavaSource( Set<String> resources, JarOutputStream jos, String name, InputStream is,
343 List<Relocator> relocators )
344 throws IOException
345 {
346 jos.putNextEntry( new JarEntry( name ) );
347
348 String sourceContent = IOUtil.toString( new InputStreamReader( is, "UTF-8" ) );
349
350 for ( Relocator relocator : relocators ) {
351 sourceContent = relocator.applyToSourceContent(sourceContent);
352 }
353
354 OutputStreamWriter writer = new OutputStreamWriter( jos, "UTF-8" );
355 IOUtil.copy( sourceContent, writer );
356 writer.flush();
357
358 resources.add( name );
359 }
360
361 private void addResource( Set<String> resources, JarOutputStream jos, String name, InputStream is )
362 throws IOException
363 {
364 jos.putNextEntry( new JarEntry( name ) );
365
366 IOUtil.copy( is, jos );
367
368 resources.add( name );
369 }
370
371 class RelocatorRemapper
372 extends Remapper
373 {
374
375 private final Pattern classPattern = Pattern.compile( "(\\[*)?L(.+);" );
376
377 List<Relocator> relocators;
378
379 public RelocatorRemapper( List<Relocator> relocators )
380 {
381 this.relocators = relocators;
382 }
383
384 public boolean hasRelocators()
385 {
386 return !relocators.isEmpty();
387 }
388
389 public Object mapValue( Object object )
390 {
391 if ( object instanceof String )
392 {
393 String name = (String) object;
394 String value = name;
395
396 String prefix = "";
397 String suffix = "";
398
399 Matcher m = classPattern.matcher( name );
400 if ( m.matches() )
401 {
402 prefix = m.group( 1 ) + "L";
403 suffix = ";";
404 name = m.group( 2 );
405 }
406
407 for ( Relocator r : relocators )
408 {
409 if ( r.canRelocateClass( name ) )
410 {
411 value = prefix + r.relocateClass( name ) + suffix;
412 break;
413 }
414 else if ( r.canRelocatePath( name ) )
415 {
416 value = prefix + r.relocatePath( name ) + suffix;
417 break;
418 }
419 }
420
421 return value;
422 }
423
424 return super.mapValue( object );
425 }
426
427 public String map( String name )
428 {
429 String value = name;
430
431 String prefix = "";
432 String suffix = "";
433
434 Matcher m = classPattern.matcher( name );
435 if ( m.matches() )
436 {
437 prefix = m.group( 1 ) + "L";
438 suffix = ";";
439 name = m.group( 2 );
440 }
441
442 for ( Relocator r : relocators )
443 {
444 if ( r.canRelocatePath( name ) )
445 {
446 value = prefix + r.relocatePath( name ) + suffix;
447 break;
448 }
449 }
450
451 return value;
452 }
453
454 }
455
456 }