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