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      /**
59       * @param project {@link MavenProject}
60       * @param log {@link Log}
61       * @throws IOException in case of error.
62       */
63      public MinijarFilter( MavenProject project, Log log )
64          throws IOException
65      {
66          this( project, log, Collections.<SimpleFilter>emptyList() );
67      }
68  
69      /**
70       * @param project {@link MavenProject}
71       * @param log {@link Log}
72       * @param simpleFilters {@link SimpleFilter}
73       * @throws IOException in case of errors.
74       * @since 1.6
75       */
76      @SuppressWarnings( { "unchecked" } )
77      public MinijarFilter( MavenProject project, Log log, List<SimpleFilter> simpleFilters )
78          throws IOException
79      {
80  
81          this.log = log;
82  
83          Clazzpath cp = new Clazzpath();
84  
85          ClazzpathUnit artifactUnit =
86              cp.addClazzpathUnit( new FileInputStream( project.getArtifact().getFile() ), project.toString() );
87  
88          for ( Artifact dependency : project.getArtifacts() )
89          {
90              addDependencyToClasspath( cp, dependency );
91          }
92  
93          removable = cp.getClazzes();
94          removePackages( artifactUnit );
95          removable.removeAll( artifactUnit.getClazzes() );
96          removable.removeAll( artifactUnit.getTransitiveDependencies() );
97          removeSpecificallyIncludedClasses( project, simpleFilters == null ? Collections.<SimpleFilter>emptyList()
98                          : simpleFilters );
99      }
100 
101     private ClazzpathUnit addDependencyToClasspath( Clazzpath cp, Artifact dependency )
102         throws IOException
103     {
104         InputStream is = null;
105         ClazzpathUnit clazzpathUnit = null;
106         try
107         {
108             is = new FileInputStream( dependency.getFile() );
109             clazzpathUnit = cp.addClazzpathUnit( is, dependency.toString() );
110         }
111         catch ( ZipException e )
112         {
113             log.warn( dependency.getFile()
114                 + " could not be unpacked/read for minimization; dependency is probably malformed." );
115             IOException ioe =
116                 new IOException( "Dependency " + dependency.toString() + " in file " + dependency.getFile()
117                     + " could not be unpacked. File is probably corrupt" );
118             ioe.initCause( e );
119             throw ioe;
120         }
121         catch ( ArrayIndexOutOfBoundsException e )
122         {
123             // trap ArrayIndexOutOfBoundsExceptions caused by malformed dependency classes (MSHADE-107)
124             log.warn( dependency.toString()
125                 + " could not be analyzed for minimization; dependency is probably malformed." );
126         }
127         finally
128         {
129             IOUtil.close( is );
130         }
131 
132         return clazzpathUnit;
133     }
134 
135     private void removePackages( ClazzpathUnit artifactUnit )
136     {
137         Set<String> packageNames = new HashSet<String>();
138         removePackages( artifactUnit.getClazzes(), packageNames );
139         removePackages( artifactUnit.getTransitiveDependencies(), packageNames );
140     }
141 
142     @SuppressWarnings( "rawtypes" )
143     private void removePackages( Set clazzes, Set<String> packageNames )
144     {
145         for ( Object clazze : clazzes )
146         {
147             Clazz clazz = (Clazz) clazze;
148             String name = clazz.getName();
149             while ( name.contains( "." ) )
150             {
151                 name = name.substring( 0, name.lastIndexOf( '.' ) );
152                 if ( packageNames.add( name ) )
153                 {
154                     removable.remove( new Clazz( name + ".package-info" ) );
155                 }
156             }
157         }
158     }
159 
160     private void removeSpecificallyIncludedClasses( MavenProject project, List<SimpleFilter> simpleFilters )
161         throws IOException
162     {
163         // remove classes specifically included in filters
164         Clazzpath checkCp = new Clazzpath();
165         for ( Artifact dependency : project.getArtifacts() )
166         {
167             File jar = dependency.getFile();
168 
169             for ( SimpleFilter simpleFilter : simpleFilters )
170             {
171                 if ( simpleFilter.canFilter( jar ) )
172                 {
173                     ClazzpathUnit depClazzpathUnit = addDependencyToClasspath( checkCp, dependency );
174                     if ( depClazzpathUnit != null )
175                     {
176                         Iterator<Clazz> j = removable.iterator();
177                         while ( j.hasNext() )
178                         {
179                             Clazz clazz = j.next();
180 
181                             if ( depClazzpathUnit.getClazzes().contains( clazz ) //
182                                 && simpleFilter.isSpecificallyIncluded( clazz.getName().replace( '.', '/' ) ) )
183                             {
184                                 log.info( clazz.getName() + " not removed because it was specifically included" );
185                                 j.remove();
186                             }
187                         }
188                     }
189                 }
190             }
191         }
192     }
193 
194     /** {@inheritDoc} */
195     public boolean canFilter( File jar )
196     {
197         return true;
198     }
199 
200     /** {@inheritDoc} */
201     public boolean isFiltered( String classFile )
202     {
203         String className = classFile.replace( '/', '.' ).replaceFirst( "\\.class$", "" );
204         Clazz clazz = new Clazz( className );
205 
206         if ( removable.contains( clazz ) )
207         {
208             log.debug( "Removing " + className );
209             classesRemoved += 1;
210             return true;
211         }
212 
213         classesKept += 1;
214         return false;
215     }
216 
217     /** {@inheritDoc} */
218     public void finished()
219     {
220         int classesTotal = classesRemoved + classesKept;
221         if ( classesTotal == 0 )
222         {
223             log.info( "Minimized " + classesTotal + " -> " + classesKept + " (" + 100 * classesKept / classesTotal
224                 + "%)" );
225         }
226         else
227         {
228             log.info( "Minimized " + classesTotal + " -> " + classesKept );
229         }
230     }
231 }