All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.ide.common.blame.MergingLogPersistUtil Maven / Gradle / Ivy

/*
 * 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.ide.common.blame;

import static com.android.SdkConstants.DOT_JSON;

import com.android.annotations.NonNull;
import com.google.common.base.Charsets;
import com.google.common.collect.Maps;
import com.google.common.io.Files;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;

/**
 * Utility functions to save and load individual merge log files.
 *
 * They are sharded by the directory containing the file, giving a file layout of the form:
 *
 * .
 * ├── multi
 * │   └── values.json
 * └── single
 *     ├── drawable.json
 *     ├── layout.json
 *     └── layout-land.json
 *
 * This allows incremental changes to only have to load and rewrite a small fraction of the
 * total log data.
 */
public class MergingLogPersistUtil {

    private static final SourceFileJsonTypeAdapter mSourceFileJsonTypeAdapter
            = new SourceFileJsonTypeAdapter();

    private static final SourcePositionJsonTypeAdapter mSourcePositionJsonTypeAdapter
            = new SourcePositionJsonTypeAdapter();

    private static final SourceFilePositionJsonSerializer mSourceFilePositionJsonTypeAdapter
            = new SourceFilePositionJsonSerializer();

    private static final String KEY_OUTPUT_FILE = "outputFile";
    private static final String KEY_FROM = "from";
    private static final String KEY_TO = "to";
    private static final String KEY_MERGED = "merged";
    private static final String KEY_SOURCE = "source";
    private static final String KEY_MAP = "map";
    private static final String INDENT_STRING = "    ";

    private static File getMultiFile(File folder, String shard) {
        return new File (new File(folder, "multi"), shard + DOT_JSON);
    }

    private static File getSingleFile(File folder, String shard) {
        return new File (new File(folder, "single"), shard + DOT_JSON);
    }

    /**
     * File format example for values files: (all paths are absolute)
     * 
[
     *     {
     *         "outputFile": "/path/build/intermediates/res/merged/f1/debug/values/values.xml",
     *         "map": [
     *             {
     *                 "to": {
     *                     "startLine": 2,
     *                     "startColumn": 4,
     *                     "startOffset": 55,
     *                     "endColumn": 54,
     *                     "endOffset": 105
     *                 },
     *                 "from": {
     *                     "file": "/path/src/f1/res/values/strings.xml",
     *                     "position": {
     *                         "startLine": 2,
     *                         "startColumn": 4,
     *                         "startOffset": 55,
     *                         "endColumn": 54,
     *                         "endOffset": 105
     *                     }
     *                 }
     *             },
     *             ...
     *         ]
     *     },
     *     ...
     * ]
* * There should not be multiple object in the outer list for the same outputFile. */ static void saveToMultiFile( @NonNull File folder, @NonNull String shard, @NonNull Map> map) throws IOException { File file = getMultiFile(folder, shard); file.getParentFile().mkdir(); JsonWriter out = new JsonWriter(Files.newWriter(file, Charsets.UTF_8)); try { out.setIndent(INDENT_STRING); out.beginArray(); for (Map.Entry> entry : map.entrySet()) { out.beginObject().name(KEY_OUTPUT_FILE); mSourceFileJsonTypeAdapter.write(out, entry.getKey()); out.name(KEY_MAP); out.beginArray(); for (Map.Entry innerEntry : entry.getValue().entrySet()) { out.beginObject(); out.name(KEY_TO); mSourcePositionJsonTypeAdapter.write(out, innerEntry.getKey()); out.name(KEY_FROM); mSourceFilePositionJsonTypeAdapter.write(out, innerEntry.getValue()); out.endObject(); } out.endArray(); out.endObject(); } out.endArray(); } finally { out.close(); } } @NonNull static Map> loadFromMultiFile( @NonNull File folder, @NonNull String shard) { Map> map = Maps.newConcurrentMap(); JsonReader reader; File file = getMultiFile(folder, shard); if (!file.exists()) { return map; } try { reader = new JsonReader(Files.newReader(file, Charsets.UTF_8)); } catch (FileNotFoundException e) { // Shouldn't happen unless it disappears under us. return map; } try { reader.beginArray(); while (reader.peek() != JsonToken.END_ARRAY) { reader.beginObject(); SourceFile toFile = SourceFile.UNKNOWN; Map innerMap = Maps.newLinkedHashMap(); while (reader.peek() != JsonToken.END_OBJECT) { final String name = reader.nextName(); if (name.equals(KEY_OUTPUT_FILE)) { toFile = mSourceFileJsonTypeAdapter.read(reader); } else if (name.equals(KEY_MAP)) { reader.beginArray(); while (reader.peek() != JsonToken.END_ARRAY) { reader.beginObject(); SourceFilePosition from = null; SourcePosition to = null; while (reader.peek() != JsonToken.END_OBJECT) { final String innerName = reader.nextName(); if (innerName.equals(KEY_FROM)) { from = mSourceFilePositionJsonTypeAdapter.read(reader); } else if (innerName.equals(KEY_TO)) { to = mSourcePositionJsonTypeAdapter.read(reader); } else { throw new IOException( String.format("Unexpected property: %s", innerName)); } } if (from == null || to == null) { throw new IOException("Each record must contain both from and to."); } innerMap.put(to, from); reader.endObject(); } reader.endArray(); } else { throw new IOException(String.format("Unexpected property: %s", name)); } } map.put(toFile, innerMap); reader.endObject(); } reader.endArray(); return map; } catch (IOException e) { // TODO: trigger a non-incremental merge if this happens. throw new RuntimeException(e); } finally { try { reader.close(); } catch (Throwable e2) { // well, we tried. } } } /** * File format for single file blame. *
[
     *     {
     *         "merged": "/path/build/intermediates/res/merged/f1/debug/layout/main.xml",
     *         "source": "/path/src/main/res/layout/main.xml"
     *     },
     *     ...
     * ]
* @param folder * @param shard * @param map * @throws IOException */ static void saveToSingleFile( @NonNull File folder, @NonNull String shard, @NonNull Map map) throws IOException { File file = getSingleFile(folder, shard); file.getParentFile().mkdir(); JsonWriter out = new JsonWriter(Files.newWriter(file, Charsets.UTF_8)); try { out.setIndent(INDENT_STRING); out.beginArray(); for (Map.Entry entry : map.entrySet()) { out.beginObject(); out.name(KEY_MERGED); mSourceFileJsonTypeAdapter.write(out, entry.getKey()); out.name(KEY_SOURCE); mSourceFileJsonTypeAdapter.write(out, entry.getValue()); out.endObject(); } out.endArray(); } finally { out.close(); } } @NonNull static Map loadFromSingleFile( @NonNull File folder, @NonNull String shard) { Map fileMap = Maps.newConcurrentMap(); JsonReader reader; File file = getSingleFile(folder, shard); if (!file.exists()) { return fileMap; } try { reader = new JsonReader(Files.newReader(file, Charsets.UTF_8)); } catch (FileNotFoundException e) { // Shouldn't happen unless it disappears under us. return fileMap; } try { reader.beginArray(); while (reader.peek() != JsonToken.END_ARRAY) { reader.beginObject(); SourceFile merged = SourceFile.UNKNOWN; SourceFile source = SourceFile.UNKNOWN; while (reader.peek() != JsonToken.END_OBJECT) { String name = reader.nextName(); if (name.equals(KEY_MERGED)) { merged = mSourceFileJsonTypeAdapter.read(reader); } else if (name.equals(KEY_SOURCE)) { source = mSourceFileJsonTypeAdapter.read(reader); } else { throw new IOException(String.format("Unexpected property: %s", name)); } } reader.endObject(); fileMap.put(merged, source); } reader.endArray(); return fileMap; } catch (IOException e) { // TODO: trigger a non-incremental merge if this happens. throw new RuntimeException(e); } finally { try { reader.close(); } catch (Throwable e) { // well, we tried. } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy