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