| package com.android.tools.r8.dex; | |
| import com.android.tools.r8.graph.DexString; | |
| import com.google.gson.JsonElement; | |
| import com.google.gson.JsonObject; | |
| import com.google.gson.JsonParser; | |
| import com.google.gson.JsonSyntaxException; | |
| import it.unimi.dsi.fastutil.objects.Object2LongMap; | |
| import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; | |
| import java.io.UTFDataFormatException; | |
| import java.util.Comparator; | |
| import java.util.Map; | |
| public class ClassesChecksum { | |
| private static final String PREFIX = "~~~"; | |
| private static final char PREFIX_CHAR0 = '~'; | |
| private static final char PREFIX_CHAR1 = '~'; | |
| private static final char PREFIX_CHAR2 = '~'; | |
| private final Object2LongMap<String> dictionary = new Object2LongOpenHashMap<>(); | |
| public ClassesChecksum() { | |
| assert PREFIX.length() == 3; | |
| assert PREFIX.charAt(0) == PREFIX_CHAR0; | |
| assert PREFIX.charAt(1) == PREFIX_CHAR1; | |
| assert PREFIX.charAt(2) == PREFIX_CHAR2; | |
| } | |
| private void append(JsonObject json) { | |
| json.entrySet() | |
| .forEach( | |
| entry -> | |
| dictionary.put(entry.getKey(), Long.parseLong(entry.getValue().getAsString(), 16))); | |
| } | |
| public void addChecksum(String classDescriptor, long crc) { | |
| dictionary.put(classDescriptor, crc); | |
| } | |
| public Object2LongMap<String> getChecksums() { | |
| return dictionary; | |
| } | |
| public String toJsonString() { | |
| // In order to make printing of markers deterministic we sort the entries by key. | |
| final JsonObject sortedJson = new JsonObject(); | |
| dictionary.object2LongEntrySet().stream() | |
| .sorted(Comparator.comparing(Map.Entry::getKey)) | |
| .forEach( | |
| entry -> | |
| sortedJson.addProperty(entry.getKey(), Long.toString(entry.getLongValue(), 16))); | |
| return "" + PREFIX_CHAR0 + PREFIX_CHAR1 + PREFIX_CHAR2 + sortedJson; | |
| } | |
| // Try to parse the string as a marker and append its content if successful. | |
| public void tryParseAndAppend(DexString dexString) { | |
| if (dexString.size > 2 | |
| && dexString.content[0] == PREFIX_CHAR0 | |
| && dexString.content[1] == PREFIX_CHAR1 | |
| && dexString.content[2] == PREFIX_CHAR2) { | |
| String str = dexString.toString().substring(3); | |
| try { | |
| JsonElement result = new JsonParser().parse(str); | |
| if (result.isJsonObject()) { | |
| append(result.getAsJsonObject()); | |
| } | |
| } catch (JsonSyntaxException ignored) {} | |
| } | |
| } | |
| /** | |
| * Check if this string will definitely preceded the checksum marker. | |
| * | |
| * <p>If true is returned the string passed definitely preceded the checksum marker. If false is | |
| * returned the string passed might still preceded, so this can give false negatives. | |
| * | |
| * @param string String to check if definitely preceded the checksum marker. | |
| * @return If the string passed definitely preceded the checksum marker | |
| */ | |
| public static boolean definitelyPrecedesChecksumMarker(DexString string) { | |
| try { | |
| assert PREFIX.length() == 3; | |
| char[] prefix = new char[PREFIX.length()]; | |
| int prefixLength = string.decodePrefix(prefix); | |
| if (prefixLength == 0) { | |
| return true; | |
| } | |
| if (prefix[0] != PREFIX_CHAR0) { | |
| return prefix[0] < PREFIX_CHAR0; | |
| } | |
| if (prefixLength == 1) { | |
| return true; | |
| } | |
| if (prefix[1] != PREFIX_CHAR1) { | |
| return prefix[1] < PREFIX_CHAR1; | |
| } | |
| if (prefixLength == 2) { | |
| return true; | |
| } | |
| return prefix[2] < PREFIX_CHAR2; | |
| } catch (UTFDataFormatException e) { | |
| throw new RuntimeException("Bad format", e); | |
| } | |
| } | |
| } |