blob: d6bd4da835d323f4feff1427f5ba53e9a3faf825 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.timezone.distro.tools;
import com.android.timezone.distro.DistroException;
import com.android.timezone.distro.DistroVersion;
import com.android.timezone.distro.TimeZoneDistro;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* A class for creating a {@link TimeZoneDistro} containing timezone update data. Used in real
* distro creation code and tests.
*/
public final class TimeZoneDistroBuilder {
/**
* An arbitrary timestamp (actually 1/1/2017 00:00:00 UTC) used as the modification time for all
* files within a distro to reduce unnecessary differences when a distro is regenerated from the
* same input data.
*/
private static long ENTRY_TIMESTAMP = 1483228800000L;
private DistroVersion distroVersion;
private byte[] tzData;
private byte[] icuData;
private String tzLookupXml;
public TimeZoneDistroBuilder setDistroVersion(DistroVersion distroVersion) {
this.distroVersion = distroVersion;
return this;
}
public TimeZoneDistroBuilder clearVersionForTests() {
// This has the effect of omitting the version file in buildUnvalidated().
this.distroVersion = null;
return this;
}
public TimeZoneDistroBuilder replaceFormatVersionForTests(int majorVersion, int minorVersion) {
try {
distroVersion = new DistroVersion(
majorVersion, minorVersion, distroVersion.rulesVersion, distroVersion.revision);
} catch (DistroException e) {
throw new IllegalArgumentException();
}
return this;
}
public TimeZoneDistroBuilder setTzDataFile(File tzDataFile) throws IOException {
return setTzDataFile(readFileAsByteArray(tzDataFile));
}
public TimeZoneDistroBuilder setTzDataFile(byte[] tzData) {
this.tzData = tzData;
return this;
}
// For use in tests.
public TimeZoneDistroBuilder clearTzDataForTests() {
this.tzData = null;
return this;
}
public TimeZoneDistroBuilder setIcuDataFile(File icuDataFile) throws IOException {
return setIcuDataFile(readFileAsByteArray(icuDataFile));
}
public TimeZoneDistroBuilder setIcuDataFile(byte[] icuData) {
this.icuData = icuData;
return this;
}
public TimeZoneDistroBuilder setTzLookupFile(File tzLookupFile) throws IOException {
return setTzLookupXml(readFileAsUtf8(tzLookupFile));
}
public TimeZoneDistroBuilder setTzLookupXml(String tzlookupXml) {
this.tzLookupXml = tzlookupXml;
return this;
}
// For use in tests.
public TimeZoneDistroBuilder clearIcuDataForTests() {
this.icuData = null;
return this;
}
/**
* For use in tests. Use {@link #buildBytes()} for a version with validation.
*/
public byte[] buildUnvalidatedBytes() throws DistroException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
if (distroVersion != null) {
addZipEntry(zos, TimeZoneDistro.DISTRO_VERSION_FILE_NAME, distroVersion.toBytes());
}
if (tzData != null) {
addZipEntry(zos, TimeZoneDistro.TZDATA_FILE_NAME, tzData);
}
if (icuData != null) {
addZipEntry(zos, TimeZoneDistro.ICU_DATA_FILE_NAME, icuData);
}
if (tzLookupXml != null) {
addZipEntry(zos, TimeZoneDistro.TZLOOKUP_FILE_NAME,
tzLookupXml.getBytes(StandardCharsets.UTF_8));
}
} catch (IOException e) {
throw new DistroException("Unable to create zip file", e);
}
return baos.toByteArray();
}
/**
* Builds a {@code byte[]} for a Distro .zip file.
*/
public byte[] buildBytes() throws DistroException {
if (distroVersion == null) {
throw new IllegalStateException("Missing distroVersion");
}
if (icuData == null) {
throw new IllegalStateException("Missing icuData");
}
if (tzData == null) {
throw new IllegalStateException("Missing tzData");
}
return buildUnvalidatedBytes();
}
private static void addZipEntry(ZipOutputStream zos, String name, byte[] content)
throws DistroException {
try {
ZipEntry zipEntry = new ZipEntry(name);
zipEntry.setSize(content.length);
// Set the time to a fixed value so the zip entry is deterministic.
zipEntry.setTime(ENTRY_TIMESTAMP);
zos.putNextEntry(zipEntry);
zos.write(content);
zos.closeEntry();
} catch (IOException e) {
throw new DistroException("Unable to add zip entry", e);
}
}
/**
* Returns the contents of 'path' as a byte array.
*/
private static byte[] readFileAsByteArray(File file) throws IOException {
byte[] buffer = new byte[8192];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (FileInputStream fis = new FileInputStream(file)) {
int count;
while ((count = fis.read(buffer)) != -1) {
baos.write(buffer, 0, count);
}
}
return baos.toByteArray();
}
/**
* Returns the contents of 'path' as a String, having interpreted the file as UTF-8.
*/
private String readFileAsUtf8(File file) throws IOException {
return new String(readFileAsByteArray(file), StandardCharsets.UTF_8);
}
}