DexIncrementalRenameManager implements Closeable { @NonNull Set<PackagedFileUpdate> update(@NonNull ImmutableMap<RelativeFile, FileStatus> files) throws IOException { Deque<String> nameList = Lists.newLinkedList(); DexFileNameSupplier nameSupplier = new DexFileNameSupplier(); for (int i = 0; i < mNameMap.size(); i++) { String nextName = nameSupplier.get(); nameList.add(nextName); Verify.verify(mNameMap.containsValue(nextName), "mNameMap does not contain '" + nextName + "', but has a total of " + mNameMap.size() + " entries {mNameMap = " + mNameMap + "}"); } Deque<String> deletedNames = Lists.newLinkedList(); Map<String, RelativeFile> deletedFiles = Maps.newHashMap(); for (RelativeFile deletedRf : Maps.filterValues(files, Predicates.equalTo(FileStatus.REMOVED)).keySet()) { String deletedName = mNameMap.get(deletedRf); if (deletedName == null) { throw new IOException("Incremental update refers to relative file '" + deletedRf + "' as deleted, but this file is not known."); } if (deletedName.equals(SdkConstants.FN_APK_CLASSES_DEX)) { deletedNames.addFirst(deletedName); } else { deletedNames.add(deletedName); } deletedFiles.put(deletedName, deletedRf); mNameMap.remove(deletedRf); } AtomicBoolean addingClassesDex = new AtomicBoolean(false); Deque<RelativeFile> addedFiles = Lists.newLinkedList( Maps.filterValues(files, Predicates.equalTo(FileStatus.NEW)).keySet().stream() .peek(rf -> { if (getOsIndependentFileName(rf).equals( SdkConstants.FN_APK_CLASSES_DEX)) { addingClassesDex.set(true); } }) .sorted(new DexNameComparator()) .collect(Collectors.toList())); if (addingClassesDex.get()) { RelativeFile mappingToClassesDex = mNameMap.inverse().get(SdkConstants.FN_APK_CLASSES_DEX); if (mappingToClassesDex != null) { if (!getOsIndependentFileName(mappingToClassesDex).equals( SdkConstants.FN_APK_CLASSES_DEX)) { mNameMap.remove(mappingToClassesDex); addedFiles.add(mappingToClassesDex); deletedNames.add(SdkConstants.FN_APK_CLASSES_DEX); } } } Set<String> addedNames = Sets.newHashSet(); Set<String> updatedNames = Sets.newHashSet(); Iterator<String> deletedNamesIterator = deletedNames.iterator(); for (RelativeFile addedRf : addedFiles) { if (deletedNamesIterator.hasNext()) { String toUse = deletedNamesIterator.next(); deletedNamesIterator.remove(); deletedFiles.remove(toUse); updatedNames.add(toUse); mNameMap.put(addedRf, toUse); } else { String addedName = nameSupplier.get(); addedNames.add(addedName); nameList.add(addedName); mNameMap.put(addedRf, addedName); } } for (RelativeFile updatedRf : Maps.filterValues(files, Predicates.equalTo(FileStatus.CHANGED)).keySet()) { String updatedName = mNameMap.get(updatedRf); if (updatedName == null) { throw new IOException("Incremental update refers to relative file '" + updatedRf + "' as updated, but this file is not known."); } updatedNames.add(updatedName); } Set<String> finallyDeletedNames = Sets.newHashSet(); while (true) { if (deletedNames.isEmpty()) { break; } if (deletedNames.getLast().equals(nameList.getLast())) { nameList.removeLast(); finallyDeletedNames.add(deletedNames.removeLast()); continue; } String lastInNames = nameList.removeLast(); String firstInDeleted = deletedNames.remove(); finallyDeletedNames.add(lastInNames); updatedNames.remove(lastInNames); updatedNames.add(firstInDeleted); RelativeFile file = mNameMap.inverse().get(lastInNames); Verify.verifyNotNull(file, "file == null"); mNameMap.put(file, firstInDeleted); deletedFiles.put(lastInNames, file); } Set<PackagedFileUpdate> updates = Sets.newHashSet(); for (String addedName : addedNames) { RelativeFile file = Verify.verifyNotNull(mNameMap.inverse().get(addedName)); updates.add(new PackagedFileUpdate(file, addedName, FileStatus.NEW)); } for (String updatedName : updatedNames) { RelativeFile file = Verify.verifyNotNull(mNameMap.inverse().get(updatedName)); updates.add(new PackagedFileUpdate(file, updatedName, FileStatus.CHANGED)); } for (String deletedName : finallyDeletedNames) { RelativeFile file = Verify.verifyNotNull(deletedFiles.get(deletedName)); updates.add(new PackagedFileUpdate(file, deletedName, FileStatus.REMOVED)); } return updates; } DexIncrementalRenameManager(@NonNull File incrementalDir); @Override void close();  }
@Test public void addFilesToEmptyArchive() throws Exception { DexIncrementalRenameManager mgr = make(); ImmutableMap<RelativeFile, FileStatus> set = makeNew("a/b", "b/c"); Set<PackagedFileUpdate> updates = mgr.update(set); assertEquals(2, updates.size()); if (updates.contains(packaged("a/b", "classes.dex", FileStatus.NEW))) { assertTrue(updates.contains(packaged("b/c", "classes2.dex", FileStatus.NEW))); } else { assertTrue(updates.contains(packaged("a/b", "classes2.dex", FileStatus.NEW))); assertTrue(updates.contains(packaged("b/c", "classes.dex", FileStatus.NEW))); } set = makeNew("a/d"); updates = mgr.update(set); assertEquals(1, updates.size()); assertTrue(updates.contains(packaged("a/d", "classes3.dex", FileStatus.NEW))); }
@Test public void updateFilesInArchive() throws Exception { DexIncrementalRenameManager mgr = make(); ImmutableMap<RelativeFile, FileStatus> set = makeNew("a/b", "b/c"); mgr.update(set); set = ImmutableMap.of(makeRelative("a/b"), FileStatus.CHANGED); Set<PackagedFileUpdate> updates = mgr.update(set); assertEquals(1, updates.size()); assertTrue(updates.contains(packaged("a/b", "classes.dex", FileStatus.CHANGED))); }
@Test public void deleteFilesInArchive() throws Exception { DexIncrementalRenameManager mgr = make(); assertEquals(1, mgr.update(makeNew("x/a")).size()); assertEquals(1, mgr.update(makeNew("x/b")).size()); assertEquals(1, mgr.update(makeNew("x/c")).size()); assertEquals(1, mgr.update(makeNew("x/d")).size()); Set<PackagedFileUpdate> updates = mgr.update( ImmutableMap.of(makeRelative("x/d"), FileStatus.REMOVED)); assertEquals(1, updates.size()); assertTrue(updates.contains(packaged("x/d", "classes4.dex", FileStatus.REMOVED))); updates = mgr.update(ImmutableMap.of(makeRelative("x/b"), FileStatus.REMOVED)); assertEquals(2, updates.size()); assertTrue(updates.contains(packaged("x/c", "classes3.dex", FileStatus.REMOVED))); assertTrue(updates.contains(packaged("x/c", "classes2.dex", FileStatus.CHANGED))); }
@Test public void addAndDeleteMoreAdds() throws Exception { DexIncrementalRenameManager mgr = make(); assertEquals(1, mgr.update(makeNew("x/a")).size()); assertEquals(1, mgr.update(makeNew("x/b")).size()); assertEquals(1, mgr.update(makeNew("x/c")).size()); assertEquals(1, mgr.update(makeNew("x/d")).size()); Set<PackagedFileUpdate> updates = mgr.update(ImmutableMap.of( makeRelative("x/e"), FileStatus.NEW, makeRelative("x/f"), FileStatus.NEW, makeRelative("x/b"), FileStatus.REMOVED)); assertEquals(2, updates.size()); if (updates.contains(packaged("x/e", "classes2.dex", FileStatus.CHANGED))) { assertTrue(updates.contains(packaged("x/f", "classes5.dex", FileStatus.NEW))); } else { assertTrue(updates.contains(packaged("x/e", "classes5.dex", FileStatus.NEW))); assertTrue(updates.contains(packaged("x/f", "classes2.dex", FileStatus.CHANGED))); } }
@Test public void addAndDeleteMoreDeletes() throws Exception { DexIncrementalRenameManager mgr = make(); assertEquals(1, mgr.update(makeNew("x/a")).size()); assertEquals(1, mgr.update(makeNew("x/b")).size()); assertEquals(1, mgr.update(makeNew("x/c")).size()); assertEquals(1, mgr.update(makeNew("x/d")).size()); Set<PackagedFileUpdate> updates = mgr.update(ImmutableMap.of( makeRelative("x/e"), FileStatus.NEW, makeRelative("x/a"), FileStatus.REMOVED, makeRelative("x/b"), FileStatus.REMOVED)); assertEquals(3, updates.size()); assertTrue(updates.contains(packaged("x/e", "classes.dex", FileStatus.CHANGED))); assertTrue(updates.contains(packaged("x/d", "classes2.dex", FileStatus.CHANGED))); assertTrue(updates.contains(packaged("x/d", "classes4.dex", FileStatus.REMOVED))); }
@Test public void classesDexIsNotRenamed() throws Exception { DexIncrementalRenameManager mgr = make(); PackagedFileUpdate pfu = Iterables.getOnlyElement(mgr.update(makeNew("x/y/classes.dex"))); assertEquals("classes.dex", pfu.getName()); assertEquals("y/classes.dex", pfu.getSource().getOsIndependentRelativePath()); pfu = Iterables.getOnlyElement(mgr.update(makeNew("a/b/classes.dex"))); assertEquals("classes2.dex", pfu.getName()); assertEquals("b/classes.dex", pfu.getSource().getOsIndependentRelativePath()); }
@Test public void initialClassesDexNameIsKept() throws Exception { DexIncrementalRenameManager mgr = make(); Set<PackagedFileUpdate> updates = mgr.update(makeNew("a/abc.dex", "a/classes.dex", "a/foo.dex")); PackagedFileUpdate cdex = Iterables.getOnlyElement( updates.stream() .filter(u -> u.getName().equals("classes.dex")) .collect(Collectors.toList())); assertEquals("classes.dex", cdex.getSource().getOsIndependentRelativePath()); }
@Test public void classesDexIsRemovedAndLaterAdded() throws Exception { DexIncrementalRenameManager mgr = make(); PackagedFileUpdate pfu = Iterables.getOnlyElement(mgr.update(makeNew("x/y/classes.dex"))); assertEquals("classes.dex", pfu.getName()); PackagedFileUpdate pfu2 = Iterables.getOnlyElement(mgr.update(makeNew("x/y/aaa.dex"))); assertEquals("classes2.dex", pfu2.getName()); Set<PackagedFileUpdate> pfu3 = mgr.update(ImmutableMap.of( makeRelative("x/y/classes.dex"), FileStatus.REMOVED)); assertEquals(2, pfu3.size()); PackagedFileUpdate pfu3_1 = Iterables.getOnlyElement(pfu3.stream() .filter(u -> u.getName().equals("classes.dex")) .collect(Collectors.toList())); PackagedFileUpdate pfu3_2 = Iterables.getOnlyElement(pfu3.stream() .filter(u -> u.getName().equals("classes2.dex")) .collect(Collectors.toList())); assertEquals(FileStatus.CHANGED, pfu3_1.getStatus()); assertEquals("classes.dex", pfu3_1.getName()); assertEquals("y/aaa.dex", pfu3_1.getSource().getOsIndependentRelativePath()); assertEquals(FileStatus.REMOVED, pfu3_2.getStatus()); assertEquals("classes2.dex", pfu3_2.getName()); assertEquals("y/aaa.dex", pfu3_2.getSource().getOsIndependentRelativePath()); Set<PackagedFileUpdate> pfu4 = mgr.update(makeNew("x/y/z/classes.dex")); assertEquals(2, pfu4.size()); PackagedFileUpdate pfu4_1 = Iterables.getOnlyElement(pfu4.stream() .filter(u -> u.getName().equals("classes.dex")) .collect(Collectors.toList())); PackagedFileUpdate pfu4_2 = Iterables.getOnlyElement(pfu4.stream() .filter(u -> u.getName().equals("classes2.dex")) .collect(Collectors.toList())); assertEquals(FileStatus.CHANGED, pfu4_1.getStatus()); assertEquals("classes.dex", pfu4_1.getName()); assertEquals("y/z/classes.dex", pfu4_1.getSource().getOsIndependentRelativePath()); assertEquals(FileStatus.NEW, pfu4_2.getStatus()); assertEquals("classes2.dex", pfu4_2.getName()); assertEquals("y/aaa.dex", pfu4_2.getSource().getOsIndependentRelativePath()); }