package com.reandroid.lib.arsc.item; import com.reandroid.lib.arsc.base.Block; import com.reandroid.lib.arsc.io.BlockReader; import com.reandroid.lib.arsc.pool.BaseStringPool; import com.reandroid.lib.json.JSONConvert; import com.reandroid.lib.json.JSONObject; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class StringItem extends BlockItem implements JSONConvert { private String mCache; private boolean mUtf8; private final List mReferencedList; public StringItem(boolean utf8) { super(0); this.mUtf8=utf8; this.mReferencedList=new ArrayList<>(); } public boolean removeReference(ReferenceItem ref){ return mReferencedList.remove(ref); } public boolean removeAllReference(Collection referenceItems){ return mReferencedList.removeAll(referenceItems); } public void removeAllReference(){ mReferencedList.clear(); } public List getReferencedList(){ return mReferencedList; } public void addReference(ReferenceItem ref){ if(ref!=null){ mReferencedList.add(ref); } } public void addReference(Collection refList){ if(refList==null){ return; } for(ReferenceItem ref:refList){ addReference(ref); } } private void reUpdateReferences(int newIndex){ List referenceItems=new ArrayList<>(mReferencedList); for(ReferenceItem ref:referenceItems){ ref.set(newIndex); } } @Override public void onIndexChanged(int oldIndex, int newIndex){ reUpdateReferences(newIndex); } public String getHtml(){ String str=get(); if(str==null){ return null; } StyleItem styleItem=getStyle(); if(styleItem==null){ return str; } return styleItem.applyHtml(str); } public String get(){ return mCache; } public void set(String str){ String old=get(); if(str==null){ if(old==null){ return; } }else if(str.equals(old)){ return; } byte[] bts=encodeString(str); setBytesInternal(bts); } public boolean isUtf8(){ return mUtf8; } public void setUtf8(boolean utf8){ if(utf8==mUtf8){ return; } mUtf8=utf8; onBytesChanged(); } @Override public void onBytesChanged() { mCache=decodeString(); } @Override public void onReadBytes(BlockReader reader) throws IOException { if(reader.available()<4){ return; } int len=calculateReadLength(reader); setBytesLength(len, false); byte[] bts=getBytesInternal(); reader.readFully(bts); onBytesChanged(); } int calculateReadLength(BlockReader reader) throws IOException { if(reader.available()<4){ return reader.available(); } byte[] bts=new byte[4]; reader.readFully(bts); reader.offset(-4); int[] len; if(isUtf8()){ len=decodeUtf8StringByteLength(bts); }else { len=decodeUtf16StringByteLength(bts); } int add=isUtf8()?1:2; return len[0]+len[1]+add; } String decodeString(){ return decodeString(getBytesInternal(), mUtf8); } byte[] encodeString(String str){ if(mUtf8){ return encodeUtf8ToBytes(str); }else { return encodeUtf16ToBytes(str); } } private String decodeString(byte[] allStringBytes, boolean isUtf8) { if(isNullBytes(allStringBytes)){ if(allStringBytes==null||allStringBytes.length==0){ return null; } return ""; } int[] offLen; if(isUtf8){ offLen=decodeUtf8StringByteLength(allStringBytes); }else { offLen=decodeUtf16StringByteLength(allStringBytes); } CharsetDecoder charsetDecoder; if(isUtf8){ charsetDecoder=UTF8_DECODER; }else { charsetDecoder=UTF16LE_DECODER; } try { ByteBuffer buf=ByteBuffer.wrap(allStringBytes, offLen[0], offLen[1]); CharBuffer charBuffer=charsetDecoder.decode(buf); return charBuffer.toString(); } catch (CharacterCodingException ex) { return new String(allStringBytes, offLen[0], offLen[1], StandardCharsets.UTF_16LE); } } public boolean hasStyle(){ StyleItem styleItem=getStyle(); if(styleItem==null){ return false; } return styleItem.getSpanInfoList().size()>0; } public StyleItem getStyle(){ BaseStringPool stringPool=getStringPool(); if(stringPool==null){ return null; } int index=getIndex(); return stringPool.getStyle(index); } private BaseStringPool getStringPool(){ Block parent=getParent(); while (parent!=null){ if(parent instanceof BaseStringPool){ return (BaseStringPool)parent; } parent=parent.getParent(); } return null; } @Override public JSONObject toJson() { if(isNull()){ return null; } JSONObject jsonObject=new JSONObject(); jsonObject.put(NAME_string, get()); StyleItem styleItem=getStyle(); if(styleItem!=null){ JSONObject styleJson=styleItem.toJson(); if(styleJson!=null){ jsonObject.put(NAME_style, styleJson); } } return jsonObject; } @Override public void fromJson(JSONObject json) { String str = json.getString(NAME_string); set(str); throw new IllegalArgumentException("Not implemented"); } @Override public String toString(){ String str=get(); if(str==null){ return "NULL"; } return str; } private static int[] decodeUtf8StringByteLength(byte[] lengthBytes) { int offset=0; int val = lengthBytes[offset]; int length; if ((val & 0x80) != 0) { offset += 2; } else { offset += 1; } val = lengthBytes[offset]; offset += 1; if ((val & 0x80) != 0) { int low = (lengthBytes[offset] & 0xFF); length = val & 0x7F; length = length << 8; length = length + low; offset += 1; } else { length = val; } return new int[] { offset, length}; } private static int[] decodeUtf16StringByteLength(byte[] lengthBytes) { int val = ((lengthBytes[1] & 0xFF) << 8 | lengthBytes[0] & 0xFF); if ((val & 0x8000) != 0) { int high = (lengthBytes[3] & 0xFF) << 8; int low = (lengthBytes[2] & 0xFF); int len_value = ((val & 0x7FFF) << 16) + (high + low); return new int[] {4, len_value * 2}; } return new int[] {2, val * 2}; } static boolean isNullBytes(byte[] bts){ if(bts==null){ return true; } int max=bts.length; if(max<2){ return true; } for(int i=2; i>8; lenBytes[3]=(byte) (l2); lenBytes[2]=(byte) (l1|0x80); strLen=str.length(); l2=strLen&0xff; l1=(strLen-l2)>>8; lenBytes[1]=(byte) (l2); lenBytes[0]=(byte) (l1|0x80); }else{ lenBytes=new ShortItem((short) strLen).getBytesInternal(); lenBytes[1]=lenBytes[0]; lenBytes[0]=(byte)str.length(); } }else { bts=new byte[0]; } return addBytes(lenBytes, bts, new byte[1]); } private static byte[] encodeUtf16ToBytes(String str){ if(str==null){ return null; } byte[] lenBytes; byte[] bts=getUtf16Bytes(str); int strLen=bts.length; strLen=strLen/2; if((strLen & 0xffff8000)!=0){ lenBytes=new byte[4]; int low=strLen&0xff; int high=(strLen-low)&0xff00; int rem=strLen-low-high; lenBytes[3]=(byte) (high>>8); lenBytes[2]=(byte) (low); low=rem&0xff; high=(rem&0xff00)>>8; lenBytes[1]=(byte) (high|0x80); lenBytes[0]=(byte) (low); }else{ lenBytes=new ShortItem((short) strLen).getBytesInternal(); } return addBytes(lenBytes, bts, new byte[2]); } static byte[] getUtf16Bytes(String str){ return str.getBytes(StandardCharsets.UTF_16LE); } private static byte[] addBytes(byte[] bts1, byte[] bts2, byte[] bts3){ if(bts1==null && bts2==null && bts3==null){ return null; } int len=0; if(bts1!=null){ len=bts1.length; } if(bts2!=null){ len+=bts2.length; } if(bts3!=null){ len+=bts3.length; } byte[] result=new byte[len]; int start=0; if(bts1!=null){ start=bts1.length; System.arraycopy(bts1, 0, result, 0, start); } if(bts2!=null){ System.arraycopy(bts2, 0, result, start, bts2.length); start+=bts2.length; } if(bts3!=null){ System.arraycopy(bts3, 0, result, start, bts3.length); } return result; } private final CharsetDecoder UTF16LE_DECODER = StandardCharsets.UTF_16LE.newDecoder(); private final CharsetDecoder UTF8_DECODER = StandardCharsets.UTF_8.newDecoder(); public static final String NAME_string="string"; public static final String NAME_style="style"; }