| package com.android.aaptcompiler |
| |
| import com.android.aapt.Resources |
| import com.android.aaptcompiler.android.isTruthy |
| import com.android.resources.ResourceVisibility |
| import com.android.utils.ILogger |
| import java.io.File |
| import java.util.SortedMap |
| import java.util.TreeMap |
| |
| /** |
| * The container and index for all resources defined for a given app. |
| * |
| * <p> The Resource Table is an organizer of all resources. It sorts the resources by package, then |
| * by type, then by name, by config, and finally by product, if applicable. [addResource], |
| * [addFileReference] are used to add resources declared in the current app and have their resource |
| * names validated. While [addResourceMangled] and [addFileReferenceMangled] are used to add |
| * resources from libraries and are not validated. |
| */ |
| class ResourceTable(val validateResources: Boolean = false, val logger: ILogger? = null) { |
| /** |
| * The string pool used by this resource table. Values that reference strings must use |
| * this pool to create their strings. |
| */ |
| val stringPool = StringPool() |
| |
| /** The list of packages in this table. */ |
| internal val packages = mutableListOf<ResourceTablePackage>() |
| |
| /** |
| * Set of dynamic packages that this table may reference. Their package names get encoded into the |
| * resources.arsc along with their compile-time assigned IDs. |
| */ |
| private val includedPackages = mutableMapOf<Int, String>() |
| |
| enum class CollisionResult { |
| KEEP_ORIGINAL, |
| CONFLICT, |
| TAKE_NEW |
| } |
| |
| data class SearchResult( |
| val tablePackage: ResourceTablePackage, val group: ResourceGroup, val entry: ResourceEntry) |
| |
| /** |
| * Adds a resource to the table with the given value. |
| * |
| * @param name The full name of this resource. This includes package name, resource type, and the |
| * entry name of the resource. |
| * @param config The configuration this value for the given resource apply. |
| * @param product The product for which this value applies. |
| * @param value The value associated with the given resource. |
| * @return Returns false if and only if the resource was not able to be added. This is caused if |
| * the resource name failed to be validated or there exists a conflict with a resource already in |
| * the table. |
| */ |
| fun addResource( |
| name: ResourceName, config: ConfigDescription, product: String, value: Value): Boolean = |
| addResourceImpl( |
| name, 0, config, product, value, ::resourceNameValidator, ::resolveValueCollision) |
| |
| |
| /** |
| * Adds a resource to the table with the given id and value. |
| * |
| * @param name The full name of this resource. This includes package name, resource type, and the |
| * entry name of the resource. |
| * @param id the id of the given resource. |
| * @param config The configuration this value for the given resource apply. |
| * @param product The product for which this value applies. |
| * @param value The value associated with the given resource. |
| * @return Returns false if and only if the resource was not able to be added. This is caused if |
| * the resource name failed to be validated, there exists a conflict with a resource already in |
| * the table, or the id is a dynamic id but does not match with the package, type, or entry. |
| */ |
| fun addResourceWithId( |
| name: ResourceName, |
| id: Int, |
| config: ConfigDescription, |
| product: String, |
| value: Value): Boolean = |
| addResourceImpl( |
| name, id, config, product, value, ::resourceNameValidator, ::resolveValueCollision) |
| |
| /** |
| * Adds a file reference resource to the table with the given file path. |
| * |
| * @param name The full name of this resource. This includes package name, resource type and the |
| * entry name of the resource. |
| * @param config The configuration this value for the given file reference applies. |
| * @param source The place in source where the reference exists. |
| * @param path The path name to the file being referenced. |
| * @return Returns false if and only if the resource was not able to be added. This is caused if |
| * the resource name failed to be validated or there exists a conflict with a resource already in |
| * the table. |
| */ |
| fun addFileReference( |
| name: ResourceName,config: ConfigDescription, source: Source, path: String): Boolean = |
| addFileReferenceImpl( |
| name, config, source, path, null, ::resourceNameValidator) |
| |
| /** |
| * Same as [addFileReference], but doesn't verify the name of the file reference resource. This is |
| * used when loading resources from an existing binary resource table that may have mangled |
| * resources. |
| * |
| * @param name The full name of this resource. This includes package name, resource type and the |
| * entry name of the resource. |
| * @param config The configuration this value for the given file reference applies. |
| * @param source The place in source where the reference exists. |
| * @param path The path name to the file being referenced. |
| * @param file The file object referenced by this resource. |
| * @return Returns false if and only if the resource was not able to be added. This is caused if |
| * the resource name failed to be validated or there exists a conflict with a resource already in |
| * the table. |
| */ |
| fun addFileReferenceMangled( |
| name: ResourceName, |
| config: ConfigDescription, |
| source: Source, |
| path: String, |
| file: File): Boolean = |
| addFileReferenceImpl(name, config, source, path, file, ::skipNameValidator) |
| |
| /** |
| * Same as [addResource], but doesn't verify the name of the resource. This is used when loading |
| * resources from an existing binary resource table that may have mangled names. |
| * |
| * @param name The full name of this resource. This includes package name, resource type, and the |
| * entry name of the resource. |
| * @param config The configuration this value for the given resource apply. |
| * @param product The product for which this value applies. |
| * @param value The value associated with the given resource. |
| * @return Returns false if and only if the resource was not able to be added. This is caused if |
| * there exists a conflict with a resource already in the table. |
| */ |
| fun addResourceMangled( |
| name: ResourceName, config: ConfigDescription, product: String, value: Value): Boolean = |
| addResourceImpl( |
| name, 0, config, product, value, ::skipNameValidator, ::resolveValueCollision) |
| |
| /** |
| * Same as [addResourceWithId], but doesn't verify the name of the resource. This is used when |
| * loading resources from an existing binary resource table that may have mangled names. |
| * |
| * @param name The full name of this resource. This includes package name, resource type, and the |
| * entry name of the resource. |
| * @param id the id of the given resource. |
| * @param config The configuration this value for the given resource apply. |
| * @param product The product for which this value applies. |
| * @param value The value associated with the given resource. |
| * @return Returns false if and only if the resource was not able to be added. This is caused if |
| * there exists a conflict with a resource already in the table, or the id is a dynamic id but |
| * does not match with the package, type, or entry. |
| */ |
| fun addResourceWithIdMangled( |
| name: ResourceName, |
| id: Int, |
| config: ConfigDescription, |
| product: String, |
| value: Value) = |
| addResourceImpl( |
| name, id, config, product, value, ::skipNameValidator, ::resolveValueCollision) |
| |
| /** |
| * Sets the resource with the given name to the set visibility. If this resource has no value in |
| * the table, the resource will be created with no value set to the given visibility. |
| * |
| * @param name The full name of the resource, whose visibility will be modified. |
| * @param visibility the new Visibility of the resource. |
| * @return true, if and only if the visibility was able to be updated. This may not happen if the |
| * visibility has already been set to at least as visible as the new visibility. |
| */ |
| fun setVisibility(name: ResourceName, visibility: Visibility) = |
| setVisibilityImpl(name, visibility, 0, ::resourceNameValidator) |
| |
| fun setVisibilityWithId(name: ResourceName, visibility: Visibility, id: Int) = |
| setVisibilityImpl(name, visibility, id, ::resourceNameValidator) |
| |
| fun setVisibilityMangled(name: ResourceName, visibility: Visibility) = |
| setVisibilityImpl(name, visibility, 0, ::skipNameValidator) |
| |
| fun setVisibilityWithIdMangled(name: ResourceName, visibility: Visibility, id: Int) = |
| setVisibilityImpl(name, visibility, id, ::skipNameValidator) |
| |
| fun setAllowNew(name: ResourceName, allowNew: AllowNew) = |
| setAllowNewImpl(name, allowNew, ::resourceNameValidator) |
| |
| fun setAllowNewMangled(name: ResourceName, allowNew: AllowNew) = |
| setAllowNewImpl(name, allowNew, ::skipNameValidator) |
| |
| fun setOverlayable(name: ResourceName, overlayable: OverlayableItem) = |
| setOverlayableImpl(name, overlayable, ::resourceNameValidator) |
| |
| fun setOverlayableMangled(name: ResourceName, overlayable: OverlayableItem) = |
| setOverlayableImpl(name, overlayable, ::skipNameValidator) |
| |
| fun findResource(name: ResourceName): SearchResult? { |
| val tablePackage = findPackage(name.pck!!) |
| tablePackage ?: return null |
| |
| val group = tablePackage.findGroup(name.type) |
| group ?: return null |
| |
| val entry = group.findEntry(name.entry!!) |
| entry ?: return null |
| |
| return SearchResult(tablePackage, group, entry) |
| } |
| |
| /** |
| * Returns the package struct with the given name, or null if such a package does not |
| * exist. The empty string is a valid package and typically is used to represent the |
| * 'current' package before it is known to the ResourceTable. |
| * |
| * @param name the name of the package. |
| * @return the [ResourceTablePackage] with the requested name or {@code null} if that package |
| * does not exist in the table. |
| */ |
| fun findPackage(name: String): ResourceTablePackage? { |
| return packages.find { it.name == name } |
| } |
| |
| fun findPackageById(id: Byte): ResourceTablePackage? { |
| return packages.find { it.id == id } |
| } |
| |
| fun createPackage(name: String, id: Byte = 0): ResourceTablePackage? { |
| val tablePackage = findOrCreatePackage(name) |
| |
| if (id != 0.toByte()) { |
| when { |
| tablePackage.id == null -> { |
| tablePackage.id = id |
| return tablePackage |
| } |
| tablePackage.id != id -> return null |
| } |
| } |
| |
| return tablePackage |
| } |
| |
| /** |
| * Attempts to find a package having the specified name and ID. If not found, a new package |
| * of the specified parameters is created and returned. |
| */ |
| fun createPackageAllowingDuplicateNames(name: String, id: Byte): ResourceTablePackage { |
| val match = packages.find { it.name == name && it.id == id } |
| if (match != null) { |
| return match |
| } |
| val newPackage = ResourceTablePackage(name, id) |
| packages.add(newPackage) |
| return newPackage |
| } |
| |
| private fun findOrCreatePackage(name: String): ResourceTablePackage { |
| val tablePackage = findPackage(name) |
| return when (tablePackage) { |
| null -> { |
| val newPackage = ResourceTablePackage() |
| newPackage.name = name |
| packages.add(newPackage) |
| newPackage |
| } |
| else -> tablePackage |
| } |
| } |
| |
| fun sort() { |
| packages.sortWith(compareBy({it.name}, {it.id})) |
| for (pkg in packages) { |
| pkg.groups.sortWith(compareBy({it.type.ordinal}, {it.id})) |
| for (group in pkg.groups) { |
| for (entryByName in group.entries.values) { |
| for (entry in entryByName.values) { |
| entry.values.sortWith(compareBy({ it.config }, { it.product })) |
| } |
| } |
| } |
| } |
| } |
| |
| private fun logError(formatMessage: String, vararg args: Any?) { |
| logger?.error(null, formatMessage, args) |
| } |
| |
| private fun validateName( |
| nameValidator: (String) -> String, name: ResourceName, source: Source): Boolean { |
| val badCodePoint = nameValidator.invoke(name.entry!!) |
| if (badCodePoint.isNotEmpty()) { |
| val errorMsg = "%s, Resource '%s' has invalid entry name '%s'. Invalid character '%s'." |
| logError(errorMsg, blameSource(source), name, name.entry, badCodePoint) |
| return false |
| } |
| return true |
| } |
| |
| private fun addResourceImpl( |
| name: ResourceName, |
| id: Int, |
| config: ConfigDescription, |
| product: String, |
| value: Value, |
| nameValidator: (String) -> String, |
| conflictResolver: (Value, Value) -> CollisionResult): Boolean { |
| |
| val source = value.source |
| |
| if (!validateName(nameValidator, name, source)) { |
| return false |
| } |
| |
| val tablePackage = findOrCreatePackage(name.pck!!) |
| val packageId = tablePackage.id |
| if (id.isValidDynamicId() && packageId != null && id.getPackageId() != packageId) { |
| val errorMsg = |
| "%s, Failed to add resource '%s' with ID %s because package '%s' already has ID %s." |
| logError( |
| errorMsg, |
| blameSource(value.source), |
| name, |
| id.toString(16), |
| tablePackage.name, |
| packageId.toString(16)) |
| return false |
| } |
| |
| val checkId = validateResources && id.isValidDynamicId() |
| val useId = !validateResources && id.isValidDynamicId() |
| |
| val resourceGroup = |
| tablePackage.findOrCreateGroup(name.type, if (useId) id.getTypeId() else null) |
| val groupId = resourceGroup.id |
| if (checkId && groupId != null && groupId != id.getTypeId()) { |
| val errorMsg = |
| "%s, Failed to add resource '%s' with ID %s because type '%s' already has ID %s." |
| logError( |
| errorMsg, |
| blameSource(value.source), |
| name, |
| id.toString(16), |
| resourceGroup.type.tagName, |
| groupId.toString(16)) |
| return false |
| } |
| |
| val resourceEntry = |
| resourceGroup.findOrCreateEntry(name.entry!!, if (useId) id.getEntryId() else null) |
| val entryId = resourceEntry.id |
| if (checkId && entryId != null && id.getEntryId() != entryId) { |
| val errorMsg = |
| "%s, Failed to add resource '%s' with ID %s, because resource already has ID %s." |
| logError( |
| errorMsg, |
| blameSource(value.source), |
| name, |
| id.toString(16), |
| resourceIdFromParts(packageId!!, groupId!!, entryId).toString(16)) |
| return false |
| } |
| |
| val configValue = resourceEntry.findOrCreateValue(config, product) |
| val oldValue = configValue.value |
| if (oldValue == null) { |
| // Resource does not exist, add it now. |
| configValue.value = value |
| } else { |
| when (conflictResolver.invoke(oldValue, value)) { |
| CollisionResult.TAKE_NEW -> configValue.value = value |
| CollisionResult.CONFLICT -> { |
| val errorMsg = |
| "%s, Duplicate value for resource '%s' with config '%s' and product '%s'. Resource " + |
| "was previously defined here: %s." |
| logError( |
| errorMsg, |
| blameSource(value.source), |
| name, |
| config, |
| product, |
| blameSource(oldValue.source)) |
| return false |
| } |
| CollisionResult.KEEP_ORIGINAL -> {} |
| } |
| } |
| |
| if (id.isValidDynamicId()) { |
| tablePackage.id = id.getPackageId() |
| resourceGroup.id = id.getTypeId() |
| resourceEntry.id = id.getEntryId() |
| } |
| |
| return true |
| } |
| |
| private fun addFileReferenceImpl( |
| name: ResourceName, |
| config: ConfigDescription, |
| source: Source, |
| path: String, |
| file: File?, |
| nameValidator: (String) -> String): Boolean { |
| val fileReference = FileReference(stringPool.makeRef(path)) |
| fileReference.source = source |
| fileReference.file = file |
| return addResourceImpl( |
| name, 0, config, "", fileReference, nameValidator, ::resolveValueCollision) |
| } |
| |
| private fun setVisibilityImpl( |
| name: ResourceName, |
| visibility: Visibility, |
| id: Int, |
| nameValidator: (String) -> String) : Boolean { |
| val source = visibility.source |
| if (!validateName(nameValidator, name, source)) { |
| return false |
| } |
| |
| val tablePackage = findOrCreatePackage(name.pck!!) |
| val packageId = tablePackage.id |
| if (id.isValidDynamicId() && packageId != null && id.getPackageId() != packageId) { |
| val errorMsg = |
| "%s, Failed to add resource '%s' with ID %s because package '%s' already has ID %s." |
| logError( |
| errorMsg, |
| blameSource(source), |
| name, |
| id.toString(16), |
| tablePackage.name, |
| packageId.toString(16)) |
| return false |
| } |
| |
| val checkId = validateResources && id.isValidDynamicId() |
| val useId = !validateResources && id.isValidDynamicId() |
| |
| val resourceGroup = |
| tablePackage.findOrCreateGroup(name.type, if(useId) id.getTypeId() else null) |
| val groupId = resourceGroup.id |
| if (checkId && groupId != null && groupId != id.getTypeId()) { |
| val errorMsg = |
| "%s, Failed to add resource '%s' with ID %s because type '%s' already has ID %s." |
| logError( |
| errorMsg, |
| blameSource(source), |
| name, |
| id.toString(16), |
| resourceGroup.type.tagName, |
| groupId.toString(16)) |
| return false |
| } |
| |
| val resourceEntry = |
| resourceGroup.findOrCreateEntry(name.entry!!, if (useId) id.getEntryId() else null) |
| val entryId = resourceEntry.id |
| if (checkId && entryId != null && id.getEntryId() != entryId) { |
| val errorMsg = |
| "%s, Failed to add resource '%s' with ID %s, because resource already has ID %s." |
| logError( |
| errorMsg, |
| blameSource(source), |
| name, |
| id.toString(16), |
| resourceIdFromParts(packageId!!, groupId!!, entryId).toString(16)) |
| return false |
| } |
| |
| if (id.isValidDynamicId()) { |
| tablePackage.id = id.getPackageId() |
| resourceGroup.id = id.getTypeId() |
| resourceEntry.id = id.getEntryId() |
| } |
| |
| // Only mark the group visibility level as public, it doesn't care about being private. |
| if (visibility.level == ResourceVisibility.PUBLIC) { |
| resourceGroup.visibility = ResourceVisibility.PUBLIC |
| } |
| |
| when { |
| visibility.level == ResourceVisibility.UNDEFINED && |
| resourceEntry.visibility.level != ResourceVisibility.UNDEFINED -> { |
| // We can't undefine a symbol. Ignore |
| } |
| visibility.level == ResourceVisibility.PRIVATE && |
| resourceEntry.visibility.level == ResourceVisibility.PUBLIC -> { |
| // We can't downgrade public to private. Ignore. |
| } |
| else -> { |
| // This symbol definition takes precedence. |
| resourceEntry.visibility = visibility |
| } |
| } |
| |
| return true |
| } |
| |
| private fun setAllowNewImpl( |
| name: ResourceName, allowNew: AllowNew, nameValidator: (String) -> String): Boolean { |
| if (!validateName(nameValidator, name, allowNew.source)) { |
| return false |
| } |
| |
| val tablePackage = findOrCreatePackage(name.pck!!) |
| val group = tablePackage.findOrCreateGroup(name.type) |
| val entry = group.findOrCreateEntry(name.entry!!) |
| entry.allowNew = allowNew |
| return true |
| } |
| |
| private fun setOverlayableImpl( |
| name: ResourceName, overlayable: OverlayableItem, nameValidator: (String) -> String): Boolean { |
| if (!validateName(nameValidator, name, overlayable.source)) { |
| return false |
| } |
| |
| val tablePackage = findOrCreatePackage(name.pck!!) |
| val group = tablePackage.findOrCreateGroup(name.type) |
| val entry = group.findOrCreateEntry(name.entry!!) |
| |
| val oldEntry = entry.overlayable |
| if (oldEntry != null) { |
| val errorMsg = |
| "%s, Failed to add overlayable declaration for resource '%s', because resource already " + |
| "has an overlayable defined here: %s." |
| logError( |
| errorMsg, |
| blameSource(overlayable.source), |
| name, |
| blameSource(oldEntry.source) |
| ) |
| return false |
| } |
| entry.overlayable = overlayable |
| return true |
| } |
| |
| companion object { |
| fun resolveValueCollision(existing: Value, incoming: Value):CollisionResult { |
| val existingAttr = existing as? AttributeResource |
| val incomingAttr = incoming as? AttributeResource |
| |
| incomingAttr ?: return when { |
| incoming.weak -> CollisionResult.KEEP_ORIGINAL |
| existing.weak -> CollisionResult.TAKE_NEW |
| else -> CollisionResult.CONFLICT |
| } |
| |
| existingAttr ?: return when { |
| existing.weak -> CollisionResult.TAKE_NEW |
| else -> CollisionResult.CONFLICT |
| } |
| |
| // Attribute specific handling. Since declarations and definitions of attributes can happen |
| // almost anywhere, we need special handling to see which definition sticks. |
| if (existingAttr.isCompatibleWith(incomingAttr)) { |
| // The two attributes are both DECLs, but they are plain attributes with compatible formats. |
| // keep the strongest. |
| return if (existingAttr.weak) CollisionResult.TAKE_NEW else CollisionResult.KEEP_ORIGINAL |
| } |
| |
| if (existingAttr.weak && existingAttr.typeMask == Resources.Attribute.FormatFlags.ANY_VALUE) { |
| // Any incoming attribute is better than this. |
| return CollisionResult.TAKE_NEW |
| } |
| |
| if (incomingAttr.weak && incomingAttr.typeMask == Resources.Attribute.FormatFlags.ANY_VALUE) { |
| // The incoming attribute may be a USE instead of a DECL. Keep the existing attribute. |
| return CollisionResult.KEEP_ORIGINAL |
| } |
| |
| return CollisionResult.CONFLICT |
| } |
| |
| fun resourceNameValidator(name: String): String = |
| if (isValidResourceEntryName(name)) "" else name |
| |
| fun skipNameValidator(name: String): String { |
| return "" |
| } |
| } |
| } |
| |
| /** the public status of a resource */ |
| class Visibility( |
| val source: Source = Source(""), |
| val comment: String = "", |
| val level: ResourceVisibility = ResourceVisibility.UNDEFINED) |
| |
| /** Represents <add-resource> in an overlay */ |
| class AllowNew ( |
| val source: Source, |
| val comment: String) |
| |
| class Overlayable ( |
| val name: String, |
| val actor: String, |
| val source: Source) { |
| |
| constructor(): this("","", Source("")) |
| |
| companion object { |
| const val ACTOR_SCHEME = "overlay" |
| const val ACTOR_SCHEME_URI = "$ACTOR_SCHEME://" |
| } |
| |
| override fun equals(other: Any?): Boolean { |
| if (other is Overlayable) { |
| return name == other.name && actor == other.actor |
| } |
| return false |
| } |
| } |
| |
| class OverlayableItem( |
| val overlayable: Overlayable, |
| val policies: Int = Policy.NONE, |
| val comment: String = "", |
| val source: Source = Source("")) { |
| |
| override fun equals(other: Any?): Boolean { |
| if (other is OverlayableItem) { |
| return overlayable == other.overlayable && policies == other.policies |
| } |
| return false |
| } |
| |
| /** Represents the types of overlays that are allowed to overlay the resource. */ |
| object Policy { |
| const val NONE = 0 |
| /** The resource can be overlaid by any overlay. */ |
| const val PUBLIC = 1 shl 0 |
| /** The resource can be overlaid by any overlay on the system partition. */ |
| const val SYSTEM = 1 shl 1 |
| /** The resource can be overlaid by any overlay on the vendor partition. */ |
| const val VENDOR = 1 shl 2 |
| /** The resource can be overlaid by any overlay on the product partition. */ |
| const val PRODUCT = 1 shl 3 |
| /** The resource can be overlaid by any overlay signed with the same signature as its actor. */ |
| const val SIGNATURE = 1 shl 4 |
| /** The resource can be overlaid by any overlay on the odm partition. */ |
| const val ODM = 1 shl 5 |
| /** The resource can be overlaid by any overlay on the oem partition. */ |
| const val OEM = 1 shl 6 |
| } |
| } |
| |
| /** Represents all groups by type under a single package. */ |
| class ResourceTablePackage(var name: String = "", var id: Byte? = null) { |
| |
| internal val groups = mutableListOf<ResourceGroup>() |
| |
| fun findGroup(type: AaptResourceType, groupId: Byte? = null) = |
| if (groupId != null) { |
| groups.find {type == it.type && groupId == it.id} ?: |
| groups.find {type == it.type && it.id == null} |
| } else { |
| groups.find {type == it.type} |
| } |
| |
| fun findOrCreateGroup(type: AaptResourceType, groupId: Byte? = null): ResourceGroup { |
| val group = findGroup(type, groupId) |
| return when(group) { |
| null -> { |
| val newGroup = ResourceGroup(type) |
| newGroup.id = groupId |
| groups.add(newGroup) |
| newGroup |
| } |
| else -> group |
| } |
| } |
| } |
| |
| /** |
| * Represents all resource entries grouped under a resource type (eg. string, drawable, layout, |
| * etc.). |
| */ |
| class ResourceGroup(val type : AaptResourceType) { |
| var id: Byte? = null |
| var visibility = ResourceVisibility.UNDEFINED |
| |
| internal val entries = sortedMapOf<String, SortedMap<Short?, ResourceEntry>>() |
| |
| fun findEntry(name: String, entryId: Short? = null): ResourceEntry? { |
| val nameGroup = entries[name] ?: return null |
| return if (entryId != null) { |
| nameGroup[entryId] ?: nameGroup[null] |
| } else { |
| nameGroup[nameGroup.firstKey()] |
| } |
| } |
| |
| fun findOrCreateEntry(name: String, entryId: Short? = null): ResourceEntry { |
| val entry = findEntry(name, entryId) |
| return when(entry) { |
| null -> { |
| val newEntry = ResourceEntry(name) |
| newEntry.id = entryId |
| entries.getOrPut(name) { TreeMap(nullsFirst()) }[entryId] = newEntry |
| newEntry |
| } |
| else -> entry |
| } |
| } |
| } |
| |
| /** Represents a resource entry, which may have varying values for each defined configuration. */ |
| class ResourceEntry(val name : String) { |
| var id: Short? = null |
| var visibility = Visibility(Source(""), "", ResourceVisibility.UNDEFINED) |
| |
| var allowNew: AllowNew? = null |
| var overlayable: OverlayableItem? = null |
| |
| internal val values = mutableListOf<ResourceConfigValue>() |
| |
| fun findValue(config: ConfigDescription, product: String = ""): ResourceConfigValue? { |
| return values.find { it.config == config && it.product == product } |
| } |
| |
| fun findOrCreateValue(config: ConfigDescription, product: String = ""): ResourceConfigValue { |
| val configValue = findValue(config, product) |
| return when (configValue) { |
| null -> { |
| val newConfigValue = ResourceConfigValue(config, product) |
| values.add(newConfigValue) |
| newConfigValue |
| } |
| else -> configValue |
| } |
| } |
| } |
| |
| /** |
| * Represents a single value for an entry for a given Configuration. |
| * |
| * @property config The configuration for which this value is defined. |
| * @property product The product name for which this value is defined. |
| * @property value The actual Value. |
| */ |
| data class ResourceConfigValue( |
| val config: ConfigDescription, |
| val product : String, |
| var value: Value? = null) |