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