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 List jarFilters = getFilters( jar, filters );
110
111 JarFile jarFile = newJarFile( jar );
112
113 for ( Enumeration j = jarFile.entries(); j.hasMoreElements(); )
114 {
115 JarEntry entry = (JarEntry) j.nextElement();
116
117 String name = entry.getName();
118
119 if ( "META-INF/INDEX.LIST".equals( name ) )
120 {
121
122
123
124 continue;
125 }
126
127 if ( !entry.isDirectory() && !isFiltered( jarFilters, name ) )
128 {
129 InputStream is = jarFile.getInputStream( entry );
130
131 String mappedName = remapper.map( name );
132
133 int idx = mappedName.lastIndexOf( '/' );
134 if ( idx != -1 )
135 {
136
137 String dir = mappedName.substring( 0, idx );
138 if ( !resources.contains( dir ) )
139 {
140 addDirectory( resources, jos, dir );
141 }
142 }
143
144 if ( name.endsWith( ".class" ) )
145 {
146 addRemappedClass( remapper, jos, jar, name, is );
147 }
148 else
149 {
150 if ( !resourceTransformed( transformers, mappedName, is, relocators ) )
151 {
152
153 if ( resources.contains( mappedName ) )
154 {
155 continue;
156 }
157
158 addResource( resources, jos, mappedName, is );
159 }
160 }
161
162 IOUtil.close( is );
163 }
164 }
165
166 jarFile.close();
167 }
168
169 for ( Iterator i = transformers.iterator(); i.hasNext(); )
170 {
171 ResourceTransformer transformer = (ResourceTransformer) i.next();
172
173 if ( transformer.hasTransformedResource() )
174 {
175 transformer.modifyOutputStream( jos );
176 }
177 }
178
179 IOUtil.close( jos );
180 }
181
182 private JarFile newJarFile( File jar )
183 throws IOException
184 {
185 try
186 {
187 return new JarFile( jar );
188 }
189 catch ( ZipException zex )
190 {
191
192
193 throw new ZipException( "error in opening zip file " + jar );
194 }
195 }
196
197 private List getFilters( File jar, List filters )
198 {
199 List list = new ArrayList();
200
201 for ( int i = 0; i < filters.size(); i++ )
202 {
203 Filter filter = (Filter) filters.get( i );
204
205 if ( filter.canFilter( jar ) )
206 {
207 list.add( filter );
208 }
209
210 }
211
212 return list;
213 }
214
215 private void addDirectory( Set resources, JarOutputStream jos, String name )
216 throws IOException
217 {
218 if ( name.lastIndexOf( '/' ) > 0 )
219 {
220 String parent = name.substring( 0, name.lastIndexOf( '/' ) );
221 if ( !resources.contains( parent ) )
222 {
223 addDirectory( resources, jos, parent );
224 }
225 }
226
227
228 JarEntry entry = new JarEntry( name + "/" );
229 jos.putNextEntry( entry );
230
231 resources.add( name );
232 }
233
234 private void addRemappedClass( RelocatorRemapper remapper, JarOutputStream jos, File jar, String name,
235 InputStream is )
236 throws IOException
237 {
238 if ( !remapper.hasRelocators() )
239 {
240 try
241 {
242 jos.putNextEntry( new JarEntry( name ) );
243 IOUtil.copy( is, jos );
244 }
245 catch ( ZipException e )
246 {
247 getLogger().warn( "We have a duplicate " + name + " in " + jar );
248 }
249
250 return;
251 }
252
253 ClassReader cr = new ClassReader( is );
254
255 ClassWriter cw = new ClassWriter( cr, 0 );
256
257 ClassVisitor cv = new TempRemappingClassAdapter( cw, remapper );
258
259 cr.accept( cv, ClassReader.EXPAND_FRAMES );
260
261 byte[] renamedClass = cw.toByteArray();
262
263
264 String mappedName = remapper.map( name.substring( 0, name.indexOf( '.' ) ) );
265
266 try
267 {
268
269 jos.putNextEntry( new JarEntry( mappedName + ".class" ) );
270
271 IOUtil.copy( renamedClass, jos );
272 }
273 catch ( ZipException e )
274 {
275 getLogger().warn( "We have a duplicate " + mappedName + " in " + jar );
276 }
277 }
278
279 private boolean isFiltered( List filters, String name )
280 {
281 for ( int i = 0; i < filters.size(); i++ )
282 {
283 Filter filter = (Filter) filters.get( i );
284
285 if ( filter.isFiltered( name ) )
286 {
287 return true;
288 }
289 }
290
291 return false;
292 }
293
294 private boolean resourceTransformed( List resourceTransformers, String name, InputStream is, List relocators )
295 throws IOException
296 {
297 boolean resourceTransformed = false;
298
299 for ( Iterator k = resourceTransformers.iterator(); k.hasNext(); )
300 {
301 ResourceTransformer transformer = (ResourceTransformer) k.next();
302
303 if ( transformer.canTransformResource( name ) )
304 {
305 transformer.processResource( name, is, relocators );
306
307 resourceTransformed = true;
308
309 break;
310 }
311 }
312 return resourceTransformed;
313 }
314
315 private void addResource( Set resources, JarOutputStream jos, String name, InputStream is )
316 throws IOException
317 {
318 jos.putNextEntry( new JarEntry( name ) );
319
320 IOUtil.copy( is, jos );
321
322 resources.add( name );
323 }
324
325 class RelocatorRemapper
326 extends Remapper
327 {
328
329 private final Pattern classPattern = Pattern.compile( "(\\[*)?L(.+);" );
330
331 List relocators;
332
333 public RelocatorRemapper( List relocators )
334 {
335 this.relocators = relocators;
336 }
337
338 public boolean hasRelocators()
339 {
340 return !relocators.isEmpty();
341 }
342
343 public Object mapValue( Object object )
344 {
345 if ( object instanceof String )
346 {
347 String name = (String) object;
348 String value = name;
349
350 String prefix = "";
351 String suffix = "";
352
353 Matcher m = classPattern.matcher( name );
354 if ( m.matches() )
355 {
356 prefix = m.group( 1 ) + "L";
357 suffix = ";";
358 name = m.group( 2 );
359 }
360
361 for ( Iterator i = relocators.iterator(); i.hasNext(); )
362 {
363 Relocator r = (Relocator) i.next();
364
365 if ( r.canRelocateClass( name ) )
366 {
367 value = prefix + r.relocateClass( name ) + suffix;
368 break;
369 }
370 else if ( r.canRelocatePath( name ) )
371 {
372 value = prefix + r.relocatePath( name ) + suffix;
373 break;
374 }
375 }
376
377 return value;
378 }
379
380 return super.mapValue( object );
381 }
382
383 public String map( String name )
384 {
385 String value = name;
386
387 String prefix = "";
388 String suffix = "";
389
390 Matcher m = classPattern.matcher( name );
391 if ( m.matches() )
392 {
393 prefix = m.group( 1 ) + "L";
394 suffix = ";";
395 name = m.group( 2 );
396 }
397
398 for ( Iterator i = relocators.iterator(); i.hasNext(); )
399 {
400 Relocator r = (Relocator) i.next();
401
402 if ( r.canRelocatePath( name ) )
403 {
404 value = prefix + r.relocatePath( name ) + suffix;
405 break;
406 }
407 }
408
409 return value;
410 }
411
412 }
413
414 }