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