All Downloads are FREE. Search and download functionalities are using the official Maven repository.

app.cash.paparazzi.internal.resources.MultiResourceRepository.kt Maven / Gradle / Ivy

Go to download

An Android library to render your application screens without a physical device or emulator

The newest version!
/*
 * Copyright (C) 2023 Square, Inc.
 *
 * 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 app.cash.paparazzi.internal.resources

import com.android.ide.common.rendering.api.ResourceNamespace
import com.android.ide.common.resources.ResourceItem
import com.android.ide.common.resources.ResourceRepository
import com.android.ide.common.resources.ResourceTable
import com.android.ide.common.resources.ResourceVisitor
import com.android.ide.common.resources.ResourceVisitor.VisitResult
import com.android.ide.common.resources.ResourceVisitor.VisitResult.ABORT
import com.android.ide.common.resources.ResourceVisitor.VisitResult.CONTINUE
import com.android.ide.common.resources.SingleNamespaceResourceRepository
import com.android.ide.common.resources.configuration.FolderConfiguration
import com.android.resources.ResourceType
import com.google.common.collect.ArrayListMultimap
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableListMultimap
import com.google.common.collect.ImmutableListMultimap.Builder
import com.google.common.collect.ListMultimap
import com.google.common.collect.Maps
import com.google.common.collect.Multimap
import com.google.common.collect.Multiset
import com.google.common.collect.Table
import com.google.common.collect.Tables
import java.util.function.Predicate
import kotlin.collections.Map.Entry

/**
 * Ported from: [MultiResourceRepository.java](https://cs.android.com/android-studio/platform/tools/adt/idea/+/55991b4380c1ac18e81151493f59228220c5b72a:android/src/com/android/tools/idea/res/MultiResourceRepository.java)
 *
 * A super class for several of the other repositories. Its only purpose is to be able to combine
 * multiple resource repositories and expose it as a single one, applying the “override” semantics
 * of resources: earlier children defining the same resource namespace/type/name combination will
 * replace/hide any subsequent definitions of the same resource.
 *
 * 

In the resource repository hierarchy, MultiResourceRepository is an internal node, never a leaf. */ internal abstract class MultiResourceRepository internal constructor(displayName: String) : LocalResourceRepository(displayName) { private var localResources = listOf() private var libraryResources = listOf() /** A concatenation of [localResources] and [libraryResources]. */ private var children = listOf() /** Leaf resource repositories keyed by namespace. */ private var leafsByNamespace = ImmutableListMultimap.of() /** Contained single-namespace resource repositories keyed by namespace. */ private var repositoriesByNamespace = ImmutableListMultimap.of() private var resourceComparator = ResourceItemComparator(ResourcePriorityComparator(ImmutableList.of())) private val cachedMaps = ResourceTable() /** Names of resources from local leaf repositories. */ private val resourceNames: Table> = Tables.newCustomTable(HashMap()) { Maps.newEnumMap(ResourceType::class.java) } fun setChildren( localResources: List, libraryResources: Collection ) { this.localResources = localResources.toList() this.libraryResources = libraryResources.toList() this.children = buildList(this.localResources.size + this.libraryResources.size) { addAll([email protected]) addAll([email protected]) } leafsByNamespace = ImmutableListMultimap.builder() .apply { computeLeafs(this@MultiResourceRepository, this) } .build() repositoriesByNamespace = ImmutableListMultimap.builder() .apply { computeNamespaceMap(this@MultiResourceRepository, this) } .build() resourceComparator = ResourceItemComparator(ResourcePriorityComparator(leafsByNamespace.values())) cachedMaps.clear() } override fun getNamespaces(): Set = repositoriesByNamespace.keySet() override fun accept(visitor: ResourceVisitor): VisitResult { for (namespace in namespaces) { if (visitor.shouldVisitNamespace(namespace)) { for (type in ResourceType.values()) { if (visitor.shouldVisitResourceType(type)) { val map = getMap(namespace, type) if (map != null) { for (item in map.values()) { if (visitor.visit(item) == ABORT) { return ABORT } } } } } } } return CONTINUE } override fun getMap( namespace: ResourceNamespace, resourceType: ResourceType ): ListMultimap? { val repositoriesForNamespace = leafsByNamespace[namespace] if (repositoriesForNamespace.size == 1) { val repository = repositoriesForNamespace[0] return getResources(repository, namespace, resourceType) } var map = cachedMaps[namespace, resourceType] if (map != null) { return map } // Merge all items of the given type. for (repository in repositoriesForNamespace) { val items = getResources(repository, namespace, resourceType) if (!items.isEmpty) { if (map == null) { // Create a new map. // We only add a duplicate item if there isn't an item with the same qualifiers, and it // is not a styleable or an id. Styleables and ids are allowed to be defined in multiple // places even with the same qualifiers. map = if (resourceType === ResourceType.STYLEABLE || resourceType === ResourceType.ID) { ArrayListMultimap.create() } else { PerConfigResourceMap(resourceComparator) } cachedMaps.put(namespace, resourceType, map) } map!!.putAll(items) if (repository is LocalResourceRepository) { resourceNames.put(repository, resourceType, items.keySet().toSet()) } } } return map } override fun getLeafResourceRepositories(): Collection = leafsByNamespace.values() private class ResourcePriorityComparator(repositories: Collection) : Comparator { private val repositoryOrdering: MutableMap init { repositoryOrdering = HashMap(repositories.size) var i = 0 for (repository in repositories) { repositoryOrdering[repository] = i++ } } override fun compare(item1: ResourceItem, item2: ResourceItem): Int { return getOrdering(item1).compareTo(getOrdering(item2)) } private fun getOrdering(item: ResourceItem): Int { val ordering: Int = repositoryOrdering[item.repository] ?: 0 assert(ordering >= 0) return ordering } } /** * Custom implementation of [ListMultimap] that may store multiple resource items for * the same folder configuration, but for readers exposes ot most one resource item per folder * configuration. * * * This ListMultimap implementation is not as robust as Guava multimaps but is sufficient * for MultiResourceRepository because the latter always copies data to immutable containers * before exposing it to callers. */ private class PerConfigResourceMap(private val comparator: ResourceItemComparator) : ListMultimap { private val map: MutableMap> = HashMap() private var size = 0 private var values: Values? = null override fun get(key: String?): List { val items: List? = key?.let { map[key] } return items ?: ImmutableList.of() } override fun keySet(): Set = map.keys override fun keys(): Multiset = throw UnsupportedOperationException() override fun values(): Collection { var values = this.values if (values == null) { values = Values(size) this.values = values } return values } override fun entries(): Collection> = throw UnsupportedOperationException() override fun removeAll(key: Any?): List { val removed: List? = key?.let { map.remove(it) } if (removed != null) { size -= removed.size } return removed ?: ImmutableList.of() } fun removeIf(key: String, filter: Predicate): Boolean { val list: MutableList = map[key] ?: return false val oldSize = list.size val removed = list.removeIf(filter) size += list.size - oldSize if (list.isEmpty()) { map.remove(key) } return removed } override fun clear() { map.clear() size = 0 } override fun size() = size override fun isEmpty() = size == 0 override fun containsKey(key: Any?) = key?.let { map.containsKey(it) } ?: false override fun containsValue(value: Any?): Boolean = throw UnsupportedOperationException() override fun containsEntry(key: Any?, value: Any?): Boolean = throw UnsupportedOperationException() override fun put(key: String, item: ResourceItem): Boolean { val list = map.computeIfAbsent(key) { _ -> PerConfigResourceList() } val oldSize = list.size list += item size += list.size - oldSize return true } override fun remove(key: Any?, value: Any?): Boolean = throw UnsupportedOperationException() override fun putAll(key: String, items: Iterable): Boolean { if (items is Collection<*>) { if (items.isEmpty()) { return false } val list = map.computeIfAbsent(key) { _ -> PerConfigResourceList() } val oldSize = list.size val added = list.addAll(items as Collection) size += list.size - oldSize return added } var added = false var list: MutableList? = null var oldSize = 0 for (item in items) { if (list == null) { list = map.computeIfAbsent(key) { _ -> PerConfigResourceList() } oldSize = list.size } added = list.add(item) } if (list != null) { size += list.size - oldSize } return added } override fun putAll(multimap: Multimap): Boolean { for ((key, items) in multimap.asMap().entries) { if (!items.isEmpty()) { val list = map.computeIfAbsent(key) { _ -> PerConfigResourceList() } val oldSize = list.size list += items size += list.size - oldSize } } return !multimap.isEmpty } override fun replaceValues( key: String?, values: Iterable ): List = throw UnsupportedOperationException() override fun asMap(): Map> = map /** * This class has a split personality. The class may store multiple resource items for the same * folder configuration, but for callers of non-mutating methods ([.get], * [.size], [Iterator.next], etc) it exposes at most one resource item per * folder configuration. Which of the resource items with the same folder configuration is * visible to non-mutating methods is determined by [ResourcePriorityComparator]. */ private inner class PerConfigResourceList : java.util.AbstractList() { /** Resource items sorted by folder configurations. Nested lists are sorted by repository priority. */ private val resourceItems: ArrayList> = ArrayList() override val size: Int get() = resourceItems.size override fun get(index: Int): ResourceItem = resourceItems[index][0] override fun add(element: ResourceItem): Boolean { add(element, 0) return true } override fun addAll(elements: Collection): Boolean { if (elements.isEmpty()) { return false } if (elements.size == 1) { return add(elements.iterator().next()) } val sortedItems: List = sortedItems(elements) var start = 0 for (item in sortedItems) { start = add(item, start) } return true } private fun add(item: ResourceItem, start: Int): Int { var index = findConfigIndex(item, start, resourceItems.size) if (index < 0) { index = index.inv() resourceItems.add(index, mutableListOf(item)) } else { val nested = resourceItems[index] // Iterate backwards since it is likely to require fewer iterations. var i = nested.size while (--i >= 0) { if (comparator.priorityComparator.compare(item, nested[i]) > 0) { break } } nested.add(i + 1, item) } return index } private fun sortedItems(items: Collection): List = items.sortedWith(comparator) /** * Returns index in [.resourceItems] of the existing resource item with the same * configuration as the `item` parameter. If [.resourceItems] doesn't contains * resources with the same configuration, returns binary complement of the insertion point. */ private fun findConfigIndex(item: ResourceItem, start: Int, end: Int): Int { val config: FolderConfiguration = item.configuration var low = start var high = end while (low < high) { val mid = low + high ushr 1 val value: FolderConfiguration = resourceItems[mid][0].configuration val c = value.compareTo(config) if (c < 0) { low = mid + 1 } else if (c > 0) { high = mid } else { return mid } } return low.inv() // Not found. } } private inner class Values(override val size: Int) : AbstractCollection() { override fun iterator(): Iterator { return ValuesIterator() } private inner class ValuesIterator : MutableIterator { private val outerCursor: Iterator> = map.values.iterator() private var currentList: List? = null private var innerCursor = 0 override fun hasNext(): Boolean = currentList != null || outerCursor.hasNext() override fun next(): ResourceItem { if (currentList == null) { currentList = outerCursor.next() innerCursor = 0 } return try { val item: ResourceItem = currentList!![innerCursor] if (++innerCursor >= currentList!!.size) { currentList = null } item } catch (e: IndexOutOfBoundsException) { throw NoSuchElementException() } } override fun remove() = throw UnsupportedOperationException() } } } private class ResourceItemComparator(val priorityComparator: Comparator) : Comparator { override fun compare(item1: ResourceItem, item2: ResourceItem): Int { val c: Int = item1.configuration.compareTo(item2.configuration) return if (c != 0) { c } else { priorityComparator.compare(item1, item2) } } } companion object { private fun computeLeafs( repository: ResourceRepository, result: Builder ) { if (repository is MultiResourceRepository) { for (child in repository.children) { computeLeafs(child, result) } } else { for (resourceRepository in repository.leafResourceRepositories) { result.put(resourceRepository.namespace, resourceRepository) } } } private fun computeNamespaceMap( repository: ResourceRepository, result: Builder ) { if (repository is SingleNamespaceResourceRepository) { result.put(repository.namespace, repository) } else if (repository is MultiResourceRepository) { for (child in (repository).children) { computeNamespaceMap(child, result) } } } private fun getResources( repository: SingleNamespaceResourceRepository, namespace: ResourceNamespace, type: ResourceType ): ListMultimap { if (repository is LocalResourceRepository) { val map = repository.getMapPackageAccessible(namespace, type) return map ?: ImmutableListMultimap.of() } return repository.getResources(namespace, type) } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy