Home | History | Annotate | Download | only in proguard
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2009 Eric Lafortune (eric (at) graphics.cornell.edu)
      6  *
      7  * This program is free software; you can redistribute it and/or modify it
      8  * under the terms of the GNU General Public License as published by the Free
      9  * Software Foundation; either version 2 of the License, or (at your option)
     10  * any later version.
     11  *
     12  * This program is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
     15  * more details.
     16  *
     17  * You should have received a copy of the GNU General Public License along
     18  * with this program; if not, write to the Free Software Foundation, Inc.,
     19  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
     20  */
     21 package proguard;
     22 
     23 import proguard.classfile.ClassPool;
     24 import proguard.classfile.util.ClassUtil;
     25 import proguard.io.*;
     26 
     27 import java.io.IOException;
     28 import java.util.*;
     29 
     30 /**
     31  * This class writes the output class files.
     32  *
     33  * @author Eric Lafortune
     34  */
     35 public class OutputWriter
     36 {
     37     private final Configuration configuration;
     38 
     39 
     40     /**
     41      * Creates a new OutputWriter to write output class files as specified by
     42      * the given configuration.
     43      */
     44     public OutputWriter(Configuration configuration)
     45     {
     46         this.configuration = configuration;
     47     }
     48 
     49 
     50     /**
     51      * Writes the given class pool to class files, based on the current
     52      * configuration.
     53      */
     54     public void execute(ClassPool programClassPool) throws IOException
     55     {
     56         ClassPath programJars = configuration.programJars;
     57 
     58         // Perform a check on the first jar.
     59         ClassPathEntry firstEntry = programJars.get(0);
     60         if (firstEntry.isOutput())
     61         {
     62             throw new IOException("The output jar [" + firstEntry.getName() +
     63                                   "] must be specified after an input jar, or it will be empty.");
     64         }
     65 
     66         // Perform some checks on the output jars.
     67         for (int index = 0; index < programJars.size() - 1; index++)
     68         {
     69             ClassPathEntry entry = programJars.get(index);
     70             if (entry.isOutput())
     71             {
     72                 // Check if all but the last output jars have filters.
     73                 if (entry.getFilter()    == null &&
     74                     entry.getJarFilter() == null &&
     75                     entry.getWarFilter() == null &&
     76                     entry.getEarFilter() == null &&
     77                     entry.getZipFilter() == null &&
     78                     programJars.get(index + 1).isOutput())
     79                 {
     80                     throw new IOException("The output jar [" + entry.getName() +
     81                                           "] must have a filter, or all subsequent jars will be empty.");
     82                 }
     83 
     84                 // Check if the output jar name is different from the input jar names.
     85                 for (int inIndex = 0; inIndex < programJars.size(); inIndex++)
     86                 {
     87                     ClassPathEntry otherEntry = programJars.get(inIndex);
     88 
     89                     if (!otherEntry.isOutput() &&
     90                         entry.getFile().equals(otherEntry.getFile()))
     91                     {
     92                         throw new IOException("The output jar [" + entry.getName() +
     93                                               "] must be different from all input jars.");
     94                     }
     95                 }
     96             }
     97         }
     98 
     99         int firstInputIndex = 0;
    100         int lastInputIndex  = 0;
    101 
    102         // Go over all program class path entries.
    103         for (int index = 0; index < programJars.size(); index++)
    104         {
    105             // Is it an input entry?
    106             ClassPathEntry entry = programJars.get(index);
    107             if (!entry.isOutput())
    108             {
    109                 // Remember the index of the last input entry.
    110                 lastInputIndex = index;
    111             }
    112             else
    113             {
    114                 // Check if this the last output entry in a series.
    115                 int nextIndex = index + 1;
    116                 if (nextIndex == programJars.size() ||
    117                     !programJars.get(nextIndex).isOutput())
    118                 {
    119                     // Write the processed input entries to the output entries.
    120                     writeOutput(programClassPool,
    121                                 programJars,
    122                                 firstInputIndex,
    123                                 lastInputIndex + 1,
    124                                 nextIndex);
    125 
    126                     // Start with the next series of input entries.
    127                     firstInputIndex = nextIndex;
    128                 }
    129             }
    130         }
    131     }
    132 
    133 
    134     /**
    135      * Transfers the specified input jars to the specified output jars.
    136      */
    137     private void writeOutput(ClassPool programClassPool,
    138                              ClassPath classPath,
    139                              int       fromInputIndex,
    140                              int       fromOutputIndex,
    141                              int       toOutputIndex)
    142     throws IOException
    143     {
    144         try
    145         {
    146             // Construct the writer that can write jars, wars, ears, zips, and
    147             // directories, cascading over the specified output entries.
    148             DataEntryWriter writer =
    149                 DataEntryWriterFactory.createDataEntryWriter(classPath,
    150                                                              fromOutputIndex,
    151                                                              toOutputIndex);
    152 
    153             // The writer will be used to write possibly obfuscated class files.
    154             DataEntryReader classRewriter =
    155                 new ClassRewriter(programClassPool, writer);
    156 
    157             // The writer will also be used to write resource files.
    158             DataEntryReader resourceCopier =
    159                 new DataEntryCopier(writer);
    160 
    161             DataEntryReader resourceRewriter = resourceCopier;
    162 
    163             // Wrap the resource writer with a filter and a data entry rewriter,
    164             // if required.
    165             if (configuration.adaptResourceFileContents != null)
    166             {
    167                 resourceRewriter =
    168                     new NameFilter(configuration.adaptResourceFileContents,
    169                     new NameFilter("META-INF/**",
    170                         new ManifestRewriter(programClassPool, writer),
    171                         new DataEntryRewriter(programClassPool, writer)),
    172                     resourceRewriter);
    173             }
    174 
    175             // Wrap the resource writer with a filter and a data entry renamer,
    176             // if required.
    177             if (configuration.adaptResourceFileNames != null)
    178             {
    179                 Map packagePrefixMap = createPackagePrefixMap(programClassPool);
    180 
    181                 resourceRewriter =
    182                     new NameFilter(configuration.adaptResourceFileNames,
    183                     new DataEntryObfuscator(programClassPool,
    184                                             packagePrefixMap,
    185                                             resourceRewriter),
    186                     resourceRewriter);
    187             }
    188 
    189             DataEntryReader directoryRewriter = null;
    190 
    191             // Wrap the directory writer with a filter and a data entry renamer,
    192             // if required.
    193             if (configuration.keepDirectories != null)
    194             {
    195                 Map packagePrefixMap = createPackagePrefixMap(programClassPool);
    196 
    197                 directoryRewriter =
    198                     new NameFilter(configuration.keepDirectories,
    199                     new DataEntryRenamer(packagePrefixMap,
    200                                          resourceCopier,
    201                                          resourceCopier));
    202             }
    203 
    204             // Create the reader that can write class files and copy directories
    205             // and resource files to the main writer.
    206             DataEntryReader reader =
    207                 new ClassFilter(    classRewriter,
    208                 new DirectoryFilter(directoryRewriter,
    209                                     resourceRewriter));
    210 
    211             // Go over the specified input entries and write their processed
    212             // versions.
    213             new InputReader(configuration).readInput("  Copying resources from program ",
    214                                                      classPath,
    215                                                      fromInputIndex,
    216                                                      fromOutputIndex,
    217                                                      reader);
    218 
    219             // Close all output entries.
    220             writer.close();
    221         }
    222         catch (IOException ex)
    223         {
    224             throw new IOException("Can't write [" + classPath.get(fromOutputIndex).getName() + "] (" + ex.getMessage() + ")");
    225         }
    226     }
    227 
    228 
    229     /**
    230      * Creates a map of old package prefixes to new package prefixes, based on
    231      * the given class pool.
    232      */
    233     private static Map createPackagePrefixMap(ClassPool classPool)
    234     {
    235         Map PackagePrefixMap = new HashMap();
    236 
    237         Iterator iterator = classPool.classNames();
    238         while (iterator.hasNext())
    239         {
    240             String className     = (String)iterator.next();
    241             String PackagePrefix = ClassUtil.internalPackagePrefix(className);
    242 
    243             String mappedNewPackagePrefix = (String)PackagePrefixMap.get(PackagePrefix);
    244             if (mappedNewPackagePrefix == null ||
    245                 !mappedNewPackagePrefix.equals(PackagePrefix))
    246             {
    247                 String newClassName     = classPool.getClass(className).getName();
    248                 String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName);
    249 
    250                 PackagePrefixMap.put(PackagePrefix, newPackagePrefix);
    251             }
    252         }
    253 
    254         return PackagePrefixMap;
    255     }
    256 }
    257