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