Logo Search packages:      
Sourcecode: pcalendar version File versions  Download package

Engine.java

/**
 *  Copyright (C) 2009-2010 by Mar'yan Rachynsky
 *  mrach@users.sourceforge.net
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.sf.linuxorg.pcal.engine;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import java.util.prefs.Preferences;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import javax.crypto.SecretKey;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import net.sf.linuxorg.pcal.PCalDayInfo;
import net.sf.linuxorg.pcal.PCalPeriodInfo;
import net.sf.linuxorg.pcal.PCalendar;
import net.sf.linuxorg.pcal.messages.Messages;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

/**
 * @author Mar'yan Rachynskyy
 *
 */

00069 public class Engine {

00071       public class PasswordRequiredException extends Exception {
            private static final long serialVersionUID = 1L;
      };

      private EnginePreferences enginePreferences = new EnginePreferences();

      private static final String FILE_VERSION = "2.0"; //$NON-NLS-1$
      private static final int FILE_TYPE_PLAIN = 0;
      private static final int FILE_TYPE_ENCRYPTED = 1;
      private static final int FILE_TYPE_UNKNOWN = -1;
      private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd"); //$NON-NLS-1$
      // Date record type ID's
      protected static final Integer PCAL_TYPE_MENS = 1;
      protected static final Integer PCAL_TYPE_PREGNANCY = 2;
      protected static final Integer PCAL_TYPE_BIRTH = 3;
      protected static final Integer PCAL_TYPE_PREGNANCY_INT = 4;
      protected static final Integer PCAL_TYPE_OVULATION = 5;

      // Date-value pairs type ID's
      private static final Integer PCAL_TYPE_NOTES = 1;
      private static final Integer PCAL_TYPE_BBT = 2;
      private static final Integer PCAL_TYPE_DATE_INTS = 3;

      private static final int DEFAULT_BUFFER_DAYS = 0;
      private static final int DEFAULT_BAD_FEEL_DAYS_BEFORE = 0;
      private static final int DEFAULT_BAD_FEEL_DAYS_AFTER = 0;
      private static final int DEFAULT_REGULAR_MIN_LENGTH = 24;
      private static final int DEFAULT_REGULAR_MAX_LENGTH = 35;

      private static final String BBT_VALUE = "BBT_VALUE"; //$NON-NLS-1$
      private static final String BBT_SYMPTHOM = "BBT_SYMPTHOM"; //$NON-NLS-1$

      // These are the engine prediction constants which are used during the calculations
      private static final int REGULAR_PREGNANCY_DAYS = 280; /* 40 weeks * 7 */
      private static final int MAX_PREGNANCY_DAYS = 308; /* 44 weeks * 7 */
      private static final int RELIABLE_LENGTH_DIFF = 2;
      private static final int RELIABLE_MAX_AVG_LENGTH = 33;
      private static final int RELIABLE_MIN_AVG_LENGTH = 25;
      private static final int RELIABLE_PERIODS_COUNT = 5;
      private static final int UNRELIABLE_MAX_AVG_LENGTH = 35;
      private static final int UNRELIABLE_MIN_AVG_LENGTH = 24;
      private static final int UNRELIABLE_LENGTH_DIFF = 3;
      private static final int PERIOD_FERTILE_DAYS_COUNT = 6;

      private static final String FILE_PROPERTIES_TAG = "FileProperties"; //$NON-NLS-1$

      private static final String CELSIUS_SCALE = "CELSIUS_SCALE"; //$NON-NLS-1$
      private static final String TRUE = "True";                              //$NON-NLS-1$
      private static final String FALSE = "False";                      //$NON-NLS-1$

      private TreeSet<Date> startDates = new TreeSet<Date>(); /// the calendar events index
00122       private HashMap<Date, Integer> dateTypes = new HashMap<Date, Integer>(); /// the types of the calendar events listed in start_dates
00123       private HashMap<Date, String> notesContainer = new HashMap<Date, String>(); /// the notes container
00124       private HashMap<Date, Integer> bBTList = new HashMap<Date, Integer>(); /// the BBT records list
00125       private boolean fileBBTCelsiusScale = true; // if true - the file contains BBT records in the Celsius scale, otherwise - in Fahrenheit
      private HashMap<Date, DateIntsContainer> dateIntValues = new HashMap<Date, DateIntsContainer>(); /// the date-value container for the integer values
00127       private BBTSympthomsSetDefinition sympthomsDefinition = new BBTSympthomsSetDefinition(); // the container for the additional sympthoms list to be monitored
      private HashMap<Integer, String> dateTypeToTagMap = new HashMap<Integer, String>(); /// map the date type code to the tag name
00129       private HashMap<Integer, String> dateValueTypeToTagMap = new HashMap<Integer, String>(); /// map the date-value type code to the tag name

00131       private boolean modified = false; /// true if any changes were done since last save or load the statistics data
00132       private int periodsCount = 0; /// regular periods count
00133       private int avgLength = 0;
      private int minLength = 0;
      private int maxLength = 0;
      private int minOvulationDayNum = 0;
      private int maxOvulationDayNum = 0;
      private int recordedOvulations = 0;
      private int calMethodAccuracy = 3; /// 0 - high, 1 - moderate, 2 - low, 3 - no data 

00141       private File workFile = null;
      private SecretKey workFileKey = null;

      public Engine() {

            // initialize the date types map
            dateTypeToTagMap.put(PCAL_TYPE_MENS, "mens"); //$NON-NLS-1$
            dateTypeToTagMap.put(PCAL_TYPE_PREGNANCY, "pregnancy"); //$NON-NLS-1$
            dateTypeToTagMap.put(PCAL_TYPE_BIRTH, "birth");  //$NON-NLS-1$
            dateTypeToTagMap.put(PCAL_TYPE_PREGNANCY_INT, "pregnancyint");  //$NON-NLS-1$
            dateTypeToTagMap.put(PCAL_TYPE_OVULATION, "ovulation");  //$NON-NLS-1$

            // initialize the date-value pair types map
            dateValueTypeToTagMap.put(PCAL_TYPE_NOTES, "note"); //$NON-NLS-1$
            dateValueTypeToTagMap.put(PCAL_TYPE_BBT, "bbt"); //$NON-NLS-1$
            dateValueTypeToTagMap.put(PCAL_TYPE_DATE_INTS, "dateints"); //$NON-NLS-1$

            Preferences enginePrefsNode = PCalendar.settings.node("Engine"); //$NON-NLS-1$
            enginePreferences.bufferDays = enginePrefsNode.getInt("Buffer Days", DEFAULT_BUFFER_DAYS); //$NON-NLS-1$
            enginePreferences.badFeelDaysBefore = enginePrefsNode.getInt("Bad Feels Before", DEFAULT_BAD_FEEL_DAYS_BEFORE); //$NON-NLS-1$
            enginePreferences.badFeelDaysAfter = enginePrefsNode.getInt("Bad Feels After", DEFAULT_BAD_FEEL_DAYS_AFTER); //$NON-NLS-1$
            enginePreferences.regularMinLength = enginePrefsNode.getInt("Reg. Min Length", DEFAULT_REGULAR_MIN_LENGTH); //$NON-NLS-1$
            enginePreferences.regularMaxLength = enginePrefsNode.getInt("Reg. Max Length", DEFAULT_REGULAR_MAX_LENGTH); //$NON-NLS-1$
            newFile();
            recalculate();

            //listen to the sympthom list changes
            sympthomsDefinition.addModifiedListener(new PropertyChangeListener() {
                  @Override
                  public void propertyChange(PropertyChangeEvent evt) {
                        modified = true;              
                  }                 
            });
      }

      /**
       * This is the main method of the entire application. The calendar day color and properties are determined here
       * @param date - the date being analyzed
       * @return date information or NULL if no data available
       */
00181       public PCalDayInfo getDayInfo(final Date date)
      {
            if(startDates.isEmpty()) {
                  // no data at all
                  return null;
            } else { 
                  PCalDayInfo info = null;

                  Integer rectype = dateTypes.get(date);
                  if(rectype != null) {
                        if(rectype == PCAL_TYPE_OVULATION) {
                              // ovulation is processed in other way
                              info = getOvulationDayInfo(date);
                              info.ovulation = true;
                        } else {
                              // this date is really in the database as day 1 (mens, pregnancy or birth)
                              info = DayInfoRecordsFactory.getDay1Info(rectype);
                        }
                  }
                  else
                        if(date.before(startDates.first())) {
                              // the requested date is before the recorded periods
                              return null;
                        } else {
                              Date last_date = startDates.last();
                              // is the date in the recorded period?
                              if(date.before(last_date)) {
                                    info = getDayInfoRecorded(date); 
                              } else {
                                    info = getDayInfoEstimated(date, last_date); 
                              }
                        }
                  return info;
            }
      }

      /**
       * This inner method evaluated the date information for the dates beyond the recorded periods 
       * @param date - the date being analyzed
       * @param last_date - the last recorded date
       * @return the day info record or NULL if no data available
       */
00223       private PCalDayInfo getDayInfoEstimated(final Date date, Date last_date) {
            PCalDayInfo info;

            // the day parameters should be predicted
            int day_num = dateDiff(last_date, date);
            int last_date_type = dateTypes.get(last_date);

            if(last_date_type == PCAL_TYPE_OVULATION) {
                  if(periodsCount == 0) {
                        // There is nothing we can say about this day
                        return null;
                  }

                  Date periodStartDate = getPeriodStartDate(last_date, false);
                  if(periodStartDate == null) {
                        // can really say nothing since no adequate period start record found
                        return null;
                  }

                  day_num = dateDiff(periodStartDate, date);
                  if(day_num < avgLength) {
                        // the date in question is after the ovulation but still within a single period
                        // with the last recorded ovulation date - this day is definitely not fertile
                        info = DayInfoRecordsFactory.getNonFertileDayInfo(day_num);
                        info.estimate = true;
                        return info;
                  } else {
                        // in this case, the ovulation record has no influence on the date in question
                        // let's proceed with the regular estimation process
                        last_date_type = dateTypes.get(periodStartDate);
                  }
            }

            if(last_date_type == PCAL_TYPE_MENS) {
                  if(periodsCount == 0) {
                        // There is nothing we can say about this day
                        return null;
                  }

                  // the cycle number is calculated by the average cycle length but
                  // the fertility will be 
                  if(avgLength!=0)
                        day_num %= avgLength;

                  if(day_num == 0) {
                        // most probably this will be a day 1
                        info = DayInfoRecordsFactory.getDay1Info(PCAL_TYPE_MENS);                     
                  } else {
                        info = DayInfoRecordsFactory.getNonFertileDayInfo(day_num);
                        // this is a day after the last recorded period start
                        if(recordedOvulations >12 ) {
                              // use the average ovulation date as a basis to predict fertility
                              info.fertile = (info.day_num >= (minOvulationDayNum - 9 - enginePreferences.bufferDays)) 
                              && (info.day_num <= (maxOvulationDayNum + enginePreferences.bufferDays));
                        } else {
                              // the number of non-fertile days at the beginning of the cycle is 
                              // considered as the minimum cycle length - 20
                              info.fertile = (info.day_num >= (minLength - 20 - enginePreferences.bufferDays)) 
                              && (info.day_num < (maxLength - 11 + enginePreferences.bufferDays));
                        }
                        info.badFeel = (info.day_num<=enginePreferences.badFeelDaysAfter)||
                        ((avgLength-info.day_num)<=enginePreferences.badFeelDaysBefore);
                  }
                  //info = DayInfoRecordsFactory.getNonFertileDayInfo(-1);
            } else 
                  if(last_date_type == PCAL_TYPE_PREGNANCY) {
                        if(day_num < REGULAR_PREGNANCY_DAYS) {
                              info = DayInfoRecordsFactory.getPregnancyDayInfo(day_num);
                        } else
                              if(day_num == REGULAR_PREGNANCY_DAYS) {
                                    info = DayInfoRecordsFactory.getDay1Info(PCAL_TYPE_BIRTH);
                              } else {
                                    // this is a day after the normal pregnancy period
                                    // know nothing
                                    info = DayInfoRecordsFactory.getNonFertileDayInfo(-1);
                              }
                  } else {
                        // know nothing
                        info = DayInfoRecordsFactory.getNonFertileDayInfo(-1);                        
                  }
            info.estimate = true;
            return info;
      }


      /**
       * This inner method evaluated the date information for the dates within recorded periods
       * @param date - the date being analyzed
       * @return the day info record or NULL if no data available
       */
00313       private PCalDayInfo getDayInfoRecorded(final Date date) {
            PCalDayInfo info = null;

            // find the starting date of the date's period
            Date periodStartDate = getPeriodStartDate(date, true);
            if(periodStartDate!=null) {                
                  // this is a date in the complete period
                  // this is not a day 1 - we have already checked this
                  int date_type = dateTypes.get(periodStartDate);
                  int day_num = dateDiff(date, periodStartDate);

                  if(date_type == PCAL_TYPE_MENS) {
                        Date periodEndDate = getMensPeriodEndDate(periodStartDate);
                        Date periodOvulationDate = getMensPeriodOvulationDate(periodStartDate);
                        boolean fertileDay = false;
                        int period_length = 0;

                        if(periodEndDate == null) {
                              period_length = avgLength;
                        } else {
                              period_length = dateDiff(periodStartDate, periodEndDate);
                        }
                        // zero means we have no periods statistics
                        int daysTillEnd = (period_length==0)?0:(period_length - day_num);  

                        if(periodOvulationDate == null) {
                              if(daysTillEnd==0) {
                                    // better to say unknown here... this case normally should never happen
                                    fertileDay  = false;
                              } else {
                                    fertileDay = (daysTillEnd < 18) && (daysTillEnd > 11);
                              }
                        } else {
                              if(date.before(periodOvulationDate)) {
                                    // the date is between the cycle start and the ovulation date
                                    int daysTillOvulationEnd = dateDiff(date, periodOvulationDate);
                                    fertileDay = daysTillOvulationEnd < PERIOD_FERTILE_DAYS_COUNT;
                              } else {
                                    // This is definitely non-fertile date
                                    fertileDay = false;
                              }
                        }

                        if(fertileDay) {
                              info = DayInfoRecordsFactory.getFertileDayInfo(day_num);
                        } else {
                              info = DayInfoRecordsFactory.getNonFertileDayInfo(day_num);
                        }
                        if(daysTillEnd == 0) {
                              info.badFeel = (day_num<=enginePreferences.badFeelDaysAfter);
                        } else {
                              info.badFeel = (day_num<=enginePreferences.badFeelDaysAfter)||(daysTillEnd<=enginePreferences.badFeelDaysBefore);
                        }

                  } else 
                        if(date_type == PCAL_TYPE_PREGNANCY) {
                              Date nextDate = startDates.higher(periodStartDate);
                              int nextDateType = dateTypes.get(nextDate);
                              if(((nextDateType == PCAL_TYPE_BIRTH) || 
                                          (nextDateType == PCAL_TYPE_PREGNANCY_INT))
                                          && (day_num <= MAX_PREGNANCY_DAYS)) {
                                    // the pregnancy maybe took longer but still completion was recorded
                                    info = DayInfoRecordsFactory.getPregnancyDayInfo(day_num);
                              } else
                                    if(day_num <= REGULAR_PREGNANCY_DAYS) {
                                          //pregnancy completion was not recorded - actually this is the prediction
                                          info = DayInfoRecordsFactory.getPregnancyDayInfo(day_num);
                                    } else {
                                          // this is a day after the normal pregnancy period
                                          // know nothing
                                          info = DayInfoRecordsFactory.getNonFertileDayInfo(-1);
                                    }
                        } else {
                              // know nothing
                              info = DayInfoRecordsFactory.getNonFertileDayInfo(-1);
                        }
            } else {
                  // we still can say something if the date is followed by the ovulation date
                  Boolean ovulationFound = checkForFutureOvulation(date);

                  if(ovulationFound == null) {
                        // know nothing
                        info = DayInfoRecordsFactory.getNonFertileDayInfo(-1);
                  } else 
                        if(ovulationFound) {
                              // at least we know this is a fertile day
                              info = DayInfoRecordsFactory.getFertileDayInfo(-1);
                        }
            }
            return info;
      }

      /**
       * Get the day info record for the recorded ovulation date
       * @param date - the record date
       * @return the day information record
       */
00410       private PCalDayInfo getOvulationDayInfo(final Date date) {
            final int MIN_CYCLE_DAYS_AFTER_OVULATION = 14;

            PCalDayInfo info = DayInfoRecordsFactory.getFertileDayInfo(-1);

            // calculating the period day number is the only non-trivial step here
            Date period_start_date = getPeriodStartDate(date, true);
            if(dateTypes.get(period_start_date) == PCAL_TYPE_MENS) {
                  info.day_num = dateDiff(date, period_start_date);

                  // the ovulation can no be at the very end of the cycle
                  if(info.day_num > (enginePreferences.regularMaxLength - MIN_CYCLE_DAYS_AFTER_OVULATION)) {
                        // we are out of the regular period, don't know the period day #
                        info.day_num = -1;
                  }
            }
            return info;
      }

      /**
       * Find the date's period start date. Returns null if no adequate date was found.
       * This method can return the menstrual period start or the pregnancy start.
       * This method does not check if the menstrual period is not shorter 
       * than the minimum allowed. 
       * @param date - a date within the period
       * @param checkMensLength - if true, the menstrual period length boundaries are checked. False is useful for the future predictions, when the date can be beyond the length of a single cycle
       * @return - the day info record
       */
00438       private Date getPeriodStartDate(final Date date, final boolean checkMensLength) {
            Date period_start_date = null;

            if(date == null) return period_start_date;

            Date date_pointer = startDates.lower(date);

            while(date_pointer != null) {
                  int pointer_date_type = dateTypes.get(date_pointer);

                  if((pointer_date_type == PCAL_TYPE_BIRTH) || 
                              (pointer_date_type == PCAL_TYPE_PREGNANCY_INT)) {
                        // the last period has ended, the new period was not started
                        break;
                  } else 
                        if(pointer_date_type == PCAL_TYPE_MENS) {
                              int day_number = dateDiff(date_pointer, date);
                              if(checkMensLength) {
                                    if(day_number < enginePreferences.regularMaxLength) {
                                          period_start_date = date_pointer;
                                    } 
                              } else {
                                    period_start_date = date_pointer;
                              }
                              break;
                        }
                  if(pointer_date_type == PCAL_TYPE_PREGNANCY) {
                        int day_number = dateDiff(date_pointer, date);
                        if(checkMensLength) {
                              if(day_number < MAX_PREGNANCY_DAYS) {
                                    period_start_date = date_pointer;
                              }                             
                        } else {
                              period_start_date = date_pointer;
                        }
                        break;
                  }
                  else {
                        // lets look for the earlier recorded date
                        date_pointer = startDates.lower(date_pointer);
                  }
            }
            return period_start_date;
      }

      /**
       * This method locates the menstrual period end date
       * @param periodStartDate - period start date
       * @return end date or null if periodStartDate does not start a valid period
       */
00488       private Date getMensPeriodEndDate(final Date periodStartDate) {
            Date periodEndDate = null;

            if(periodStartDate == null) return periodEndDate;
            if(dateTypes.get(periodStartDate) == null) return periodEndDate; 

            Date date_pointer = startDates.higher(periodStartDate);

            while(date_pointer != null) {
                  int pointer_date_type = dateTypes.get(date_pointer);

                  if((pointer_date_type == PCAL_TYPE_BIRTH) || 
                              (pointer_date_type == PCAL_TYPE_PREGNANCY_INT)) {
                        // the last period has ended, the new period was not started
                        break;
                  } else 
                        if((pointer_date_type == PCAL_TYPE_MENS) || 
                                    (pointer_date_type == PCAL_TYPE_PREGNANCY)) {
                              int periodLength = dateDiff(periodStartDate, date_pointer);
                              if((periodLength < enginePreferences.regularMinLength) ||
                                          (periodLength > enginePreferences.regularMaxLength)) {
                                    periodEndDate = date_pointer;
                              }                       
                              break;
                        }
                        else {
                              // lets look for the earlier recorded date
                              date_pointer = startDates.higher(date_pointer);
                        }
            }
            return periodEndDate; 
      }

      /**
       * This method locates the menstrual period ovulation date
       * @param periodStartDate - period start date
       * @return ovulation date or null if periodStartDate does not contain an ovulation date
       */
00526       private Date getMensPeriodOvulationDate(final Date periodStartDate) {
            Date periodOvulationDate = null;

            if(periodStartDate == null) return periodOvulationDate;
            if(dateTypes.get(periodStartDate) == null) return periodOvulationDate; 

            periodOvulationDate = startDates.higher(periodStartDate);

            if(periodOvulationDate != null) {
                  if(dateTypes.get(periodOvulationDate) == PCAL_TYPE_OVULATION) {
                        int ovulationDayNumber = dateDiff(periodStartDate, periodOvulationDate);
                        if(ovulationDayNumber>(enginePreferences.regularMaxLength)) {
                              // this ovulation is beyond this period
                              periodOvulationDate = null;
                        }
                  } else {
                        periodOvulationDate = null;
                  }
            }
            return periodOvulationDate; 
      }

      /**
       * Checks if after the date specified, there is an ovulation record. 
       * @param date - the date under investigation
       * @return - null if there is no ovulation recorded after the date specified
       *                 True - if the ovulation record is present and the date is fertile 
       *           False - if the ovulation record is present can not cause this date fertility
       */
00555       private Boolean checkForFutureOvulation(Date date) {
            Date next_date = startDates.higher(date);
            int next_date_type = dateTypes.get(next_date);
            if(next_date_type == PCAL_TYPE_OVULATION) {
                  int days_till_ovulation = dateDiff(date, next_date);
                  return days_till_ovulation < PERIOD_FERTILE_DAYS_COUNT; 
            } else {
                  return null;
            }
      }

      /**
       * Load the data from file. If the file was opened with a password, Engine will store
       * the password internally so transparent "Save" operation will be performed without
       * re-requesting the password entry.
       * @param file - a file to be loaded
       * @param password - a password to be used for the file. 
       * If the file appears to be unencrypted, this parameter is ignored. 
       * @throws SAXException - file content format is unparsable
       * @throws IOException - can not read a file specified
       * @throws ParserConfigurationException - can not set up the file parser - internal error
       * @throws PasswordRequiredException is thrown if password parameter is empty but the file is encrypted 
       */
00578       public void loadFromFile(final File file, char[] password) 
      throws SAXException, IOException, ParserConfigurationException, PasswordRequiredException
      {
            FileInputStream fileStream = new FileInputStream(file);
            GZIPInputStream f;
            SecretKey secretKey = null;

            fileStream = new FileInputStream(file);
            f = new GZIPInputStream(fileStream);

            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(f);

            int docType = getDocumentType(doc);

            switch(docType) {
            case FILE_TYPE_UNKNOWN:
                  throw new SAXException(Messages.getString("Engine.0")); //$NON-NLS-1$
            case FILE_TYPE_ENCRYPTED: {
                  if((password == null) || (password.length==0)) {
                        throw new PasswordRequiredException();
                  }
                  secretKey = EngineXMLCrypter.generateSecretKey(password);
                  doc = EngineXMLCrypter.decryptXML(doc, secretKey);

                  //check if the encryption container contains a regular plain file
                  int internalDocType = getDocumentType(doc);
                  if(internalDocType != FILE_TYPE_PLAIN) {
                        throw new SAXException(Messages.getString("Engine.0")); //$NON-NLS-1$
                  }
            }
            case FILE_TYPE_PLAIN:
                  // actually - nothing to do here. The file is ready for parsing.
            }

            //reinitialize the containers
            clear();

            //load the date types (mens, pregnancy, birh)
            loadFromFileDates(doc);

            //load the dates with the associated values (notes, BBT values, integers)
            loadFromFileDateValues(doc);

            //load the file properties
            loadFromFileProperties(doc);

            // store the password if applicable
            if(docType == FILE_TYPE_ENCRYPTED) {
                  workFileKey = secretKey; 
            }

            setWorkFile(file);
            modified = false;
            recalculate();
      }

      /**
       * loadFromFile helper
       * @param doc
       * @throws SAXException
       */
00639       private void loadFromFileDates(final Document doc) throws SAXException {
            for(Integer typeID: dateTypeToTagMap.keySet()) {
                  NodeList nl;
                  String text;
                  Date date;
                  nl = doc.getElementsByTagName(dateTypeToTagMap.get(typeID));
                  for(int i = 0; i<nl.getLength();i++) {
                        text = nl.item(i).getTextContent();
                        if(text == null) {
                              throw new SAXException(Messages.getString("Engine.1")); //$NON-NLS-1$
                        }
                        try {
                              date = DATE_FORMAT.parse(text);
                        } catch (ParseException e) {
                              throw new SAXException(Messages.getString("Engine.2")+text); //$NON-NLS-1$
                        }
                        startDates.add(date);
                        dateTypes.put(date, typeID);
                  }
            }
      }

      /**
       * loadFromFile helper
       * @param doc
       * @throws SAXException
       */
00666       private void loadFromFileDateValues(final Document doc) throws SAXException {
            for(Integer valueTypeID: dateValueTypeToTagMap.keySet()) {
                  NodeList nl;
                  Date date;
                  String text;
                  nl = doc.getElementsByTagName(dateValueTypeToTagMap.get(valueTypeID));              
                  for(int i = 0; i<nl.getLength();i++) {
                        NodeList nmp = nl.item(i).getChildNodes();
                        date = null;
                        text = null;
                        for(int j=0;j<nmp.getLength();j++) {
                              Node tmpNode = nmp.item(j);
                              if(tmpNode.getNodeName().equals("date")) { //$NON-NLS-1$
                                    String tmpText = tmpNode.getTextContent();                  
                                    if(tmpText == null) {
                                          throw new SAXException(Messages.getString("Engine.1")); //$NON-NLS-1$
                                    }
                                    try {
                                          date = DATE_FORMAT.parse(tmpText);
                                    } catch (ParseException e) {
                                          throw new SAXException(Messages.getString("Engine.2")+text); //$NON-NLS-1$
                                    }

                              } else 
                                    if(tmpNode.getNodeName().equals("text")) { //$NON-NLS-1$
                                          text = tmpNode.getTextContent();
                                    }
                        }
                        if((text == null) || (date == null)) {
                              throw new SAXException(Messages.getString("Engine.1")); //$NON-NLS-1$
                        }

                        if(valueTypeID.equals(PCAL_TYPE_NOTES)) {
                              notesContainer.put(date, text);
                        } else
                              if(valueTypeID.equals(PCAL_TYPE_BBT)) {
                                    try {
                                          bBTList.put(date, Integer.parseInt(text));                                          
                                    }
                                    catch (NumberFormatException e) {
                                          // Even the minor file corruption can be a big problem. Stop everything.
                                          // Incorrect BBT entry
                                          throw new SAXException(Messages.getString("Engine.4")); //$NON-NLS-1$
                                    }
                              } else
                                    if(valueTypeID.equals(PCAL_TYPE_DATE_INTS)) {
                                          try {
                                                dateIntValues.put(date, DateIntsContainer.parseFromStringFactory(text));
                                          }
                                          catch (NumberFormatException e) {
                                                // Even the minor file corruption can be a big problem. Stop everything.
                                                // Incorrect Date Integer Entry
                                                throw new SAXException(Messages.getString("Engine.3")); //$NON-NLS-1$
                                          }
                                    }

                  }
            }
      }

      /**
       * Load from file helper. Loads the properties associated with the file
       * @param doc
       * @throws SAXException 
       */
00731       private void loadFromFileProperties(Document doc) throws SAXException {
            Properties fileProperties = null;
            NodeList nl;
            nl = doc.getElementsByTagName(FILE_PROPERTIES_TAG);
            if(nl.getLength()>0) {
                  if(nl.getLength()>1) {
                        // something wrong - too many properties sections
                        throw new SAXException(Messages.getString("Engine.0")); //$NON-NLS-1$
                  }
                  String text = nl.item(0).getTextContent();                  
                  fileProperties = new Properties();

                  // all non-ascii chars are translated automatically to the escape sequences
                  // it should be safe to use direct bytes transformation here
                  ByteArrayInputStream bai = new ByteArrayInputStream(text.getBytes());               
                  try {
                        fileProperties.load(bai);
                  } catch (IOException e) {
                        throw new SAXException(Messages.getString("Engine.0")); //$NON-NLS-1$
                  }
                  parseLoadedFileProperties(fileProperties);
            }
      }

      /**
       * Process the file properties and fill the internal structures with their values
       * @param fileProperties the file properties to be processed
       */
00759       private void parseLoadedFileProperties(Properties fileProperties) {
            // load the BBT preferences
            int i = 0;
            while(fileProperties.containsKey(BBT_SYMPTHOM+i)) {
                  String sympthom = fileProperties.getProperty(BBT_SYMPTHOM+i);

                  Vector<String> values = new Vector<String>();

                  int j=0;
                  while(fileProperties.containsKey(BBT_SYMPTHOM+i+BBT_VALUE+j)) {
                        values.add(fileProperties.getProperty(BBT_SYMPTHOM+i+BBT_VALUE+j));
                        j++;              
                  }
                  sympthomsDefinition.setSypmthomNameAndValues(i, sympthom, values);
                  i++;
            }
            String celsiusScale = fileProperties.getProperty(CELSIUS_SCALE, TRUE);
            fileBBTCelsiusScale = celsiusScale.equals(TRUE);
      }

      /**
       * Helper method for the loadFromFile
       * @param doc - the XML document to be analyzed
       * @return FILE_TYPE_PLAIN if the document is proper plain not encrypted one, 
       * FILE_TYPE_ENCRYPTED if the document is proper encrypted one, 
       * FILE_TYPE_UNKNOWN if the document is improper or have wrong version
       */
00786       private int getDocumentType(Document doc)  {

            int fileType = FILE_TYPE_UNKNOWN;

            NodeList nl = doc.getElementsByTagName("EncryptedData"); //$NON-NLS-1$
            if(nl.getLength() == 1) {
                  fileType = FILE_TYPE_ENCRYPTED;
            } else {
                  // check for the root tag first
                  Node n = doc.getFirstChild();
                  if(n.getNodeName().equals("pcalendar")) { //$NON-NLS-1$
                        nl = doc.getElementsByTagName("version"); //$NON-NLS-1$
                        if(nl.getLength() == 1) {
                              String file_version = nl.item(0).getTextContent();
                              if(file_version != null) {
                                    if(file_version.equals(FILE_VERSION)) {
                                          fileType = FILE_TYPE_PLAIN;
                                    }                       
                              }
                        }
                  }
            }

            return fileType;
      }

      /**
       * Save the current data to the file specified.
       * @param file - a file reference
       * @param password - a password to be applied to the file. If null - 
       * the file will be saved with the original password (or unencrypted as applicable).
       * If empty string - the password will be removed and the file will be unencrypted. 
       * @return true on success
       * @throws ParserConfigurationException
       * @throws IOException
       */
00822       public void saveToFile(final File file, final char[] password) throws ParserConfigurationException, IOException
      {
            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();

            Element parent_element = doc.createElement("pcalendar"); //$NON-NLS-1$
            doc.appendChild(parent_element);

            Element element = doc.createElement("version"); //$NON-NLS-1$
            element.appendChild(doc.createTextNode(FILE_VERSION));
            parent_element.appendChild(element);

            element = doc.createElement("data"); //$NON-NLS-1$
            parent_element.appendChild(element);
            parent_element = element;

            saveToFileDates(doc, parent_element);
            saveToFileDateValues(doc, parent_element);
            saveToFileFileProperties(doc, parent_element);

            SecretKey secretKey = null;

            if(password != null) {
                  // new password was specified
                  if(password.length > 0) {
                        secretKey = EngineXMLCrypter.generateSecretKey(password);
                  }
            } else {
                  secretKey = workFileKey;
            }

            if(secretKey != null) {
                  doc = EngineXMLCrypter.encryptXML(doc, secretKey);
            }

            GZIPOutputStream zippedFile;

            FileOutputStream fileStream = new FileOutputStream(file);         

            zippedFile =  new GZIPOutputStream(fileStream);

            OutputFormat formatter = new OutputFormat();
            formatter.setPreserveSpace(true);
            XMLSerializer serializer =
                  new XMLSerializer(zippedFile, formatter);
            serializer.serialize(doc);
            zippedFile.finish();

            setWorkFile(file);
            workFileKey = secretKey;
            modified = false;
      }

      /**
       * A short wrapper for the main saveToFile method. Null password is used.
       * @param file
       * @throws ParserConfigurationException
       * @throws IOException
       */
00880       public void saveToFile(final File file) throws ParserConfigurationException, IOException {
            saveToFile(file, null);
      }

      /**
       * Save to file date records
       * saveToFile helper
       * @param doc
       * @param parent_element
       */
00890       private void saveToFileDates(Document doc, Element parent_element) {
            Element element;
            for(Date d: startDates) {
                  int type = dateTypes.get(d); 
                  if(dateTypeToTagMap.containsKey(type)) {
                        element = doc.createElement(dateTypeToTagMap.get(type));                      
                        element.appendChild(doc.createTextNode(DATE_FORMAT.format(d))); 
                        parent_element.appendChild(element);                        
                  }
            }
      }

      /**
       * Save to file the Date-Value pairs
       * saveToFile helper
       * @param doc
       * @param parent_element
       */
00908       private void saveToFileDateValues(Document doc, Element parent_element) {
            for(Integer valueTypeID: dateValueTypeToTagMap.keySet()) {
                  Element element;
                  Set<Date> dateSet = null;
                  if(valueTypeID.equals(PCAL_TYPE_NOTES)) {
                        dateSet = notesContainer.keySet();
                  } else 
                        if(valueTypeID.equals(PCAL_TYPE_BBT)) {
                              dateSet = bBTList.keySet();
                        } else
                              if(valueTypeID.equals(PCAL_TYPE_DATE_INTS)) {
                                    dateSet = dateIntValues.keySet();
                              }

                  for(Date d: dateSet) {
                        element = doc.createElement(dateValueTypeToTagMap.get(valueTypeID));
                        Element e1 =doc.createElement("date"); //$NON-NLS-1$
                        e1.appendChild(doc.createTextNode(DATE_FORMAT.format(d))); 
                        element.appendChild(e1);

                        e1 =doc.createElement("text"); //$NON-NLS-1$                      

                        String text = ""; //$NON-NLS-1$
                        if(valueTypeID.equals(PCAL_TYPE_NOTES)) {
                              text = notesContainer.get(d);
                        } else 
                              if(valueTypeID.equals(PCAL_TYPE_BBT)) {
                                    text = bBTList.get(d).toString();
                              } else 
                                    if(valueTypeID.equals(PCAL_TYPE_DATE_INTS)) {
                                          text = dateIntValues.get(d).toString();
                                    }

                        e1.appendChild(doc.createTextNode(text));
                        element.appendChild(e1);

                        parent_element.appendChild(element);
                  }
            }
      }

      /**
       * Save to file the associated properties
       * @param doc
       * @param parent_element
       * @throws IOException 
       */
00955       private void saveToFileFileProperties(Document doc, Element parent_element) throws IOException {
            Properties fileProperties = new Properties();

            int i = 0;
            for(String sympthom: sympthomsDefinition.getSympthoms()) {
                  if(sympthom!=null) {
                        fileProperties.setProperty(BBT_SYMPTHOM+i, sympthom);
                        int j = 0;
                        for(String value: sympthomsDefinition.getSympthomValues(i)) {
                              fileProperties.setProperty(BBT_SYMPTHOM+i+BBT_VALUE+j, value);                      
                              j++;
                        }     
                  }
                  i++;
            }

            fileProperties.setProperty(CELSIUS_SCALE, fileBBTCelsiusScale?TRUE:FALSE);

            ByteArrayOutputStream bas = new ByteArrayOutputStream();
            fileProperties.store(bas, ""); //$NON-NLS-1$

            Element element = doc.createElement(FILE_PROPERTIES_TAG);
            element.appendChild(doc.createTextNode(bas.toString()));

            parent_element.appendChild(element);
      }


      /**
       * Import list of menstruation dates from a plain text file (Lorg 1.x file version)
       * @param fname name of the file
       * @return true on success
       * @throws IOException 
       * @throws ParseException 
       */
00990       public boolean importFromFile(final File file) throws IOException, ParseException
      {
            BufferedReader inputStream = null;

            try {
                  inputStream = 
                        new BufferedReader(new FileReader(file));
                  String l;
                  while ((l = inputStream.readLine()) != null) {
                        Date d = DATE_FORMAT.parse(l);
                        startDates.add(d);
                        dateTypes.put(d, PCAL_TYPE_MENS);
                  }
            }
            finally {
                  if (inputStream != null) {
                        inputStream.close();
                  }
            }

            modified = true;
            recalculate();
            return true;
      }

      /**
       * Export list of menstruation dates to a plain text file (Lorg 1.x file version)
       * @param fname name of the file
       * @return true on success
       * @throws IOException 
       */
01021       public void exportToFile(final File file) throws IOException
      {
            PrintWriter outputStream = null;

            try {
                  outputStream = 
                        new PrintWriter(new FileWriter(file));

                  for(Date d : startDates) {
                        if(dateTypes.get(d) == PCAL_TYPE_MENS) {
                              outputStream.println(DATE_FORMAT.format(d)); 
                        }
                  }
            }
            finally {
                  if (outputStream != null) {
                        outputStream.close();
                  }
            }
      }

      public void addStartDate(final Date date)
      {
            startDates.add(date);
            dateTypes.put(date, PCAL_TYPE_MENS);
            modified = true;
            recalculate();
      }
      public void addPregnancyDate(final Date date)
      {
            startDates.add(date);
            dateTypes.put(date, PCAL_TYPE_PREGNANCY);
            modified = true;
            recalculate();
      }

      public void addPregnancyInterruptDate(final Date date)
      {
            startDates.add(date);
            dateTypes.put(date, PCAL_TYPE_PREGNANCY_INT);
            modified = true;
            recalculate();
      }

      public void addBirthDate(final Date date)
      {
            startDates.add(date);
            dateTypes.put(date, PCAL_TYPE_BIRTH);
            modified = true;
            recalculate();
      }

      public void addOvulationDate(final Date date) {
            startDates.add(date);
            dateTypes.put(date, PCAL_TYPE_OVULATION);
            modified = true;
            recalculate();          
      }

      public void removeDateRecord(final Date date)
      {
            startDates.remove(date);
            dateTypes.remove(date);
            modified = true;
            recalculate();
      }

      /**
       * This method adds a BBT record to the list
       * @param date - date of the record
       * @param i - a temperature in Celsius or Fahrenheit multiplied by 10 
       */
01093       public void addBBT(final Date date, int i) {
            bBTList.put(date, i);
            modified = true;
      }

      /**
       * Get the BBT for the date specified
       * @param date - the date to be queried
       * @return - a BBT recorded for the date specified. 0 if there is no information
       */
01103       public int getBBT(final Date date) {
            if(bBTList.containsKey(date)) {
                  return bBTList.get(date);
            } else { 
                  return 0;
            }
      }

      /**
       * Remove the BBT record for the date specified
       * @param date - a date to be cleared
       */
01115       public void removeBBT(final Date date) {
            bBTList.remove(date);
            modified = true;
      }

      /**
       * This method associates an integer with the specified date
       * @param date - date of the record
       * @param index - an integer index within the integers container 
       * @param value - a value to be set
       */
01126       public void setDateInteger(final Date date, final int index, Integer value) {
            DateIntsContainer container = dateIntValues.get(date);
            if(container == null) {
                  dateIntValues.put(date, new DateIntsContainer(index, value));
            } else {
                  container.setIntValue(index, value);
            }
            modified = true;
      }

      /**
       * This method associates an integers record with the specified date
       * @param date - date of the record
       * @param record - a value to be set
       */
01141       public void setDateIntegers(final Date date, DateIntsContainer record) {
            dateIntValues.put(date, record);
            modified = true;
      }


      /**
       * This method returns an integers record associated with the specified date
       * @param date - the date to be queried
       * @return - an integers record associated with the date specified or null if nothing found
       */
01152       public DateIntsContainer getDateIntegers(final Date date) {
            if(dateIntValues.containsKey(date)) {
                  return dateIntValues.get(date);
            } else { 
                  return null;
            }
      }

      /**
       * This method returns a date integers container for the fast internal operations
       * @return - an integers record associated with the date specified or null if nothing found
       */
01164       public HashMap<Date, DateIntsContainer> getAllDateIntegers() {
            return dateIntValues;
      }

      /**
       * Remove the integers record associated with the date specified
       * @param date - a date to be cleared
       */
01172       public void removeDateIntegers(final Date date) {
            dateIntValues.remove(date);
            modified = true;
      }

      public void newFile() {
            clear();
            sympthomsDefinition.setDefaults();
      }

      private void clear() {
            startDates.clear();
            dateTypes.clear();
            notesContainer.clear();
            bBTList.clear();
            dateIntValues.clear();
            sympthomsDefinition.clear();
            modified = false;
            setWorkFile(null);
            workFileKey = null;
            recalculate();
      }

      /**
       * This function is called each time the statistics needs to be recalculated
       */
01198       private void recalculate()
      {
            // at this point we have the sorted starting dates list
            periodsCount = 0;
            avgLength = 0;
            minLength = 0;
            maxLength = 0;
            int sumLength = 0;
            recordedOvulations = 0;
            minOvulationDayNum = 0;
            maxOvulationDayNum = 0;
            calMethodAccuracy = 3;
            if(startDates.size()<2) {
                  // there are no even a single period
                  return;
            }

            Date prevdate = null;

            // we need to calculate the average, min and max period length
            for(Date pstart: startDates)
            {
                  if(prevdate == null) { 
                        prevdate = pstart;
                        continue;
                  }
                  int pDateType = dateTypes.get(pstart);
                  int prevDateType = dateTypes.get(prevdate); 

                  if(pDateType == PCAL_TYPE_OVULATION) {
                        if(prevDateType == PCAL_TYPE_MENS) {
                              int ovulDayNumber = dateDiff(prevdate, pstart);
                              if(ovulDayNumber < enginePreferences.regularMaxLength) {
                                    if(ovulDayNumber > maxOvulationDayNum) maxOvulationDayNum = ovulDayNumber;
                                    if((ovulDayNumber < minOvulationDayNum)||
                                                (minOvulationDayNum == 0)) minOvulationDayNum = ovulDayNumber;
                                    recordedOvulations++;
                              }
                        }
                        // keep prev date the same jump to the next date
                        continue;
                  }

                  // skip the non-menstruation neighbor records
                  if((pDateType != PCAL_TYPE_MENS)||
                              (prevDateType != PCAL_TYPE_MENS)) {
                        prevdate = pstart;
                        continue;
                  }
                  // pstart and pend points to starting and ending date of the period
                  int p_length = dateDiff(prevdate, pstart);

                  if((p_length < enginePreferences.regularMinLength) ||
                              (p_length > enginePreferences.regularMaxLength)) {
                        // bad period should not be taken into account

                  } else {
                        periodsCount++;
                        if(p_length>maxLength) maxLength = p_length;
                        if((p_length<minLength) || minLength == 0) minLength = p_length;
                        sumLength+=p_length;        
                  }
                  prevdate = pstart;
            }
            if(periodsCount>0) avgLength = Math.round(sumLength/periodsCount);

            // calendar method accuracy depends on the medium period length,
            // the difference between the longest and the shortest periodsCount
            // and the number of the recorded periods
            // 0 - high, 1 - moderate, 2 - low
            // for the high accuracy the conditions are almost unreal
            if((periodsCount>RELIABLE_PERIODS_COUNT) &&
                        (avgLength>RELIABLE_MIN_AVG_LENGTH) &&
                        (avgLength<RELIABLE_MAX_AVG_LENGTH) &&
                        ((maxLength - minLength)<RELIABLE_LENGTH_DIFF) &&
                        (recordedOvulations > 12))
                  calMethodAccuracy = 0;
            else
                  if((periodsCount<RELIABLE_PERIODS_COUNT) ||
                              ((maxLength - minLength)>UNRELIABLE_LENGTH_DIFF) ||
                              ((avgLength<UNRELIABLE_MIN_AVG_LENGTH) &&
                                          (avgLength>UNRELIABLE_MAX_AVG_LENGTH))) 
                        calMethodAccuracy = 2;
                  else 
                        calMethodAccuracy = 1;
      }

      public final Vector<PCalPeriodInfo> getPeriodsStats()
      {
            Vector<PCalPeriodInfo> result = new Vector<PCalPeriodInfo>();

            if(startDates.size()>1) {
                  Date prevdate = null;

                  for(Date pstart: startDates)
                  {
                        if(prevdate == null) { 
                              prevdate = pstart;
                              continue;
                        }

                        int pDateType = dateTypes.get(pstart);

                        if(pDateType == PCAL_TYPE_OVULATION) {
                              // keep prev date the same jump to the next pointer date
                              continue;
                        }


                        // skip the non-menstruation neighbor records
                        if((pDateType != PCAL_TYPE_MENS)||
                                    (dateTypes.get(prevdate) != PCAL_TYPE_MENS)) {
                              prevdate = pstart;
                              continue;
                        }

                        int p_length = dateDiff(prevdate, pstart);

                        if((p_length < enginePreferences.regularMinLength) ||
                                    (p_length > enginePreferences.regularMaxLength)) {
                              // bad period should not be taken into account            
                        } else {
                              PCalPeriodInfo pinfo = new PCalPeriodInfo();
                              pinfo.startDate = prevdate;
                              pinfo.endDate = pstart;
                              pinfo.length = p_length;
                              result.add(pinfo);
                        }
                        prevdate = pstart;
                  }
            }

            return result;
      }

      /**
       * @return Last menstruation date or null if none
       */
01336       public final Date getLastMenstruationDate()
      {
            if(startDates.size()>0) {
                  Date last_date = startDates.last();
                  if(dateTypes.get(last_date) == PCAL_TYPE_MENS) {
                        return last_date;
                  }        
                  return null;
            } else {
                  return null;
            }
      }

      /**
       * @param date
       * @return note for the specified date or null if none
       */
01353       public String getDateNote(final Date date)
      {
            return notesContainer.get(date);
      }

      /**
       * @param date
       * @return true if the note exists for the date
       */
01362       public boolean existsDateNote(final Date date)
      {
            return notesContainer.containsKey(date);
      }

      public void setDateNote(final Date date, final String note)
      {
            // need to check if any changes not to fall to the modified state
            // without a reason
            String old_note = notesContainer.get(date);
            if(old_note!=null) {
                  if(old_note.equals(note)) {
                        return;
                  }
            }           
            notesContainer.put(date, note);           
            modified = true;
      }

      /**
       * @return the list of the dates which have notes
       */
01384       public final Set<Date> getNoteDates()
      {
            return notesContainer.keySet();
      }

      public void removeDateNote(final Date date)
      {
            notesContainer.remove(date);
            modified = true;
      } 

      /** the difference in days between 2 dates within the Gregorian calendar - <b>after</b> 1582
       * @param earlier the start date
       * @param later the end date
       * @return number of days
       */
01400       public static int dateDiff( java.util.Date earlier, java.util.Date later ) {
            java.util.GregorianCalendar start = new
            java.util.GregorianCalendar();
            java.util.GregorianCalendar finish = new
            java.util.GregorianCalendar();
            if (later.compareTo( earlier ) > 0 ) { // this Date is after the Date argument
                  start.setTime(later);
                  finish.setTime(earlier);
            } else 
                  if (later.compareTo( earlier ) < 0 ) { // this Date is      before the Date argument
                        start.setTime(earlier);
                        finish.setTime(later);
                  } else {
                        return 0; // must be the same date
                  }
            int days = 1;
            //if dates in the same year
            if ( start.get(Calendar.YEAR) == finish.get(Calendar.YEAR) ) {
                  days = start.get(Calendar.DAY_OF_YEAR) -
                  finish.get(Calendar.DAY_OF_YEAR);
            } else {
                  days = start.get(Calendar.DAY_OF_YEAR); // number of days in the current year
                  while ( start.get(Calendar.YEAR) - 1 > finish.get(Calendar.YEAR)
                  ) {
                        start.add(Calendar.YEAR, -1); // keep counting back till they are the same year
                        days += 365;
                        if ( start.isLeapYear(start.get(Calendar.YEAR) ) ) {
                              days++;
                        }
                  }
                  start.add(Calendar.YEAR, -1); // get the days in the last year
                  days += 365 - finish.get(Calendar.DAY_OF_YEAR);
                  if ( start.isLeapYear(start.get(Calendar.YEAR) ) )
                        days++;
            }
            return days;
      }

      /**
       * Get the predicted birthday for a specified date and pregnancy day number
       * @param date - currently selected date
       * @param pregnancyDayNumber - the corresponding pregnancy day number
       * @return predicted date of the birth
       */
01444       public Date getPredictedBirthDay(Date date, int pregnancyDayNumber) {
            java.util.GregorianCalendar calendar = new java.util.GregorianCalendar();

            calendar.setTime(date);
            calendar.add(Calendar.DATE, REGULAR_PREGNANCY_DAYS - pregnancyDayNumber);
            return calendar.getTime();
      }

      /**
       * @return the modified
       */
01455       public boolean isModified() {
            return modified;
      }

      /**
       * @return the calMethodAccuracy
       */
01462       public int getCalMethodAccuracy() {
            return calMethodAccuracy;
      }

      /**
       * @return the workFileName
       */
01469       public final File getWorkFile() {
            return workFile;
      }

      /**
       * Sets workFile
       * @param f - a file reference
       */
01477       private void setWorkFile(File f) {
            workFile = f;
      }


      /**
       * @return the avgLength
       */
01485       public int getAvgLength() {
            return avgLength;
      }

      /**
       * @return the maxLength
       */
01492       public int getMaxLength() {
            return maxLength;
      }

      /**
       * @return the minLength
       */
01499       public int getMinLength() {
            return minLength;
      }

      /**
       * @return the periodsCount
       */
01506       public int getPeriodsCount() {
            return periodsCount;
      }

      /**
       * @return the enginePreferences
       */
01513       public final EnginePreferences getEnginePreferences() {
            return new EnginePreferences(enginePreferences);
      }

      /**
       * Set preferences, save them and recalculate the internal data
       * @param enginePreferences the enginePreferences to set
       */
01521       public void setEnginePreferences(EnginePreferences enginePreferences) {

            this.enginePreferences = new EnginePreferences(enginePreferences);

            Preferences enginePrefsNode = PCalendar.settings.node("Engine"); //$NON-NLS-1$            
            enginePrefsNode.putInt("Buffer Days", enginePreferences.bufferDays); //$NON-NLS-1$
            enginePrefsNode.putInt("Bad Feels Before", enginePreferences.badFeelDaysBefore); //$NON-NLS-1$
            enginePrefsNode.putInt("Bad Feels After", enginePreferences.badFeelDaysAfter); //$NON-NLS-1$
            enginePrefsNode.putInt("Reg. Min Length", enginePreferences.regularMinLength); //$NON-NLS-1$
            enginePrefsNode.putInt("Reg. Max Length", enginePreferences.regularMaxLength); //$NON-NLS-1$
            recalculate();
      }

      /**
       * This method goes through the BBT records list and adjusts the temperatures either to Celsius or to Fahrenheit
       * @param toCelsiusScale - if true, the conversion is performed from Celsius to Fahrenheit, if false - vice versa
       */
01538       public void changeBBTScale(final boolean toCelsiusScale) {

            // check if anything needs to be done
            if(toCelsiusScale == fileBBTCelsiusScale) return; 

            for(Date date: bBTList.keySet()) {
                  bBTList.put(date, 
                              convertTemperatureScale(toCelsiusScale, bBTList.get(date)));
            }

            fileBBTCelsiusScale = toCelsiusScale;
            modified = true;
      }

      /**
       * Converts temperatures between Celsius and Fahrenheit scales 
       * @param toCelsiusScale - if true, the conversion is performed from Celsius to Fahrenheit, if false - vice versa
       * @param integer - a temperature value multiplied by 10
       * @return a temperature value converted to the opposite scale and multiplied by 10
       */
01558       public static int convertTemperatureScale(final boolean toCelsiusScale,
                  final int source) {
            if(toCelsiusScale) {
                  return java.lang.Math.round((source - 320)*5.0f/9.0f);
            } else {                
                  return java.lang.Math.round(source*9.0f/5.0f+320);
            }
      }

      /**
       * Returns if the file BBT temperatures are in the Celsius scale
       * @return the fileBBTCelsiusScale
       */
01571       public boolean isFileBBTCelsiusScale() {
            return fileBBTCelsiusScale;
      }

      /**
       * @return the default engine preferences
       */
01578       public EnginePreferences getDefaultPreferences() {
            EnginePreferences enginePreferences = new EnginePreferences();
            enginePreferences.bufferDays = DEFAULT_BUFFER_DAYS;
            enginePreferences.badFeelDaysBefore = DEFAULT_BAD_FEEL_DAYS_BEFORE;
            enginePreferences.badFeelDaysAfter = DEFAULT_BAD_FEEL_DAYS_AFTER;
            enginePreferences.regularMinLength = DEFAULT_REGULAR_MIN_LENGTH;
            enginePreferences.regularMaxLength = DEFAULT_REGULAR_MAX_LENGTH;
            return enginePreferences;
      }

      /**
       * Engine does not care about these sympthoms. Feel free to modify this list as needed
       * @return the reference to the container of the sympthoms to be monitored in addition to the BBT
       */
01592       public BBTSympthomsSetDefinition getBBTSympthoms() {
            return sympthomsDefinition;
      }

      /**
       * Swap positions of the two sympthoms
       * @param sympthomIndex1 sympthom index #1
       * @param sympthomIndex2 sympthom index #2
       */
01601       public void swapSympthoms(int sympthomIndex1, int sympthomIndex2) {
            // change the names and values in the container
            String[] sympthomsStringList = sympthomsDefinition.getSympthoms(); 
            String tmpString = sympthomsStringList[sympthomIndex2];
            Vector<String>tmpVector = sympthomsDefinition.getSympthomValuesVector(sympthomIndex2);
            sympthomsDefinition.setSypmthomNameAndValues(sympthomIndex2, 
                        sympthomsStringList[sympthomIndex1], 
                        sympthomsDefinition.getSympthomValuesVector(sympthomIndex1));
            sympthomsDefinition.setSypmthomNameAndValues(sympthomIndex1, 
                        tmpString,tmpVector);

            // update the date-integers pairs according to this change
            for(DateIntsContainer intCnt: PCalendar.engine.getAllDateIntegers().values()) {
                  if(intCnt == null) continue;
                  Integer tempInt = intCnt.getIntValue(sympthomIndex2);
                  intCnt.setIntValue(sympthomIndex2, intCnt.getIntValue(sympthomIndex1));
                  intCnt.setIntValue(sympthomIndex1, tempInt);
            }
            modified = true;
      }

      /**
       * Swaps two values of a single sympthom
       * @param sympthomIndex
       * @param valueIndex1
       * @param valueIndex2
       */
01628       public void swapSympthomValues(int sympthomIndex, int valueIndex1, int valueIndex2) {
            if((sympthomIndex >=0) && (sympthomIndex < sympthomsDefinition.getSympthoms().length)) {        
                  String tmpValue = sympthomsDefinition.getSympthomValue(sympthomIndex, valueIndex1);
                  sympthomsDefinition.setSypmthomValue(sympthomIndex, valueIndex1, sympthomsDefinition.getSympthomValue(sympthomIndex, valueIndex2));
                  sympthomsDefinition.setSypmthomValue(sympthomIndex, valueIndex2, tmpValue);

                  // update the date-integets pairs according to this change
                  for(DateIntsContainer intCnt: PCalendar.engine.getAllDateIntegers().values()) {
                        Integer tempInt = intCnt.getIntValue(sympthomIndex);
                        if(tempInt!=null) {
                              if(tempInt.equals(valueIndex1)) {
                                    intCnt.setIntValue(sympthomIndex, valueIndex2);
                              } else 
                                    if(tempInt.equals(valueIndex2)) {
                                          intCnt.setIntValue(sympthomIndex, valueIndex1);
                                    } 
                        }
                  }
                  modified = true;
            }
      }

      /**
       * Insert a new empty sympthom value to the position specified.
       * @param sympthomIndex
       * @param newValueIndex
       */
01655       public void insertSypmthomValue(int sympthomIndex, int newValueIndex) {
            int insertedPosition = sympthomsDefinition.insertSypmthomValue(sympthomIndex, newValueIndex, ""); //$NON-NLS-1$
            if(insertedPosition == -1) return;

            applyDateIntDelta(sympthomIndex, newValueIndex, 1);

            modified = true;
      }

      /**
       * Deletes the sympthom value from the definition and updates the file data
       * @param sympthomIndex
       * @param deletedValueIndex
       */
01669       public void deleteSypmthomValue(int sympthomIndex,    int deletedValueIndex) {
            sympthomsDefinition.getSympthomValuesVector(sympthomIndex).remove(deletedValueIndex);

            if(deletedValueIndex>0) {
                  applyDateIntDelta(sympthomIndex, deletedValueIndex, -1);
            } else {
                  applyDateIntDelta(sympthomIndex, deletedValueIndex+1, -1);
            }

            modified = true;
      }

      /**
       * This is a helper method for shifting all date-ints up or down when a sympthom value is added or deleted 
       * @param sympthomIndex
       * @param startingValue apply to values equal or larger than this
       * @param delta
       */
01687       private void applyDateIntDelta(int sympthomIndex, int startingValue, int delta) {
            for(DateIntsContainer intCnt: PCalendar.engine.getAllDateIntegers().values()) {
                  Integer tempInt = intCnt.getIntValue(sympthomIndex);
                  if(tempInt!=null) {
                        if(tempInt >= startingValue) {
                              intCnt.setIntValue(sympthomIndex, tempInt + delta);
                        }
                  }
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index