/** * Copyright (c) 2011 Cloudsmith Inc. and other contributors, as listed below. * 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: * Cloudsmith * */ package org.cloudsmith.geppetto.forge.util; import static org.cloudsmith.geppetto.forge.Forge.METADATA_JSON_NAME; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.cloudsmith.geppetto.common.os.FileUtils; import org.cloudsmith.geppetto.common.os.StreamUtil; /** * Utilities for computing MD5 checksums on files. */ public class Checksums { private static final char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; public static void appendChangedFiles(Map<String, byte[]> checksums, File file, List<File> result, FileFilter exclusionFilter) throws IOException { appendChangedFiles( checksums, file, getMessageDigest(), file.getAbsolutePath().length() + 1, result, exclusionFilter); } private static void appendChangedFiles(Map<String, byte[]> checksums, File file, MessageDigest md, int baseDirLen, List<File> result, FileFilter exclusionFilter) throws IOException { if(!isChecksumCandidate(file, exclusionFilter)) return; File[] children = file.listFiles(); if(children != null) { for(File child : children) appendChangedFiles(checksums, child, md, baseDirLen, result, exclusionFilter); return; } byte[] oldChecksum = checksums.get(file.getAbsolutePath().substring(baseDirLen)); if(oldChecksum == null) result.add(file); else { byte[] newChecksum = computeChecksum(file, md); if(!Arrays.equals(oldChecksum, newChecksum)) result.add(file); } } public static void appendHex(StringBuilder bld, byte b) { bld.append(hexChars[(b & 0xf0) >> 4]); bld.append(hexChars[b & 0x0f]); } public static void appendSHA1(StringBuilder bld, String value) { try { MessageDigest md = MessageDigest.getInstance("SHA1"); byte[] digest = md.digest(value.getBytes("UTF-8")); for(int idx = 0; idx < digest.length; ++idx) appendHex(bld, digest[idx]); } catch(UnsupportedEncodingException e) { throw new RuntimeException(e); } catch(NoSuchAlgorithmException e) { throw new RuntimeException(e); } } public static byte[] computeChecksum(File file, MessageDigest md) throws IOException { InputStream input = new FileInputStream(file); md.reset(); try { byte[] buf = new byte[0x1000]; int cnt; while((cnt = input.read(buf)) > 0) md.update(buf, 0, cnt); } finally { StreamUtil.close(input); } return md.digest(); } /** * Returns the hexadecimal SHA1 representation of the argument * * @param value * The value to compute the digest for * @return The SHA1 hex string */ public static String createSHA1(String value) { StringBuilder bld = new StringBuilder(); appendSHA1(bld, value); return bld.toString(); } private static MessageDigest getMessageDigest() { try { return MessageDigest.getInstance("MD5"); } catch(NoSuchAlgorithmException e) { throw new RuntimeException(e); } } private static boolean isChecksumCandidate(File file, FileFilter filter) throws IOException { String filename = file.getName(); if(METADATA_JSON_NAME.equals(filename) || "REVISION".equals(filename)) return false; return filter.accept(file) && !FileUtils.isSymlink(file); } public static Map<String, byte[]> loadChecksums(File moduleDir, FileFilter exclusionFilter) throws IOException { if(exclusionFilter == null) exclusionFilter = ModuleUtils.DEFAULT_FILE_FILTER; Map<String, byte[]> checksums = new TreeMap<String, byte[]>(); loadChecksums( checksums, getMessageDigest(), moduleDir, moduleDir.getAbsolutePath().length() + 1, exclusionFilter); return checksums; } private static void loadChecksums(Map<String, byte[]> checksums, MessageDigest md, File file, int basedirLen, FileFilter exclusionFilter) throws IOException { if(!isChecksumCandidate(file, exclusionFilter)) return; File[] children = file.listFiles(); if(children == null) checksums.put(file.getAbsolutePath().substring(basedirLen), computeChecksum(file, md)); else { for(File child : children) loadChecksums(checksums, md, child, basedirLen, exclusionFilter); } } }