blob: fe2f26f29bfdb2e1354b82f400777d3d6813a7d5 [file] [log] [blame]
/*
* Copyright (C) 2018 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.tools.deployer;
import com.android.tools.deployer.model.Apk;
import com.android.tools.deployer.model.ApkEntry;
import com.android.tools.deployer.model.FileDiff;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class ApkDiffer {
public List<FileDiff> specDiff(DeploymentCacheDatabase.Entry cacheEntry, List<Apk> newApks)
throws DeployerException {
if (cacheEntry == null) {
// TODO: We could just fall back to non-optimistic swap.
throw DeployerException.remoteApkNotFound();
}
// This function performs a standard diff, but additionally ensures that any resource not
// currently present in the overlay is added to the list of file diffs.
DiffFunction compare =
(oldFile, newFile) -> {
Optional<FileDiff> normalDiff = standardDiff(oldFile, newFile);
// If there's a real diff, prefer to return that.
if (normalDiff.isPresent()) {
return normalDiff;
}
// If newFile == null, standardDiff would have returned a diff. We can assume
// newFile is not null from this point forward.
boolean inOverlay =
cacheEntry
.getOverlayContents()
.containsFile(newFile.getQualifiedPath());
boolean isResource = newFile.getName().startsWith("res");
if (!inOverlay && isResource) {
return Optional.of(
new FileDiff(
null, newFile, FileDiff.Status.RESOURCE_NOT_IN_OVERLAY));
}
return Optional.empty();
};
return diff(cacheEntry.getApks(), newApks, compare);
}
public List<FileDiff> diff(List<Apk> oldApks, List<Apk> newApks) throws DeployerException {
return diff(oldApks, newApks, ApkDiffer::standardDiff);
}
public List<FileDiff> diff(List<Apk> oldApks, List<Apk> newApks, DiffFunction compare)
throws DeployerException {
List<ApkEntry> oldFiles = new ArrayList<>();
Map<String, Map<String, ApkEntry>> oldMap = new HashMap<>();
groupFiles(oldApks, oldFiles, oldMap);
List<ApkEntry> newFiles = new ArrayList<>();
Map<String, Map<String, ApkEntry>> newMap = new HashMap<>();
groupFiles(newApks, newFiles, newMap);
if (newMap.size() != oldMap.size()) {
throw DeployerException.apkCountMismatch();
}
if (!newMap.keySet().equals(oldMap.keySet())) {
throw DeployerException.apkNameMismatch();
}
// Traverse local and remote list of crcs in order to detect what has changed in a local apk.
List<FileDiff> diffs = new ArrayList<>();
for (ApkEntry newFile : newFiles) {
ApkEntry oldFile = oldMap.get(newFile.getApk().name).get(newFile.getName());
compare.diff(oldFile, newFile).ifPresent(diffs::add);
}
for (ApkEntry oldFile : oldFiles) {
ApkEntry newFile = newMap.get(oldFile.getApk().name).get(oldFile.getName());
if (newFile == null) {
compare.diff(oldFile, null).ifPresent(diffs::add);
}
}
return diffs;
}
private static void groupFiles(
List<Apk> apks, List<ApkEntry> entries, Map<String, Map<String, ApkEntry>> map) {
for (Apk apk : apks) {
map.putIfAbsent(apk.name, apk.apkEntries);
entries.addAll(apk.apkEntries.values());
}
}
private static Optional<FileDiff> standardDiff(ApkEntry oldFile, ApkEntry newFile) {
FileDiff.Status status = null;
if (oldFile == null) {
status = FileDiff.Status.CREATED;
} else if (newFile == null) {
status = FileDiff.Status.DELETED;
} else if (oldFile.getChecksum() != newFile.getChecksum()) {
status = FileDiff.Status.MODIFIED;
}
if (status != null) {
return Optional.of(new FileDiff(oldFile, newFile, status));
}
return Optional.empty();
}
@FunctionalInterface
private interface DiffFunction {
Optional<FileDiff> diff(ApkEntry oldFile, ApkEntry newFile);
}
}