net.java.sip.communicator.impl.history.HistoryReaderImpl.java Source code

Java tutorial

Introduction

Here is the source code for net.java.sip.communicator.impl.history.HistoryReaderImpl.java

Source

/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package net.java.sip.communicator.impl.history;

import static net.java.sip.communicator.service.history.HistoryService.DATE_FORMAT;

import java.text.*;
import java.util.*;
import java.util.regex.*;

import net.java.sip.communicator.service.history.*;
import net.java.sip.communicator.service.history.event.*;
import net.java.sip.communicator.service.history.records.*;

import org.apache.commons.lang3.*;
import org.w3c.dom.*;

/**
 * @author Alexander Pelov
 * @author Damian Minkov
 * @author Yana Stamcheva
 */
public class HistoryReaderImpl implements HistoryReader {
    private HistoryImpl historyImpl;
    private Vector<HistorySearchProgressListener> progressListeners = new Vector<HistorySearchProgressListener>();

    // regexp used for index of case(in)sensitive impl
    private static String REGEXP_END = ".*$";
    private static String REGEXP_SENSITIVE_START = "(?s)^.*";
    private static String REGEXP_INSENSITIVE_START = "(?si)^.*";

    /**
     * Creates an instance of <tt>HistoryReaderImpl</tt>.
     * @param historyImpl the parent History implementation
     */
    protected HistoryReaderImpl(HistoryImpl historyImpl) {
        this.historyImpl = historyImpl;
    }

    /**
     * Searches the history for all records with timestamp after
     * <tt>startDate</tt>.
     *
     * @param startDate the date after all records will be returned
     * @return the found records
     * @throws RuntimeException
     *             Thrown if an exception occurs during the execution of the
     *             query, such as internal IO error.
     */
    public synchronized QueryResultSet<HistoryRecord> findByStartDate(Date startDate) throws RuntimeException {
        return find(startDate, null, null, null, false);
    }

    /**
     * Searches the history for all records with timestamp before
     * <tt>endDate</tt>.
     *
     * @param endDate the date before which all records will be returned
     * @return the found records
     * @throws RuntimeException
     *             Thrown if an exception occurs during the execution of the
     *             query, such as internal IO error.
     */
    public synchronized QueryResultSet<HistoryRecord> findByEndDate(Date endDate) throws RuntimeException {
        return find(null, endDate, null, null, false);
    }

    /**
     * Searches the history for all records with timestamp between
     * <tt>startDate</tt> and <tt>endDate</tt>.
     *
     * @param startDate start of the interval in which we search
     * @param endDate end of the interval in which we search
     * @return the found records
     * @throws RuntimeException
     *             Thrown if an exception occurs during the execution of the
     *             query, such as internal IO error.
     */
    public synchronized QueryResultSet<HistoryRecord> findByPeriod(Date startDate, Date endDate)
            throws RuntimeException {
        return find(startDate, endDate, null, null, false);
    }

    /**
     * Searches the history for all records containing the <tt>keyword</tt>.
     *
     * @param keyword the keyword to search for
     * @param field the field where to look for the keyword
     * @return the found records
     * @throws RuntimeException
     *             Thrown if an exception occurs during the execution of the
     *             query, such as internal IO error.
     */
    public synchronized QueryResultSet<HistoryRecord> findByKeyword(String keyword, String field)
            throws RuntimeException {
        return findByKeywords(new String[] { keyword }, field);
    }

    /**
     * Searches the history for all records containing all <tt>keywords</tt>.
     *
     * @param keywords array of keywords we search for
     * @param field the field where to look for the keyword
     * @return the found records
     * @throws RuntimeException
     *             Thrown if an exception occurs during the execution of the
     *             query, such as internal IO error.
     */
    public synchronized QueryResultSet<HistoryRecord> findByKeywords(String[] keywords, String field)
            throws RuntimeException {
        return find(null, null, keywords, field, false);
    }

    /**
     * Searches for all history records containing all <tt>keywords</tt>,
     * with timestamp between <tt>startDate</tt> and <tt>endDate</tt>.
     *
     * @param startDate start of the interval in which we search
     * @param endDate end of the interval in which we search
     * @param keywords array of keywords we search for
     * @param field the field where to look for the keyword
     * @return the found records
     * @throws UnsupportedOperationException
     *             Thrown if an exception occurs during the execution of the
     *             query, such as internal IO error.
     */
    public synchronized QueryResultSet<HistoryRecord> findByPeriod(Date startDate, Date endDate, String[] keywords,
            String field) throws UnsupportedOperationException {
        return find(startDate, endDate, keywords, field, false);
    }

    /**
     * Returns the last <tt>count</tt> messages.
     * No progress firing as this method is supposed to be used
     * in message windows and is supposed to be as quick as it can.
     *
     * @param count int
     * @return QueryResultSet
     * @throws RuntimeException
     */
    public synchronized QueryResultSet<HistoryRecord> findLast(int count) throws RuntimeException {
        return findLast(count, null, null, false);
    }

    /**
     * Returns the supplied number of recent messages
     * containing all <tt>keywords</tt>.
     *
     * @param count messages count
     * @param keywords array of keywords we search for
     * @param field the field where to look for the keyword
     * @param caseSensitive is keywords search case sensitive
     * @return the found records
     * @throws RuntimeException
     */
    public synchronized QueryResultSet<HistoryRecord> findLast(int count, String[] keywords, String field,
            boolean caseSensitive) throws RuntimeException {
        // the files are supposed to be ordered from oldest to newest
        Vector<String> filelist = filterFilesByDate(this.historyImpl.getFileList(), null, null);

        TreeSet<HistoryRecord> result = new TreeSet<HistoryRecord>(new HistoryRecordComparator());
        int leftCount = count;
        int currentFile = filelist.size() - 1;

        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
        while (leftCount > 0 && currentFile >= 0) {
            Document doc = this.historyImpl.getDocumentForFile(filelist.get(currentFile));

            if (doc == null) {
                currentFile--;
                continue;
            }

            // will get nodes and construct a List of nodes
            // so we can easily get sublist of it
            List<Node> nodes = new ArrayList<Node>();
            NodeList nodesList = doc.getElementsByTagName("record");
            for (int i = 0; i < nodesList.getLength(); i++) {
                nodes.add(nodesList.item(i));
            }

            List<Node> lNodes = null;

            if (nodes.size() > leftCount) {
                lNodes = nodes.subList(nodes.size() - leftCount, nodes.size());
                leftCount = 0;
            } else {
                lNodes = nodes;
                leftCount -= nodes.size();
            }

            Iterator<Node> i = lNodes.iterator();
            while (i.hasNext()) {
                Node node = i.next();

                NodeList propertyNodes = node.getChildNodes();

                Date timestamp;
                String ts = node.getAttributes().getNamedItem("timestamp").getNodeValue();
                try {
                    timestamp = sdf.parse(ts);
                } catch (ParseException e) {
                    timestamp = new Date(Long.parseLong(ts));
                }

                HistoryRecord record = filterByKeyword(propertyNodes, timestamp, keywords, field, caseSensitive);

                if (record != null) {
                    result.add(record);
                }
            }

            currentFile--;
        }

        return new OrderedQueryResultSet<HistoryRecord>(result);
    }

    /**
     * Searches the history for all records containing the <tt>keyword</tt>.
     *
     * @param keyword the keyword to search for
     * @param field the field where to look for the keyword
     * @param caseSensitive is keywords search case sensitive
     * @return the found records
     * @throws RuntimeException
     *             Thrown if an exception occurs during the execution of the
     *             query, such as internal IO error.
     */
    public synchronized QueryResultSet<HistoryRecord> findByKeyword(String keyword, String field,
            boolean caseSensitive) throws RuntimeException {
        return findByKeywords(new String[] { keyword }, field, caseSensitive);
    }

    /**
     * Searches the history for all records containing all <tt>keywords</tt>.
     *
     * @param keywords array of keywords we search for
     * @param field the field where to look for the keyword
     * @param caseSensitive is keywords search case sensitive
     * @return the found records
     * @throws RuntimeException
     *             Thrown if an exception occurs during the execution of the
     *             query, such as internal IO error.
     */
    public synchronized QueryResultSet<HistoryRecord> findByKeywords(String[] keywords, String field,
            boolean caseSensitive) throws RuntimeException {
        return find(null, null, keywords, field, caseSensitive);
    }

    /**
     * Searches for all history records containing all <tt>keywords</tt>,
     * with timestamp between <tt>startDate</tt> and <tt>endDate</tt>.
     *
     * @param startDate start of the interval in which we search
     * @param endDate end of the interval in which we search
     * @param keywords array of keywords we search for
     * @param field the field where to look for the keyword
     * @param caseSensitive is keywords search case sensitive
     * @return the found records
     * @throws UnsupportedOperationException
     *             Thrown if an exception occurs during the execution of the
     *             query, such as internal IO error.
     */
    public synchronized QueryResultSet<HistoryRecord> findByPeriod(Date startDate, Date endDate, String[] keywords,
            String field, boolean caseSensitive) throws UnsupportedOperationException {
        return find(startDate, endDate, keywords, field, caseSensitive);
    }

    /**
     * Returns the supplied number of recent messages after the given date
     *
     * @param date messages after date
     * @param count messages count
     * @return QueryResultSet the found records
     * @throws RuntimeException
     */
    public QueryResultSet<HistoryRecord> findFirstRecordsAfter(Date date, int count) throws RuntimeException {
        TreeSet<HistoryRecord> result = new TreeSet<HistoryRecord>(new HistoryRecordComparator());

        Vector<String> filelist = filterFilesByDate(this.historyImpl.getFileList(), date, null);

        int leftCount = count;
        int currentFile = 0;

        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
        while (leftCount > 0 && currentFile < filelist.size()) {
            Document doc = this.historyImpl.getDocumentForFile(filelist.get(currentFile));

            if (doc == null) {
                currentFile++;
                continue;
            }

            NodeList nodes = doc.getElementsByTagName("record");

            Node node;
            for (int i = 0; i < nodes.getLength() && leftCount > 0; i++) {
                node = nodes.item(i);

                NodeList propertyNodes = node.getChildNodes();

                Date timestamp;
                String ts = node.getAttributes().getNamedItem("timestamp").getNodeValue();
                try {
                    timestamp = sdf.parse(ts);
                } catch (ParseException e) {
                    timestamp = new Date(Long.parseLong(ts));
                }

                if (!isInPeriod(timestamp, date, null))
                    continue;

                ArrayList<String> nameVals = new ArrayList<String>();

                boolean isRecordOK = true;
                int len = propertyNodes.getLength();
                for (int j = 0; j < len; j++) {
                    Node propertyNode = propertyNodes.item(j);
                    if (propertyNode.getNodeType() == Node.ELEMENT_NODE) {
                        // Get nested TEXT node's value
                        Node nodeValue = propertyNode.getFirstChild();

                        if (nodeValue != null) {
                            nameVals.add(propertyNode.getNodeName());
                            nameVals.add(nodeValue.getNodeValue());
                        } else
                            isRecordOK = false;
                    }
                }

                // if we found a broken record - just skip it
                if (!isRecordOK)
                    continue;

                String[] propertyNames = new String[nameVals.size() / 2];
                String[] propertyValues = new String[propertyNames.length];
                for (int j = 0; j < propertyNames.length; j++) {
                    propertyNames[j] = nameVals.get(j * 2);
                    propertyValues[j] = nameVals.get(j * 2 + 1);
                }

                HistoryRecord record = new HistoryRecord(propertyNames, propertyValues, timestamp);

                result.add(record);
                leftCount--;
            }

            currentFile++;
        }

        return new OrderedQueryResultSet<HistoryRecord>(result);
    }

    /**
     * Returns the supplied number of recent messages before the given date
     *
     * @param date messages before date
     * @param count messages count
     * @return QueryResultSet the found records
     * @throws RuntimeException
     */
    public QueryResultSet<HistoryRecord> findLastRecordsBefore(Date date, int count) throws RuntimeException {
        // the files are supposed to be ordered from oldest to newest
        Vector<String> filelist = filterFilesByDate(this.historyImpl.getFileList(), null, date);

        TreeSet<HistoryRecord> result = new TreeSet<HistoryRecord>(new HistoryRecordComparator());
        int leftCount = count;

        int currentFile = filelist.size() - 1;

        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
        while (leftCount > 0 && currentFile >= 0) {
            Document doc = this.historyImpl.getDocumentForFile(filelist.get(currentFile));

            if (doc == null) {
                currentFile--;
                continue;
            }

            NodeList nodes = doc.getElementsByTagName("record");

            Node node;
            for (int i = nodes.getLength() - 1; i >= 0 && leftCount > 0; i--) {
                node = nodes.item(i);
                NodeList propertyNodes = node.getChildNodes();

                Date timestamp;
                String ts = node.getAttributes().getNamedItem("timestamp").getNodeValue();
                try {
                    timestamp = sdf.parse(ts);
                } catch (ParseException e) {
                    timestamp = new Date(Long.parseLong(ts));
                }

                if (!isInPeriod(timestamp, null, date))
                    continue;

                ArrayList<String> nameVals = new ArrayList<String>();

                boolean isRecordOK = true;
                int len = propertyNodes.getLength();
                for (int j = 0; j < len; j++) {
                    Node propertyNode = propertyNodes.item(j);
                    if (propertyNode.getNodeType() == Node.ELEMENT_NODE) {
                        // Get nested TEXT node's value
                        Node nodeValue = propertyNode.getFirstChild();

                        if (nodeValue != null) {
                            nameVals.add(propertyNode.getNodeName());
                            nameVals.add(nodeValue.getNodeValue());
                        } else
                            isRecordOK = false;
                    }
                }

                // if we found a broken record - just skip it
                if (!isRecordOK)
                    continue;

                String[] propertyNames = new String[nameVals.size() / 2];
                String[] propertyValues = new String[propertyNames.length];
                for (int j = 0; j < propertyNames.length; j++) {
                    propertyNames[j] = nameVals.get(j * 2);
                    propertyValues[j] = nameVals.get(j * 2 + 1);
                }

                HistoryRecord record = new HistoryRecord(propertyNames, propertyValues, timestamp);

                result.add(record);
                leftCount--;
            }

            currentFile--;
        }

        return new OrderedQueryResultSet<HistoryRecord>(result);
    }

    private QueryResultSet<HistoryRecord> find(Date startDate, Date endDate, String[] keywords, String field,
            boolean caseSensitive) {
        TreeSet<HistoryRecord> result = new TreeSet<HistoryRecord>(new HistoryRecordComparator());

        Vector<String> filelist = filterFilesByDate(this.historyImpl.getFileList(), startDate, endDate);

        double currentProgress = HistorySearchProgressListener.PROGRESS_MINIMUM_VALUE;
        double fileProgressStep = HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE;

        if (filelist.size() != 0)
            fileProgressStep = HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE / filelist.size();

        // start progress - minimum value
        fireProgressStateChanged(startDate, endDate, keywords,
                HistorySearchProgressListener.PROGRESS_MINIMUM_VALUE);

        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
        Iterator<String> fileIterator = filelist.iterator();
        while (fileIterator.hasNext()) {
            String filename = fileIterator.next();

            Document doc = this.historyImpl.getDocumentForFile(filename);

            if (doc == null)
                continue;

            NodeList nodes = doc.getElementsByTagName("record");

            double nodesProgressStep = fileProgressStep;

            if (nodes.getLength() != 0)
                nodesProgressStep = fileProgressStep / nodes.getLength();

            Node node;
            for (int i = 0; i < nodes.getLength(); i++) {
                node = nodes.item(i);

                Date timestamp;
                String ts = node.getAttributes().getNamedItem("timestamp").getNodeValue();
                try {
                    timestamp = sdf.parse(ts);
                } catch (ParseException e) {
                    timestamp = new Date(Long.parseLong(ts));
                }

                if (isInPeriod(timestamp, startDate, endDate)) {
                    NodeList propertyNodes = node.getChildNodes();

                    HistoryRecord record = filterByKeyword(propertyNodes, timestamp, keywords, field,
                            caseSensitive);

                    if (record != null) {
                        result.add(record);
                    }
                }

                currentProgress += nodesProgressStep;
                fireProgressStateChanged(startDate, endDate, keywords, (int) currentProgress);
            }
        }

        // if maximum value is not reached fire an event
        if ((int) currentProgress < HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE) {
            fireProgressStateChanged(startDate, endDate, keywords,
                    HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE);
        }

        return new OrderedQueryResultSet<HistoryRecord>(result);
    }

    /**
     * Evaluetes does <tt>timestamp</tt> is in the given time period.
     *
     * @param timestamp Date
     * @param startDate Date the start of the period
     * @param endDate Date the end of the period
     * @return boolean
     */
    static boolean isInPeriod(Date timestamp, Date startDate, Date endDate) {
        Long startLong;
        Long endLong;
        Long tsLong = timestamp.getTime();

        if (startDate == null)
            startLong = Long.MIN_VALUE;
        else
            startLong = startDate.getTime();

        if (endDate == null)
            endLong = Long.MAX_VALUE;
        else
            endLong = endDate.getTime();

        return startLong <= tsLong && tsLong < endLong;
    }

    /**
     * If there is keyword restriction and doesn't match the conditions
     * return null. Otherwise return the HistoryRecord corresponding the
     * given nodes.
     *
     * @param propertyNodes NodeList
     * @param timestamp Date
     * @param keywords String[]
     * @param field String
     * @param caseSensitive boolean
     * @return HistoryRecord
     */
    static HistoryRecord filterByKeyword(NodeList propertyNodes, Date timestamp, String[] keywords, String field,
            boolean caseSensitive) {
        ArrayList<String> nameVals = new ArrayList<String>();
        int len = propertyNodes.getLength();
        boolean targetNodeFound = false;
        for (int j = 0; j < len; j++) {
            Node propertyNode = propertyNodes.item(j);
            if (propertyNode.getNodeType() == Node.ELEMENT_NODE) {
                String nodeName = propertyNode.getNodeName();

                Node nestedNode = propertyNode.getFirstChild();

                if (nestedNode == null)
                    continue;

                // Get nested TEXT node's value
                String nodeValue = nestedNode.getNodeValue();

                // unescape xml chars, we have escaped when writing values
                nodeValue = StringEscapeUtils.unescapeXml(nodeValue);

                if (field != null && field.equals(nodeName)) {
                    targetNodeFound = true;

                    if (!matchKeyword(nodeValue, keywords, caseSensitive))
                        return null; // doesn't match the given keyword(s)
                                     // so return nothing
                }

                nameVals.add(nodeName);
                // Get nested TEXT node's value
                nameVals.add(nodeValue);

            }
        }

        // if we need to find a particular record but the target node is not
        // present skip this record
        if (keywords != null && keywords.length > 0 && !targetNodeFound) {
            return null;
        }

        String[] propertyNames = new String[nameVals.size() / 2];
        String[] propertyValues = new String[propertyNames.length];
        for (int j = 0; j < propertyNames.length; j++) {
            propertyNames[j] = nameVals.get(j * 2);
            propertyValues[j] = nameVals.get(j * 2 + 1);
        }

        return new HistoryRecord(propertyNames, propertyValues, timestamp);
    }

    /**
     * Check if a value is in the given keyword(s)
     * If no keyword(s) given must return true
     *
     * @param value String
     * @param keywords String[]
     * @param caseSensitive boolean
     * @return boolean
     */
    static boolean matchKeyword(String value, String[] keywords, boolean caseSensitive) {
        if (keywords != null) {
            String regexpStart = null;
            if (caseSensitive)
                regexpStart = REGEXP_SENSITIVE_START;
            else
                regexpStart = REGEXP_INSENSITIVE_START;

            for (int i = 0; i < keywords.length; i++) {
                if (!value.matches(regexpStart + Pattern.quote(keywords[i]) + REGEXP_END))
                    return false;
            }

            // all keywords match return true
            return true;
        }

        // if no keyword or keywords given
        // we must not filter this record so will return true
        return true;
    }

    /**
     * Used to limit the files if any starting or ending date exist
     * So only few files to be searched.
     *
     * @param filelist Iterator
     * @param startDate Date
     * @param endDate Date
     * @return Iterator
     */
    static Vector<String> filterFilesByDate(Iterator<String> filelist, Date startDate, Date endDate) {
        return filterFilesByDate(filelist, startDate, endDate, false);
    }

    /**
     * Used to limit the files if any starting or ending date exist
     * So only few files to be searched.
     *
     * @param filelist Iterator
     * @param startDate Date
     * @param endDate Date
     * @param reverseOrder reverse order of files
     * @return Vector
     */
    static Vector<String> filterFilesByDate(Iterator<String> filelist, Date startDate, Date endDate,
            final boolean reverseOrder) {
        if (startDate == null && endDate == null) {
            // no filtering needed then just return the same list
            Vector<String> result = new Vector<String>();
            while (filelist.hasNext()) {
                result.add(filelist.next());
            }

            Collections.sort(result, new Comparator<String>() {

                public int compare(String o1, String o2) {
                    if (reverseOrder)
                        return o2.compareTo(o1);
                    else
                        return o1.compareTo(o2);
                }
            });

            return result;
        }
        // first convert all files to long
        TreeSet<Long> files = new TreeSet<Long>();
        while (filelist.hasNext()) {
            String filename = filelist.next();

            files.add(Long.parseLong(filename.substring(0, filename.length() - 4)));
        }

        TreeSet<Long> resultAsLong = new TreeSet<Long>();

        // Temporary fix of a NoSuchElementException
        if (files.size() == 0) {
            return new Vector<String>();
        }

        Long startLong;
        Long endLong;

        if (startDate == null)
            startLong = Long.MIN_VALUE;
        else
            startLong = startDate.getTime();

        if (endDate == null)
            endLong = Long.MAX_VALUE;
        else
            endLong = endDate.getTime();

        // get all records inclusive the one before the startdate
        for (Long f : files) {
            if (startLong <= f && f <= endLong) {
                resultAsLong.add(f);
            }
        }

        // get the subset before the start date, to get its last element
        // if exists
        if (!files.isEmpty() && files.first() <= startLong) {
            SortedSet<Long> setBeforeTheInterval = files.subSet(files.first(), true, startLong, true);
            if (!setBeforeTheInterval.isEmpty())
                resultAsLong.add(setBeforeTheInterval.last());
        }

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

        Iterator<Long> iter = resultAsLong.iterator();
        while (iter.hasNext()) {
            Long item = iter.next();
            result.add(item.toString() + ".xml");
        }

        Collections.sort(result, new Comparator<String>() {

            public int compare(String o1, String o2) {
                if (reverseOrder)
                    return o2.compareTo(o1);
                else
                    return o1.compareTo(o2);
            }
        });

        return result;
    }

    private void fireProgressStateChanged(Date startDate, Date endDate, String[] keywords, int progress) {
        ProgressEvent event = new ProgressEvent(this, startDate, endDate, keywords, progress);

        synchronized (progressListeners) {
            Iterator<HistorySearchProgressListener> iter = progressListeners.iterator();
            while (iter.hasNext()) {
                HistorySearchProgressListener item = iter.next();
                item.progressChanged(event);
            }
        }
    }

    /**
     * Adding progress listener for monitoring progress of search process
     *
     * @param listener HistorySearchProgressListener
     */
    public void addSearchProgressListener(HistorySearchProgressListener listener) {
        synchronized (progressListeners) {
            progressListeners.add(listener);
        }
    }

    /**
     * Removing progress listener
     *
     * @param listener HistorySearchProgressListener
     */
    public void removeSearchProgressListener(HistorySearchProgressListener listener) {
        synchronized (progressListeners) {
            progressListeners.remove(listener);
        }
    }

    /**
     * Count the number of messages that a search will return
     * Actually only the last file is parsed and its nodes are counted.
     * We accept that the other files are full with max records,
     * this way we escape parsing all files which will significantly
     * slow the process and for one search will parse the files twice.
     *
     * @return the number of searched messages
     * @throws UnsupportedOperationException
     *              Thrown if an exception occurs during the execution of the
     *              query, such as internal IO error.
     */
    public int countRecords() throws UnsupportedOperationException {
        int result = 0;
        String lastFile = null;
        Iterator<String> filelistIter = this.historyImpl.getFileList();
        while (filelistIter.hasNext()) {
            lastFile = filelistIter.next();
            result += HistoryWriterImpl.MAX_RECORDS_PER_FILE;
        }

        if (lastFile == null)
            return result;

        Document doc = this.historyImpl.getDocumentForFile(lastFile);

        if (doc == null)
            return result;

        NodeList nodes = doc.getElementsByTagName("record");

        result += nodes.getLength();

        return result;
    }

    /**
     * Used to compare HistoryRecords
     * ant to be ordered in TreeSet
     */
    private static class HistoryRecordComparator implements Comparator<HistoryRecord> {
        public int compare(HistoryRecord h1, HistoryRecord h2) {
            return h1.getTimestamp().compareTo(h2.getTimestamp());
        }
    }
}