/** * Post merge clean up. * * <p>- Remove the removed items. - Clear the state of all the items (this allow newly overridden * items to lose their WRITTEN state) - Set the items that are part of the new merge to be WRITTEN * to allow the next merge to be incremental. */ private void postMergeCleanUp() { ListMultimap<String, I> itemMap = ArrayListMultimap.create(); // remove all removed items, and copy the rest in the full map while resetting their state. for (S dataSet : mDataSets) { ListMultimap<String, I> map = dataSet.getDataMap(); List<String> keys = Lists.newArrayList(map.keySet()); for (String key : keys) { List<I> list = map.get(key); for (int i = 0; i < list.size(); ) { I item = list.get(i); if (item.isRemoved()) { list.remove(i); } else { //noinspection unchecked itemMap.put(key, (I) item.resetStatus()); i++; } } } } // for the last items (the one that have been written into the consumer), set their // state to WRITTEN for (String key : itemMap.keySet()) { List<I> itemList = itemMap.get(key); itemList.get(itemList.size() - 1).resetStatusToWritten(); } }
/** * Returns the number of items. * * @return the number of items. * @see DataMap */ @Override public int size() { // put all the resource keys in a set. Set<String> keys = Sets.newHashSet(); for (S resourceSet : mDataSets) { ListMultimap<String, I> map = resourceSet.getDataMap(); keys.addAll(map.keySet()); } return keys.size(); }
/** * Returns a map of the data items. * * @return a map of items. * @see DataMap */ @NonNull @Override public ListMultimap<String, I> getDataMap() { // put all the sets in a multimap. The result is that for each key, // there is a sorted list of items from all the layers, including removed ones. ListMultimap<String, I> fullItemMultimap = ArrayListMultimap.create(); for (S resourceSet : mDataSets) { ListMultimap<String, I> map = resourceSet.getDataMap(); for (Map.Entry<String, Collection<I>> entry : map.asMap().entrySet()) { fullItemMultimap.putAll(entry.getKey(), entry.getValue()); } } return fullItemMultimap; }
/** * Sets the post blob load state to TOUCHED. * * <p>After a load from the blob file, all items have their state set to nothing. If the load mode * is not set to incrementalState then we want the items that are in the current merge result to * have their state be TOUCHED. * * <p>This will allow the first use of {@link #mergeData(MergeConsumer, boolean)} to add these to * the consumer as if they were new items. * * @see #loadFromBlob(java.io.File, boolean) * @see DataItem#isTouched() */ private void setPostBlobLoadStateToTouched() { ListMultimap<String, I> itemMap = ArrayListMultimap.create(); // put all the sets into list per keys. The order is important as the lower sets are // overridden by the higher sets. for (S dataSet : mDataSets) { ListMultimap<String, I> map = dataSet.getDataMap(); for (Map.Entry<String, Collection<I>> entry : map.asMap().entrySet()) { itemMap.putAll(entry.getKey(), entry.getValue()); } } // the items that represent the current state is the last item in the list for each key. for (String key : itemMap.keySet()) { List<I> itemList = itemMap.get(key); itemList.get(itemList.size() - 1).resetStatusToTouched(); } }
/** * Merges the data into a given consumer. * * @param consumer the consumer of the merge. * @param doCleanUp clean up the state to be able to do further incremental merges. If this is a * one-shot merge, this can be false to improve performance. * @throws MergingException such as a DuplicateDataException or a MergeConsumer.ConsumerException * if something goes wrong */ public void mergeData(@NonNull MergeConsumer<I> consumer, boolean doCleanUp) throws MergingException { consumer.start(mFactory); try { // get all the items keys. Set<String> dataItemKeys = Sets.newHashSet(); for (S dataSet : mDataSets) { // quick check on duplicates in the resource set. dataSet.checkItems(); ListMultimap<String, I> map = dataSet.getDataMap(); dataItemKeys.addAll(map.keySet()); } // loop on all the data items. for (String dataItemKey : dataItemKeys) { if (requiresMerge(dataItemKey)) { // get all the available items, from the lower priority, to the higher // priority List<I> items = Lists.newArrayListWithExpectedSize(mDataSets.size()); for (S dataSet : mDataSets) { // look for the resource key in the set ListMultimap<String, I> itemMap = dataSet.getDataMap(); List<I> setItems = itemMap.get(dataItemKey); items.addAll(setItems); } mergeItems(dataItemKey, items, consumer); continue; } // for each items, look in the data sets, starting from the end of the list. I previouslyWritten = null; I toWrite = null; /* * We are looking for what to write/delete: the last non deleted item, and the * previously written one. */ boolean foundIgnoredItem = false; setLoop: for (int i = mDataSets.size() - 1; i >= 0; i--) { S dataSet = mDataSets.get(i); // look for the resource key in the set ListMultimap<String, I> itemMap = dataSet.getDataMap(); List<I> items = itemMap.get(dataItemKey); if (items.isEmpty()) { continue; } // The list can contain at max 2 items. One touched and one deleted. // More than one deleted means there was more than one which isn't possible // More than one touched means there is more than one and this isn't possible. for (int ii = items.size() - 1; ii >= 0; ii--) { I item = items.get(ii); if (consumer.ignoreItemInMerge(item)) { foundIgnoredItem = true; continue; } if (item.isWritten()) { assert previouslyWritten == null; previouslyWritten = item; } if (toWrite == null && !item.isRemoved()) { toWrite = item; } if (toWrite != null && previouslyWritten != null) { break setLoop; } } } // done searching, we should at least have something, unless we only // found items that are not meant to be written (attr inside declare styleable) assert foundIgnoredItem || previouslyWritten != null || toWrite != null; //noinspection ConstantConditions if (previouslyWritten == null && toWrite == null) { continue; } // now need to handle, the type of each (single res file, multi res file), whether // they are the same object or not, whether the previously written object was deleted. if (toWrite == null) { // nothing to write? delete only then. assert previouslyWritten.isRemoved(); consumer.removeItem(previouslyWritten, null /*replacedBy*/); } else if (previouslyWritten == null || previouslyWritten == toWrite) { // easy one: new or updated res consumer.addItem(toWrite); } else { // replacement of a resource by another. // force write the new value toWrite.setTouched(); consumer.addItem(toWrite); // and remove the old one consumer.removeItem(previouslyWritten, toWrite); } } } finally { consumer.end(); } if (doCleanUp) { // reset all states. We can't just reset the toWrite and previouslyWritten objects // since overlayed items might have been touched as well. // Should also clean (remove) objects that are removed. postMergeCleanUp(); } }