com.android.ide.common.blame.MergingLogPersistUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sdk-common Show documentation
Show all versions of sdk-common Show documentation
sdk-common library used by other Android tools libraries.
/*
* 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