View Javadoc
1   package org.apache.maven.plugins.shade.filter;
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.artifact.Artifact;
23  import org.apache.maven.plugin.logging.Log;
24  import org.apache.maven.project.MavenProject;
25  import org.codehaus.plexus.util.IOUtil;
26  import org.vafer.jdependency.Clazz;
27  import org.vafer.jdependency.Clazzpath;
28  import org.vafer.jdependency.ClazzpathUnit;
29  
30  import java.io.File;
31  import java.io.FileInputStream;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.util.Collections;
35  import java.util.HashSet;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Set;
39  import java.util.zip.ZipException;
40  
41  /**
42   * A filter that prevents the inclusion of classes not required in the final jar.
43   *
44   * @author Torsten Curdt
45   */
46  public class MinijarFilter
47      implements Filter
48  {
49  
50      private Log log;
51  
52      private Set<Clazz> removable;
53  
54      private int classesKept;
55  
56      private int classesRemoved;
57  
58      public MinijarFilter( MavenProject project, Log log )
59          throws IOException
60      {
61          this( project, log, Collections.<SimpleFilter>emptyList() );
62      }
63  
64      /**
65       * @since 1.6
66       */
67      @SuppressWarnings({ "unchecked" })
68      public MinijarFilter( MavenProject project, Log log, List<SimpleFilter> simpleFilters )
69          throws IOException
70      {
71  
72          this.log = log;
73  
74          Clazzpath cp = new Clazzpath();
75  
76          ClazzpathUnit artifactUnit =
77              cp.addClazzpathUnit( new FileInputStream( project.getArtifact().getFile() ), project.toString() );
78  
79          for ( Artifact dependency : project.getArtifacts() )
80          {
81              addDependencyToClasspath( cp, dependency );
82          }
83  
84          removable = cp.getClazzes();
85          removePackages( artifactUnit );
86          removable.removeAll( artifactUnit.getClazzes() );
87          removable.removeAll( artifactUnit.getTransitiveDependencies() );
88          removeSpecificallyIncludedClasses( project, simpleFilters == null
89              ? Collections.<SimpleFilter>emptyList()
90              : simpleFilters );
91      }
92  
93      private ClazzpathUnit addDependencyToClasspath( Clazzpath cp, Artifact dependency )
94          throws IOException
95      {
96          InputStream is = null;
97          ClazzpathUnit clazzpathUnit = null;
98          try
99          {
100             is = new FileInputStream( dependency.getFile() );
101             clazzpathUnit = cp.addClazzpathUnit( is, dependency.toString() );
102         }
103         catch ( ZipException e )
104         {
105             log.warn( dependency.getFile()
106                           + " could not be unpacked/read for minimization; dependency is probably malformed." );
107             IOException ioe = new IOException(
108                 "Dependency " + dependency.toString() + " in file " + dependency.getFile()
109                     + " could not be unpacked. File is probably corrupt"
110             );
111             ioe.initCause( e );
112             throw ioe;
113         }
114         catch ( ArrayIndexOutOfBoundsException e )
115         {
116             //trap ArrayIndexOutOfBoundsExceptions caused by malformed dependency classes (MSHADE-107)
117             log.warn(
118                 dependency.toString() + " could not be analyzed for minimization; dependency is probably malformed." );
119         }
120         finally
121         {
122             IOUtil.close( is );
123         }
124 
125         return clazzpathUnit;
126     }
127 
128     private void removePackages( ClazzpathUnit artifactUnit )
129     {
130         Set<String> packageNames = new HashSet<String>();
131         removePackages( artifactUnit.getClazzes(), packageNames );
132         removePackages( artifactUnit.getTransitiveDependencies(), packageNames );
133     }
134 
135     @SuppressWarnings("rawtypes")
136     private void removePackages( Set clazzes, Set<String> packageNames )
137     {
138         for ( Object clazze : clazzes )
139         {
140             Clazz clazz = (Clazz) clazze;
141             String name = clazz.getName();
142             while ( name.contains( "." ) )
143             {
144                 name = name.substring( 0, name.lastIndexOf( '.' ) );
145                 if ( packageNames.add( name ) )
146                 {
147                     removable.remove( new Clazz( name + ".package-info" ) );
148                 }
149             }
150         }
151     }
152 
153     private void removeSpecificallyIncludedClasses( MavenProject project, List<SimpleFilter> simpleFilters )
154         throws IOException
155     {
156         //remove classes specifically included in filters
157         Clazzpath checkCp = new Clazzpath();
158         for ( Artifact dependency : project.getArtifacts() )
159         {
160             File jar = dependency.getFile();
161 
162             for ( SimpleFilter simpleFilter : simpleFilters )
163             {
164                 if ( simpleFilter.canFilter( jar ) )
165                 {
166                     ClazzpathUnit depClazzpathUnit = addDependencyToClasspath( checkCp, dependency );
167                     if ( depClazzpathUnit != null )
168                     {
169                         Iterator<Clazz> j = removable.iterator();
170                         while ( j.hasNext() )
171                         {
172                             Clazz clazz = j.next();
173 
174                             if ( depClazzpathUnit.getClazzes().contains( clazz ) //
175                                 && simpleFilter.isSpecificallyIncluded( clazz.getName().replace( '.', '/' ) ) )
176                             {
177                                 log.info( clazz.getName() + " not removed because it was specifically included" );
178                                 j.remove();
179                             }
180                         }
181                     }
182                 }
183             }
184         }
185     }
186 
187     public boolean canFilter( File jar )
188     {
189         return true;
190     }
191 
192     public boolean isFiltered( String classFile )
193     {
194         String className = classFile.replace( '/', '.' ).replaceFirst( "\\.class$", "" );
195         Clazz clazz = new Clazz( className );
196 
197         if ( removable.contains( clazz ) )
198         {
199             log.debug( "Removing " + className );
200             classesRemoved += 1;
201             return true;
202         }
203 
204         classesKept += 1;
205         return false;
206     }
207 
208     public void finished()
209     {
210         int classesTotal = classesRemoved + classesKept;
211         log.info( "Minimized " + classesTotal + " -> " + classesKept + " (" + 100 * classesKept / classesTotal + "%)" );
212     }
213 }