com.reandroid.arsc.value.ValueItem Maven / Gradle / Ivy
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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.reandroid.arsc.value;
import com.reandroid.arsc.base.Block;
import com.reandroid.arsc.chunk.MainChunk;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.chunk.ParentChunk;
import com.reandroid.arsc.chunk.TableBlock;
import com.reandroid.arsc.coder.CoderUnknownStringRef;
import com.reandroid.arsc.coder.EncodeResult;
import com.reandroid.arsc.coder.ValueCoder;
import com.reandroid.arsc.coder.XmlSanitizer;
import com.reandroid.arsc.item.*;
import com.reandroid.arsc.model.ResourceEntry;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.pool.StringPool;
import com.reandroid.arsc.pool.TableStringPool;
import com.reandroid.utils.HexUtil;
import com.reandroid.json.JSONConvert;
import com.reandroid.json.JSONObject;
import com.reandroid.utils.StringsUtil;
import com.reandroid.xml.StyleDocument;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.Objects;
public abstract class ValueItem extends BlockItem implements Value,
JSONConvert{
private ReferenceItem mStringReference;
private final int sizeOffset;
public ValueItem(int bytesLength, int sizeOffset) {
super(bytesLength);
this.sizeOffset = sizeOffset;
writeSize();
}
public boolean isUndefined(){
return getValueType() == ValueType.NULL && getData() == 0;
}
public ResourceEntry resolve(int resourceId){
PackageBlock context = getPackageBlock();
if(context == null){
return null;
}
TableBlock tableBlock = context.getTableBlock();
if(tableBlock == null){
return null;
}
return tableBlock.getResource(context, resourceId);
}
public PackageBlock getPackageBlock(){
ParentChunk parentChunk = getParentChunk();
if(parentChunk != null){
return parentChunk.getPackageBlock();
}
return null;
}
void linkTableStrings(TableStringPool tableStringPool){
if(getValueType() == ValueType.STRING){
linkStringReference(tableStringPool);
}
}
public void onRemoved(){
unLinkStringReference();
}
protected void onDataChanged(){
}
public void refresh(){
writeSize();
}
byte getRes0(){
return getBytesInternal()[this.sizeOffset + OFFSET_RES0];
}
public byte getType(){
return getBytesInternal()[this.sizeOffset + OFFSET_TYPE];
}
public void setType(byte type){
if(type == getType()){
return;
}
byte[] bts = getBytesInternal();
int offset = this.sizeOffset + OFFSET_TYPE;
byte old = bts[offset];
bts[offset] = type;
onTypeChanged(old, type);
onDataChanged();
}
public int getSize(){
return 0xffff & getShort(getBytesInternal(), this.sizeOffset + OFFSET_SIZE);
}
public void setSize(int size){
size = this.sizeOffset + size;
setBytesLength(size, false);
writeSize();
}
private void writeSize(){
int offset = this.sizeOffset;
int size = countBytes() - offset;
putShort(getBytesInternal(), offset + OFFSET_SIZE, (short) size);
}
protected void onDataLoaded(){
if(getValueType() == ValueType.STRING){
linkStringReference();
}else {
unLinkStringReference();
}
}
@Override
public ValueType getValueType(){
return ValueType.valueOf(getType());
}
@Override
public void setValueType(ValueType valueType){
byte type = 0;
if(valueType!=null){
type = valueType.getByte();
}
setType(type);
}
@Override
public int getData(){
return getInteger(getBytesInternal(), this.sizeOffset + OFFSET_DATA);
}
@Override
public void setData(int data){
byte[] bts = getBytesInternal();
int old = getInteger(bts, this.sizeOffset + OFFSET_DATA);
if(old == data){
return;
}
unLinkStringReference();
putInteger(bts, this.sizeOffset + OFFSET_DATA, data);
if(ValueType.STRING==getValueType()){
linkStringReference();
}
onDataChanged();
}
public StringItem getDataAsPoolString(){
if(getValueType()!=ValueType.STRING){
return null;
}
StringPool> stringPool = getStringPool();
if(stringPool == null){
return null;
}
return stringPool.get(getData());
}
private void onTypeChanged(byte old, byte type){
byte typeString = ValueType.STRING.getByte();
if(old == typeString){
unLinkStringReference();
}else if(type == typeString){
linkStringReference();
}
}
private void linkStringReference(){
StringPool> stringPool = getStringPool();
if(stringPool == null || stringPool.isStringLinkLocked()){
return;
}
linkStringReference(stringPool);
}
private void linkStringReference(StringPool> stringPool){
StringItem tableString = stringPool.get(getData());
if(tableString == null){
unLinkStringReference();
return;
}
ReferenceItem stringReference = mStringReference;
if(stringReference!=null){
unLinkStringReference();
}
stringReference = new ReferenceBlock<>(this, this.sizeOffset + OFFSET_DATA);
mStringReference = stringReference;
tableString.addReference(stringReference);
}
private void unLinkStringReference(){
ReferenceItem stringReference = mStringReference;
if(stringReference==null){
return;
}
mStringReference = null;
onUnlinkDataString(stringReference);
}
protected void onUnlinkDataString(ReferenceItem referenceItem){
StringPool> stringPool = getStringPool();
if(stringPool == null){
return;
}
stringPool.removeReference(referenceItem);
}
public StringPool> getStringPool(){
Block parent = getParent();
while (parent!=null){
if(parent instanceof MainChunk){
return ((MainChunk) parent).getStringPool();
}
parent=parent.getParent();
}
return null;
}
@Override
public void onReadBytes(BlockReader reader) throws IOException {
int readSize = initializeBytes(reader);
super.onReadBytes(reader);
if(readSize<8){
setBytesLength(this.sizeOffset + 8, false);
writeSize();
}
}
private int initializeBytes(BlockReader reader) throws IOException {
int position = reader.getPosition();
int offset = this.sizeOffset;
reader.offset(offset);
int readSize = reader.readUnsignedShort();
int size = readSize;
if(size<8){
if(reader.available()>=8){
size = 8;
}
}
reader.seek(position);
setBytesLength(offset + size, false);
return readSize;
}
@Override
public String getValueAsString(){
StringItem stringItem = getDataAsPoolString();
if(stringItem!=null){
String value = stringItem.getXml();
if(value == null){
value = "";
}
return value;
}
return null;
}
public StyleDocument getValueAsStyleDocument(){
StringItem stringItem = getDataAsPoolString();
if(!(stringItem instanceof TableString)){
return null;
}
return ((TableString)stringItem).getStyleDocument();
}
public void setValueAsString(StyleDocument styledString){
if(styledString == null){
setValueAsString("");
return;
}
StringPool> stringPool = getStringPool();
if(!styledString.hasElements() || !(stringPool instanceof TableStringPool)){
setValueAsString(XmlSanitizer.unEscapeUnQuote(styledString.getXml(false)));
return;
}
TableStringPool tableStringPool = (TableStringPool) stringPool;
StringItem stringItem = tableStringPool.getOrCreate(styledString);
setData(stringItem.getIndex());
setValueType(ValueType.STRING);
}
public void setValueAsString(String str){
if(getValueType() == ValueType.STRING
&& Objects.equals(str, getValueAsString())){
return;
}
if(str == null){
str = "";
}
StringItem stringItem = getStringPool().getOrCreate(str);
setData(stringItem.getIndex());
setValueType(ValueType.STRING);
}
public void serializeText(XmlSerializer serializer) throws IOException {
if(getValueType() == ValueType.STRING){
StringItem stringItem = getDataAsPoolString();
if(stringItem != null){
stringItem.serializeText(serializer);
}else {
// TODO: should throw ?
serializer.text(CoderUnknownStringRef.INS.decode(getData()));
}
return;
}
String value = decodeValue();
if(value == null){
// TODO: could not happen ?
value = "";
}
serializer.text(value);
}
public void serializeAttribute(XmlSerializer serializer, String name) throws IOException {
serializeAttribute(serializer, null, name, false);
}
public void serializeAttribute(XmlSerializer serializer, String name, boolean ignore_empty) throws IOException {
serializeAttribute(serializer, null, name, ignore_empty);
}
public void serializeAttribute(XmlSerializer serializer, String namespace, String name, boolean ignore_empty) throws IOException {
if(getValueType() == ValueType.STRING){
StringItem stringItem = getDataAsPoolString();
if(stringItem != null){
stringItem.serializeAttribute(serializer, namespace, name);
}else {
// TODO: should throw ?
serializer.attribute(namespace, name, CoderUnknownStringRef.INS.decode(getData()));
}
return;
}
String value = decodeValue();
if(ignore_empty && StringsUtil.isEmpty(value)){
return;
}
if(value == null){
value = "";
}
serializer.attribute(namespace, name, value);
}
public boolean getValueAsBoolean(){
return getData()!=0;
}
public void setValueAsBoolean(boolean val){
setValueType(ValueType.BOOLEAN);
int data=val?0xffffffff:0;
setData(data);
}
@Override
public void setValue(EncodeResult encodeResult){
if(encodeResult == null){
throw new NullPointerException();
}
if(encodeResult.isError()){
throw new IllegalArgumentException("Can not set error value: "
+ encodeResult.getError());
}
setTypeAndData(encodeResult.valueType, encodeResult.value);
}
public void setTypeAndData(ValueType valueType, int data){
setData(data);
setValueType(valueType);
}
public void merge(ValueItem valueItem){
if(valueItem == null || valueItem==this){
return;
}
setSize(valueItem.getSize());
ValueType coming = valueItem.getValueType();
if(coming == ValueType.STRING){
setValueAsString(valueItem.getValueAsString());
}else {
setTypeAndData(coming, valueItem.getData());
}
}
public String decodeValue(){
ValueType valueType = getValueType();
if(valueType == null){
return null;
}
if(valueType.isReference()){
return decodeAsReferenceString(valueType);
}
if(valueType == ValueType.STRING){
return getValueAsString();
}
return ValueCoder.decode(valueType, getData());
}
private String decodeAsReferenceString(ValueType valueType){
int data = getData();
if(data == 0){
if(valueType == ValueType.ATTRIBUTE){
return "?null";
}
return "@null";
}
PackageBlock packageBlock = getPackageBlock();
if(packageBlock == null){
throw new NullPointerException("Parent package block is null");
}
TableBlock tableBlock = packageBlock.getTableBlock();
if(tableBlock == null){
throw new NullPointerException("Parent table block is null");
}
ResourceEntry resourceEntry = tableBlock.getResource(packageBlock, data);
if(resourceEntry == null || !resourceEntry.isDeclared()){
return ValueCoder.decodeUnknownResourceId(valueType.isReference(), data);
}
return resourceEntry.buildReference(packageBlock, valueType);
}
@Override
public JSONObject toJson() {
if(isNull()){
return null;
}
JSONObject jsonObject = new JSONObject();
ValueType valueType = getValueType();
jsonObject.put(NAME_value_type, valueType.name());
if(valueType==ValueType.STRING){
jsonObject.put(NAME_data, getValueAsString());
}else if(valueType==ValueType.BOOLEAN){
jsonObject.put(NAME_data, getValueAsBoolean());
}else {
jsonObject.put(NAME_data, getData());
}
return jsonObject;
}
@Override
public void fromJson(JSONObject json) {
ValueType valueType = ValueType.fromName(json.getString(NAME_value_type));
if(valueType==ValueType.STRING){
setValueAsString(json.optString(NAME_data, ""));
}else if(valueType==ValueType.BOOLEAN){
setValueAsBoolean(json.getBoolean(NAME_data));
}else {
setValueType(valueType);
setData(json.getInt(NAME_data));
}
}
@Override
public String toString(){
if(getPackageBlock() != null){
return getValueType() + ":" + HexUtil.toHex8(getData()) + " " + decodeValue();
}
StringBuilder builder = new StringBuilder();
int size = getSize();
if(size!=8){
builder.append("size=").append(getSize());
builder.append(", ");
}
builder.append("type=");
ValueType valueType=getValueType();
if(valueType!=null){
builder.append(valueType);
}else {
builder.append(HexUtil.toHex2(getType()));
}
builder.append(", data=");
int data = getData();
if(valueType==ValueType.STRING){
StringItem tableString = getDataAsPoolString();
if(tableString!=null){
builder.append(tableString.getHtml());
}else {
builder.append(HexUtil.toHex8(data));
}
}else {
builder.append(HexUtil.toHex8(data));
}
return builder.toString();
}
private static final int OFFSET_SIZE = 0;
private static final int OFFSET_RES0 = 2;
private static final int OFFSET_TYPE = 3;
private static final int OFFSET_DATA = 4;
public static final String NAME_data = "data";
public static final String NAME_value_type = "value_type";
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy