| /******************************************************************************* |
| * Copyright (c) 2007, 2010 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.artifact.optimizers.jardelta; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.util.zip.*; |
| |
| public class DeltaComputer { |
| private File target; |
| private File base; |
| private File destination; |
| private ZipFile baseJar; |
| private ZipFile targetJar; |
| private Set<String> baseEntries; |
| private ArrayList<ZipEntry> additions; |
| private ArrayList<ZipEntry> changes; |
| private ZipFile manifestJar = null; |
| |
| public DeltaComputer(File base, File target, File destination) { |
| this.base = base; |
| this.target = target; |
| this.destination = destination; |
| } |
| |
| public void run() throws IOException { |
| try { |
| if (!openJars()) |
| return; |
| computeDelta(); |
| writeDelta(); |
| } finally { |
| closeJars(); |
| } |
| } |
| |
| private void writeDelta() { |
| ZipOutputStream result = null; |
| try { |
| try { |
| result = new ZipOutputStream(new FileOutputStream(destination)); |
| // if the delta includes the manifest, be sure to write it first |
| if (manifestJar != null) |
| writeEntry(result, manifestJar.getEntry("META-INF/MANIFEST.MF"), manifestJar, true); |
| // write out the removals. These are all the entries left in the baseEntries |
| // since they were not seen in the targetJar. Here just write out an empty |
| // entry with a name that signals the delta processor to delete. |
| for (String baseEntry : baseEntries) |
| writeEntry(result, new ZipEntry(baseEntry + ".delete"), null, false); |
| // write out the additions. |
| for (ZipEntry entry : additions) |
| writeEntry(result, entry, targetJar, false); |
| // write out the changes. |
| for (ZipEntry entry : changes) |
| writeEntry(result, entry, targetJar, false); |
| } finally { |
| if (result != null) |
| result.close(); |
| } |
| } catch (IOException e) { |
| e.printStackTrace(); |
| return; |
| } |
| } |
| |
| private void writeEntry(ZipOutputStream result, ZipEntry entry, ZipFile sourceJar, boolean manifest) throws IOException { |
| if (!manifest && entry.getName().equalsIgnoreCase("META-INF/MANIFEST.MF")) |
| return; |
| // add the entry |
| result.putNextEntry(entry); |
| try { |
| // if there is a sourceJar copy over the content for the entry into the result |
| if (sourceJar != null) { |
| InputStream contents = sourceJar.getInputStream(entry); |
| try { |
| transferStreams(contents, result); |
| } finally { |
| contents.close(); |
| } |
| } |
| } finally { |
| result.closeEntry(); |
| } |
| } |
| |
| /** |
| * Transfers all available bytes from the given input stream to the given |
| * output stream. Does not close either stream. |
| * |
| * @param source |
| * @param destination |
| * @throws IOException |
| */ |
| public static void transferStreams(InputStream source, OutputStream destination) throws IOException { |
| source = new BufferedInputStream(source); |
| destination = new BufferedOutputStream(destination); |
| try { |
| byte[] buffer = new byte[8192]; |
| while (true) { |
| int bytesRead = -1; |
| if ((bytesRead = source.read(buffer)) == -1) |
| break; |
| destination.write(buffer, 0, bytesRead); |
| } |
| } finally { |
| destination.flush(); |
| } |
| } |
| |
| private void computeDelta() throws IOException { |
| changes = new ArrayList<ZipEntry>(); |
| additions = new ArrayList<ZipEntry>(); |
| // start out assuming that all the base entries are being removed |
| baseEntries = getEntries(baseJar); |
| for (Enumeration<? extends ZipEntry> e = targetJar.entries(); e.hasMoreElements();) |
| check(e.nextElement(), targetJar); |
| } |
| |
| private boolean openJars() { |
| try { |
| baseJar = new ZipFile(base); |
| targetJar = new ZipFile(target); |
| } catch (IOException e) { |
| return false; |
| } |
| return true; |
| } |
| |
| private void closeJars() { |
| if (baseJar != null) |
| try { |
| baseJar.close(); |
| } catch (IOException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| if (targetJar != null) |
| try { |
| targetJar.close(); |
| } catch (IOException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Compare the given entry against the base JAR to see if/how it differs. Update the appropriate set |
| * based on the discovered difference. |
| * @param entry the entry to test |
| * @throws IOException |
| */ |
| private void check(ZipEntry entry, ZipFile file) throws IOException { |
| ZipEntry baseEntry = baseJar.getEntry(entry.getName()); |
| |
| // remember the manifest if we see it |
| checkForManifest(entry, file); |
| // if there is no entry then this is an addition. remember the addition and return; |
| if (baseEntry == null) { |
| additions.add(entry); |
| return; |
| } |
| // now we know each JAR has an entry for the name, compare and see how/if they differ |
| boolean changed = !equals(entry, baseEntry); |
| if (changed) |
| changes.add(entry); |
| baseEntries.remove(baseEntry.getName()); |
| } |
| |
| // compare the two entries. We already know that they have the same name. |
| private boolean equals(ZipEntry entry, ZipEntry baseEntry) { |
| if (entry.getSize() != baseEntry.getSize()) |
| return false; |
| // make sure the entries are of the same type |
| if (entry.isDirectory() != baseEntry.isDirectory()) |
| return false; |
| // if the entries are files then compare the times. |
| if (!entry.isDirectory()) |
| if (entry.getTime() != baseEntry.getTime()) |
| return false; |
| return true; |
| } |
| |
| private Set<String> getEntries(ZipFile jar) { |
| HashSet<String> result = new HashSet<String>(jar.size()); |
| for (Enumeration<? extends ZipEntry> e = jar.entries(); e.hasMoreElements();) { |
| ZipEntry entry = e.nextElement(); |
| checkForManifest(entry, jar); |
| result.add(entry.getName()); |
| } |
| return result; |
| } |
| |
| /** |
| * Check to see if the given entry is the manifest. If so, remember it for use when writing |
| * the resultant JAR. |
| * @param entry |
| * @param jar |
| */ |
| private void checkForManifest(ZipEntry entry, ZipFile jar) { |
| if (entry.getName().equalsIgnoreCase("META-INF/MANIFEST.MF")) |
| manifestJar = jar; |
| } |
| } |