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