/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2010 Eric Lafortune (eric@graphics.cornell.edu) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard; import proguard.classfile.ClassPool; import proguard.classfile.util.ClassUtil; import proguard.io.*; import java.io.IOException; import java.util.*; /** * This class writes the output class files. * * @author Eric Lafortune */ public class OutputWriter { private final Configuration configuration; /** * Creates a new OutputWriter to write output class files as specified by * the given configuration. */ public OutputWriter(Configuration configuration) { this.configuration = configuration; } /** * Writes the given class pool to class files, based on the current * configuration. */ public void execute(ClassPool programClassPool) throws IOException { ClassPath programJars = configuration.programJars; // Perform a check on the first jar. ClassPathEntry firstEntry = programJars.get(0); if (firstEntry.isOutput()) { throw new IOException("The output jar [" + firstEntry.getName() + "] must be specified after an input jar, or it will be empty."); } // Check if the first of two subsequent the output jars has a filter. for (int index = 0; index < programJars.size() - 1; index++) { ClassPathEntry entry = programJars.get(index); if (entry.isOutput()) { if (entry.getFilter() == null && entry.getJarFilter() == null && entry.getWarFilter() == null && entry.getEarFilter() == null && entry.getZipFilter() == null && programJars.get(index + 1).isOutput()) { throw new IOException("The output jar [" + entry.getName() + "] must have a filter, or all subsequent output jars will be empty."); } } } // Check if the output jar names are different from the input jar names. for (int outIndex = 0; outIndex < programJars.size() - 1; outIndex++) { ClassPathEntry entry = programJars.get(outIndex); if (entry.isOutput()) { for (int inIndex = 0; inIndex < programJars.size(); inIndex++) { ClassPathEntry otherEntry = programJars.get(inIndex); if (!otherEntry.isOutput() && entry.getFile().equals(otherEntry.getFile())) { throw new IOException("The output jar [" + entry.getName() + "] must be different from all input jars."); } } } } int firstInputIndex = 0; int lastInputIndex = 0; // Go over all program class path entries. for (int index = 0; index < programJars.size(); index++) { // Is it an input entry? ClassPathEntry entry = programJars.get(index); if (!entry.isOutput()) { // Remember the index of the last input entry. lastInputIndex = index; } else { // Check if this the last output entry in a series. int nextIndex = index + 1; if (nextIndex == programJars.size() || !programJars.get(nextIndex).isOutput()) { // Write the processed input entries to the output entries. writeOutput(programClassPool, programJars, firstInputIndex, lastInputIndex + 1, nextIndex); // Start with the next series of input entries. firstInputIndex = nextIndex; } } } } /** * Transfers the specified input jars to the specified output jars. */ private void writeOutput(ClassPool programClassPool, ClassPath classPath, int fromInputIndex, int fromOutputIndex, int toOutputIndex) throws IOException { try { // Construct the writer that can write jars, wars, ears, zips, and // directories, cascading over the specified output entries. DataEntryWriter writer = DataEntryWriterFactory.createDataEntryWriter(classPath, fromOutputIndex, toOutputIndex); // The writer will be used to write possibly obfuscated class files. DataEntryReader classRewriter = new ClassRewriter(programClassPool, writer); // The writer will also be used to write resource files. DataEntryReader resourceCopier = new DataEntryCopier(writer); DataEntryReader resourceRewriter = resourceCopier; // Wrap the resource writer with a filter and a data entry rewriter, // if required. if (configuration.adaptResourceFileContents != null) { resourceRewriter = new NameFilter(configuration.adaptResourceFileContents, new NameFilter("META-INF/*", new ManifestRewriter(programClassPool, writer), new DataEntryRewriter(programClassPool, writer)), resourceRewriter); } // Wrap the resource writer with a filter and a data entry renamer, // if required. if (configuration.adaptResourceFileNames != null) { Map packagePrefixMap = createPackagePrefixMap(programClassPool); resourceRewriter = new NameFilter(configuration.adaptResourceFileNames, new DataEntryObfuscator(programClassPool, packagePrefixMap, resourceRewriter), resourceRewriter); } DataEntryReader directoryRewriter = null; // Wrap the directory writer with a filter and a data entry renamer, // if required. if (configuration.keepDirectories != null) { Map packagePrefixMap = createPackagePrefixMap(programClassPool); directoryRewriter = new NameFilter(configuration.keepDirectories, new DataEntryRenamer(packagePrefixMap, resourceCopier, resourceCopier)); } // Create the reader that can write class files and copy directories // and resource files to the main writer. DataEntryReader reader = new ClassFilter( classRewriter, new DirectoryFilter(directoryRewriter, resourceRewriter)); // Go over the specified input entries and write their processed // versions. new InputReader(configuration).readInput(" Copying resources from program ", classPath, fromInputIndex, fromOutputIndex, reader); // Close all output entries. writer.close(); } catch (IOException ex) { throw new IOException("Can't write [" + classPath.get(fromOutputIndex).getName() + "] (" + ex.getMessage() + ")"); } } /** * Creates a map of old package prefixes to new package prefixes, based on * the given class pool. */ private static Map createPackagePrefixMap(ClassPool classPool) { Map packagePrefixMap = new HashMap(); Iterator iterator = classPool.classNames(); while (iterator.hasNext()) { String className = (String)iterator.next(); String packagePrefix = ClassUtil.internalPackagePrefix(className); String mappedNewPackagePrefix = (String)packagePrefixMap.get(packagePrefix); if (mappedNewPackagePrefix == null || !mappedNewPackagePrefix.equals(packagePrefix)) { String newClassName = classPool.getClass(className).getName(); String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName); packagePrefixMap.put(packagePrefix, newPackagePrefix); } } return packagePrefixMap; } }