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  
32  import org.apache.maven.artifact.Artifact;
33  import org.apache.maven.plugin.logging.Log;
34  import org.apache.maven.project.MavenProject;
35  import org.codehaus.plexus.util.IOUtil;
36  import org.vafer.jdependency.Clazz;
37  import org.vafer.jdependency.Clazzpath;
38  import org.vafer.jdependency.ClazzpathUnit;
39  
40  /**
41   * A filter that prevents the inclusion of classes not required in the final jar.
42   *
43   * @author Torsten Curdt
44   */
45  public class MinijarFilter
46      implements Filter
47  {
48  
49      private Log log;
50  
51      private Set<Clazz> removable;
52  
53      private int classesKept;
54  
55      private int classesRemoved;
56  
57      public MinijarFilter( MavenProject project, Log log )
58          throws IOException
59      {
60          this( project, log, Collections.<SimpleFilter>emptyList() );
61      }
62  
63      /**
64       *
65       * @since 1.6
66       */
67      @SuppressWarnings( { "unchecked", "rawtypes" } )
68      public MinijarFilter( MavenProject project, Log log, List<SimpleFilter> simpleFilters )
69          throws IOException
70      {
71  
72          this.log = log;
73  
74          Clazzpath cp = new Clazzpath();
75  
76          ClazzpathUnit artifactUnit =
77              cp.addClazzpathUnit( new FileInputStream( project.getArtifact().getFile() ), project.toString() );
78  
79          for ( Iterator it = project.getArtifacts().iterator(); it.hasNext(); )
80          {
81              Artifact dependency = (Artifact) it.next();
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 ) throws IOException
95      {
96          InputStream is = null;
97          ClazzpathUnit clazzpathUnit = null;
98          try
99          {
100             is = new FileInputStream( dependency.getFile() );
101             clazzpathUnit = cp.addClazzpathUnit( is, dependency.toString() );
102         }
103         catch( ArrayIndexOutOfBoundsException e )
104         {
105             //trap ArrayIndexOutOfBoundsExceptions caused by malformed dependency classes (MSHADE-107)
106             log.warn( dependency.toString() + " could not be analyzed for minimization; dependency is probably malformed." );
107         }
108         finally
109         {
110             IOUtil.close( is );
111         }
112         
113         return clazzpathUnit;
114     }
115     
116     private void removePackages( ClazzpathUnit artifactUnit )
117     {
118         Set<String> packageNames = new HashSet<String>();
119         removePackages( artifactUnit.getClazzes(), packageNames );
120         removePackages( artifactUnit.getTransitiveDependencies(), packageNames );
121     }
122 
123     @SuppressWarnings( "rawtypes" )
124     private void removePackages( Set clazzes, Set<String> packageNames )
125     {
126         Iterator it = clazzes.iterator();
127         while ( it.hasNext() )
128         {
129             Clazz clazz = (Clazz) it.next();
130             String name = clazz.getName();
131             while ( name.contains( "." ) )
132             {
133                 name = name.substring( 0, name.lastIndexOf( '.' ) );
134                 if ( packageNames.add( name ) )
135                 {
136                     removable.remove( new Clazz( name + ".package-info" ) );
137                 }
138             }
139         }
140     }
141 
142     @SuppressWarnings( "rawtypes" )
143     private void removeSpecificallyIncludedClasses( MavenProject project, List<SimpleFilter> simpleFilters )
144         throws IOException
145     {
146         //remove classes specifically included in filters
147         Clazzpath checkCp = new Clazzpath();
148         for ( Iterator it = project.getArtifacts().iterator(); it.hasNext(); )
149         {
150             Artifact dependency = (Artifact) it.next();
151             File jar = dependency.getFile();
152 
153             for ( Iterator<SimpleFilter> i = simpleFilters.iterator(); i.hasNext(); )
154             {
155                 SimpleFilter simpleFilter = i.next();
156                 if ( simpleFilter.canFilter( jar ) )
157                 {
158                     ClazzpathUnit depClazzpathUnit = addDependencyToClasspath( checkCp, dependency );
159                     if ( depClazzpathUnit != null )
160                     {
161                         Iterator<Clazz> j = removable.iterator();
162                         while ( j.hasNext() )
163                         {
164                             Clazz clazz = j.next();
165 
166                             if ( depClazzpathUnit.getClazzes().contains( clazz ) && simpleFilter.isSpecificallyIncluded(
167                                 clazz.getName().replace( '.', '/' ) ) )
168                             {
169                                 log.info( clazz.getName() + " not removed because it was specifically included" );
170                                 j.remove();
171                             }
172                         }
173                     }
174                 }
175             }
176         }
177     }
178 
179     public boolean canFilter( File jar )
180     {
181         return true;
182     }
183 
184     public boolean isFiltered( String classFile )
185     {
186         String className = classFile.replace( '/', '.' ).replaceFirst( "\\.class$", "" );
187         Clazz clazz = new Clazz( className );
188 
189         if ( removable.contains( clazz ) )
190         {
191             log.debug( "Removing " + className );
192             classesRemoved += 1;
193             return true;
194         }
195 
196         classesKept += 1;
197         return false;
198     }
199 
200     public void finished()
201     {
202         int classes_total = classesRemoved + classesKept;
203         log.info(
204             "Minimized " + classes_total + " -> " + classesKept + " (" + (int) ( 100 * classesKept / classes_total )
205                 + "%)" );
206     }
207 }