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