/* * Copyright 2017-present Facebook, 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 com.facebook.buck.android.resources; import com.facebook.buck.util.MoreCollectors; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.io.PrintStream; import java.nio.ByteBuffer; import java.util.List; import java.util.Objects; /** * ResTableTypeSpec is a ResChunk specifying the flags for each resource of a given type. These * flags specify which types of configuration contain multiple values for a given resource. A * ResTableTypeSpec consists of: ResChunk_header u16 type u16 header_size u32 chunk_size u8 id u8 * 0x00 u16 0x0000 u32 entry_count * * <p>This is then followed by entry_count u32s giving the flags for each resource of this type. * * <p>In practice, this is then followed by a ResTableType for each configuration that has resources * of this type. For convenience, those are considered to be part of the ResTableTypeSpec. */ public class ResTableTypeSpec extends ResChunk { private static final int HEADER_SIZE = 16; private final int id; private final int entryCount; private final List<ResTableType> configs; private final int totalSize; private final ByteBuffer entryFlags; public static ResTableTypeSpec slice(ResTableTypeSpec spec, int count) { ImmutableList<ResTableType> configs = spec.getConfigs() .stream() .map(config -> ResTableType.slice(config, count)) .filter(Objects::nonNull) .collect(MoreCollectors.toImmutableList()); return new ResTableTypeSpec( spec.id, count, copy(slice(spec.entryFlags, 0, 4 * count)), configs); } @Override public void put(ByteBuffer output) { Preconditions.checkState(output.remaining() >= totalSize); int start = output.position(); putChunkHeader(output); output.put((byte) (id + 1)); output.put((byte) 0); output.putShort((short) 0); output.putInt(entryCount); output.put(slice(entryFlags, 0)); configs.forEach(c -> c.put(output)); Preconditions.checkState(output.position() == start + totalSize); } public static ResTableTypeSpec get(ByteBuffer buf) { int type = buf.getShort(); int headerSize = buf.getShort(); int chunkSize = buf.getInt(); int id = (buf.get() & 0xFF) - 1; buf.get(); buf.getShort(); int entryCount = buf.getInt(); // ignored u8 // ignored u16 Preconditions.checkState(type == CHUNK_RES_TABLE_TYPE_SPEC); Preconditions.checkState(headerSize == HEADER_SIZE); Preconditions.checkState(chunkSize == HEADER_SIZE + 4 * entryCount); return new ResTableTypeSpec( id, entryCount, slice(buf, HEADER_SIZE, 4 * entryCount), getConfigsFromBuffer(slice(buf, chunkSize))); } private ResTableTypeSpec( int id, int entryCount, ByteBuffer entryFlags, List<ResTableType> configs) { super(CHUNK_RES_TABLE_TYPE_SPEC, HEADER_SIZE, HEADER_SIZE + 4 * entryCount); this.id = id; this.entryCount = entryCount; this.entryFlags = entryFlags; this.configs = configs; int configsSize = 0; for (ResTableType config : configs) { Preconditions.checkState(getResourceType() == config.getResourceType()); configsSize += config.getChunkSize(); } this.totalSize = getChunkSize() + configsSize; } private static List<ResTableType> getConfigsFromBuffer(ByteBuffer buf) { ImmutableList.Builder<ResTableType> configs = ImmutableList.builder(); while (buf.position() < buf.limit() && buf.getShort(buf.position()) == 0x201) { ResTableType config = ResTableType.get(slice(buf, buf.position())); configs.add(config); buf.position(buf.position() + config.getChunkSize()); } return configs.build(); } @Override public int getTotalSize() { return totalSize; } String getResourceName(ResTablePackage resPackage, int id) { // We need to find an actual entry in one of the configs to find the name of this resource. for (ResTableType t : configs) { int refId = t.getResourceRef(id); if (refId >= 0) { return resPackage.getKeys().getString(refId); } } throw new RuntimeException(); } public String getResourceTypeName(ResTablePackage resPackage) { return resPackage.getTypes().getString(id); } public void dump(StringPool strings, ResTablePackage resPackage, PrintStream out) { if (entryCount == 0) { return; } out.format(" type %d configCount=%d entryCount=%d\n", id, configs.size(), entryCount); for (int i = 0; i < entryCount; i++) { out.format( " spec resource 0x7f%02x%04x %s:%s/%s: flags=0x%08x\n", getResourceType(), i, resPackage.getPackageName(), getResourceTypeName(resPackage), getResourceName(resPackage, i), entryFlags.getInt(i * 4)); } for (ResTableType type : configs) { type.dump(strings, resPackage, out); } } public int getResourceType() { return id + 1; } public List<ResTableType> getConfigs() { return configs; } public void transformKeyReferences(RefTransformer visitor) { configs.forEach(c -> c.transformKeyReferences(visitor)); } public void visitKeyReferences(RefVisitor visitor) { configs.forEach(c -> c.visitKeyReferences(visitor)); } public void transformStringReferences(RefTransformer visitor) { configs.forEach(c -> c.transformStringReferences(visitor)); } public void visitStringReferences(RefVisitor visitor) { configs.forEach(c -> c.visitStringReferences(visitor)); } public void visitStringReferences(int[] ids, RefVisitor visitor) { configs.forEach(c -> c.visitStringReferences(ids, visitor)); } public void visitReferences(int[] ids, RefVisitor visitor) { configs.forEach(c -> c.visitReferences(ids, visitor)); } public void reassignIds(ReferenceMapper refMapping) { refMapping.rewrite(getResourceType(), entryFlags.asIntBuffer()); configs.forEach(c -> c.reassignIds(refMapping)); } public int getEntryCount() { return entryCount; } }