| /* |
| * Copyright (C) 2013 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.builder.internal.incremental; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.VisibleForTesting; |
| import com.google.common.base.Charsets; |
| import com.google.common.collect.ArrayListMultimap; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Multimap; |
| import com.google.common.io.Closer; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * |
| * Stores a collection of {@link DependencyData}. |
| * |
| * The format is binary and follows the following format: |
| * |
| * (Header Tag)(version number: int) |
| * (Start Tag)(Main File)[(2ndary Tag)(2ndary File)...][(Output tag)(output file)...][(2ndary Output tag)(output file)...] |
| * (Start Tag)(Main File)[(2ndary Tag)(2ndary File)...][(Output tag)(output file)...][(2ndary Output tag)(output file)...] |
| * ... |
| * |
| * All files are written as (size in int)(byte array, using UTF8 encoding). |
| */ |
| public class DependencyDataStore { |
| |
| private static final byte TAG_HEADER = 0x7F; |
| private static final byte TAG_START = 0x70; |
| private static final byte TAG_2NDARY_FILE = 0x71; |
| private static final byte TAG_OUTPUT = 0x73; |
| private static final byte TAG_2NDARY_OUTPUT = 0x74; |
| private static final byte TAG_END = 0x77; |
| |
| private static final int CURRENT_VERSION = 1; |
| |
| private final Map<String, DependencyData> mMainFileMap = Maps.newHashMap(); |
| |
| public DependencyDataStore() { |
| } |
| |
| public void addData(@NonNull List<DependencyData> dataList) { |
| for (DependencyData data : dataList) { |
| mMainFileMap.put(data.getMainFile(), data); |
| } |
| } |
| |
| public void addData(@NonNull DependencyData data) { |
| mMainFileMap.put(data.getMainFile(), data); |
| } |
| |
| public void remove(@NonNull DependencyData data) { |
| mMainFileMap.remove(data.getMainFile()); |
| } |
| |
| public void updateAll(@NonNull List<DependencyData> dataList) { |
| for (DependencyData data : dataList) { |
| mMainFileMap.put(data.getMainFile(), data); |
| } |
| } |
| |
| @NonNull |
| public Collection<DependencyData> getData() { |
| return mMainFileMap.values(); |
| } |
| |
| @VisibleForTesting |
| DependencyData getByMainFile(String path) { |
| return mMainFileMap.get(path); |
| } |
| |
| /** |
| * Returns the map of data using the main file as key. |
| * |
| * @see com.android.builder.internal.incremental.DependencyData#getMainFile() |
| */ |
| @NonNull |
| public Map<String, DependencyData> getMainFileMap() { |
| return mMainFileMap; |
| } |
| |
| /** |
| * Saves the dependency data to a given file. |
| * |
| * @param file the file to save the data to. |
| * @throws IOException |
| */ |
| public void saveTo(@NonNull File file) throws IOException { |
| |
| Closer closer = Closer.create(); |
| try { |
| FileOutputStream fos = closer.register(new FileOutputStream(file)); |
| fos.write(TAG_HEADER); |
| writeInt(fos, CURRENT_VERSION); |
| |
| for (DependencyData data : getData()) { |
| fos.write(TAG_START); |
| writePath(fos, data.getMainFile()); |
| |
| for (String path : data.getSecondaryFiles()) { |
| fos.write(TAG_2NDARY_FILE); |
| writePath(fos, path); |
| } |
| |
| for (String path : data.getOutputFiles()) { |
| fos.write(TAG_OUTPUT); |
| writePath(fos, path); |
| } |
| |
| for (String path : data.getSecondaryOutputFiles()) { |
| fos.write(TAG_2NDARY_OUTPUT); |
| writePath(fos, path); |
| } |
| |
| } |
| } catch (Throwable e) { |
| throw closer.rethrow(e); |
| } finally { |
| closer.close(); |
| } |
| } |
| |
| private static class ReusableBuffer { |
| byte[] intBuffer = new byte[4]; |
| byte[] pathBuffer = null; |
| } |
| |
| /** |
| * Loads the dependency data from the given file. |
| * |
| * @param file the file to load the data from. |
| * @return a map of file-> list of impacted dependency data. |
| * @throws IOException |
| */ |
| public Multimap<String, DependencyData> loadFrom(@NonNull File file) throws IOException { |
| Multimap<String, DependencyData> inputMap = ArrayListMultimap.create(); |
| |
| Closer closer = Closer.create(); |
| FileInputStream fis = closer.register(new FileInputStream(file)); |
| |
| // reusable buffer |
| ReusableBuffer buffers = new ReusableBuffer(); |
| |
| // read the header |
| if (readByte(fis, buffers) != TAG_HEADER) { |
| throw new IllegalStateException("Wrong first byte on " + file.getAbsolutePath()); |
| } |
| |
| int version = readInt(fis, buffers); |
| if (version != CURRENT_VERSION) { |
| throw new IOException("Unsupported file version: " + version); |
| } |
| |
| try { |
| // just read the first byte since it should be the TAG_START |
| byte currentTag = readByte(fis, buffers); |
| if (currentTag != TAG_START) { |
| throw new IllegalStateException("Wrong first tag on " + file.getAbsolutePath()); |
| } |
| |
| DependencyData currentData = new DependencyData(); |
| |
| while (currentTag != TAG_END) { |
| // read the path |
| String path = readPath(fis, buffers); |
| |
| switch (currentTag) { |
| case TAG_START: |
| currentData.setMainFile(path); |
| mMainFileMap.put(path, currentData); |
| inputMap.put(path, currentData); |
| break; |
| case TAG_2NDARY_FILE: |
| currentData.addSecondaryFile(path); |
| inputMap.put(path, currentData); |
| break; |
| case TAG_OUTPUT: |
| currentData.addOutputFile(path); |
| break; |
| case TAG_2NDARY_OUTPUT: |
| currentData.addSecondaryOutputFile(path); |
| break; |
| } |
| |
| // read the next tag. |
| currentTag = readByte(fis, buffers); |
| |
| if (currentTag == TAG_START) { |
| currentData = new DependencyData(); |
| } |
| } |
| |
| return inputMap; |
| } catch (Throwable e) { |
| throw closer.rethrow(e); |
| } finally { |
| closer.close(); |
| } |
| } |
| |
| private static void writeInt(@NonNull FileOutputStream fos, int value) throws IOException { |
| ByteBuffer b = ByteBuffer.allocate(4); |
| b.putInt(value); |
| fos.write(b.array()); |
| } |
| |
| private static void writePath(@NonNull FileOutputStream fos, String path) throws IOException { |
| byte[] pathBytes = path.getBytes(Charsets.UTF_8); |
| |
| writeInt(fos, pathBytes.length); |
| fos.write(pathBytes); |
| } |
| |
| private static byte readByte(@NonNull FileInputStream fis, @NonNull ReusableBuffer buffers) |
| throws IOException { |
| int read = fis.read(buffers.intBuffer, 0, 1); |
| if (read != 1) { |
| return TAG_END; |
| } |
| |
| return buffers.intBuffer[0]; |
| } |
| |
| private static int readInt(@NonNull FileInputStream fis, @NonNull ReusableBuffer buffers) |
| throws IOException { |
| int read = fis.read(buffers.intBuffer); |
| |
| // there must always be 4 bytes for the path length |
| if (read != 4) { |
| throw new IOException("Failed to read path length"); |
| } |
| |
| // get the int value. |
| ByteBuffer b = ByteBuffer.wrap(buffers.intBuffer); |
| return b.getInt(); |
| } |
| |
| private static String readPath(@NonNull FileInputStream fis, @NonNull ReusableBuffer buffers) |
| throws IOException { |
| int length = readInt(fis, buffers); |
| |
| if (buffers.pathBuffer == null || buffers.pathBuffer.length < length) { |
| buffers.pathBuffer = new byte[length]; |
| } |
| |
| int read = fis.read(buffers.pathBuffer, 0, length); |
| if (read != length) { |
| throw new IOException("Failed to read path"); |
| } |
| |
| return new String(buffers.pathBuffer, 0, length, Charsets.UTF_8); |
| } |
| } |