/* * @(#)JarDiffPatcher.java 1.7 05/11/17 * * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * -Redistribution of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * -Redistribution in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed, licensed or intended * for use in the design, construction, operation or maintenance of any * nuclear facility. */ package jnlp.sample.jardiff; import java.io.*; import java.util.*; import java.util.jar.*; import java.util.zip.*; /** * JarDiff is able to create a jar file containing the delta between two * jar files (old and new). The delta jar file can then be applied to the * old jar file to reconstruct the new jar file. * <p> * Refer to the JNLP spec for details on how this is done. * * @version 1.11, 06/26/03 */ public class JarDiffPatcher implements JarDiffConstants, Patcher { private static final int DEFAULT_READ_SIZE = 2048; private static byte[] newBytes = new byte[DEFAULT_READ_SIZE]; private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE]; private static ResourceBundle _resources = JarDiff.getResources(); public static ResourceBundle getResources() { return JarDiff.getResources(); } public void applyPatch(Patcher.PatchDelegate delegate, String oldJarPath, String jarDiffPath, OutputStream result) throws IOException { File oldFile = new File(oldJarPath); File diffFile = new File(jarDiffPath); JarOutputStream jos = new JarOutputStream(result); JarFile oldJar = new JarFile(oldFile); JarFile jarDiff = new JarFile(diffFile); Set ignoreSet = new HashSet(); Map renameMap = new HashMap(); determineNameMapping(jarDiff, ignoreSet, renameMap); // get all keys in renameMap Object[] keys = renameMap.keySet().toArray(); // Files to implicit move Set oldjarNames = new HashSet(); Enumeration oldEntries = oldJar.entries(); if (oldEntries != null) { while (oldEntries.hasMoreElements()) { oldjarNames.add(((JarEntry)oldEntries.nextElement()).getName()); } } // size depends on the three parameters below, which is // basically the counter for each loop that do the actual // writes to the output file // since oldjarNames.size() changes in the first two loop // below, we need to adjust the size accordingly also when // oldjarNames.size() changes double size = oldjarNames.size() + keys.length + jarDiff.size(); double currentEntry = 0; // Handle all remove commands oldjarNames.removeAll(ignoreSet); size -= ignoreSet.size(); // Add content from JARDiff Enumeration entries = jarDiff.entries(); if (entries != null) { while (entries.hasMoreElements()) { JarEntry entry = (JarEntry)entries.nextElement(); if (!INDEX_NAME.equals(entry.getName())) { updateDelegate(delegate, currentEntry, size); currentEntry++; writeEntry(jos, entry, jarDiff); // Remove entry from oldjarNames since no implicit //move is needed boolean wasInOld = oldjarNames.remove(entry.getName()); // Update progress counters. If it was in old, we do // not need an implicit move, so adjust total size. if (wasInOld) size--; } else { // no write is done, decrement size size--; } } } // go through the renameMap and apply move for each entry for (int j = 0; j < keys.length; j++) { // Apply move <oldName> <newName> command String newName = (String)keys[j]; String oldName = (String)renameMap.get(newName); // Get source JarEntry JarEntry oldEntry = oldJar.getJarEntry(oldName); if (oldEntry == null) { String moveCmd = MOVE_COMMAND + oldName + " " + newName; handleException("jardiff.error.badmove", moveCmd); } // Create dest JarEntry JarEntry newEntry = new JarEntry(newName); newEntry.setTime(oldEntry.getTime()); newEntry.setSize(oldEntry.getSize()); newEntry.setCompressedSize(oldEntry.getCompressedSize()); newEntry.setCrc(oldEntry.getCrc()); newEntry.setMethod(oldEntry.getMethod()); newEntry.setExtra(oldEntry.getExtra()); newEntry.setComment(oldEntry.getComment()); updateDelegate(delegate, currentEntry, size); currentEntry++; writeEntry(jos, newEntry, oldJar.getInputStream(oldEntry)); // Remove entry from oldjarNames since no implicit //move is needed boolean wasInOld = oldjarNames.remove(oldName); // Update progress counters. If it was in old, we do // not need an implicit move, so adjust total size. if (wasInOld) size--; } // implicit move Iterator iEntries = oldjarNames.iterator(); if (iEntries != null) { while (iEntries.hasNext()) { String name = (String)iEntries.next(); JarEntry entry = oldJar.getJarEntry(name); updateDelegate(delegate, currentEntry, size); currentEntry++; writeEntry(jos, entry, oldJar); } } updateDelegate(delegate, currentEntry, size); jos.finish(); } private void updateDelegate(Patcher.PatchDelegate delegate, double currentSize, double size) { if (delegate != null) { delegate.patching((int)(currentSize/size)); } } private void determineNameMapping(JarFile jarDiff, Set ignoreSet, Map renameMap) throws IOException { InputStream is = jarDiff.getInputStream(jarDiff.getEntry(INDEX_NAME)); if (is == null) { handleException("jardiff.error.noindex", null); } LineNumberReader indexReader = new LineNumberReader(new InputStreamReader(is, "UTF-8")); String line = indexReader.readLine(); if (line == null || !line.equals(VERSION_HEADER)) { handleException("jardiff.error.badheader", line); } while ((line = indexReader.readLine()) != null) { if (line.startsWith(REMOVE_COMMAND)) { List sub = getSubpaths(line.substring(REMOVE_COMMAND.length())); if (sub.size() != 1) { handleException("jardiff.error.badremove", line); } ignoreSet.add(sub.get(0)); } else if (line.startsWith(MOVE_COMMAND)) { List sub = getSubpaths(line.substring(MOVE_COMMAND.length())); if (sub.size() != 2) { handleException("jardiff.error.badmove", line); } // target of move should be the key if (renameMap.put(sub.get(1), sub.get(0)) != null) { // invalid move - should not move to same target twice handleException("jardiff.error.badmove", line); } } else if (line.length() > 0) { handleException("jardiff.error.badcommand", line); } } } private void handleException(String errorMsg, String line) throws IOException { try { throw new IOException(getResources().getString(errorMsg) + " " + line); } catch (MissingResourceException mre) { System.err.println("Fatal error: " + errorMsg); new Throwable().printStackTrace(System.err); System.exit(-1); } } private List getSubpaths(String path) { int index = 0; int length = path.length(); ArrayList sub = new ArrayList(); while (index < length) { while (index < length && Character.isWhitespace(path.charAt(index))) { index++; } if (index < length) { int start = index; int last = start; String subString = null; while (index < length) { char aChar = path.charAt(index); if (aChar == '\\' && (index + 1) < length && path.charAt(index + 1) == ' ') { if (subString == null) { subString = path.substring(last, index); } else { subString += path.substring(last, index); } last = ++index; } else if (Character.isWhitespace(aChar)) { break; } index++; } if (last != index) { if (subString == null) { subString = path.substring(last, index); } else { subString += path.substring(last, index); } } sub.add(subString); } } return sub; } private void writeEntry(JarOutputStream jos, JarEntry entry, JarFile file) throws IOException { writeEntry(jos, entry, file.getInputStream(entry)); } private void writeEntry(JarOutputStream jos, JarEntry entry, InputStream data) throws IOException { //Create a new ZipEntry to clear the compressed size. 5079423 jos.putNextEntry(new ZipEntry(entry.getName())); // Read the entry int size = data.read(newBytes); while (size != -1) { jos.write(newBytes, 0, size); size = data.read(newBytes); } data.close(); } }