package yuku.alkitab.yes2; import android.util.Log; import yuku.alkitab.yes2.io.RandomOutputStream; import yuku.alkitab.yes2.section.base.SectionContent; import yuku.bintex.BintexWriter; import yuku.bintex.ValueMap; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * YES version 2 file format * * Note: Below, uint8, char8, and byte are the same thing, only interpreted differently. * Note: value is Bintex value type * Note: int is 32-bit integer * * { * | uint8[8] header = 0x98 0x58 0x0d 0x0a 0x00 0x5d 0xe0 0x02 // version 2 * | int sectionIndex.size * | uint8 sectionIndexVersion = 1 * | int section_count * | { * | | uint8 sectionName.length * | | char8[sectionName.length] sectionName * | | int offset // from start of sections * | | int attributes_size * | | int content_size * | | byte[4] reserved * | }[section_count] sectionIndex * | { * | | value section.attributes * | | byte[section.content.size] section.content * | }[section_count] sections * | uint8 footer = 0 * } */ public class Yes2Writer { private static final String TAG = Yes2Writer.class.getSimpleName(); private static final byte YES_VERSION = 0x02; private static final byte[] YES_HEADER = { (byte) 0x98, 0x58, 0x0d, 0x0a, 0x00, 0x5d, (byte) 0xe0, YES_VERSION }; private static Boolean androidLogPossible = null; private static void alog(long pos, String msg) { alog("[pos=" + pos + "] " + msg, null); } private static void alog(String msg, Throwable t) { if (androidLogPossible == null) { try { Class.forName("android.util.Log"); androidLogPossible = true; } catch (Exception e) { androidLogPossible = false; } } if (androidLogPossible) { if (t != null) { Log.e(TAG, msg, t); } else { Log.d(TAG, msg); } } else { System.err.println(msg); if (t != null) { t.printStackTrace(); } } } public List<SectionContent> sections = new ArrayList<>(); public void writeToFile(RandomOutputStream output) throws IOException { BintexWriter bw = new BintexWriter(output); int section_count = sections.size(); ////////// HEADERS ////////// alog(output.getFilePointer(), "write yes header"); bw.writeRaw(YES_HEADER); ///////// SECTION INDEX /////////// long savedpos_sectionIndexSize; long savedpos_startOfSectionIndex; long[] savedpos_sectionIndexSizeEntries = new long[section_count]; long savedpos_startOfSections; int[] saved_sectionOffset = new int[section_count]; int[] savedsizes_sectionAttributes = new int[section_count]; int[] savedsizes_sectionContent = new int[section_count]; { // section index size is not known, just save position and reserve bytes savedpos_sectionIndexSize = output.getFilePointer(); bw.writeInt(-1); // for sectionIndex size } alog(output.getFilePointer(), "write sectionIndexVersion: 1"); bw.writeUint8(1); alog(output.getFilePointer(), "write section_count: " + section_count); bw.writeInt(section_count); savedpos_startOfSectionIndex = output.getFilePointer(); for (int i = 0; i < section_count; i++) { SectionContent section = sections.get(i); { // section name String name = section.getName(); byte[] name_bytes = section.getNameAsBytesWithLength(); alog(output.getFilePointer(), "write sectionName: " + name); bw.writeRaw(name_bytes); } { // sizes are not known, just save position and reserve bytes savedpos_sectionIndexSizeEntries[i] = output.getFilePointer(); bw.writeInt(-1); // for offset bw.writeInt(-1); // for attributes_size bw.writeInt(-1); // for content_size bw.writeInt(0); // for reserved } } { // now we know the size of the section index, write it into the placeholder long lastPos = output.getFilePointer(); output.seek(savedpos_sectionIndexSize); int size = (int) (lastPos - savedpos_startOfSectionIndex); alog(output.getFilePointer(), "write section index size: " + size); bw.writeInt(size); output.seek(lastPos); } //////////////// SECTION ATTRIBUTES AND CONTENT ///////////////// savedpos_startOfSections = output.getFilePointer(); for (int i = 0; i < section_count; i++) { SectionContent section = sections.get(i); String section_name = section.getName(); saved_sectionOffset[i] = (int) (output.getFilePointer() - savedpos_startOfSections); { // attributes int posBeforeAttributes = (int) output.getFilePointer(); alog(output.getFilePointer(), "write section attributes: " + section_name); ValueMap attributes = section.getAttributes(); bw.writeValueSimpleMap(attributes == null? new ValueMap(): attributes); savedsizes_sectionAttributes[i] = (int) output.getFilePointer() - posBeforeAttributes; } { // contents int posBeforeContent = (int) output.getFilePointer(); alog(output.getFilePointer(), "write section content: " + section_name); ((SectionContent.Writer) section).write(output); savedsizes_sectionContent[i] = (int) output.getFilePointer() - posBeforeContent; } } //////////// WRITE INTO PLACEHOLDERS //////////////// long lastPos = output.getFilePointer(); { // offsets and sizes in the section index for (int i = 0; i < section_count; i++) { SectionContent section = sections.get(i); String section_name = section.getName(); int offset = saved_sectionOffset[i]; int attributes_size = savedsizes_sectionAttributes[i]; int content_size = savedsizes_sectionContent[i]; output.seek(savedpos_sectionIndexSizeEntries[i]); alog(output.getFilePointer(), "write offset, attributes_size, content_size of section " + section_name + ": " + offset + ", " + attributes_size + ", " + content_size); bw.writeInt(offset); bw.writeInt(attributes_size); bw.writeInt(content_size); } } output.seek(lastPos); alog(output.getFilePointer(), "write footer"); bw.writeUint8(0); alog(output.getFilePointer(), "done"); bw.close(); } }