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 java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.util.Collections;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Set;
31  import java.util.zip.ZipException;
32  
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.plugin.logging.Log;
35  import org.apache.maven.project.MavenProject;
36  import org.codehaus.plexus.util.IOUtil;
37  import org.vafer.jdependency.Clazz;
38  import org.vafer.jdependency.Clazzpath;
39  import org.vafer.jdependency.ClazzpathUnit;
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       *
66       * @since 1.6
67       */
68      @SuppressWarnings( { "unchecked" } )
69      public MinijarFilter( MavenProject project, Log log, List<SimpleFilter> simpleFilters )
70          throws IOException
71      {
72  
73          this.log = log;
74  
75          Clazzpath cp = new Clazzpath();
76  
77          ClazzpathUnit artifactUnit =
78              cp.addClazzpathUnit( new FileInputStream( project.getArtifact().getFile() ), project.toString() );
79  
80          for ( Artifact dependency : project.getArtifacts() )
81          {
82              addDependencyToClasspath( cp, dependency );
83          }
84  
85          removable = cp.getClazzes();
86          removePackages( artifactUnit );
87          removable.removeAll( artifactUnit.getClazzes() );
88          removable.removeAll( artifactUnit.getTransitiveDependencies() );
89          removeSpecificallyIncludedClasses( project, simpleFilters == null
90              ? Collections.<SimpleFilter>emptyList()
91              : simpleFilters );
92      }
93  
94      private ClazzpathUnit addDependencyToClasspath( Clazzpath cp, Artifact dependency )
95          throws IOException
96      {
97          InputStream is = null;
98          ClazzpathUnit clazzpathUnit = null;
99          try
100         {
101             is = new FileInputStream( dependency.getFile() );
102             clazzpathUnit = cp.addClazzpathUnit( is, dependency.toString() );
103         }
104         catch ( ZipException e )
105         {
106             log.warn( dependency.getFile()
107                 + " could not be unpacked/read for minimization; dependency is probably malformed." );
108             IOException ioe =
109                 new IOException( "Dependency " + dependency.toString() + " in file " + dependency.getFile()
110                     + " could not be unpacked. File is probably corrupt" );
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( dependency.toString()
118                 + " 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         Iterator it = clazzes.iterator();
139         while ( it.hasNext() )
140         {
141             Clazz clazz = (Clazz) it.next();
142             String name = clazz.getName();
143             while ( name.contains( "." ) )
144             {
145                 name = name.substring( 0, name.lastIndexOf( '.' ) );
146                 if ( packageNames.add( name ) )
147                 {
148                     removable.remove( new Clazz( name + ".package-info" ) );
149                 }
150             }
151         }
152     }
153 
154     private void removeSpecificallyIncludedClasses( MavenProject project, List<SimpleFilter> simpleFilters )
155         throws IOException
156     {
157         //remove classes specifically included in filters
158         Clazzpath checkCp = new Clazzpath();
159         for ( Artifact dependency : project.getArtifacts() )
160         {
161             File jar = dependency.getFile();
162 
163             for ( SimpleFilter simpleFilter : simpleFilters )
164             {
165                 if ( simpleFilter.canFilter( jar ) )
166                 {
167                     ClazzpathUnit depClazzpathUnit = addDependencyToClasspath( checkCp, dependency );
168                     if ( depClazzpathUnit != null )
169                     {
170                         Iterator<Clazz> j = removable.iterator();
171                         while ( j.hasNext() )
172                         {
173                             Clazz clazz = j.next();
174 
175                             if ( depClazzpathUnit.getClazzes().contains( clazz )
176                                 && simpleFilter.isSpecificallyIncluded( clazz.getName().replace( '.', '/' ) ) )
177                             {
178                                 log.info( clazz.getName() + " not removed because it was specifically included" );
179                                 j.remove();
180                             }
181                         }
182                     }
183                 }
184             }
185         }
186     }
187 
188     public boolean canFilter( File jar )
189     {
190         return true;
191     }
192 
193     public boolean isFiltered( String classFile )
194     {
195         String className = classFile.replace( '/', '.' ).replaceFirst( "\\.class$", "" );
196         Clazz clazz = new Clazz( className );
197 
198         if ( removable.contains( clazz ) )
199         {
200             log.debug( "Removing " + className );
201             classesRemoved += 1;
202             return true;
203         }
204 
205         classesKept += 1;
206         return false;
207     }
208 
209     public void finished()
210     {
211         int classesTotal = classesRemoved + classesKept;
212         log.info(
213             "Minimized " + classesTotal + " -> " + classesKept + " (" + (int) ( 100 * classesKept / classesTotal )
214                 + "%)" );
215     }
216 }