/*
* 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.google.common.base.Preconditions;
import com.google.common.primitives.Shorts;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
/**
* ResChunk is the base of most structures in Android's .arsc and compiled .xml files. It consists
* of: u16 chunkType u16 headerSize u32 chunkSize
*
* <p>Some common types used by different chunks:
*
* <p>StringRef is a u32 string id ResRef is a u32 resource id ResValue is a chunk of: u16 size u8
* 0x00 u8 dataType (one of ResChunk.RES_XXXXXXX) u32 data
*
* <p>See
* https://android.googlesource.com/platform/frameworks/base/+/kitkat-release/include/androidfw/ResourceTypes.h
* for full specification.
*/
public abstract class ResChunk {
public static final short CHUNK_STRING_POOL = 0x001;
public static final short CHUNK_RESOURCE_TABLE = 0x002;
public static final short CHUNK_XML_TREE = 0x003;
public static final short CHUNK_XML_REF_MAP = 0x180;
public static final short CHUNK_RES_TABLE_PACKAGE = 0x0200;
public static final short CHUNK_RES_TABLE_TYPE = 0x201;
public static final short CHUNK_RES_TABLE_TYPE_SPEC = 0x202;
static final int RES_REFERENCE = 0x1;
static final int RES_ATTRIBUTE = 0x2;
static final int RES_STRING = 0x3;
static final int RES_FLOAT = 0x4;
static final int RES_DIMENSION = 0x5;
static final int RES_FRACTION = 0x6;
static final int RES_DYNAMIC_REFERENCE = 0x07;
static final int RES_DYNAMIC_ATTRIBUTE = 0x08;
static final int RES_DECIMAL = 0x10;
static final int RES_HEX = 0x11;
static final int RES_BOOL = 0x12;
static final int RES_COLOR_ARGB8 = 0x1c;
static final int RES_COLOR_RGB8 = 0x1d;
static final int RES_COLOR_ARGB4 = 0x1e;
static final int RES_COLOR_RGB4 = 0x1f;
private final short type;
private final short headerSize;
private final int chunkSize;
ResChunk(int chunkType, int headerSize, int chunkSize) {
this.type = Shorts.checkedCast(chunkType);
this.headerSize = Shorts.checkedCast(headerSize);
this.chunkSize = chunkSize;
Preconditions.checkState((chunkSize % 4) == 0);
}
public final int getType() {
return type;
}
public final int getHeaderSize() {
return headerSize;
}
public final int getChunkSize() {
return chunkSize;
}
/**
* For most chunk's totalSize == chunkSize. For some types, the type logically consists of
* multiple chunks. In these cases, totalSize is the sum of all the chunks. This is the full size
* written/read from a buffer for get/put.
*/
public int getTotalSize() {
return chunkSize;
}
public final byte[] serialize() {
byte[] data = new byte[getTotalSize()];
ByteBuffer output = wrap(data);
put(output);
Preconditions.checkState(output.position() == data.length);
return data;
}
public abstract void put(ByteBuffer output);
void putChunkHeader(ByteBuffer output) {
output.putShort(type);
output.putShort(headerSize);
output.putInt(chunkSize);
}
public interface RefTransformer {
int transform(int ref);
}
public interface RefVisitor {
void visit(int ref);
}
static void transformEntryDataOffset(ByteBuffer buf, int offset, RefTransformer visitor) {
int oldValue = buf.getInt(offset);
int newValue = visitor.transform(oldValue);
if (oldValue != newValue) {
buf.putInt(offset, newValue);
}
}
// These are some utilities used widely by subclasses for dealing with ByteBuffers.
static ByteBuffer copy(ByteBuffer buf) {
return wrap(
Arrays.copyOfRange(buf.array(), buf.arrayOffset(), buf.arrayOffset() + buf.limit()));
}
public static ByteBuffer wrap(byte[] data) {
ByteBuffer buf = ByteBuffer.wrap(data);
buf.order(ByteOrder.LITTLE_ENDIAN);
return buf;
}
public static ByteBuffer slice(ByteBuffer map, int offset) {
ByteBuffer result = map.duplicate();
result.position(offset);
result = result.slice();
result.order(ByteOrder.LITTLE_ENDIAN);
return result;
}
public static ByteBuffer slice(ByteBuffer map, int offset, int length) {
ByteBuffer result = slice(map, offset);
result.limit(length);
return result;
}
}