/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.ant.taskdefs; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Jar; import org.apache.tools.zip.ZipExtraField; import org.apache.tools.zip.ZipOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; import java.util.Random; import java.util.TreeMap; /** * A variation on Jar which handles duplicate entries by only archiving the most * recent of any given path. This is done by keeping a map of paths (as shown in * the jar file) against {@link EntryInfo} objects identifying the input source * and its timestamp. Most of the actual archiving is deferred until archive * finalization, when we've decided on the actual de-duplicated set of entries. */ public class LatestTimeJar extends Jar { /** * Metadata about pending entries in the jar, for replacement if newer entries * are found. Subclasses of EntryInfo are held in the */ protected abstract class EntryInfo { protected long timestamp; protected int mode; public EntryInfo(long lastModified, int mode) { this.timestamp = lastModified; this.mode = mode; } /** * Called to actually add the entry to a given zip stream. * * @param out * @param path * @throws IOException */ public abstract void addToZip(ZipOutputStream out, String path) throws IOException; public long getLastModified() { return timestamp; } public int getMode() { return mode; } } /** * Metadata about a directory entry. */ protected class DirEntryInfo extends EntryInfo { protected File dir; protected ZipExtraField extra[]; public DirEntryInfo(File dir, long touchTime, int mode, ZipExtraField extra[]) { super(touchTime, mode); this.dir = dir; this.extra = extra; } @Override public void addToZip(ZipOutputStream out, String path) throws IOException { doZipDir(dir, out, path, mode, extra); } } /** * Metadata about a file entry. */ protected class FileEntryInfo extends EntryInfo { private File tmpFile; private File archive; public FileEntryInfo(InputStream in, long lastModified, File fromArchive, int mode) throws IOException { super(lastModified, mode); tmpFile = createTempFile("gwtjar", ""); tmpFile.deleteOnExit(); OutputStream fos = new FileOutputStream(tmpFile); int readLen = in.read(buffer); while (readLen > 0) { fos.write(buffer, 0, readLen); readLen = in.read(buffer); } fos.close(); archive = fromArchive; } @Override public void addToZip(ZipOutputStream out, String path) throws IOException { FileInputStream inStream = new FileInputStream(tmpFile); try { doZipFile(inStream, out, path, timestamp, archive, mode); tmpFile.delete(); } finally { inStream.close(); } } } /** * Used to generate temporary file names. */ private static long counter = -1; /** * Creates a temporary file. * * @param prefix the file prefix * @param suffix the file suffix * @return the new file * @throws IOException if the file cannot be created */ private static File createTempFile(String prefix, String suffix) throws IOException { if (suffix == null) { suffix = ".tmp"; } // Get the temp file directory. File tmpDir = new File(System.getProperty("java.io.tmpdir")); tmpDir.mkdirs(); // Generate a random name. if (counter == -1) { counter = new Random().nextLong(); } boolean created = false; File tmpFile; do { counter++; tmpFile = new File(tmpDir, prefix + Long.toString(counter) + suffix); if (!tmpFile.exists()) { created = tmpFile.createNewFile(); if (!created) { // If we fail the create the temp file, it must have been created by // another thread between lines 161 and 162. We re-seed to avoid // further race conditions. counter = new Random().nextLong(); } } } while (!created); // Create the file. tmpFile.createNewFile(); return tmpFile; } private byte buffer[] = new byte[16 * 1024]; private Map<String, EntryInfo> paths = new TreeMap<String, EntryInfo>(); @Override protected void finalizeZipOutputStream(ZipOutputStream out) throws IOException, BuildException { for (String path : paths.keySet()) { paths.get(path).addToZip(out, path); } super.finalizeZipOutputStream(out); } @Override protected void zipDir(File dir, @SuppressWarnings("unused") ZipOutputStream out, String path, int mode, ZipExtraField[] extra) throws IOException { long thisTouchTime = (dir == null ? 0L : dir.lastModified()); String dirName = (dir == null ? "<null>" : dir.getAbsolutePath()); if (shouldReplace(path, thisTouchTime)) { if (paths.get(path) != null) { log("Obsoleting older " + path + " with " + dirName, Project.MSG_VERBOSE); } paths.put(path, new DirEntryInfo(dir, thisTouchTime, mode, extra)); } else { log("Newer " + path + " already added, skipping " + dirName, Project.MSG_VERBOSE); } } @Override protected void zipFile(InputStream in, @SuppressWarnings("unused") ZipOutputStream out, String path, long lastModified, File fromArchive, int mode) throws IOException { String desc = (fromArchive == null ? "file" : "file from " + fromArchive.getAbsolutePath()); if (shouldReplace(path, lastModified)) { if (paths.get(path) != null) { log("Obsoleting older " + path + " with " + desc, Project.MSG_VERBOSE); } paths.put(path, new FileEntryInfo(in, lastModified, fromArchive, mode)); } else { log("Newer " + path + " already added, skipping " + desc, Project.MSG_VERBOSE); } } private void doZipDir(File dir, ZipOutputStream out, String entryName, int mode, ZipExtraField[] extra) throws IOException { super.zipDir(dir, out, entryName, mode, extra); } private void doZipFile(InputStream inStream, ZipOutputStream out, String entryName, long timestamp, File archive, int mode) throws IOException { super.zipFile(inStream, out, entryName, timestamp, archive, mode); } /** * Checks whether an entry should be replaced, by touch dates and duplicates * setting. * * @param path the path of an entry being considered * @param touchTime the lastModified of the candiate replacement * @return true if the file should be replaced */ private boolean shouldReplace(String path, long touchTime) { EntryInfo oldInfo = paths.get(path); // adding from jars, we get directories with 0L time; missing should be // earlier than that, -1L. long existingTouchTime = ((oldInfo != null) ? oldInfo.getLastModified() : -1L); return (existingTouchTime < touchTime || (existingTouchTime == touchTime && this.duplicate.equals("add"))); } }