Home | History | Annotate | Download | only in proguard
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2013 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         // Check if the first of two subsequent the output jars has a filter.
     67         for (int index = 0; index < programJars.size() - 1; index++)
     68         {
     69             ClassPathEntry entry = programJars.get(index);
     70             if (entry.isOutput())
     71             {
     72                 if (entry.getFilter()    == null &&
     73                     entry.getJarFilter() == null &&
     74                     entry.getWarFilter() == null &&
     75                     entry.getEarFilter() == null &&
     76                     entry.getZipFilter() == null &&
     77                     programJars.get(index + 1).isOutput())
     78                 {
     79                     throw new IOException("The output jar [" + entry.getName() +
     80                                           "] must have a filter, or all subsequent output jars will be empty.");
     81                 }
     82             }
     83         }
     84 
     85         // Check if the output jar names are different from the input jar names.
     86         for (int outIndex = 0; outIndex < programJars.size() - 1; outIndex++)
     87         {
     88             ClassPathEntry entry = programJars.get(outIndex);
     89             if (entry.isOutput())
     90             {
     91                 for (int inIndex = 0; inIndex < programJars.size(); inIndex++)
     92                 {
     93                     ClassPathEntry otherEntry = programJars.get(inIndex);
     94 
     95                     if (!otherEntry.isOutput() &&
     96                         entry.getFile().equals(otherEntry.getFile()))
     97                     {
     98                         throw new IOException("The output jar [" + entry.getName() +
     99                                               "] must be different from all input jars.");
    100                     }
    101                 }
    102             }
    103         }
    104 
    105         // Check for potential problems with mixed-case class names on
    106         // case-insensitive file systems.
    107         if (configuration.obfuscate                          &&
    108             configuration.useMixedCaseClassNames             &&
    109             configuration.classObfuscationDictionary == null &&
    110             (configuration.note == null ||
    111              !configuration.note.isEmpty()))
    112         {
    113             String os = System.getProperty("os.name").toLowerCase();
    114             if (os.startsWith("windows") ||
    115                 os.startsWith("mac os"))
    116             {
    117                 // Go over all program class path entries.
    118                 for (int index = 0; index < programJars.size(); index++)
    119                 {
    120                     // Is it an output directory?
    121                     ClassPathEntry entry = programJars.get(index);
    122                     if (entry.isOutput() &&
    123                         !entry.isJar() &&
    124                         !entry.isWar() &&
    125                         !entry.isEar() &&
    126                         !entry.isZip())
    127                     {
    128                         System.out.println("Note: you're writing the processed class files to a directory [" + entry.getName() +"].");
    129                         System.out.println("      This will likely cause problems with obfuscated mixed-case class names.");
    130                         System.out.println("      You should consider writing the output to a jar file, or otherwise");
    131                         System.out.println("      specify '-dontusemixedcaseclassnames'.");
    132 
    133                         break;
    134                     }
    135                 }
    136             }
    137         }
    138 
    139         int firstInputIndex = 0;
    140         int lastInputIndex  = 0;
    141 
    142         // Go over all program class path entries.
    143         for (int index = 0; index < programJars.size(); index++)
    144         {
    145             // Is it an input entry?
    146             ClassPathEntry entry = programJars.get(index);
    147             if (!entry.isOutput())
    148             {
    149                 // Remember the index of the last input entry.
    150                 lastInputIndex = index;
    151             }
    152             else
    153             {
    154                 // Check if this the last output entry in a series.
    155                 int nextIndex = index + 1;
    156                 if (nextIndex == programJars.size() ||
    157                     !programJars.get(nextIndex).isOutput())
    158                 {
    159                     // Write the processed input entries to the output entries.
    160                     writeOutput(programClassPool,
    161                                 programJars,
    162                                 firstInputIndex,
    163                                 lastInputIndex + 1,
    164                                 nextIndex);
    165 
    166                     // Start with the next series of input entries.
    167                     firstInputIndex = nextIndex;
    168                 }
    169             }
    170         }
    171     }
    172 
    173 
    174     /**
    175      * Transfers the specified input jars to the specified output jars.
    176      */
    177     private void writeOutput(ClassPool programClassPool,
    178                              ClassPath classPath,
    179                              int       fromInputIndex,
    180                              int       fromOutputIndex,
    181                              int       toOutputIndex)
    182     throws IOException
    183     {
    184         try
    185         {
    186             // Construct the writer that can write jars, wars, ears, zips, and
    187             // directories, cascading over the specified output entries.
    188             DataEntryWriter writer =
    189                 DataEntryWriterFactory.createDataEntryWriter(classPath,
    190                                                              fromOutputIndex,
    191                                                              toOutputIndex);
    192 
    193             // The writer will be used to write possibly obfuscated class files.
    194             DataEntryReader classRewriter =
    195                 new ClassRewriter(programClassPool, writer);
    196 
    197             // The writer will also be used to write resource files.
    198             DataEntryReader resourceCopier =
    199                 new DataEntryCopier(writer);
    200 
    201             DataEntryReader resourceRewriter = resourceCopier;
    202 
    203             // Wrap the resource writer with a filter and a data entry rewriter,
    204             // if required.
    205             if (configuration.adaptResourceFileContents != null)
    206             {
    207                 resourceRewriter =
    208                     new NameFilter(configuration.adaptResourceFileContents,
    209                     new NameFilter("META-INF/MANIFEST.MF,META-INF/*.SF",
    210                         new ManifestRewriter(programClassPool, writer),
    211                         new DataEntryRewriter(programClassPool, writer)),
    212                     resourceRewriter);
    213             }
    214 
    215             // Wrap the resource writer with a filter and a data entry renamer,
    216             // if required.
    217             if (configuration.adaptResourceFileNames != null)
    218             {
    219                 Map packagePrefixMap = createPackagePrefixMap(programClassPool);
    220 
    221                 resourceRewriter =
    222                     new NameFilter(configuration.adaptResourceFileNames,
    223                     new DataEntryObfuscator(programClassPool,
    224                                             packagePrefixMap,
    225                                             resourceRewriter),
    226                     resourceRewriter);
    227             }
    228 
    229             DataEntryReader directoryRewriter = null;
    230 
    231             // Wrap the directory writer with a filter and a data entry renamer,
    232             // if required.
    233             if (configuration.keepDirectories != null)
    234             {
    235                 Map packagePrefixMap = createPackagePrefixMap(programClassPool);
    236 
    237                 directoryRewriter =
    238                     new NameFilter(configuration.keepDirectories,
    239                     new DataEntryRenamer(packagePrefixMap,
    240                                          resourceCopier,
    241                                          resourceCopier));
    242             }
    243 
    244             // Create the reader that can write class files and copy directories
    245             // and resource files to the main writer.
    246             DataEntryReader reader =
    247                 new ClassFilter(    classRewriter,
    248                 new DirectoryFilter(directoryRewriter,
    249                                     resourceRewriter));
    250 
    251             // Go over the specified input entries and write their processed
    252             // versions.
    253             new InputReader(configuration).readInput("  Copying resources from program ",
    254                                                      classPath,
    255                                                      fromInputIndex,
    256                                                      fromOutputIndex,
    257                                                      reader);
    258 
    259             // Close all output entries.
    260             writer.close();
    261         }
    262         catch (IOException ex)
    263         {
    264             throw (IOException)new IOException("Can't write [" + classPath.get(fromOutputIndex).getName() + "] (" + ex.getMessage() + ")").initCause(ex);
    265         }
    266     }
    267 
    268 
    269     /**
    270      * Creates a map of old package prefixes to new package prefixes, based on
    271      * the given class pool.
    272      */
    273     private static Map createPackagePrefixMap(ClassPool classPool)
    274     {
    275         Map packagePrefixMap = new HashMap();
    276 
    277         Iterator iterator = classPool.classNames();
    278         while (iterator.hasNext())
    279         {
    280             String className     = (String)iterator.next();
    281             String packagePrefix = ClassUtil.internalPackagePrefix(className);
    282 
    283             String mappedNewPackagePrefix = (String)packagePrefixMap.get(packagePrefix);
    284             if (mappedNewPackagePrefix == null ||
    285                 !mappedNewPackagePrefix.equals(packagePrefix))
    286             {
    287                 String newClassName     = classPool.getClass(className).getName();
    288                 String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName);
    289 
    290                 packagePrefixMap.put(packagePrefix, newPackagePrefix);
    291             }
    292         }
    293 
    294         return packagePrefixMap;
    295     }
    296 }
    297