/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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.github.nukc.plugin.axml.decode;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/**
* write and read StringBlock
* @author NTOOOOOP
*/
public class StringBlock implements IAXMLSerialize{
private static final int TAG = 0x001C0001;
private static final int INT_SIZE = 4;
private int mChunkSize;
private int mStringsCount;
private int mStylesCount;
private int mEncoder;
private int mStrBlockOffset;
private int mStyBlockOffset;
private int[] mPerStrOffset;
private int[] mPerStyOffset;
/**
* raw String
*/
private List<String> mStrings;
/**
* android can identify HTML tags in a string,all the styles are kept here
*/
private List<Style> mStyles;
public int getStringMapping(String str){
int size = mStrings.size();
for(int i=0; i< size ; i++){
if(mStrings.get(i).equals(str)){
return i;
}
}
return -1;
}
public int putString(String str){
if(containsString(str)){
return getStringMapping(str);
}
return addString(str);
}
public int addString(String str){
mStrings.add(str);
return ( mStrings.size() - 1);
}
public String setString(int index, String str){
return mStrings.set(index, str);
}
public boolean containsString(String str){
return mStrings.contains(str.trim());
}
public int getStringCount(){
return mStrings.size();
}
/**
* Reads whole (including chunk type) string block from stream.
* Stream must be at the chunk type.
*/
public void read(IntReader reader) throws IOException {
mChunkSize = reader.readInt();
mStringsCount = reader.readInt();
mStylesCount = reader.readInt();
mEncoder = reader.readInt();//utf-8 or uft16
mStrBlockOffset =reader.readInt();
mStyBlockOffset =reader.readInt();
if(mStringsCount > 0){
mPerStrOffset = reader.readIntArray(mStringsCount);
mStrings = new ArrayList<String>(mStringsCount);
}
if(mStylesCount > 0){
mPerStyOffset = reader.readIntArray(mStylesCount);
mStyles = new ArrayList<Style>();
}
//read string
if(mStringsCount >0){
int size = ((mStyBlockOffset == 0)?mChunkSize:mStyBlockOffset) - mStrBlockOffset;
byte[] rawStrings = reader.readByteArray(size);
for(int i =0; i < mStringsCount ; i++){
int offset = mPerStrOffset[i];
short len = toShort(rawStrings[offset], rawStrings[offset+1]);
mStrings.add(i,new String(rawStrings,offset+2, len*2, Charset.forName("UTF-16LE")));
}
}
//read styles
if(mStylesCount > 0){
int size = mChunkSize - mStyBlockOffset;
int[] styles = reader.readIntArray(size/4);
for(int i = 0; i< mStylesCount; i++){
int offset = mPerStyOffset[i];
int j = offset;
for(; j< styles.length; j++){
if(styles[j] == -1) break;
}
int[] array = new int[j-offset];
System.arraycopy(styles, offset, array, 0, array.length);
Style d = Style.parse(array);
mStyles.add(d);
}
}
}
@Override
public void write(IntWriter writer) throws IOException {
//base seven
int size = 0;
size += writer.writeInt(TAG);
size += writer.writeInt(mChunkSize);
size += writer.writeInt(mStringsCount);
size += writer.writeInt(mStylesCount);
size += writer.writeInt(mEncoder);
size += writer.writeInt(mStrBlockOffset);
size += writer.writeInt(mStyBlockOffset);
if(mPerStrOffset != null){
for(int offset : mPerStrOffset){
size += writer.writeInt(offset);
}
}
if(mPerStyOffset != null){
for(int offset : mPerStyOffset){
size += writer.writeInt(offset);
}
}
if(mStrings != null){
for(String s : mStrings){
byte[] raw = s.getBytes("UTF-16LE");
size += writer.writeShort((short)(s.length()));
size += writer.writeByteArray(raw);
size += writer.writeShort((short)0);
}
}
if(mStyles != null){
for(Style style : mStyles){
size += style.write(writer);
}
}
if(mChunkSize > size){
writer.writeShort((short)0);
}
}
public void prepare() throws IOException{
//mStrings
mStringsCount = mStrings == null ? 0:mStrings.size();
mStylesCount = mStyles == null ? 0: mStyles.size();
//string & style block offset
int base = INT_SIZE*7;//from 0 to string array
int strSize = 0;
int []perStrSize = null;
if(mStrings != null){
int size = 0;
perStrSize = new int[mStrings.size()];
for(int i =0; i< mStrings.size(); i++){
perStrSize[i] = size;
try{
size += 2 + mStrings.get(i).getBytes("UTF-16LE").length + 2;
}catch(UnsupportedEncodingException e){
throw new IOException(e);
}
}
strSize = size;
}
int stySize = 0;
int[] perStySize = null;
if(mStyles != null){
int size = 0;
perStySize = new int[mStyles.size()];
for(int i=0; i< mStyles.size(); i++){
perStySize[i] = size;
size += mStyles.get(i).getSize();
}
stySize = size;
}
int string_array_size = perStrSize == null ? 0: perStrSize.length*INT_SIZE;
int style_array_size = perStySize == null ? 0: perStySize.length*INT_SIZE;
if(mStrings!= null && mStrings.size() >0){
mStrBlockOffset = base + string_array_size + style_array_size;
mPerStrOffset = perStrSize;
}else{
mStrBlockOffset = 0;
mPerStrOffset = null;
}
if(mStyles != null && mStyles.size() > 0){
mStyBlockOffset = base + string_array_size + style_array_size + strSize;
mPerStyOffset = perStySize;
}else{
mStyBlockOffset = 0;
mPerStyOffset = null;
}
mChunkSize = base + string_array_size + style_array_size + strSize + stySize;
int align = mChunkSize % 4;
if(align != 0){
mChunkSize += (INT_SIZE - align);
}
}
public int getSize(){
return mChunkSize;
}
public String getStringFor(int index){
return mStrings.get(index);
}
private short toShort(short byte1, short byte2)
{
return (short)((byte2 << 8) + byte1);
}
public Style getStyle(int index){
return mStyles.get(index);
}
///////////////////////////////////////////// implementation
public StringBlock() {
}
@Override
public int getType() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void setSize(int size) {
// TODO Auto-generated method stub
}
@Override
public void setType(int type) {
// TODO Auto-generated method stub
}
public static class Style {
List<Decorator> mDct;
public Style(){
mDct = new ArrayList<Decorator>();
}
public List<Decorator> getDecorator(){
return mDct;
}
public void addStyle(Decorator style){
mDct.add(style);
}
public int getSize(){
int size = 0;
size += getCount()* Decorator.SIZE;
size += 1;//[-1] as a seperator
return size;
}
public int getCount(){
return mDct.size();
}
public static Style parse(int[] muti_triplet) throws IOException{
if(muti_triplet == null || (muti_triplet.length% Decorator.SIZE != 0)){
throw new IOException("Fail to parse style");
}
Style d = new Style();
Decorator style = null;
for(int i = 0; i < muti_triplet.length; i++){
if(i% Decorator.SIZE == 0){
new Decorator();
}
switch(i%3){
case 0:
{
style = new Decorator();
style.mTag = muti_triplet[i];
}break;
case 1:
{
style.mDoctBegin = muti_triplet[i];
}break;
case 2:
{
style.mDoctEnd = muti_triplet[i];
d.mDct.add(style);
}break;
}
}
return d;
}
public int write(IntWriter writer) throws IOException{
int size = 0;
if(mDct!= null && mDct.size() > 0){
for(Decorator dct: mDct){
size += writer.writeInt(dct.mTag);
size += writer.writeInt(dct.mDoctBegin);
size += writer.writeInt(dct.mDoctEnd);
}
size += writer.writeInt(-1);
}
return size;
}
}
public static class Decorator{
public static final int SIZE = 3;
public int mTag;
public int mDoctBegin;
public int mDoctEnd;
public Decorator(int[] triplet){
mTag = triplet[0];
mDoctBegin = triplet[1];
mDoctEnd = triplet[2];
}
public Decorator(){}
}
}