class Handler extends DefaultHandler2 {

        private static final String COPYFROM_PATH = "copyfrom-path";

        /**
         * Example of the longest date format that we should accept - SimpleDateFormat cannot cope with micro/nano seconds.
         */
        static final int SVN_MILLIS_DATE_LENGTH = "2020-03-26T15:38:55.999Z".length();

        final String prefix;
        final String home;
        final int length;
        final List<HistoryEntry> entries = new ArrayList<>();
        final Set<String> renamedFiles = new HashSet<>();
        final Map<String, String> renamedToDirectoryRevisions = new HashMap<>();
        final SubversionRepository repository;
        HistoryEntry entry;
        StringBuilder sb;
        boolean isRenamedFile;
        boolean isRenamedDir;

        Handler(String home, String prefix, int length, SubversionRepository repository) {
            this.home = home;
            this.prefix = prefix;
            this.length = length;
            this.repository = repository;
            sb = new StringBuilder();
        }

        Set<String> getRenamedFiles() {
            return renamedFiles;
        }

        Map<String, String> getRenamedDirectories() {
          return renamedToDirectoryRevisions;
        }

        @Override
        public void startElement(String uri, String localName, String qname, Attributes attr) {
            isRenamedFile = false;
            isRenamedDir = false;
            if ("logentry".equals(qname)) {
                entry = new HistoryEntry();
                entry.setActive(true);
                entry.setRevision(attr.getValue("revision"));
            } else if ("path".equals(qname) && attr.getIndex(COPYFROM_PATH) != -1) {
                LOGGER.log(Level.FINER, "rename for {0}", attr.getValue(COPYFROM_PATH));
                if ("dir".equals(attr.getValue("kind"))) {
                    isRenamedDir = true;
                } else {
                    isRenamedFile = true;
                }
            }
            sb.setLength(0);
        }

        @Override
        public void endElement(String uri, String localName, String qname) throws SAXException {
            String s = sb.toString();
            if ("author".equals(qname)) {
                entry.setAuthor(s);
            } else if ("date".equals(qname)) {
                try {
                    // need to strip microseconds off - assume final character is Z otherwise invalid anyway.
                    String dateString = s;
                    if (s.length() > SVN_MILLIS_DATE_LENGTH) {
                        dateString = dateString.substring(0, SVN_MILLIS_DATE_LENGTH - 1) +
                                dateString.charAt(dateString.length() - 1);
                    }
                    entry.setDate(repository.parse(dateString));
                } catch (ParseException ex) {
                    throw new SAXException("Failed to parse date: " + s, ex);
                }
            } else if ("path".equals(qname)) {
                /*
                 * We only want valid files in the repository, not the
                 * top-level directory itself, hence the check for inequality.
                 */
                if (s.startsWith(prefix) && !s.equals(prefix)) {
                    File file = new File(home, s.substring(prefix.length()));
                    String path = file.getAbsolutePath().substring(length);
                    // The same file names may be repeated in many commits,
                    // so intern them to reduce the memory footprint.
                    entry.addFile(path.intern());
                    if (isRenamedFile) {
                        renamedFiles.add(path.intern());
                    }
                    if (isRenamedDir) {
                        renamedToDirectoryRevisions.put(path.intern(), entry.getRevision());
                    }
                } else {
                    LOGGER.log(Level.FINER, "Skipping file ''{0}'' outside repository ''{1}''",
                            new Object[]{s, this.home});
                }
            } else if ("msg".equals(qname)) {
                entry.setMessage(s);
            }
            if ("logentry".equals(qname)) {
                // Avoid adding incomplete history entries.
                if (Objects.isNull(entry.getDate())) {
                    throw new SAXException(String.format("date is null in history entry for revision %s",
                            Optional.ofNullable(entry.getRevision()).orElse("<unknown>")));
                }
                entries.add(entry);
            }
            sb.setLength(0);
        }

        @Override
        public void characters(char[] arg0, int arg1, int arg2) {
            sb.append(arg0, arg1, arg2);
        }
    }

    /**
     * Initialize the SAX parser instance.
     */
    private void initSaxParser() throws HistoryException {<FILL_FUNCTION_BODY>

        SAXParserFactory factory = SAXParserFactory.newInstance();
        saxParser = null;
        try {
            saxParser = factory.newSAXParser();
            saxParser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); // Compliant
            saxParser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); // compliant
        } catch (Exception ex) {
            throw new HistoryException("Failed to create SAX parser", ex);
        }
    
<no_super_class>