327 lines
10 KiB
Java
Executable File

/*
* 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.io.BlockReader;
import com.reandroid.arsc.item.BlockItem;
import com.reandroid.arsc.item.ReferenceBlock;
import com.reandroid.arsc.item.ReferenceItem;
import com.reandroid.arsc.item.StringItem;
import com.reandroid.arsc.pool.StringPool;
import com.reandroid.arsc.pool.TableStringPool;
import com.reandroid.json.JSONConvert;
import com.reandroid.json.JSONObject;
import java.io.IOException;
import java.util.Objects;
public abstract class ValueItem extends BlockItem implements Value,
JSONConvert<JSONObject>{
private ReferenceItem mStringReference;
private final int sizeOffset;
public ValueItem(int bytesLength, int sizeOffset) {
super(bytesLength);
this.sizeOffset = sizeOffset;
writeSize();
}
void linkTableStrings(TableStringPool tableStringPool){
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.getHtml();
if(value == null){
value = "";
}
return value;
}
return null;
}
public void setValueAsString(String str){
if(getValueType() == ValueType.STRING
&& Objects.equals(str, getValueAsString())){
return;
}
StringItem stringItem = getStringPool().getOrCreate(str);
setData(stringItem.getIndex());
setValueType(ValueType.STRING);
}
public boolean getValueAsBoolean(){
return getData()!=0;
}
public void setValueAsBoolean(boolean val){
setValueType(ValueType.INT_BOOLEAN);
int data=val?0xffffffff:0;
setData(data);
}
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());
}
}
@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.INT_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.INT_BOOLEAN){
setValueAsBoolean(json.getBoolean(NAME_data));
}else {
setValueType(valueType);
setData(json.getInt(NAME_data));
}
}
@Override
public String toString(){
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(String.format("0x%02x", (0xff & getType())));
}
builder.append(", data=");
int data = getData();
if(valueType==ValueType.STRING){
StringItem tableString = getDataAsPoolString();
if(tableString!=null){
builder.append(tableString.getHtml());
}else {
builder.append(String.format("0x%08x", data));
}
}else {
builder.append(String.format("0x%08x", 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";
}