Coverage Report - org.apache.maven.plugins.shade.DefaultShader
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultShader
65%
84/129
56%
37/66
4.923
DefaultShader$RelocatorRemapper
78%
29/37
77%
14/18
4.923
 
 1  
 package org.apache.maven.plugins.shade;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *   http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 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  
  * @author Jason van Zyl
 57  
  */
 58  
 @Component( role = Shader.class, hint = "default" )
 59  14
 public class DefaultShader
 60  
     extends AbstractLogEnabled
 61  
     implements Shader
 62  
 {
 63  
 
 64  
     public void shade( ShadeRequest shadeRequest )
 65  
         throws IOException, MojoExecutionException
 66  
     {
 67  14
         Set<String> resources = new HashSet<String>();
 68  
 
 69  14
         ResourceTransformer manifestTransformer = null;
 70  14
         List<ResourceTransformer> transformers = new ArrayList<ResourceTransformer>( shadeRequest.getResourceTransformers() );
 71  14
         for ( Iterator<ResourceTransformer> it = transformers.iterator(); it.hasNext(); )
 72  
         {
 73  10
             ResourceTransformer transformer = it.next();
 74  10
             if ( transformer instanceof ManifestResourceTransformer )
 75  
             {
 76  0
                 manifestTransformer = transformer;
 77  0
                 it.remove();
 78  
             }
 79  10
         }
 80  
 
 81  14
         RelocatorRemapper remapper = new RelocatorRemapper( shadeRequest.getRelocators() );
 82  
 
 83  14
         shadeRequest.getUberJar().getParentFile().mkdirs();
 84  14
         JarOutputStream jos = new JarOutputStream( new FileOutputStream( shadeRequest.getUberJar() ) );
 85  
 
 86  14
         if ( manifestTransformer != null )
 87  
         {
 88  0
             for ( File jar : shadeRequest.getJars() )
 89  
             {
 90  0
                 JarFile jarFile = newJarFile( jar );
 91  0
                 for ( Enumeration<JarEntry> en = jarFile.entries(); en.hasMoreElements(); )
 92  
                 {
 93  0
                     JarEntry entry = en.nextElement();
 94  0
                     String resource = entry.getName();
 95  0
                     if ( manifestTransformer.canTransformResource( resource ) )
 96  
                     {
 97  0
                         resources.add( resource );
 98  0
                         manifestTransformer.processResource( resource, jarFile.getInputStream( entry ), shadeRequest.getRelocators() );
 99  0
                         break;
 100  
                     }
 101  0
                 }
 102  0
             }
 103  0
             if ( manifestTransformer.hasTransformedResource() )
 104  
             {
 105  0
                 manifestTransformer.modifyOutputStream( jos );
 106  
             }
 107  
         }
 108  
 
 109  14
         for ( File jar : shadeRequest.getJars() )
 110  
         {
 111  
 
 112  24
             getLogger().debug( "Processing JAR " + jar );
 113  
 
 114  24
             List<Filter> jarFilters = getFilters( jar, shadeRequest.getFilters() );
 115  
 
 116  24
             JarFile jarFile = newJarFile( jar );
 117  
 
 118  24
             for ( Enumeration<JarEntry> j = jarFile.entries(); j.hasMoreElements(); )
 119  
             {
 120  1262
                 JarEntry entry = j.nextElement();
 121  
 
 122  1262
                 String name = entry.getName();
 123  
 
 124  1262
                 if ( "META-INF/INDEX.LIST".equals( name ) )
 125  
                 {
 126  
                     // we cannot allow the jar indexes to be copied over or the
 127  
                     // jar is useless. Ideally, we could create a new one
 128  
                     // later
 129  0
                     continue;
 130  
                 }
 131  
 
 132  1262
                 if ( !entry.isDirectory() && !isFiltered( jarFilters, name ) )
 133  
                 {
 134  966
                     InputStream is = jarFile.getInputStream( entry );
 135  
 
 136  966
                     String mappedName = remapper.map( name );
 137  
 
 138  966
                     int idx = mappedName.lastIndexOf( '/' );
 139  966
                     if ( idx != -1 )
 140  
                     {
 141  
                         // make sure dirs are created
 142  966
                         String dir = mappedName.substring( 0, idx );
 143  966
                         if ( !resources.contains( dir ) )
 144  
                         {
 145  162
                             addDirectory( resources, jos, dir );
 146  
                         }
 147  
                     }
 148  
 
 149  966
                     if ( name.endsWith( ".class" ) )
 150  
                     {
 151  844
                         addRemappedClass( remapper, jos, jar, name, is );
 152  
                     }
 153  122
                     else if ( shadeRequest.isShadeSourcesContent() && name.endsWith( ".java" ) )
 154  
                     {
 155  
                         // Avoid duplicates
 156  0
                         if ( resources.contains( mappedName ) )
 157  
                         {
 158  0
                             continue;
 159  
                         }
 160  
                         
 161  0
                         addJavaSource( resources, jos, mappedName, is, shadeRequest.getRelocators() );
 162  
                     }
 163  
                     else
 164  
                     {
 165  122
                         if ( !resourceTransformed( transformers, mappedName, is, shadeRequest.getRelocators() ) )
 166  
                         {
 167  
                             // Avoid duplicates that aren't accounted for by the resource transformers
 168  112
                             if ( resources.contains( mappedName ) )
 169  
                             {
 170  10
                                 continue;
 171  
                             }
 172  
 
 173  102
                             addResource( resources, jos, mappedName, is );
 174  
                         }
 175  
                     }
 176  
 
 177  956
                     IOUtil.close( is );
 178  
                 }
 179  1252
             }
 180  
 
 181  24
             jarFile.close();
 182  24
         }
 183  
 
 184  14
         for ( ResourceTransformer transformer : transformers )
 185  
         {
 186  10
             if ( transformer.hasTransformedResource() )
 187  
             {
 188  10
                 transformer.modifyOutputStream( jos );
 189  
             }
 190  
         }
 191  
 
 192  14
         IOUtil.close( jos );
 193  
 
 194  14
         for ( Filter filter : shadeRequest.getFilters() )
 195  
         {
 196  0
             filter.finished();
 197  
         }
 198  14
     }
 199  
 
 200  
     private JarFile newJarFile( File jar )
 201  
         throws IOException
 202  
     {
 203  
         try
 204  
         {
 205  24
             return new JarFile( jar );
 206  
         }
 207  0
         catch ( ZipException zex )
 208  
         {
 209  
             // JarFile is not very verbose and doesn't tell the user which file it was
 210  
             // so we will create a new Exception instead
 211  0
             throw new ZipException( "error in opening zip file " + jar );
 212  
         }
 213  
     }
 214  
 
 215  
     private List<Filter> getFilters( File jar, List<Filter> filters )
 216  
     {
 217  24
         List<Filter> list = new ArrayList<Filter>();
 218  
 
 219  24
         for ( Filter filter : filters )
 220  
         {
 221  0
             if ( filter.canFilter( jar ) )
 222  
             {
 223  0
                 list.add( filter );
 224  
             }
 225  
 
 226  
         }
 227  
 
 228  24
         return list;
 229  
     }
 230  
 
 231  
     private void addDirectory( Set<String> resources, JarOutputStream jos, String name )
 232  
         throws IOException
 233  
     {
 234  300
         if ( name.lastIndexOf( '/' ) > 0 )
 235  
         {
 236  258
             String parent = name.substring( 0, name.lastIndexOf( '/' ) );
 237  258
             if ( !resources.contains( parent ) )
 238  
             {
 239  138
                 addDirectory( resources, jos, parent );
 240  
             }
 241  
         }
 242  
 
 243  
         // directory entries must end in "/"
 244  300
         JarEntry entry = new JarEntry( name + "/" );
 245  300
         jos.putNextEntry( entry );
 246  
 
 247  300
         resources.add( name );
 248  300
     }
 249  
 
 250  
     private void addRemappedClass( RelocatorRemapper remapper, JarOutputStream jos, File jar, String name,
 251  
                                    InputStream is )
 252  
         throws IOException, MojoExecutionException
 253  
     {
 254  844
         if ( !remapper.hasRelocators() )
 255  
         {
 256  
             try
 257  
             {
 258  0
                 jos.putNextEntry( new JarEntry( name ) );
 259  0
                 IOUtil.copy( is, jos );
 260  
             }
 261  0
             catch ( ZipException e )
 262  
             {
 263  0
                 getLogger().warn( "We have a duplicate " + name + " in " + jar );
 264  0
             }
 265  
 
 266  0
             return;
 267  
         }
 268  
 
 269  844
         ClassReader cr = new ClassReader( is );
 270  
 
 271  
         // We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant pool.
 272  
         // Copying the original constant pool should be avoided because it would keep references
 273  
         // to the original class names. This is not a problem at runtime (because these entries in the
 274  
         // constant pool are never used), but confuses some tools such as Felix' maven-bundle-plugin
 275  
         // that use the constant pool to determine the dependencies of a class.
 276  844
         ClassWriter cw = new ClassWriter( 0 );
 277  
 
 278  844
         ClassVisitor cv = new RemappingClassAdapter( cw, remapper );
 279  
 
 280  
         try
 281  
         {
 282  844
             cr.accept( cv, ClassReader.EXPAND_FRAMES );
 283  
         }
 284  0
         catch ( Throwable ise )
 285  
         {
 286  0
             throw new MojoExecutionException( "Error in ASM processing class " + name, ise );
 287  844
         }
 288  
 
 289  844
         byte[] renamedClass = cw.toByteArray();
 290  
 
 291  
         // Need to take the .class off for remapping evaluation
 292  844
         String mappedName = remapper.map( name.substring( 0, name.indexOf( '.' ) ) );
 293  
 
 294  
         try
 295  
         {
 296  
             // Now we put it back on so the class file is written out with the right extension.
 297  844
             jos.putNextEntry( new JarEntry( mappedName + ".class" ) );
 298  
 
 299  844
             IOUtil.copy( renamedClass, jos );
 300  
         }
 301  0
         catch ( ZipException e )
 302  
         {
 303  0
             getLogger().warn( "We have a duplicate " + mappedName + " in " + jar );
 304  844
         }
 305  844
     }
 306  
 
 307  
     private boolean isFiltered( List<Filter> filters, String name )
 308  
     {
 309  966
         for ( Filter filter : filters )
 310  
         {
 311  0
             if ( filter.isFiltered( name ) )
 312  
             {
 313  0
                 return true;
 314  
             }
 315  
         }
 316  
 
 317  966
         return false;
 318  
     }
 319  
 
 320  
     private boolean resourceTransformed( List<ResourceTransformer> resourceTransformers, String name, InputStream is,
 321  
                                          List<Relocator> relocators )
 322  
         throws IOException
 323  
     {
 324  122
         boolean resourceTransformed = false;
 325  
 
 326  122
         for ( ResourceTransformer transformer : resourceTransformers )
 327  
         {
 328  110
             if ( transformer.canTransformResource( name ) )
 329  
             {
 330  10
                 getLogger().debug( "Transforming " + name + " using " + transformer.getClass().getName() );
 331  
 
 332  10
                 transformer.processResource( name, is, relocators );
 333  
 
 334  10
                 resourceTransformed = true;
 335  
 
 336  10
                 break;
 337  
             }
 338  
         }
 339  122
         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  0
         jos.putNextEntry( new JarEntry( name ) );
 347  
 
 348  0
         String sourceContent = IOUtil.toString( new InputStreamReader( is, "UTF-8" ) );
 349  
         
 350  0
         for ( Relocator relocator : relocators ) {
 351  0
                 sourceContent = relocator.applyToSourceContent(sourceContent);
 352  
         }
 353  
         
 354  0
         OutputStreamWriter writer = new OutputStreamWriter( jos, "UTF-8" );
 355  0
         IOUtil.copy( sourceContent, writer );
 356  0
         writer.flush();
 357  
 
 358  0
         resources.add( name );
 359  0
     }
 360  
 
 361  
     private void addResource( Set<String> resources, JarOutputStream jos, String name, InputStream is )
 362  
         throws IOException
 363  
     {
 364  102
         jos.putNextEntry( new JarEntry( name ) );
 365  
 
 366  102
         IOUtil.copy( is, jos );
 367  
 
 368  102
         resources.add( name );
 369  102
     }
 370  
 
 371  14
     class RelocatorRemapper
 372  
         extends Remapper
 373  
     {
 374  
 
 375  14
         private final Pattern classPattern = Pattern.compile( "(\\[*)?L(.+);" );
 376  
 
 377  
         List<Relocator> relocators;
 378  
 
 379  
         public RelocatorRemapper( List<Relocator> relocators )
 380  14
         {
 381  14
             this.relocators = relocators;
 382  14
         }
 383  
 
 384  
         public boolean hasRelocators()
 385  
         {
 386  844
             return !relocators.isEmpty();
 387  
         }
 388  
 
 389  
         public Object mapValue( Object object )
 390  
         {
 391  16424
             if ( object instanceof String )
 392  
             {
 393  9206
                 String name = (String) object;
 394  9206
                 String value = name;
 395  
 
 396  9206
                 String prefix = "";
 397  9206
                 String suffix = "";
 398  
 
 399  9206
                 Matcher m = classPattern.matcher( name );
 400  9206
                 if ( m.matches() )
 401  
                 {
 402  0
                     prefix = m.group( 1 ) + "L";
 403  0
                     suffix = ";";
 404  0
                     name = m.group( 2 );
 405  
                 }
 406  
 
 407  9206
                 for ( Relocator r : relocators )
 408  
                 {
 409  9206
                     if ( r.canRelocateClass( name ) )
 410  
                     {
 411  2
                         value = prefix + r.relocateClass( name ) + suffix;
 412  2
                         break;
 413  
                     }
 414  9204
                     else if ( r.canRelocatePath( name ) )
 415  
                     {
 416  0
                         value = prefix + r.relocatePath( name ) + suffix;
 417  0
                         break;
 418  
                     }
 419  
                 }
 420  
 
 421  9206
                 return value;
 422  
             }
 423  
 
 424  7218
             return super.mapValue( object );
 425  
         }
 426  
 
 427  
         public String map( String name )
 428  
         {
 429  203786
             String value = name;
 430  
 
 431  203786
             String prefix = "";
 432  203786
             String suffix = "";
 433  
 
 434  203786
             Matcher m = classPattern.matcher( name );
 435  203786
             if ( m.matches() )
 436  
             {
 437  0
                 prefix = m.group( 1 ) + "L";
 438  0
                 suffix = ";";
 439  0
                 name = m.group( 2 );
 440  
             }
 441  
 
 442  203786
             for ( Relocator r : relocators )
 443  
             {
 444  203786
                 if ( r.canRelocatePath( name ) )
 445  
                 {
 446  41870
                     value = prefix + r.relocatePath( name ) + suffix;
 447  41870
                     break;
 448  
                 }
 449  
             }
 450  
 
 451  203786
             return value;
 452  
         }
 453  
 
 454  
     }
 455  
 
 456  
 }