001package org.eclipse.aether.util; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.BufferedReader; 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.IOException; 026import java.io.InputStreamReader; 027import java.security.MessageDigest; 028import java.security.NoSuchAlgorithmException; 029import java.util.Collection; 030import java.util.LinkedHashMap; 031import java.util.Map; 032 033/** 034 * A utility class to assist in the verification and generation of checksums. 035 */ 036public final class ChecksumUtils 037{ 038 039 private ChecksumUtils() 040 { 041 // hide constructor 042 } 043 044 /** 045 * Extracts the checksum from the specified file. 046 * 047 * @param checksumFile The path to the checksum file, must not be {@code null}. 048 * @return The checksum stored in the file, never {@code null}. 049 * @throws IOException If the checksum does not exist or could not be read for other reasons. 050 */ 051 public static String read( File checksumFile ) 052 throws IOException 053 { 054 String checksum = ""; 055 056 FileInputStream fis = new FileInputStream( checksumFile ); 057 try 058 { 059 BufferedReader br = new BufferedReader( new InputStreamReader( fis, "UTF-8" ), 512 ); 060 try 061 { 062 while ( true ) 063 { 064 String line = br.readLine(); 065 if ( line == null ) 066 { 067 break; 068 } 069 line = line.trim(); 070 if ( line.length() > 0 ) 071 { 072 checksum = line; 073 break; 074 } 075 } 076 } 077 finally 078 { 079 try 080 { 081 br.close(); 082 } 083 catch ( IOException e ) 084 { 085 // ignored 086 } 087 } 088 } 089 finally 090 { 091 try 092 { 093 fis.close(); 094 } 095 catch ( IOException e ) 096 { 097 // ignored 098 } 099 } 100 101 if ( checksum.matches( ".+= [0-9A-Fa-f]+" ) ) 102 { 103 int lastSpacePos = checksum.lastIndexOf( ' ' ); 104 checksum = checksum.substring( lastSpacePos + 1 ); 105 } 106 else 107 { 108 int spacePos = checksum.indexOf( ' ' ); 109 110 if ( spacePos != -1 ) 111 { 112 checksum = checksum.substring( 0, spacePos ); 113 } 114 } 115 116 return checksum; 117 } 118 119 /** 120 * Calculates checksums for the specified file. 121 * 122 * @param dataFile The file for which to calculate checksums, must not be {@code null}. 123 * @param algos The names of checksum algorithms (cf. {@link MessageDigest#getInstance(String)} to use, must not be 124 * {@code null}. 125 * @return The calculated checksums, indexed by algorithm name, or the exception that occurred while trying to 126 * calculate it, never {@code null}. 127 * @throws IOException If the data file could not be read. 128 */ 129 public static Map<String, Object> calc( File dataFile, Collection<String> algos ) 130 throws IOException 131 { 132 Map<String, Object> results = new LinkedHashMap<String, Object>(); 133 134 Map<String, MessageDigest> digests = new LinkedHashMap<String, MessageDigest>(); 135 for ( String algo : algos ) 136 { 137 try 138 { 139 digests.put( algo, MessageDigest.getInstance( algo ) ); 140 } 141 catch ( NoSuchAlgorithmException e ) 142 { 143 results.put( algo, e ); 144 } 145 } 146 147 FileInputStream fis = new FileInputStream( dataFile ); 148 try 149 { 150 for ( byte[] buffer = new byte[32 * 1024];; ) 151 { 152 int read = fis.read( buffer ); 153 if ( read < 0 ) 154 { 155 break; 156 } 157 for ( MessageDigest digest : digests.values() ) 158 { 159 digest.update( buffer, 0, read ); 160 } 161 } 162 } 163 finally 164 { 165 try 166 { 167 fis.close(); 168 } 169 catch ( IOException e ) 170 { 171 // ignored 172 } 173 } 174 175 for ( Map.Entry<String, MessageDigest> entry : digests.entrySet() ) 176 { 177 byte[] bytes = entry.getValue().digest(); 178 179 results.put( entry.getKey(), toHexString( bytes ) ); 180 } 181 182 return results; 183 } 184 185 /** 186 * Creates a hexadecimal representation of the specified bytes. Each byte is converted into a two-digit hex number 187 * and appended to the result with no separator between consecutive bytes. 188 * 189 * @param bytes The bytes to represent in hex notation, may be be {@code null}. 190 * @return The hexadecimal representation of the input or {@code null} if the input was {@code null}. 191 */ 192 public static String toHexString( byte[] bytes ) 193 { 194 if ( bytes == null ) 195 { 196 return null; 197 } 198 199 StringBuilder buffer = new StringBuilder( bytes.length * 2 ); 200 201 for ( byte aByte : bytes ) 202 { 203 int b = aByte & 0xFF; 204 if ( b < 0x10 ) 205 { 206 buffer.append( '0' ); 207 } 208 buffer.append( Integer.toHexString( b ) ); 209 } 210 211 return buffer.toString(); 212 } 213 214}