package edu.byu.cs.roots.opg.io; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.swing.JFileChooser; import edu.byu.cs.roots.opg.io.PAF5RecordObject.PAF5RecordType; import edu.byu.cs.roots.opg.model.Event; import edu.byu.cs.roots.opg.model.EventType; import edu.byu.cs.roots.opg.model.Family; import edu.byu.cs.roots.opg.model.Gender; import edu.byu.cs.roots.opg.model.Individual; public class PAF5Parser { private static final int PAF5_MAP_TOTALS_SIZE = 8192; private static final int PAF5_CLUSTER_SIZE = 67108864; private static final int PAF5_BLOCK_SIZE = 8192; private static final int PAF5_MAX_CLUSTERS = 31; private static final int PAF5_MAX_BLOCK_TYPES = 32; private static final int PAF5_MAP_RECORD_SIZE = 8192; static LEndianRandomAccessFile file = null; static private PAF5Header header = new PAF5Header(); private static short dBMap[] = new short[PAF5_MAP_RECORD_SIZE / 2]; private static short[][] mapBlocks = new short[PAF5_MAX_CLUSTERS][]; public static void main(String[] args) throws IOException { //testing driver JFileChooser chooser = new JFileChooser(); int option = chooser.showOpenDialog(null); if (option == JFileChooser.APPROVE_OPTION) PAF5Parser.parsePAF5File(chooser.getSelectedFile().getAbsolutePath()); System.exit(0); } static public GedcomRecord parsePAF5File(String fileName) throws IOException{ GedcomRecord record = null; record = new GedcomRecord(); String tempFileName = null; try{ if (fileName.endsWith(".zip")) { //extract the paf file from the zip(first paf found) (if it exists) // to allow randomFileAccess ZipFile zipFile = new ZipFile(fileName); Enumeration<? extends ZipEntry> zipEntries = zipFile.entries(); while (zipEntries.hasMoreElements()) { ZipEntry entry = (ZipEntry)zipEntries.nextElement(); if (entry.getName().endsWith(".paf")) { InputStream is = null; OutputStream dest = null; is = new BufferedInputStream (zipFile.getInputStream(entry)); int count; byte data[] = new byte[2048]; tempFileName = "~" + entry.getName(); FileOutputStream fos = new FileOutputStream(tempFileName); dest = new BufferedOutputStream(fos, 2048); while ((count = is.read(data, 0, 2048)) != -1) { dest.write(data, 0, count); } dest.flush(); dest.close(); is.close(); break; } } if (tempFileName == null) throw new IOException(fileName + "is not a PAF backup file."); file = new LEndianRandomAccessFile(tempFileName, "r"); } else if (fileName.endsWith(".paf")) file = new LEndianRandomAccessFile(fileName, "r"); else throw new IOException(fileName + "is not a PAF file."); //read file header header.read(file); // if (!loadMapBlocks()) throw new IOException(fileName + "has invalid database map Block(s)."); loadDBMap(); //loop over all individuals in the individual index getIndividuals(record); //get families getFamilies(record); record.linkRecord(); } catch(IOException e){ System.err.println("Error reading PAF database: " + fileName); e.printStackTrace(); } finally { if (file != null) file.close(); if (tempFileName != null) { File tempFile = new File(tempFileName); tempFile.delete(); } } return record; } private static void getIndividuals(GedcomRecord record) throws IOException { int maxRin = header.fileBlocks.indiCount; for (int i = 1; i <= maxRin; ++i) { Individual indi = getIndividual(i); if (indi != null) { record.addIndividual(indi.id, indi); } else maxRin++; } } private static Individual getIndividual(int rin) throws IOException { Individual indi = new Individual("I" + rin); PAF5IndiRecord indiRecord = new PAF5IndiRecord(); loadFromDatabase(indiRecord, rin); //name PAF5IndiNameRecord nameRecord = new PAF5IndiNameRecord(); file.seek(indiRecord.nameOffset); nameRecord.read(file); parseIndiName(indi, nameRecord.text); //gender if (indiRecord.sex == 'M') indi.gender = Gender.MALE; else if (indiRecord.sex == 'F') indi.gender = Gender.FEMALE; else if (indiRecord.sex == 'U') indi.gender = Gender.UNKNOWN; else return null; //deleted record //birth if (indiRecord.birth.placeNameOffset != 0 || !indiRecord.birth.date.dateString.equals("")) { indi.birth = new Event(EventType.BIRTH); if (indiRecord.birth.placeNameOffset != 0) { PAF5NameRecord birthPlace = new PAF5NameRecord(); file.seek(indiRecord.birth.placeNameOffset); birthPlace.read(file); indi.birth.place = birthPlace.text; } if (!indiRecord.birth.date.dateString.equals("")) indi.birth.date = indiRecord.birth.date.dateString; indi.birth.parseDateParts(); } //death if (indiRecord.death.placeNameOffset != 0 || !indiRecord.death.date.dateString.equals("")) { indi.death = new Event(EventType.DEATH); if (indiRecord.death.placeNameOffset != 0) { PAF5NameRecord deathPlace = new PAF5NameRecord(); file.seek(indiRecord.death.placeNameOffset); deathPlace.read(file); indi.death.place = deathPlace.text; } if (!indiRecord.death.date.dateString.equals("")) indi.death.date = indiRecord.death.date.dateString; indi.death.parseDateParts(); } //todo - photo and ordinances return indi; } private static void getFamilies(GedcomRecord record) throws IOException { int maxRin = header.fileBlocks.marriageCount; for (int i = 1; i <= maxRin; ++i) { Family fam = getFamily(i); if (fam != null) { record.addFamily(fam.id, fam); //add fams and famc ids in for individuals in marriage if (fam.husbandId != null) { Individual husband = record.getIndividuals().get(fam.husbandId); if (husband != null) husband.famsIds.add(fam.id); } if (fam.wifeId != null) { Individual wife = record.getIndividuals().get(fam.wifeId); if (wife != null) wife.famsIds.add(fam.id); } for(String childId : fam.childrenXRefIds) { Individual child = record.getIndividuals().get(childId); if (child != null) child.famcIds.add(fam.id); } } else maxRin++; } } private static Family getFamily(int mRin) throws IOException { Family fam = new Family("F" + mRin); PAF5MarriageRecord marriageRecord = new PAF5MarriageRecord(); loadFromDatabase(marriageRecord, mRin); //check for deleted record if (marriageRecord.divorcedStatus == 'D') return null; //husband if (marriageRecord.husbandRin != 0) fam.husbandId = "I" + marriageRecord.husbandRin; //wife if (marriageRecord.wifeRin != 0) fam.wifeId = "I" + marriageRecord.wifeRin; //marriage event if (marriageRecord.marriageEvent.placeNameOffset != 0 || !marriageRecord.marriageEvent.date.dateString.equals("")) { fam.marriage = new Event(EventType.MARRIAGE); if (marriageRecord.marriageEvent.placeNameOffset != 0) { PAF5NameRecord marriagePlace = new PAF5NameRecord(); file.seek(marriageRecord.marriageEvent.placeNameOffset); marriagePlace.read(file); fam.marriage.place = marriagePlace.text; } if (!marriageRecord.marriageEvent.date.dateString.equals("")) fam.marriage.date = marriageRecord.marriageEvent.date.dateString; } //children PAF5FamilyLinkRecord famLink = new PAF5FamilyLinkRecord(); int curChildLinkRin = marriageRecord.firstChildLinkRin; while (curChildLinkRin > 0) { loadFromDatabase(famLink, curChildLinkRin); if (famLink.linkType == 'D') break; if (famLink.rin == 0) break; fam.childrenXRefIds.add("I" + famLink.rin); curChildLinkRin = famLink.nextSiblngRin; if (curChildLinkRin == marriageRecord.firstChildLinkRin) break; } //todo - sealing status info return fam; } private static void parseIndiName(Individual indi, String text) { indi.givenName = getGivenName(text); indi.surname = getSurname(text); indi.nameSuffix = getNameSuffix(text); } private static String getGivenName(String nameline) { int end = nameline.indexOf("/"); if(end != -1) return nameline.substring(0,end); else return nameline.trim(); } private static String getSurname(String nameline) { int start = nameline.indexOf("/"); int end = nameline.lastIndexOf("/"); if(start != -1 && end != -1 && start < nameline.length()-1 && start != end){ return nameline.substring(start+1, end); } else if(start != -1 && end == -1 && start < nameline.length()-1 && start != end){ return nameline.substring(start+1); } else return ""; } private static String getNameSuffix(String nameline) { int start = nameline.lastIndexOf("/"); if(start != -1 && start < nameline.length()-1) return nameline.substring(start+1); else return ""; } /* private static String getStringFromFileOffset(long nameOffset) throws IOException { //get a null terminated string given an offset in the open file byte[] buffer = new byte[256]; int pos = 0; do { buffer[pos] = file.readByte(); } while (buffer[pos++] != 0); byte[] buffer2 = new byte[pos-2]; for (int i = 0; i < buffer2.length; ++i) buffer2[i] = buffer[i]; return new String(buffer2, "UTF-8"); }*/ public static String getNullTermStringFromBuff(byte[] buffer) { int charCount = 0; for(int i = 0; i < buffer.length; ++i) { if(buffer[i] != 0) ++charCount; else break; } try { return new String(buffer, 0, charCount, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return null; } } static private void loadDBMap() throws IOException { file.seek(PAF5Header.size); for (int i = 0; i < dBMap.length; ++i) dBMap[i] = file.readShortLE(); } static private boolean loadMapBlocks() throws IOException { boolean retVal = true; for(int i = 0; i < header.fileBlocks.mapBlockCount; ++i) retVal &= loadMapBlock(i); return retVal; } static private boolean loadMapBlock(int cluster) throws IOException { // Load the Map Block byte[] mapBlockTmp = new byte[PAF5_BLOCK_SIZE]; long filePos = PAF5Header.size + PAF5_MAP_TOTALS_SIZE + cluster * PAF5_CLUSTER_SIZE; file.seek(filePos); file.readFully(mapBlockTmp); if (mapBlockTmp[0] != PAF5RecordType.MAP.typeId) return false; // Allocate space for the pre-compiled Map Block int clusterCount = 0; while (clusterCount <= cluster) { mapBlocks[clusterCount] = new short[PAF5_MAX_BLOCK_TYPES + PAF5_BLOCK_SIZE]; ++clusterCount; } short[] mapBlock = mapBlocks[cluster]; // Precompile it to a table of indexes so we don't have to walk it int[] blockCount = new int[PAF5_MAX_BLOCK_TYPES]; // Count number of each kind of block for (int i = 0; i < PAF5_BLOCK_SIZE; ++i) { if (mapBlockTmp[i] > 0 && mapBlockTmp[i] < PAF5_MAX_BLOCK_TYPES + 1) blockCount[mapBlockTmp[i] - 1]++; } // Set Front Indexes to point to where block location will be stored int tmp = 0; for (int i = 0; i < PAF5_MAX_BLOCK_TYPES; ++i) { mapBlock[i] = (short)(tmp + PAF5_MAX_BLOCK_TYPES); tmp += blockCount[i]; } // Now fill the chart int[] foundBlocks = new int[PAF5_MAX_BLOCK_TYPES]; for (int i = 0; i < PAF5_BLOCK_SIZE; ++i) { tmp = mapBlockTmp[i] - 1; if (tmp >= 0 && tmp < PAF5_MAX_BLOCK_TYPES) { mapBlock[mapBlock[tmp] + foundBlocks[tmp]] = (short)i; foundBlocks[tmp]++; } } return true; } static PAF5RecordObject loadFromDatabase(PAF5RecordObject obj, long rin) throws IOException { long filePos = findRecordPos(obj.type, rin); if (filePos == 0) return null; file.seek(filePos); obj.read(file); return obj; } private static long findRecordPos(PAF5RecordObject.PAF5RecordType type, long rin) { PAFCBR loc = findCBR(rin, type); if (rin < 1 || loc == null) return 0; return PAF5Header.size + PAF5_MAP_TOTALS_SIZE + loc.cluster * PAF5_CLUSTER_SIZE + loc.block * PAF5_BLOCK_SIZE + loc.record * type.size; } private static PAFCBR findCBR(long rin, PAF5RecordType type) { PAFCBR result = new PAFCBR(); long gotSoFar; long recordsLeft; gotSoFar = findWhichCluster(rin, type, result); if (result.cluster < 0 || result.cluster >= PAF5_MAX_CLUSTERS) return null; recordsLeft = rin - gotSoFar; result.block = findWhichBlock(result.cluster, rin, type); if (result.block < 1 || result.block > PAF5_BLOCK_SIZE) return null; result.record = (int)(recordsLeft - type.numPerBlock*((recordsLeft - 1)/type.numPerBlock)) - 1; if (result.record < 0 || result.record > PAF5_BLOCK_SIZE) return null; return result; } private static int findWhichBlock(int cluster, long rin, PAF5RecordType type) { short[] mapBlock = mapBlocks[cluster]; int tmp = mapBlock[type.typeId - 1] + (int)((rin - 1) / type.numPerBlock); return mapBlock[tmp]; } private static long findWhichCluster(long rin, PAF5RecordType type, PAFCBR result) { long gotSoFar = 0; for (result.cluster = 0; rin > gotSoFar + (long)dBMap[(int)PAF5_MAX_BLOCK_TYPES * result.cluster + type.typeId - 1] * (long)type.numPerBlock;) { gotSoFar += (long)dBMap[PAF5_MAX_BLOCK_TYPES * result.cluster + type.typeId - 1] * (long)type.numPerBlock; result.cluster++; if (result.cluster >= PAF5_MAX_CLUSTERS) { gotSoFar=0; break; } } return gotSoFar; } static class PAFCBR { int cluster; int block; int record; } } abstract class PAF5RecordObject { enum PAF5RecordType { NAME(1, 269), INDI(2, 221), MARRIAGE(3, 106), NOTE(2, 256), FAMILY_LINK(5, 46), CITATION(6, 141), SOURCE(7, 507), REPOSITORY(8, 497), INDI_NAME(9, 271), INDI_INDEX(10, 16), MAP(11, 8192), USER_EVENT(12, 59), EVENT_TYPE(13, 140), CONTACT(17, 57), OTHER_NAME(18, 269), MEDIA(20, 62); public int typeId; public int size; //size in database in bytes public int numPerBlock; public int BLOCK_SIZE = 8192; private PAF5RecordType(int typeId, int size){ this.typeId = typeId; this.numPerBlock = BLOCK_SIZE / size; this.size = size; } } PAF5RecordType type; abstract public void read(LEndianRandomAccessFile file) throws IOException; } class PAF5NameRecord extends PAF5RecordObject { public long alphaLessOffset; public long alphaGreaterOffset; public long parentRecordOffset; byte nameType; String text; public PAF5NameRecord() { type = PAF5RecordType.NAME; type.size = 269; } @Override public void read(LEndianRandomAccessFile file) throws IOException { alphaLessOffset = (long)file.readIntLE(); alphaGreaterOffset = (long)file.readIntLE(); parentRecordOffset = (long)file.readIntLE(); nameType = file.readByte(); byte[] buffer = new byte[256]; file.readFully(buffer); //? maybe read one byte at a time until hit null byte text = PAF5Parser.getNullTermStringFromBuff(buffer); } } class PAF5IndiNameRecord extends PAF5RecordObject { public long nextOffset; public long previousOffset; public long ownerRin; byte nameType; short textLength; String text; public PAF5IndiNameRecord() { type = PAF5RecordType.INDI_NAME; type.size = 271; //variable on read (based on textLength } @Override public void read(LEndianRandomAccessFile file) throws IOException { nextOffset = file.readIntLE(); previousOffset = file.readIntLE(); ownerRin = file.readIntLE(); nameType = file.readByte(); textLength = file.readShortLE(); byte[] buffer = new byte[textLength+1]; file.readFully(buffer); text = PAF5Parser.getNullTermStringFromBuff(buffer); } } class PAF5IndiIndexRecord extends PAF5RecordObject { public int alphaLessRecord; public int alphaGreaterRecord; public int parentRecord; public long nameOffset; public PAF5IndiIndexRecord() { type = PAF5RecordType.INDI_INDEX; type.size = 16; } @Override public void read(LEndianRandomAccessFile file) throws IOException { alphaLessRecord = file.readIntLE(); alphaGreaterRecord = file.readIntLE(); parentRecord = file.readIntLE(); nameOffset = file.readLongLE(); } } class PAF5IndiRecord extends PAF5RecordObject { public long nameOffset; public int firstMRin; public int firstFamLinkRin; public int noteRin; public int firstCitationRin; public long modifiedTime; public long submittedTime; public int firstUserEventRin; public int mediaRin; public int contactRin; public long relDescNameOffset; public int primaryMRin; byte[] uId = new byte[16]; PAF5Event birth = new PAF5Event(); PAF5Event christening = new PAF5Event(); PAF5Event death = new PAF5Event(); PAF5Event burial = new PAF5Event(); PAF5OrdinanceEvent ldsBaptism = new PAF5OrdinanceEvent(); PAF5OrdinanceEvent ldsEndowment = new PAF5OrdinanceEvent(); char sex; byte numEventsWithSources; byte numSPEventsWtihSources; public PAF5IndiRecord() { type = PAF5RecordType.INDI; type.size = 221; } @Override public void read(LEndianRandomAccessFile file) throws IOException { nameOffset = file.readIntLE(); firstMRin = file.readIntLE(); firstFamLinkRin = file.readIntLE(); noteRin = file.readIntLE(); firstCitationRin = file.readIntLE(); modifiedTime = file.readIntLE(); submittedTime = file.readIntLE(); firstUserEventRin = file.readIntLE(); mediaRin = file.readIntLE(); contactRin = file.readIntLE(); relDescNameOffset = file.readIntLE(); primaryMRin = file.readIntLE(); file.readFully(uId); birth.read(file); christening.read(file); death.read(file); burial.read(file); ldsBaptism.read(file); ldsEndowment.read(file); byte[] byteBuf = new byte[1]; byteBuf[0] = (byte)file.read(); sex = new String(byteBuf, "ASCII").charAt(0); numEventsWithSources = file.readByte(); numSPEventsWtihSources = file.readByte(); } } class PAF5MarriageRecord extends PAF5RecordObject { public long modifiedTime; public int husbandRin; public int wifeRin; public int firstChildLinkRin; public int husbandNextMRin; public int wifeNextMRin; public int firstUserEventRin; public int noteRin; public int citationRin; byte[] uId = new byte[16]; PAF5Event marriageEvent = new PAF5Event(); PAF5OrdinanceEvent ldsSealingEvent = new PAF5OrdinanceEvent(); char divorcedStatus; public PAF5MarriageRecord() { type = PAF5RecordType.MARRIAGE; type.size = 106; } @Override public void read(LEndianRandomAccessFile file) throws IOException { modifiedTime = file.readIntLE(); husbandRin = file.readIntLE(); wifeRin = file.readIntLE(); firstChildLinkRin = file.readIntLE(); husbandNextMRin = file.readIntLE(); wifeNextMRin = file.readIntLE(); firstUserEventRin = file.readIntLE(); noteRin = file.readIntLE(); citationRin = file.readIntLE(); file.readFully(uId); marriageEvent.read(file); ldsSealingEvent.read(file); byte[] byteBuf = new byte[1]; byteBuf[0] = (byte)file.read(); divorcedStatus = new String(byteBuf, "ASCII").charAt(0); byte[] buffer = new byte[8]; file.readFully(buffer); } } class PAF5FamilyLinkRecord extends PAF5RecordObject { public int mRin; //??marriage this individual is a child in?? public int nextSiblngRin; public int nextParentsMRin; public int rin; PAF5OrdinanceEvent ldsSealingToParents = new PAF5OrdinanceEvent(); char linkType; public PAF5FamilyLinkRecord() { type = PAF5RecordType.FAMILY_LINK; type.size = 46; } @Override public void read(LEndianRandomAccessFile file) throws IOException { mRin = file.readIntLE(); nextSiblngRin = file.readIntLE(); nextParentsMRin = file.readIntLE(); rin = file.readIntLE(); ldsSealingToParents.read(file); byte[] byteBuf = new byte[1]; byteBuf[0] = (byte)file.read(); linkType = new String(byteBuf, "ASCII").charAt(0); byte[] buffer = new byte[8]; file.readFully(buffer); } } class PAF5MediaRecord extends PAF5RecordObject { public int ownerRin; public int nextMediaRin; public int previousMediaRin; public int pathNameRin; public int captionNameRin; public int descriptionNameRin; public int audioPathNameRin; char mediaType; byte ownerType; short flags; short width; short height; short clipTop; short clipBottom; short clipLeft; short clipRight; int slideTime; public PAF5MediaRecord() { type = PAF5RecordType.MEDIA; type.size = 62; } @Override public void read(LEndianRandomAccessFile file) throws IOException { ownerRin = file.readIntLE(); nextMediaRin = file.readIntLE(); previousMediaRin = file.readIntLE(); pathNameRin = file.readIntLE(); captionNameRin = file.readIntLE(); descriptionNameRin = file.readIntLE(); audioPathNameRin = file.readIntLE(); byte[] byteBuf = new byte[1]; byteBuf[0] = (byte)file.read(); mediaType = new String(byteBuf, "ASCII").charAt(0); ownerType = (byte)file.read(); flags = file.readShortLE(); width = file.readShortLE(); height = file.readShortLE(); clipTop = file.readShortLE(); clipBottom = file.readShortLE(); clipLeft = file.readShortLE(); clipRight = file.readShortLE(); slideTime = ((int)file.readShort() & 0x0000FFFF); //stored in file as unsigned short byte[] buffer = new byte[16]; file.readFully(buffer); } } class PAF5Event { public PAF5Date date = new PAF5Date(); public long placeNameOffset; public long ageAtNameOffset; public int citationRin; public void read(LEndianRandomAccessFile file) throws IOException { date.read(file); placeNameOffset = file.readIntLE(); ageAtNameOffset = file.readIntLE(); citationRin = file.readIntLE(); } } class PAF5OrdinanceEvent { public PAF5Date date = new PAF5Date(); public long placeNameOffset; public int citationRin; byte isTemple; public void read(LEndianRandomAccessFile file) throws IOException { date.read(file); placeNameOffset = file.readIntLE(); citationRin = file.readIntLE(); isTemple = file.readByte(); } } class PAF5Date { byte[] dateInfo = new byte[12]; String dateString = ""; public static int getJulianDay(int year, int month, int day) { int result = day - 32075 + 1461 * ( year + 4800 - ( 14 - month ) / 12 )/4 + 367 * ( month - 2 + ( ( 14 - month ) / 12 ) * 12 ) / 12 - 3 * ( ( year + 4900 - ( 14 - month ) / 12 ) / 100 ) / 4; return result; } public int getJulianDayDate1() { int julianDay = (dateInfo[4] & 0xFF) + ((dateInfo[5] & 0xFF) << 8) + ((dateInfo[6] & 0xFF) << 16); //read julian date flags return julianDay; } public int getJulianDayDate2() { int julianDay = (dateInfo[9] & 0xFF) + ((dateInfo[10] & 0xFF) << 8) + ((dateInfo[11] & 0xFF) << 16); return julianDay; } public static Calendar getCalendarFromJulianDay(int julianDay) { int jd = julianDay; int jdate_tmp; int y, m, d; jdate_tmp = jd - 1721119; y = (4 * jdate_tmp - 1)/146097; jdate_tmp = 4 * jdate_tmp - 1 - 146097 * y; d = jdate_tmp/4; jdate_tmp = (4 * d + 3)/1461; d = 4 * d + 3 - 1461 * jdate_tmp; d = (d + 4)/4; m = (5 * d - 3)/153; d = 5 * d - 3 - 153 * m; d = (d + 5) / 5; y = 100 * y + jdate_tmp; if(m < 10) { m += 3; } else { m -= 9; ++y; } Calendar cal = new GregorianCalendar(y, m-1, d); return cal; } public void read(LEndianRandomAccessFile file) throws IOException { file.readFully(dateInfo); int dateRecordDescriptor = dateInfo[0] & 0x07; int date1Type = (dateInfo[0] >>> 4) & 0x03; int date2Type = (dateInfo[0] >>> 6) & 0x03; if (dateRecordDescriptor == 0)// no date return; // int dateStatusOrModifier = (dateInfo[1]) & 0xFF; String date1String = null; String date2String = null; boolean date1NoDay = false; boolean date1NoMonth = false; boolean date1NoYear = false; boolean date2NoDay = false; boolean date2NoMonth = false; boolean date2NoYear = false; int julianDayDate1 = 0; int julianDayDate2 = 0; if (dateRecordDescriptor == 1) //date status (e.g. BIC, etc) { //check flags in dateStatusOrModifier dateString = "Date Status"; return; } if (date1Type == 1)//julian date { //read date flags julianDayDate1 = getJulianDayDate1(); date1NoDay = (((dateInfo[3] >> 5) & 0x1) == 1)? true : false; date1NoMonth = (((dateInfo[3] >> 6) & 0x1) == 1)? true : false; date1NoYear = (((dateInfo[3] >> 7) & 0x1) == 1)? true : false; // int eraId = (dateInfo[2] & 0xFF) + // ((dateInfo[3] & 0x0F) << 8); date1String = getJulianDateString(julianDayDate1, date1NoDay, date1NoMonth, date1NoYear); } else if (date1Type == 2) //non-standard date { //get date text from name record long oldFilePos = file.getFilePointer(); int nameOffset = (dateInfo[2] & 0xFF) + ((dateInfo[3] & 0xFF) << 8) + ((dateInfo[4] & 0xFF) << 16) + ((dateInfo[5] & 0xFF) << 24); PAF5NameRecord dateName = new PAF5NameRecord(); file.seek(nameOffset); dateName.read(file); file.seek(oldFilePos); date1String = dateName.text; } if (dateRecordDescriptor == 3 || dateRecordDescriptor == 4) { if (date2Type == 1)//julian date { julianDayDate2 = getJulianDayDate2(); date2NoDay = (((dateInfo[3] >> 5) & 0x1) == 1)? true : false; date2NoMonth = (((dateInfo[3] >> 6) & 0x1) == 1)? true : false; date2NoYear = (((dateInfo[3] >> 7) & 0x1) == 1)? true : false; // int eraId = (dateInfo[7] & 0xFF) + // ((dateInfo[8] & 0x0F) << 8); date2String = getJulianDateString(julianDayDate2, date2NoDay, date2NoMonth, date2NoYear); } else if (date2Type == 2) //non-standard date { //get date text from name record long oldFilePos = file.getFilePointer(); int nameOffset = (dateInfo[2] & 0xFF) + ((dateInfo[3] & 0xFF) << 8) + ((dateInfo[4] & 0xFF) << 16) + ((dateInfo[5] & 0xFF) << 24); PAF5NameRecord dateName = new PAF5NameRecord(); file.seek(nameOffset); dateName.read(file); file.seek(oldFilePos); date2String = dateName.text; } } switch (dateRecordDescriptor) { case 2://single date dateString = date1String; break; case 3://split date //NOTE: PAF5 only allows one field of day/month/year to differ Calendar cal1 = getCalendarFromJulianDay(julianDayDate1); Calendar cal2 = getCalendarFromJulianDay(julianDayDate2); boolean first = true; if (!date1NoDay) { first = false; DateFormat format = new SimpleDateFormat("d"); if (cal1.get(Calendar.DAY_OF_MONTH) != cal2.get(Calendar.DAY_OF_MONTH) && !date2NoDay) { dateString += format.format(cal1.getTime()) + "/"; dateString += format.format(cal2.getTime()); } else dateString += format.format(cal1.getTime()); } if (!date1NoMonth) { if (!first) dateString += " "; first = false; DateFormat format = new SimpleDateFormat("MMM"); if (cal1.get(Calendar.MONTH) != cal2.get(Calendar.MONTH) && !date2NoMonth) { dateString += format.format(cal1.getTime()) + "/"; dateString += format.format(cal2.getTime()); } else dateString += format.format(cal1.getTime()); } if (!date1NoYear) { if (!first) dateString += " "; first = false; DateFormat format = new SimpleDateFormat("yyyy"); if ((cal1.get(Calendar.YEAR) != cal2.get(Calendar.YEAR) || (cal1.get(Calendar.ERA) != cal2.get(Calendar.ERA))) && !date2NoYear) { if (cal1.get(Calendar.ERA) == GregorianCalendar.BC) format = new SimpleDateFormat("yyyy G"); dateString += format.format(cal1.getTime()) + "/"; if (cal2.get(Calendar.ERA) == GregorianCalendar.BC) format = new SimpleDateFormat("yyyy G"); else format = new SimpleDateFormat("yyyy"); dateString += format.format(cal2.getTime()); } else dateString += format.format(cal1.getTime()); } break; case 4://date range dateString = "from " + date1String + " to " + date2String; break; }; } private String getJulianDateString(int julianDayDate, boolean noDay, boolean noMonth, boolean noYear) { Calendar cal = getCalendarFromJulianDay(julianDayDate); boolean first = true; String formatString = ""; if (!noDay) { first = false; formatString += "d"; } if (!noMonth) { if (!first) formatString += " "; first = false; formatString += "MMM"; } if (!noYear) { if (!first) formatString += " "; first = false; formatString += "yyyy"; } if (cal.get(Calendar.ERA) == GregorianCalendar.BC) formatString += " G"; DateFormat format = new SimpleDateFormat(formatString);; return format.format(cal.getTime()); } } class PAF5Header { static final int size = 4096; public void read(LEndianRandomAccessFile file) throws IOException { byte[] buffer4 = new byte[4]; file.readFully(buffer4);//ignore last version of paf to write to file lastVersion = PAF5Parser.getNullTermStringFromBuff(buffer4); file.readFully(buffer4);//earliest version of paf that can read the file String pafVersion; try { pafVersion = PAF5Parser.getNullTermStringFromBuff(buffer4); } catch (NumberFormatException e) { throw new IOException("File read problem - Invalid paf version."); } if (Integer.parseInt(pafVersion) < 500) throw new IOException("File read problem - PAF file is not at least version 5."); file.readFully(buffer4);//file identifier - must be "PAF" identifier = PAF5Parser.getNullTermStringFromBuff(buffer4); if (!identifier.equals("PAF")) throw new IOException("File read problem - not a PAF file."); databaseVersion = file.readShortLE(); fileBlocks.read(file); pedigreeIndiRecordNum = file.readIntLE(); userData.read(file); for (int i = 0; i < 10; ++i) lastSavedIndiRecordNums[i] = file.readIntLE(); for (int i = 0; i < 10; ++i) lastSavedCitationRecordNums[i] = file.readIntLE(); for (int i = 0; i < 10; ++i) lastSavedMarriageRecordNums[i] = file.readIntLE(); lastViewedIndiRecordNum = file.readIntLE(); cLog = file.readByte(); numTimesClosedSinceBackup = file.readShortLE(); showRelationshipIndiRecordNum = file.readIntLE(); //system time - last modified for (int i = 0; i < 8; ++i) modifedTime[i] = file.readShortLE(); databaseUId = file.readLongLE(); auxValue1 = file.readLongLE(); auxValue2 = file.readLongLE(); flags1 = file.readLongLE(); flags2 = file.readLongLE(); flags3 = file.readLongLE(); flags4 = file.readLongLE(); flags5 = file.readLongLE(); } String lastVersion; String version; String identifier; short databaseVersion; PAF5FileBlocks fileBlocks = new PAF5FileBlocks(); int pedigreeIndiRecordNum; PAF5UserData userData = new PAF5UserData(); int[] lastSavedIndiRecordNums = new int[10]; int[] lastSavedCitationRecordNums = new int[10]; int[] lastSavedMarriageRecordNums = new int[10]; int lastViewedIndiRecordNum; byte cLog; short numTimesClosedSinceBackup; int showRelationshipIndiRecordNum; //system time - last modified short[] modifedTime = new short[8]; long databaseUId; long auxValue1; long auxValue2; long flags1; long flags2; long flags3; long flags4; long flags5; } class PAF5FileBlocks { static final int size = 716; int mapCount; int mapBlockCount; int indiCount; int indiBlockCount; int firstIndi; int firstFreeIndi; int indiFreeCount; int indiIdxCount; int indiIdxBlockCount; int firstIndiIdx; int firstFreeIndiIdx; int indiIdxFreeCount; int indiNameCount; int indiNameBlockCount; int indiNameStart; int indiNameOffset; int indiNameHighOffset; int firstFreeIndiName; int indiNameFreeCount; int nameCount; int nameBlockCount; int nameStart; int nameOffset; int nameHighOffset; int otherNameCount; int otherNameBlockCount; int otherNameStart; int otherNameOffset; int otherNameHighOffset; int marriageCount; int marriageBlockCount; int firstMarriage; int firstFreeMarriage; int marriageFreeCount; int noteCount; int noteBlockCount; int firstNote; int firstFreeNote; int noteFreeCount; int citationCount; int citationBlockCount; int firstCitation; int firstFreeCitation; int citationFreeCount; int sourceCount; int sourceBlockCount; int firstSource; int firstFreeSource; int sourceFreeCount; int repositoryCount; int repositoryBlockCount; int firstRepository; int firstFreeRepository; int repositoryFreeCount; int familyLinkCount; int familyLinkBlockCount; int firstFamilyLink; int firstFreeFamilyLink; int familyLinkFreeCount; int eventTypeCount; int eventTypeBlockCount; int firstEventType; int firstFreeEventType; int eventTypeFreeCount; int mediaCount; int mediaBlockCount; int firstMedia; int firstFreeMedia; int mediaFreeCount; int eventCount; int eventBlockCount; int firstEvent; int firstFreeEvent; int eventFreeCount; int contactCount; int contactBlockCount; int firstContact; int firstFreeContact; int contactFreeCount; public void read(LEndianRandomAccessFile file) throws IOException { mapCount = file.readIntLE(); mapBlockCount = file.readIntLE(); indiCount = file.readIntLE(); indiBlockCount = file.readIntLE(); firstIndi = file.readIntLE(); firstFreeIndi = file.readIntLE(); indiFreeCount = file.readIntLE(); indiIdxCount = file.readIntLE(); indiIdxBlockCount = file.readIntLE(); firstIndiIdx = file.readIntLE(); firstFreeIndiIdx = file.readIntLE(); indiIdxFreeCount = file.readIntLE(); indiNameCount = file.readIntLE(); indiNameBlockCount = file.readIntLE(); indiNameStart = file.readIntLE(); indiNameOffset = file.readIntLE(); indiNameHighOffset = file.readIntLE(); firstFreeIndiName = file.readIntLE(); indiNameFreeCount = file.readIntLE(); nameCount = file.readIntLE(); nameBlockCount = file.readIntLE(); nameStart = file.readIntLE(); nameOffset = file.readIntLE(); nameHighOffset = file.readIntLE(); otherNameCount = file.readIntLE(); otherNameBlockCount = file.readIntLE(); otherNameStart = file.readIntLE(); otherNameOffset = file.readIntLE(); otherNameHighOffset = file.readIntLE(); marriageCount = file.readIntLE(); marriageBlockCount = file.readIntLE(); firstMarriage = file.readIntLE(); firstFreeMarriage = file.readIntLE(); marriageFreeCount = file.readIntLE(); noteCount = file.readIntLE(); noteBlockCount = file.readIntLE(); firstNote = file.readIntLE(); firstFreeNote = file.readIntLE(); noteFreeCount = file.readIntLE(); citationCount = file.readIntLE(); citationBlockCount = file.readIntLE(); firstCitation = file.readIntLE(); firstFreeCitation = file.readIntLE(); citationFreeCount = file.readIntLE(); sourceCount = file.readIntLE(); sourceBlockCount = file.readIntLE(); firstSource = file.readIntLE(); firstFreeSource = file.readIntLE(); sourceFreeCount = file.readIntLE(); repositoryCount = file.readIntLE(); repositoryBlockCount = file.readIntLE(); firstRepository = file.readIntLE(); firstFreeRepository = file.readIntLE(); repositoryFreeCount = file.readIntLE(); familyLinkCount = file.readIntLE(); familyLinkBlockCount = file.readIntLE(); firstFamilyLink = file.readIntLE(); firstFreeFamilyLink = file.readIntLE(); familyLinkFreeCount = file.readIntLE(); eventTypeCount = file.readIntLE(); eventTypeBlockCount = file.readIntLE(); firstEventType = file.readIntLE(); firstFreeEventType = file.readIntLE(); eventTypeFreeCount = file.readIntLE(); mediaCount = file.readIntLE(); mediaBlockCount = file.readIntLE(); firstMedia = file.readIntLE(); firstFreeMedia = file.readIntLE(); mediaFreeCount = file.readIntLE(); eventCount = file.readIntLE(); eventBlockCount = file.readIntLE(); firstEvent = file.readIntLE(); firstFreeEvent = file.readIntLE(); eventFreeCount = file.readIntLE(); contactCount = file.readIntLE(); contactBlockCount = file.readIntLE(); firstContact = file.readIntLE(); firstFreeContact = file.readIntLE(); contactFreeCount = file.readIntLE(); //read 400 spare bytes byte[] extra = new byte[400]; file.readFully(extra); } } class PAF5UserData { static final int size = 768; String name; String address1; String address2; String address3; String address4; String country; String phone; String submitterAFN; String submitterEmail; long submitterUId; long defaultLanguage; public void read(LEndianRandomAccessFile file) throws IOException { byte[] buffer96 = new byte[96]; file.read(buffer96, 0, 84); name = PAF5Parser.getNullTermStringFromBuff(buffer96); file.read(buffer96, 0, 84); address1 = PAF5Parser.getNullTermStringFromBuff(buffer96); file.read(buffer96, 0, 84); address2 = PAF5Parser.getNullTermStringFromBuff(buffer96); file.read(buffer96, 0, 84); address3 = PAF5Parser.getNullTermStringFromBuff(buffer96); file.read(buffer96, 0, 84); address4 = PAF5Parser.getNullTermStringFromBuff(buffer96); file.read(buffer96, 0, 84); country = PAF5Parser.getNullTermStringFromBuff(buffer96); file.read(buffer96, 0, 52); phone = PAF5Parser.getNullTermStringFromBuff(buffer96); file.read(buffer96, 0, 28); submitterAFN = PAF5Parser.getNullTermStringFromBuff(buffer96); file.read(buffer96, 0, 84); submitterEmail = PAF5Parser.getNullTermStringFromBuff(buffer96); submitterUId = file.readLongLE(); defaultLanguage = file.readLongLE(); //read extra space - 96 bytes file.read(buffer96, 0, 96); } } class LEndianRandomAccessFile extends RandomAccessFile { public LEndianRandomAccessFile(String fileName, String mode) throws FileNotFoundException { super(fileName, mode); } public LEndianRandomAccessFile(File file, String mode) throws FileNotFoundException { super(file, mode); } /** * @return next four bytes of the stream as a Little Endian integer * @throws IOException */ public int readIntLE() throws IOException { int b4 = read(); int b3 = read(); int b2 = read(); int b1 = read(); return b4 + (b3 << 8) + (b2 << 16) + (b1 << 24); } public short readShortLE() throws IOException { int b2 = read(); int b1 = read(); return (short)(b2 + (b1 << 8)); } public long readLongLE() throws IOException { int b8 = read(); int b7 = read(); int b6 = read(); int b5 = read(); int b4 = read(); int b3 = read(); int b2 = read(); int b1 = read(); return (b8 + ((long)b7 << 8) + ((long)b6 << 16) + ((long)b5 << 24) + ((long)b4 << 32) + ((long)b3 << 40) + ((long)b2 << 48) + ((long)b1 << 56)); } }