001: /***
002: * Retrotranslator: a Java bytecode transformer that translates Java classes
003: * compiled with JDK 5.0 into classes that can be run on JVM 1.4.
004: *
005: * Copyright (c) 2005 - 2008 Taras Puchko
006: * All rights reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: * 3. Neither the name of the copyright holders nor the names of its
017: * contributors may be used to endorse or promote products derived from
018: * this software without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
021: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
022: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
023: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
024: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
025: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
026: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
027: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
028: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
029: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
030: * THE POSSIBILITY OF SUCH DAMAGE.
031: */package net.sf.retrotranslator.transformer;
032:
033: import java.io.*;
034: import java.util.*;
035: import java.util.jar.*;
036: import java.util.regex.Pattern;
037: import java.util.zip.*;
038:
039: /**
040: * @author Taras Puchko
041: */
042: class JarFileContainer extends FileContainer {
043:
044: private static final String META_INF_NAME = "META-INF/";
045: private static final Pattern SIGNATURE_ENTRY = Pattern
046: .compile("META-INF/SIG-.+|META-INF/.+\\.(SF|DSA|RSA)");
047: private static final Pattern SIGNATURE_ATTRIBUTE = Pattern
048: .compile("Magic|.+-Digest(-.+)?");
049:
050: private Map<String, JarFileEntry> entries;
051: private boolean modified;
052:
053: public JarFileContainer(File location) {
054: super (location);
055: }
056:
057: public Collection<? extends FileEntry> getEntries() {
058: if (entries == null)
059: loadEntries();
060: return new ArrayList<JarFileEntry>(entries.values());
061: }
062:
063: public void removeEntry(String name) {
064: if (entries != null) {
065: entries.remove(name);
066: }
067: }
068:
069: private void loadEntries() {
070: initEntries();
071: try {
072: long lastModified = location.lastModified();
073: FileInputStream fileInputStream = new FileInputStream(
074: location);
075: try {
076: ZipInputStream stream = new ZipInputStream(
077: fileInputStream);
078: ZipEntry entry;
079: while ((entry = stream.getNextEntry()) != null) {
080: if (!entry.isDirectory()) {
081: byte[] content = readFully(stream, (int) entry
082: .getSize());
083: entries.put(entry.getName(), new JarFileEntry(
084: entry.getName(), content, lastModified,
085: false));
086: }
087: }
088: } finally {
089: fileInputStream.close();
090: }
091: } catch (IOException e) {
092: throw new RuntimeException(e);
093: }
094: }
095:
096: public void putEntry(String name, byte[] contents, boolean modified) {
097: initEntries();
098: entries
099: .put(name,
100: new JarFileEntry(name, contents, 0, modified));
101: this .modified = true;
102: }
103:
104: private void initEntries() {
105: if (entries == null) {
106: entries = new LinkedHashMap<String, JarFileEntry>();
107: }
108: }
109:
110: public void flush(SystemLogger logger) {
111: if (!modified)
112: return;
113: try {
114: FileOutputStream stream = new FileOutputStream(location);
115: try {
116: flush(stream, logger);
117: modified = false;
118: } finally {
119: stream.close();
120: }
121: } catch (IOException e) {
122: throw new RuntimeException(e);
123: }
124: }
125:
126: public boolean containsUpToDate(String name, long sourceTime) {
127: return false;
128: }
129:
130: public long lastModified() {
131: return location.lastModified();
132: }
133:
134: private void flush(FileOutputStream fileOutputStream,
135: SystemLogger logger) throws IOException {
136: Manifest manifest = getManifest();
137: boolean signatureRemoved = removeSignature(manifest);
138: List<JarFileEntry> fileEntries = getFileEntries(logger,
139: signatureRemoved);
140: Set<String> folderNames = getFolderNames(fileEntries);
141: JarOutputStream stream = new JarOutputStream(fileOutputStream);
142: stream.setLevel(Deflater.BEST_COMPRESSION);
143: stream.putNextEntry(new ZipEntry(META_INF_NAME));
144: stream.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME));
145: manifest.write(stream);
146: for (String name : folderNames) {
147: stream.putNextEntry(new ZipEntry(name));
148: }
149: for (JarFileEntry entry : fileEntries) {
150: stream.putNextEntry(new ZipEntry(entry.getName()));
151: stream.write(entry.getContent());
152: }
153: stream.close();
154: }
155:
156: private Manifest getManifest() throws IOException {
157: JarFileEntry entry = entries.get(JarFile.MANIFEST_NAME);
158: Manifest manifest = entry == null ? new Manifest()
159: : new Manifest(new ByteArrayInputStream(entry
160: .getContent()));
161: Attributes attributes = manifest.getMainAttributes();
162: attributes.putValue("Manifest-Version", "1.0");
163: attributes.putValue("Created-By", System
164: .getProperty("java.vm.version")
165: + " (" + System.getProperty("java.vm.vendor") + ")");
166: String title = getClass().getPackage().getImplementationTitle();
167: String version = getClass().getPackage()
168: .getImplementationVersion();
169: if (title != null && version != null) {
170: attributes.putValue("Retrotranslator-Version", title + " "
171: + version);
172: }
173: return manifest;
174: }
175:
176: private boolean removeSignature(Manifest manifest) {
177: boolean result = false;
178: for (Attributes attributes : manifest.getEntries().values()) {
179: Iterator<Map.Entry<Object, Object>> iterator = attributes
180: .entrySet().iterator();
181: while (iterator.hasNext()) {
182: if (SIGNATURE_ATTRIBUTE.matcher(
183: iterator.next().getKey().toString()).matches()) {
184: iterator.remove();
185: result = true;
186: }
187: }
188: }
189: return result;
190: }
191:
192: private List<JarFileEntry> getFileEntries(SystemLogger logger,
193: boolean signatureRemoved) {
194: List<JarFileEntry> result = new ArrayList<JarFileEntry>(entries
195: .values());
196: for (Iterator<JarFileEntry> iterator = result.iterator(); iterator
197: .hasNext();) {
198: JarFileEntry entry = iterator.next();
199: if (entry.getName().equals(JarFile.MANIFEST_NAME)) {
200: iterator.remove();
201: }
202: if (SIGNATURE_ENTRY.matcher(entry.getName()).matches()) {
203: iterator.remove();
204: signatureRemoved = true;
205: }
206: }
207: if (signatureRemoved) {
208: logger.log(new Message(Level.INFO,
209: "Removing digital signature from " + location,
210: location, null));
211: }
212: return result;
213: }
214:
215: private Set<String> getFolderNames(List<JarFileEntry> entries) {
216: Set<String> result = new LinkedHashSet<String>();
217: for (JarFileEntry entry : entries) {
218: String name = entry.getName();
219: int index = -1;
220: while ((index = name.indexOf('/', index + 1)) >= 0) {
221: result.add(name.substring(0, index + 1));
222: }
223: }
224: result.remove(META_INF_NAME);
225: return result;
226: }
227:
228: private static class JarFileEntry extends FileEntry {
229:
230: private byte[] content;
231: private long lastModified;
232:
233: public JarFileEntry(String name, byte[] content,
234: long lastModified, boolean modified) {
235: super (name, modified);
236: this .lastModified = lastModified;
237: this .content = content;
238: }
239:
240: public byte[] getContent() {
241: return content;
242: }
243:
244: public long lastModified() {
245: return lastModified;
246: }
247: }
248: }
|