PostingReader.java

/*
 * Copyright (C) 2016-2025 The CTAN Team and individual authors
 *
 * This file is distributed under the 3-clause BSD license.
 * See file LICENSE for details.
 */

package org.ctan.site.services.postings;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;

/**
 * This class provides a reader for mails from gzipped mail archive files.
 *
 * @author <a href="gene@ctan.org">Gerd Neugebauer</a>
 */
public class PostingReader implements AutoCloseable {

    /**
     * The field <code>reader</code> contains the reader for the data source.
     */
    private BufferedReader reader;

    /**
     * The field <code>line</code> contains the pushback buffer for read lines.
     */
    private String line = null;

    /**
     * This is the constructor for <code>PostingReader</code>.
     *
     * @param r the reader
     */
    public PostingReader(Reader r) {

        reader = new BufferedReader(r, 1024);
    }

    /**
     * This method closes this reader and the attached input sources.
     *
     * @throws IOException in case of an I/O error during closing
     */
    @Override
    public void close() throws IOException {

        if (reader != null) {
            reader.close();
        }
        reader = null;
    }

    /**
     * This method reads a mail from this reader and returns it.
     *
     * @return the mail read or {@code null} at EOF
     *
     * @throws IOException in case of an I/O error
     */
    public Posting read() throws IOException {

        if (line == null) {
            line = reader.readLine();
        }
        if (line == null || !line.startsWith("From ")) {
            return null;
        }

        var m = new Posting(line.substring(5));
        var key = "";

        for (line = reader.readLine(); line != null
                && !line.isEmpty(); line = reader.readLine()) {
            if (line.startsWith(" ") || line.startsWith("\t")) {
                m.put(key, m.get(key) + line);
            }
            var i = line.indexOf(':');
            if (i >= 0) {
                key = line.substring(0, i);
                m.put(key, line.substring(i + 2));
            } else {
                break;
            }
        }

        var buffer = new StringBuilder();
        var nl = false;

        for (line = reader.readLine(); line != null; line = reader.readLine()) {
            if (line.isEmpty()) {
                if (nl) {
                    buffer.append('\n');
                } else {
                    nl = true;
                }
            } else {
                if (nl) {
                    if (line.startsWith("From ")) {
                        break;
                    }
                    buffer.append('\n');
                    nl = false;
                }
                buffer.append(line);
                buffer.append('\n');
            }
        }

        m.setBody(buffer.toString());
        return m;
    }
}