gt: 		int itm_idx = (hi - lo) / 2;
context: /*
  XOWA: the XOWA Offline Wiki Application
  Copyright (C) 2012 gnosygnu@gmail.com
  
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Affero General Public License as
  published by the Free Software Foundation, either version 3 of the
  License, or (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Affero General Public License for more details.
  
  You should have received a copy of the GNU Affero General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  package gplx.xowa; import gplx.*;
  import gplx.ios.*;
  public class Xob_xdat_file {
  	public byte[] Src() {return src;} private byte[] src;
  	public int Src_len() {return src_len;} public Xob_xdat_file Src_len_(int v) {src_len = v; return this;} private int src_len;	// NOTE: src_len can be different than src.length (occurs when reusing brys)
  	public Xob_xdat_file Update(Bry_bfr bfr, Xob_xdat_itm itm, byte[] v) {
  		int ary_len = itm_ends.length;
  		int itm_idx = itm.Itm_idx(); 
  		int prv = itm_idx == 0 ? 0 : itm_ends[itm_idx - 1];
  		int old_end = itm_ends[itm_idx]; 
  		int new_end = prv + v.length;
  		int dif = new_end - old_end;
  		itm_ends[itm_idx] = new_end;
  		for (int i = itm_idx + 1; i < ary_len; i++) {
  			itm_ends[i] += dif;
  		}
  		Src_rebuild_hdr(bfr, ary_len);
  		bfr.Add_mid(src, itm_0_bgn, itm.Itm_bgn());
  		bfr.Add(v);
  		bfr.Add_mid(src, itm.Itm_end() + 1, src.length);	// NOTE: + 1 to skip nl
  		src = bfr.XtoAryAndClear();
  		return this;
  	}
  	byte[][] Src_extract_brys(int ary_len) {
  		byte[][] rv = new byte[ary_len][];
  		int itm_bgn = this.itm_0_bgn;
  		for (int i = 0; i < ary_len; i++) {
  			int itm_end = itm_ends[i] + itm_0_bgn;
  			rv[i] = Bry_.Mid(src, itm_bgn, itm_end);
  			itm_bgn = itm_end;
  		}
  		return rv;
  	}
  	public void Sort(Bry_bfr bfr, gplx.lists.ComparerAble comparer) {
  		int ary_len = itm_ends.length;
  		byte[][] brys = Src_extract_brys(ary_len);
  		Array_.Sort(brys, comparer);
  		Src_rebuild_hdr(bfr, ary_len);
  		itm_0_bgn = (ary_len * Len_idx_itm) + Len_itm_dlm;
  		int itm_bgn = 0;
  		for (int i = 0; i < ary_len; i++) {
  			byte[] bry = brys[i];
  			int bry_len = bry.length;
  			int itm_end = itm_bgn + bry_len;
  			itm_ends[i] = itm_end;
  			itm_bgn = itm_end;
  			bfr.Add(bry);
  		}
  		src = bfr.XtoAryAndClear(); 		
  	}
  	public void Insert(Bry_bfr bfr, byte[] itm) {
  		int ary_len = itm_ends.length;
  		itm_ends = (int[])Array_.Resize(itm_ends, ary_len + 1);
  		int prv_pos = ary_len == 0 ? 0 : itm_ends[ary_len - 1];
  		itm_ends[ary_len] = prv_pos + itm.length;
  		Src_rebuild(bfr, ary_len + 1, itm);
  	}
  	private void Src_rebuild_hdr(Bry_bfr bfr, int ary_len) {
  		int bgn = 0;
  		for (int i = 0; i < ary_len; i++) {
  			int end = itm_ends[i];
  			int len = end - bgn;
  			bfr.Add_base85_len_5(len).Add_byte(Dlm_hdr_fld);
  			bgn = end;
  		}
  		bfr.Add_byte(Dlm_row);		
  	}
  	private void Src_rebuild(Bry_bfr bfr, int ary_len, byte[] new_itm) {
  		Src_rebuild_hdr(bfr, ary_len);
  		Src_rebuild_brys(bfr, ary_len, new_itm);
  	}
  	private void Src_rebuild_brys(Bry_bfr bfr, int ary_len, byte[] new_itm) {
  		int bgn = itm_0_bgn;
  		boolean insert = new_itm != null;
  		int ary_end = insert ? ary_len - 1 : ary_len;
  		for (int i = 0; i < ary_end; i++) {
  			int end = itm_ends[i] + itm_0_bgn;
  			bfr.Add_mid(src, bgn, end);
  			bgn = end;
  		}
  		if (insert) bfr.Add(new_itm);
  		itm_0_bgn = (ary_len * Len_idx_itm) + Len_itm_dlm;
  		src = bfr.XtoAryAndClear(); 
  	}	static final byte Dlm_hdr_fld = Byte_ascii.Pipe, Dlm_row = Byte_ascii.NewLine;		
  	public void Save(Io_url url) {
  		Bry_bfr bfr = Bry_bfr.new_();
  		Srl_save_bry(bfr);
  		Io_stream_wtr wtr = Io_stream_wtr_.new_by_url_(url);
  		try {
  			wtr.Open();
  			wtr.Write(bfr.Bfr(), 0, bfr.Len());
  			wtr.Flush();
  		}
  		catch (Exception e) {throw Err_.err_(e, "failed to save file: {0}", url.Xto_api());}
  		finally {
  			wtr.Rls();
  		}
  	}
  	public void Srl_save_bry(Bry_bfr bfr) {
  		int itm_ends_len = itm_ends.length;
  		int prv_bgn = 0;
  		for (int i = 0; i < itm_ends_len; i++) {
  			int itm_end = itm_ends[i];
  			bfr.Add_base85_len_5(itm_end - prv_bgn).Add_byte(Dlm_hdr_fld);
  			prv_bgn = itm_end;
  		}
  		bfr.Add_byte(Dlm_row);
  		bfr.Add_mid(src, itm_0_bgn, src.length);
  	}
  	public byte[] Get_bry(int i) {
  		int bgn = i == 0 ? itm_0_bgn : itm_0_bgn + itm_ends[i - 1];
  		int end = itm_0_bgn + itm_ends[i];
  		return Bry_.Mid(src, bgn, end);
  	}
  	public int Count() {return itm_ends.length;}
  	public Xob_xdat_file GetAt(Xob_xdat_itm itm, int idx) {
  		itm.Src_(src);
  		itm.Itm_idx_(idx);
  		itm.Itm_bgn_(itm_0_bgn + (idx == 0 ? 0 : itm_ends[idx - 1]));
  		itm.Itm_end_(itm_0_bgn +                 itm_ends[idx] - Len_itm_dlm);
  		return this;
  	}
  	public Xob_xdat_file Find(Xob_xdat_itm itm, byte[] lkp, int lkp_bgn, byte lkp_dlm, boolean exact) {
  		itm.Clear();
  		int itm_idx = Xob_xdat_file_.BinarySearch(itm_0_bgn, src, itm_ends, lkp, lkp_bgn, lkp_dlm, 1, exact, itm); if (itm_idx == String_.Find_none) {return this;}
  		GetAt(itm, itm_idx);
  		return this;
  	}
  	public Xob_xdat_file Clear() {src = null; itm_ends = Int_.Ary_empty; return this;}
  	private int[] itm_ends = Int_.Ary_empty; private int itm_0_bgn;
  	public Xob_xdat_file Parse(byte[] src, int src_len, Io_url url) {// SEE:NOTE_1;xdat format
  		if (src_len == 0) throw Err_mgr._.fmt_(GRP_KEY, "parse.empty", "file cannot be empty for parse: ~{0}", url.Raw());
  		int itm_count = 0, tmp_len = Parse_tmp_len; int[] tmp = Parse_tmp;
  		try {
  			int slot_bgn = 0, slot_old = 0, slot_new = 0;
  			while (true) {
  				slot_bgn = itm_count * Len_idx_itm;
  				if (slot_bgn >= src_len) break;
  				if (src[slot_bgn] == Byte_ascii.NewLine) break;
  				int tmp_val = Base85_utl.XtoIntByAry(src, slot_bgn, slot_bgn + Offset_base85);
  				slot_new = slot_old + tmp_val;
  				int new_idx = itm_count + 1;
  				if (tmp_len < new_idx) {
  					tmp_len = new_idx * 2;
  					tmp = (int[])Array_.Resize(tmp, tmp_len);
  				}
  				tmp[itm_count] = slot_new;
  				itm_count = new_idx;
  				slot_old = slot_new;
  			}
  			int itm_ends_last = slot_new;
  			itm_ends = new int[itm_count];
  			for (int i = 0; i < itm_count; i++)
  				itm_ends[i] = tmp[i];
  			itm_0_bgn = slot_bgn + Len_itm_dlm;
  			this.src = Bry_.Mid(src, 0, itm_ends_last + itm_0_bgn);
  		} catch (Exception e) {throw Err_mgr._.fmt_(GRP_KEY, "parse.fail", "failed to parse idx: ~{0} ~{1} ~{2}", itm_count, url.Raw(), Err_.Message_gplx_brief(e));}
  		return this;
  	}	private static final int Parse_tmp_len = 8 * 1024; static int[] Parse_tmp = new int[Parse_tmp_len];
  	static final int Len_itm_dlm = 1, Len_idx_itm = 6, Offset_base85 = 4;	// 6 = 5 (base85_int) + 1 (new_line/pipe)
  	static final String GRP_KEY = "xowa.xdat_fil";
  	public static byte[] Rebuid_header(byte[] orig, byte[] dlm) {
  		byte[][] rows = Bry_.Split(orig, dlm);
  		int rows_len = rows.length;
  		Bry_bfr bfr = Bry_bfr.new_();
  		int dlm_len = dlm.length;
  		for (int i = 1; i < rows_len; i++) {	// i=1; skip 1st row (which is empty header)
  			byte[] row = rows[i];
  			int row_len = row.length + dlm_len;
  			bfr.Add_base85_len_5(row_len).Add_byte(Byte_ascii.Pipe);
  		}
  		bfr.Add_byte(Byte_ascii.NewLine);
  		for (int i = 1; i < rows_len; i++) {	// i=1; skip 1st row (which is empty header)
  			byte[] row = rows[i];
  			bfr.Add(row);
  			bfr.Add(dlm);
  		}
  		return bfr.XtoAryAndClear();
  	}
  }
  class Xob_xdat_file_ {
  	public static int BinarySearch(int itm_0_bgn, byte[] src, int[] itm_ends, byte[] lkp, int lkp_bgn, byte lkp_dlm, int itm_end_adj, boolean exact, Xob_xdat_itm xdat_itm) {if (lkp == null) throw Err_.null_("lkp is null");
  		int itm_ends_len = itm_ends.length; if (itm_ends_len == 0) throw Err_.new_("itm_ends_len cannot have 0 itms");
  		int lo = -1, hi = itm_ends_len - 1; // NOTE: -1 is necessary; see test
  