1 /* 2 * Copyright (c) 2021-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.ohos.hapsigntool.utils; 17 18 import com.google.gson.Gson; 19 import com.google.gson.GsonBuilder; 20 import com.ohos.hapsigntool.error.ERROR; 21 22 import org.apache.logging.log4j.LogManager; 23 import org.apache.logging.log4j.Logger; 24 25 import java.io.BufferedWriter; 26 import java.io.ByteArrayOutputStream; 27 import java.io.Closeable; 28 import java.io.DataOutputStream; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileNotFoundException; 32 import java.io.FileOutputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.OutputStream; 36 import java.io.OutputStreamWriter; 37 import java.nio.charset.Charset; 38 import java.nio.file.Files; 39 import java.util.HashMap; 40 import java.util.Map; 41 import java.util.regex.Matcher; 42 import java.util.regex.Pattern; 43 44 /** 45 * Common file operation. 46 * 47 * @since 2021/12/28 48 */ 49 public final class FileUtils { 50 /** 51 * LOGGER. 52 */ 53 private static final Logger LOGGER = LogManager.getLogger(FileUtils.class); 54 55 /** 56 * suffix regex map 57 */ 58 public static final Map<String, Pattern> SUFFIX_REGEX_MAP = new HashMap<>(); 59 60 /** 61 * add GSON static. 62 */ 63 public static final Gson GSON = (new GsonBuilder()).disableHtmlEscaping().create(); 64 65 /** 66 * add GSON_PRETTY_PRINT static. 67 */ 68 public static final Gson GSON_PRETTY_PRINT = (new GsonBuilder()).disableHtmlEscaping().setPrettyPrinting().create(); 69 70 /** 71 * File reader block size 72 */ 73 public static final int FILE_BUFFER_BLOCK = 1024 * 1024; 74 75 /** 76 * File end 77 */ 78 public static final int FILE_END = -1; 79 80 /** 81 * Expected split string length 82 */ 83 public static final int SPLIT_LENGTH = 2; 84 85 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 86 87 static { 88 SUFFIX_REGEX_MAP.put("so", Pattern.compile("\\.so(\\.[0-9]*){0,3}$")); 89 } 90 FileUtils()91 private FileUtils() { 92 } 93 94 /** 95 * Close closeable quietly. 96 * 97 * @param closeable closeable 98 */ close(Closeable closeable)99 public static void close(Closeable closeable) { 100 if (closeable != null) { 101 try { 102 closeable.close(); 103 } catch (IOException exception) { 104 LOGGER.debug(exception.getMessage(), exception); 105 } 106 } 107 } 108 109 /** 110 * Read byte from input file. 111 * 112 * @param file Which file to read 113 * @return byte content 114 * @throws IOException Read failed 115 */ readFile(File file)116 public static byte[] readFile(File file) throws IOException { 117 return read(Files.newInputStream(file.toPath())); 118 } 119 120 /** 121 * Read byte from input stream. 122 * 123 * @param input Input stream 124 * @return File content 125 * @throws IOException Read failed 126 */ read(InputStream input)127 public static byte[] read(InputStream input) throws IOException { 128 try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { 129 byte[] buffer = new byte[FILE_BUFFER_BLOCK]; 130 int read; 131 while ((read = input.read(buffer)) != FILE_END) { 132 output.write(buffer, 0, read); 133 } 134 return output.toByteArray(); 135 } finally { 136 close(input); 137 } 138 } 139 140 /** 141 * Read byte from input file. 142 * 143 * @param file input file 144 * @param offset offset 145 * @param length length 146 * @return data bytes 147 */ readFileByOffsetAndLength(File file, long offset, long length)148 public static byte[] readFileByOffsetAndLength(File file, long offset, long length) throws IOException { 149 try (FileInputStream input = new FileInputStream(file)) { 150 return readInputByOffsetAndLength(input, offset, length); 151 } 152 } 153 154 /** 155 * Read byte from input stream. 156 * 157 * @param input input stream 158 * @param offset offset 159 * @param length length 160 * @return data bytes 161 * @throws IOException read exception 162 */ readInputByOffsetAndLength(InputStream input, long offset, long length)163 public static byte[] readInputByOffsetAndLength(InputStream input, long offset, long length) throws IOException { 164 input.skip(offset); 165 return readInputByLength(input, length); 166 } 167 168 /** 169 * Read byte from input stream. 170 * 171 * @param input InputStream 172 * @param length length 173 * @return data bytes 174 */ readInputByLength(InputStream input, long length)175 public static byte[] readInputByLength(InputStream input, long length) throws IOException { 176 try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { 177 if (length > Integer.MAX_VALUE) { 178 throw new IOException("Size cannot be greater than Integer max value: " + length); 179 } 180 writeInputToOutPut(input, output, length); 181 return output.toByteArray(); 182 } 183 } 184 185 /** 186 * write input to output by length 187 */ writeInputToOutPut(InputStream input, OutputStream output, long length)188 private static void writeInputToOutPut(InputStream input, OutputStream output, long length) throws IOException { 189 byte[] buffer = new byte[FILE_BUFFER_BLOCK]; 190 long hasReadLen = 0L; 191 while (hasReadLen < length) { 192 int readLen = (int) Math.min(length - hasReadLen, FILE_BUFFER_BLOCK); 193 int len = input.read(buffer, 0, readLen); 194 if (len != readLen) { 195 throw new IOException("read" + hasReadLen + "bytes data less than " + length); 196 } 197 output.write(buffer, 0, len); 198 hasReadLen += len; 199 } 200 } 201 202 /** 203 * Out put content to file. 204 * 205 * @param content Which content to out put 206 * @param output File to write 207 * @throws IOException Write failed 208 */ write(byte[] content, File output)209 public static void write(byte[] content, File output) throws IOException { 210 try (FileOutputStream out = new FileOutputStream(output)) { 211 for (byte con : content) { 212 out.write(con); 213 } 214 } 215 } 216 217 /** 218 * Write data in file to output stream 219 * 220 * @param inFile input file path. 221 * @param out output file path. 222 * @param offset file read offset 223 * @param size file read size 224 * @return true, if write successfully. 225 */ appendWriteFileByOffsetToFile(String inFile, FileOutputStream out, long offset, long size)226 public static boolean appendWriteFileByOffsetToFile(String inFile, FileOutputStream out, long offset, long size) { 227 File inputFile = new File(inFile); 228 try (FileInputStream fis = new FileInputStream(inputFile)) { 229 fis.skip(offset); 230 writeInputToOutPut(fis, out, size); 231 return true; 232 } catch (FileNotFoundException e) { 233 LOGGER.error("Failed to get input stream object."); 234 } catch (IOException e) { 235 LOGGER.error("Failed to read or write data."); 236 } 237 return false; 238 } 239 240 /** 241 * Check file exist or not. 242 * 243 * @param filePath File path 244 * @return Is file exist 245 */ isFileExist(String filePath)246 public static boolean isFileExist(String filePath) { 247 return new File(filePath).exists(); 248 } 249 250 /** 251 * Throw runtime exception if not allowed file type. 252 * 253 * @param filePath file path 254 * @param types Such as "txt" "json" "mp3" 255 */ validFileType(String filePath, String... types)256 public static void validFileType(String filePath, String... types) { 257 String suffix = getSuffix(filePath); 258 ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(suffix), 259 ERROR.NOT_SUPPORT_ERROR, "Not support file: " + filePath); 260 boolean isMatches = false; 261 for (String type : types) { 262 if (StringUtils.isEmpty(type)) { 263 continue; 264 } 265 if (type.equalsIgnoreCase(suffix)) { 266 isMatches = true; 267 break; 268 } 269 } 270 ValidateUtils.throwIfNotMatches(isMatches, ERROR.NOT_SUPPORT_ERROR, "Not support file: " + filePath); 271 } 272 273 /** 274 * Get suffix of file. 275 * 276 * @param filePath file path 277 * @return file suffix. Such as "txt" "json" "p12" 278 */ getSuffix(String filePath)279 public static String getSuffix(String filePath) { 280 if (StringUtils.isEmpty(filePath)) { 281 return ""; 282 } 283 File file = new File(filePath); 284 String fileName = file.getName(); 285 String[] temps = fileName.split("\\."); 286 if (temps.length < SPLIT_LENGTH) { 287 return ""; 288 } 289 return temps[temps.length - 1]; 290 } 291 292 /** 293 * Write data in file to output stream 294 * 295 * @param file input file path. 296 * @param dos output stream. 297 * @return true, if write successfully. 298 */ writeFileToDos(String file, DataOutputStream dos)299 public static boolean writeFileToDos(String file, DataOutputStream dos) { 300 if (dos == null) { 301 return false; 302 } 303 File src = new File(file); 304 try (FileInputStream fileStream = new FileInputStream(src)) { 305 int temp; 306 byte[] buf = new byte[FILE_BUFFER_BLOCK]; 307 while ((temp = fileStream.read(buf)) > 0) { 308 dos.write(buf, 0, temp); 309 } 310 return true; 311 } catch (FileNotFoundException e) { 312 LOGGER.error("Failed to get input stream object."); 313 } catch (IOException e) { 314 LOGGER.error("Failed to read or write data."); 315 } 316 return false; 317 } 318 319 /** 320 * Write byte array to output stream 321 * 322 * @param data byte array 323 * @param dos output stream 324 * @return true, if write successfully. 325 */ writeByteToDos(byte[] data, DataOutputStream dos)326 public static boolean writeByteToDos(byte[] data, DataOutputStream dos) { 327 if (data == null) { 328 return true; 329 } 330 try { 331 dos.write(data); 332 } catch (IOException e) { 333 LOGGER.error("Failed to write data to output stream."); 334 return false; 335 } 336 return true; 337 } 338 339 /** 340 * Get length of file. 341 * 342 * @param filePath input file path. 343 * @return long value of file length. 344 */ getFileLen(String filePath)345 public static long getFileLen(String filePath) { 346 File file = new File(filePath); 347 if (file.exists() && file.isFile()) { 348 return file.length(); 349 } 350 return -1; 351 } 352 353 /** 354 * Write byte array data to output file. 355 * 356 * @param bytes byte array data. 357 * @param outFile output file path. 358 * @return true, if write successfully. 359 */ writeByteToOutFile(byte[] bytes, String outFile)360 public static boolean writeByteToOutFile(byte[] bytes, String outFile) { 361 try (OutputStream ops = new FileOutputStream(outFile, true)) { 362 return writeByteToOutFile(bytes, ops); 363 } catch (FileNotFoundException e) { 364 LOGGER.error("Failed to get output stream object, outfile: " + outFile); 365 } catch (IOException e) { 366 LOGGER.error("Failed to write data to ops, outfile: " + outFile); 367 } 368 return false; 369 } 370 371 /** 372 * Write byte array data to output file. 373 * 374 * @param bytes byte array data. 375 * @param outFile output file path. 376 * @return true, if write successfully. 377 */ writeByteToOutFile(byte[] bytes, OutputStream outFile)378 public static boolean writeByteToOutFile(byte[] bytes, OutputStream outFile) { 379 try { 380 outFile.write(bytes, 0, bytes.length); 381 outFile.flush(); 382 return true; 383 } catch (FileNotFoundException e) { 384 LOGGER.error("Failed to get output stream object, outfile: " + outFile); 385 } catch (IOException e) { 386 LOGGER.error("Failed to write data to ops, outfile: " + outFile); 387 } 388 return false; 389 } 390 391 /** 392 * Check input file is valid. 393 * 394 * @param file input file. 395 * @throws IOException file is a directory or can't be read. 396 */ isValidFile(File file)397 public static void isValidFile(File file) throws IOException { 398 if (!file.exists()) { 399 throw new FileNotFoundException("File '" + file + "' does not exist"); 400 } 401 402 if (file.isDirectory()) { 403 throw new IOException("File '" + file + "' exists but is a directory"); 404 } 405 406 if (!file.canRead()) { 407 throw new IOException("File '" + file + "' cannot be read"); 408 } 409 } 410 411 /** 412 * Open an input stream of input file safely. 413 * 414 * @param file input file. 415 * @return an input stream of input file 416 * @throws IOException file is a directory or can't be read. 417 */ openInputStream(File file)418 public static FileInputStream openInputStream(File file) throws IOException { 419 isValidFile(file); 420 return new FileInputStream(file); 421 } 422 toByteArray(final InputStream input, final int size)423 private static byte[] toByteArray(final InputStream input, final int size) throws IOException { 424 if (size < 0) { 425 throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); 426 } 427 428 if (size == 0) { 429 return EMPTY_BYTE_ARRAY; 430 } 431 432 final byte[] data = new byte[size]; 433 int offset = 0; 434 int read; 435 436 while (offset < size && (read = input.read(data, offset, size - offset)) != FILE_END) { 437 offset += read; 438 } 439 440 if (offset != size) { 441 throw new IOException("Unexpected read size. current: " + offset + ", expected: " + size); 442 } 443 444 return data; 445 } 446 447 /** 448 * Read file to byte array. 449 * 450 * @param file input file. 451 * @return byte array of file-content. 452 * @throws IOException fill exception 453 */ readFileToByteArray(File file)454 public static byte[] readFileToByteArray(File file) throws IOException { 455 try (InputStream in = openInputStream(file)) { 456 final long fileLength = file.length(); 457 if (fileLength > Integer.MAX_VALUE) { 458 throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + fileLength); 459 } 460 return toByteArray(in, (int) fileLength); 461 } 462 } 463 464 /** 465 * Write a string to file 466 * 467 * @param source String will be written. 468 * @param filePath path to write file. 469 * @param charset a charset 470 * @throws IOException write error. 471 */ writeStringToFile(String source, String filePath, Charset charset)472 public static void writeStringToFile(String source, String filePath, Charset charset) throws IOException { 473 try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( 474 new FileOutputStream(filePath), charset))) { 475 writer.write(source); 476 writer.flush(); 477 } 478 } 479 480 /** 481 * Delete a file quietly 482 * 483 * @param file the file to delete 484 */ deleteFile(File file)485 public static void deleteFile(File file) { 486 if (file != null && file.isFile()) { 487 try { 488 Files.delete(file.toPath()); 489 } catch (IOException e) { 490 LOGGER.warn("delete file '{}' error, error message: {}", file, e.getMessage()); 491 } 492 } 493 } 494 495 /** 496 * regex filename 497 * 498 * @param name filename 499 * @return boolean 500 */ isRunnableFile(String name)501 public static boolean isRunnableFile(String name) { 502 if (StringUtils.isEmpty(name)) { 503 return false; 504 } 505 if (name.endsWith(".an") || name.endsWith(".abc")) { 506 return true; 507 } 508 for (Pattern pattern : SUFFIX_REGEX_MAP.values()) { 509 Matcher matcher = pattern.matcher(name); 510 if (matcher.find()) { 511 return true; 512 } 513 } 514 return false; 515 } 516 } 517