176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller/* 276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * Copyright (C) 2015 The Android Open Source Project 376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * 476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * Licensed under the Apache License, Version 2.0 (the "License"); 576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * you may not use this file except in compliance with the License. 676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * You may obtain a copy of the License at 776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * 876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * http://www.apache.org/licenses/LICENSE-2.0 976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * 1076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * Unless required by applicable law or agreed to in writing, software 1176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * distributed under the License is distributed on an "AS IS" BASIS, 1276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * See the License for the specific language governing permissions and 1476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * limitations under the License. 1576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller */ 1676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerpackage com.android.timezone.distro.tools; 1776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 1876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport com.android.timezone.distro.DistroException; 1976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport com.android.timezone.distro.DistroVersion; 2076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport com.android.timezone.distro.TimeZoneDistro; 2176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 2276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.io.ByteArrayOutputStream; 2376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.io.File; 2476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.io.FileInputStream; 2576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.io.IOException; 2676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.nio.charset.StandardCharsets; 2776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.util.zip.ZipEntry; 2876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.util.zip.ZipOutputStream; 2976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 3076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller/** 3176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * A class for creating a {@link TimeZoneDistro} containing timezone update data. Used in real 3276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * distro creation code and tests. 3376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller */ 3476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerpublic final class TimeZoneDistroBuilder { 3576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 36ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller /** 37ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller * An arbitrary timestamp (actually 1/1/2017 00:00:00 UTC) used as the modification time for all 38ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller * files within a distro to reduce unnecessary differences when a distro is regenerated from the 39ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller * same input data. 40ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller */ 41ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller private static long ENTRY_TIMESTAMP = 1483228800000L; 42ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller 4376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller private DistroVersion distroVersion; 4476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller private byte[] tzData; 4576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller private byte[] icuData; 4676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller private String tzLookupXml; 4776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 4876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder setDistroVersion(DistroVersion distroVersion) { 4976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller this.distroVersion = distroVersion; 5076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return this; 5176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 5276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 5376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder clearVersionForTests() { 5476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // This has the effect of omitting the version file in buildUnvalidated(). 5576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller this.distroVersion = null; 5676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return this; 5776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 5876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 5976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder replaceFormatVersionForTests(int majorVersion, int minorVersion) { 6076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller try { 6176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller distroVersion = new DistroVersion( 6276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller majorVersion, minorVersion, distroVersion.rulesVersion, distroVersion.revision); 6376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } catch (DistroException e) { 6476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller throw new IllegalArgumentException(); 6576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 6676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return this; 6776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 6876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 6976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder setTzDataFile(File tzDataFile) throws IOException { 7076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return setTzDataFile(readFileAsByteArray(tzDataFile)); 7176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 7276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 7376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder setTzDataFile(byte[] tzData) { 7476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller this.tzData = tzData; 7576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return this; 7676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 7776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 7876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // For use in tests. 7976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder clearTzDataForTests() { 8076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller this.tzData = null; 8176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return this; 8276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 8376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 8476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder setIcuDataFile(File icuDataFile) throws IOException { 8576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return setIcuDataFile(readFileAsByteArray(icuDataFile)); 8676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 8776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 8876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder setIcuDataFile(byte[] icuData) { 8976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller this.icuData = icuData; 9076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return this; 9176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 9276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 9376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder setTzLookupFile(File tzLookupFile) throws IOException { 9476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return setTzLookupXml(readFileAsUtf8(tzLookupFile)); 9576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 9676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 9776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder setTzLookupXml(String tzlookupXml) { 9876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller this.tzLookupXml = tzlookupXml; 9976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return this; 10076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 10176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 10276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // For use in tests. 10376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistroBuilder clearIcuDataForTests() { 10476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller this.icuData = null; 10576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return this; 10676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 10776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 10876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller /** 1092cda5fcdb177de6e07ad39cc93a5d8b882300252Neil Fuller * For use in tests. Use {@link #buildBytes()} for a version with validation. 1102cda5fcdb177de6e07ad39cc93a5d8b882300252Neil Fuller */ 1112cda5fcdb177de6e07ad39cc93a5d8b882300252Neil Fuller public byte[] buildUnvalidatedBytes() throws DistroException { 11276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller ByteArrayOutputStream baos = new ByteArrayOutputStream(); 11376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller try (ZipOutputStream zos = new ZipOutputStream(baos)) { 11476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (distroVersion != null) { 11576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller addZipEntry(zos, TimeZoneDistro.DISTRO_VERSION_FILE_NAME, distroVersion.toBytes()); 11676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 11776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 11876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (tzData != null) { 11976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller addZipEntry(zos, TimeZoneDistro.TZDATA_FILE_NAME, tzData); 12076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 12176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (icuData != null) { 12276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller addZipEntry(zos, TimeZoneDistro.ICU_DATA_FILE_NAME, icuData); 12376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 12476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (tzLookupXml != null) { 12576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller addZipEntry(zos, TimeZoneDistro.TZLOOKUP_FILE_NAME, 12676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller tzLookupXml.getBytes(StandardCharsets.UTF_8)); 12776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 12876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } catch (IOException e) { 12976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller throw new DistroException("Unable to create zip file", e); 13076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 1312cda5fcdb177de6e07ad39cc93a5d8b882300252Neil Fuller return baos.toByteArray(); 13276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 13376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 13476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller /** 1352cda5fcdb177de6e07ad39cc93a5d8b882300252Neil Fuller * Builds a {@code byte[]} for a Distro .zip file. 1362cda5fcdb177de6e07ad39cc93a5d8b882300252Neil Fuller */ 1372cda5fcdb177de6e07ad39cc93a5d8b882300252Neil Fuller public byte[] buildBytes() throws DistroException { 13876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (distroVersion == null) { 13976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller throw new IllegalStateException("Missing distroVersion"); 14076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 14176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (icuData == null) { 14276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller throw new IllegalStateException("Missing icuData"); 14376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 14476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (tzData == null) { 14576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller throw new IllegalStateException("Missing tzData"); 14676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 1472cda5fcdb177de6e07ad39cc93a5d8b882300252Neil Fuller return buildUnvalidatedBytes(); 14876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 14976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 15076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller private static void addZipEntry(ZipOutputStream zos, String name, byte[] content) 15176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller throws DistroException { 15276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller try { 15376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller ZipEntry zipEntry = new ZipEntry(name); 15476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller zipEntry.setSize(content.length); 155ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller // Set the time to a fixed value so the zip entry is deterministic. 156ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller zipEntry.setTime(ENTRY_TIMESTAMP); 15776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller zos.putNextEntry(zipEntry); 15876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller zos.write(content); 15976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller zos.closeEntry(); 16076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } catch (IOException e) { 16176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller throw new DistroException("Unable to add zip entry", e); 16276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 16376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 16476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 16576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller /** 16676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * Returns the contents of 'path' as a byte array. 16776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller */ 16876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller private static byte[] readFileAsByteArray(File file) throws IOException { 16976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller byte[] buffer = new byte[8192]; 17076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller ByteArrayOutputStream baos = new ByteArrayOutputStream(); 17176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller try (FileInputStream fis = new FileInputStream(file)) { 17276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller int count; 17376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller while ((count = fis.read(buffer)) != -1) { 17476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller baos.write(buffer, 0, count); 17576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 17676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 17776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return baos.toByteArray(); 17876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 17976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 18076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller /** 18176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * Returns the contents of 'path' as a String, having interpreted the file as UTF-8. 18276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller */ 18376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller private String readFileAsUtf8(File file) throws IOException { 18476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return new String(readFileAsByteArray(file), StandardCharsets.UTF_8); 18576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 18676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller} 18776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 188