content: /**
   *  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.zzzmode.apkeditor.axmleditor.decode;
  
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.nio.charset.Charset;
  import java.util.ArrayList;
  import java.util.Arrays;
  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 String removeString(int index){
  		return mStrings.remove(index);
  	}
  		
  		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(){}
  			
  		}
  
  	@Override
  	public String toString() {
  		return "StringBlock{" +
  				"mChunkSize=" + mChunkSize +
  				", mStringsCount=" + mStringsCount +
  				", mStylesCount=" + mStylesCount +
  				", mEncoder=" + mEncoder +
  				", mStrBlockOffset=" + mStrBlockOffset +
  				", mStyBlockOffset=" + mStyBlockOffset +
  				", mPerStrOffset=" + Arrays.toString(mPerStrOffset) +
  				", mPerStyOffset=" + Arrays.toString(mPerStyOffset) +
  				", mStrings=" + mStrings +
  				", mStyles=" + mStyles +
  				'}';
  	}
  }