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