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