Home | History | Annotate | Download | only in tools
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.timezone.distro.tools;
     17 
     18 import com.android.timezone.distro.DistroException;
     19 import com.android.timezone.distro.DistroVersion;
     20 import com.android.timezone.distro.TimeZoneDistro;
     21 
     22 import java.io.ByteArrayOutputStream;
     23 import java.io.File;
     24 import java.io.FileInputStream;
     25 import java.io.IOException;
     26 import java.nio.charset.StandardCharsets;
     27 import java.util.zip.ZipEntry;
     28 import java.util.zip.ZipOutputStream;
     29 
     30 /**
     31  * A class for creating a {@link TimeZoneDistro} containing timezone update data. Used in real
     32  * distro creation code and tests.
     33  */
     34 public final class TimeZoneDistroBuilder {
     35 
     36     /**
     37      * An arbitrary timestamp (actually 1/1/1970 00:00:00 UTC) used as the modification time for all
     38      * files within a distro to reduce unnecessary differences when a distro is regenerated from the
     39      * same input data. To use UTC time in zip file, a year before 1980 is chosen.
     40      */
     41     private static final long ENTRY_TIMESTAMP = 0L;
     42 
     43     private DistroVersion distroVersion;
     44     private byte[] tzData;
     45     private byte[] icuData;
     46     private String tzLookupXml;
     47 
     48     public TimeZoneDistroBuilder setDistroVersion(DistroVersion distroVersion) {
     49         this.distroVersion = distroVersion;
     50         return this;
     51     }
     52 
     53     public TimeZoneDistroBuilder clearVersionForTests() {
     54         // This has the effect of omitting the version file in buildUnvalidated().
     55         this.distroVersion = null;
     56         return this;
     57     }
     58 
     59     public TimeZoneDistroBuilder replaceFormatVersionForTests(int majorVersion, int minorVersion) {
     60         try {
     61             distroVersion = new DistroVersion(
     62                     majorVersion, minorVersion, distroVersion.rulesVersion, distroVersion.revision);
     63         } catch (DistroException e) {
     64             throw new IllegalArgumentException();
     65         }
     66         return this;
     67     }
     68 
     69     public TimeZoneDistroBuilder setTzDataFile(File tzDataFile) throws IOException {
     70         return setTzDataFile(readFileAsByteArray(tzDataFile));
     71     }
     72 
     73     public TimeZoneDistroBuilder setTzDataFile(byte[] tzData) {
     74         this.tzData = tzData;
     75         return this;
     76     }
     77 
     78     // For use in tests.
     79     public TimeZoneDistroBuilder clearTzDataForTests() {
     80         this.tzData = null;
     81         return this;
     82     }
     83 
     84     public TimeZoneDistroBuilder setIcuDataFile(File icuDataFile) throws IOException {
     85         return setIcuDataFile(readFileAsByteArray(icuDataFile));
     86     }
     87 
     88     public TimeZoneDistroBuilder setIcuDataFile(byte[] icuData) {
     89         this.icuData = icuData;
     90         return this;
     91     }
     92 
     93     public TimeZoneDistroBuilder setTzLookupFile(File tzLookupFile) throws IOException {
     94         return setTzLookupXml(readFileAsUtf8(tzLookupFile));
     95     }
     96 
     97     public TimeZoneDistroBuilder setTzLookupXml(String tzlookupXml) {
     98         this.tzLookupXml = tzlookupXml;
     99         return this;
    100     }
    101 
    102     // For use in tests.
    103     public TimeZoneDistroBuilder clearIcuDataForTests() {
    104         this.icuData = null;
    105         return this;
    106     }
    107 
    108     /**
    109      * For use in tests. Use {@link #buildBytes()} for a version with validation.
    110      */
    111     public byte[] buildUnvalidatedBytes() throws DistroException {
    112         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    113         try (ZipOutputStream zos = new ZipOutputStream(baos)) {
    114             if (distroVersion != null) {
    115                 addZipEntry(zos, TimeZoneDistro.DISTRO_VERSION_FILE_NAME, distroVersion.toBytes());
    116             }
    117 
    118             if (tzData != null) {
    119                 addZipEntry(zos, TimeZoneDistro.TZDATA_FILE_NAME, tzData);
    120             }
    121             if (icuData != null) {
    122                 addZipEntry(zos, TimeZoneDistro.ICU_DATA_FILE_NAME, icuData);
    123             }
    124             if (tzLookupXml != null) {
    125                 addZipEntry(zos, TimeZoneDistro.TZLOOKUP_FILE_NAME,
    126                         tzLookupXml.getBytes(StandardCharsets.UTF_8));
    127             }
    128         } catch (IOException e) {
    129             throw new DistroException("Unable to create zip file", e);
    130         }
    131         return baos.toByteArray();
    132     }
    133 
    134     /**
    135      * Builds a {@code byte[]} for a Distro .zip file.
    136      */
    137     public byte[] buildBytes() throws DistroException {
    138         if (distroVersion == null) {
    139             throw new IllegalStateException("Missing distroVersion");
    140         }
    141         if (icuData == null) {
    142             throw new IllegalStateException("Missing icuData");
    143         }
    144         if (tzData == null) {
    145             throw new IllegalStateException("Missing tzData");
    146         }
    147         return buildUnvalidatedBytes();
    148     }
    149 
    150     private static void addZipEntry(ZipOutputStream zos, String name, byte[] content)
    151             throws DistroException {
    152         try {
    153             ZipEntry zipEntry = new ZipEntry(name);
    154             zipEntry.setSize(content.length);
    155             // Set the time to a fixed value so the zip entry is deterministic.
    156             zipEntry.setTime(ENTRY_TIMESTAMP);
    157             zos.putNextEntry(zipEntry);
    158             zos.write(content);
    159             zos.closeEntry();
    160         } catch (IOException e) {
    161             throw new DistroException("Unable to add zip entry", e);
    162         }
    163     }
    164 
    165     /**
    166      * Returns the contents of 'path' as a byte array.
    167      */
    168     private static byte[] readFileAsByteArray(File file) throws IOException {
    169         byte[] buffer = new byte[8192];
    170         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    171         try (FileInputStream  fis = new FileInputStream(file)) {
    172             int count;
    173             while ((count = fis.read(buffer)) != -1) {
    174                 baos.write(buffer, 0, count);
    175             }
    176         }
    177         return baos.toByteArray();
    178     }
    179 
    180     /**
    181      * Returns the contents of 'path' as a String, having interpreted the file as UTF-8.
    182      */
    183     private String readFileAsUtf8(File file) throws IOException {
    184         return new String(readFileAsByteArray(file), StandardCharsets.UTF_8);
    185     }
    186 }
    187 
    188