Article.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.net.nntp;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;

import org.apache.commons.net.util.NetConstants;

/**
 * This is a class that contains the basic state needed for message retrieval and threading. With thanks to Jamie Zawinski (jwz@jwz.org)
 */
public class Article implements Threadable {
    /**
     * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
     *
     * @param article the root of the article 'tree'
     * @since 3.4
     */
    public static void printThread(final Article article) {
        printThread(article, 0, System.out);
    }

    /**
     * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
     *
     * @param article the root of the article 'tree'
     * @param depth   the current tree depth
     */
    public static void printThread(final Article article, final int depth) {
        printThread(article, depth, System.out);
    }

    /**
     * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
     *
     * @param article the root of the article 'tree'
     * @param depth   the current tree depth
     * @param ps      the PrintStream to use
     * @since 3.4
     */
    public static void printThread(final Article article, final int depth, final PrintStream ps) {
        for (int i = 0; i < depth; ++i) {
            ps.print("==>");
        }
        ps.println(article.getSubject() + "\t" + article.getFrom() + "\t" + article.getArticleId());
        if (article.kid != null) {
            printThread(article.kid, depth + 1);
        }
        if (article.next != null) {
            printThread(article.next, depth);
        }
    }

    /**
     * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
     *
     * @param article the root of the article 'tree'
     * @param ps      the PrintStream to use
     * @since 3.4
     */
    public static void printThread(final Article article, final PrintStream ps) {
        printThread(article, 0, ps);
    }

    private long articleNumber;
    private String subject;
    private String date;
    private String articleId;

    private String simplifiedSubject;

    private String from;
    private ArrayList<String> references;

    private boolean isReply;

    public Article kid, next;

    public Article() {
        articleNumber = -1; // isDummy
    }

    @Deprecated

    public void addHeaderField(final String name, final String val) {
    }

    /**
     * Adds a message-id to the list of messages that this message references (i.e. replies to)
     *
     * @param msgId the message id to add
     */
    public void addReference(final String msgId) {
        if (msgId == null || msgId.isEmpty()) {
            return;
        }
        if (references == null) {
            references = new ArrayList<>();
        }
        isReply = true;
        Collections.addAll(references, msgId.split(" "));
    }

    private void flushSubjectCache() {
        simplifiedSubject = null;
    }

    public String getArticleId() {
        return articleId;
    }

    @Deprecated
    public int getArticleNumber() {
        return (int) articleNumber;
    }

    public long getArticleNumberLong() {
        return articleNumber;
    }

    public String getDate() {
        return date;
    }

    public String getFrom() {
        return from;
    }

    /**
     * Returns the MessageId references as an array of Strings
     *
     * @return an array of message-ids
     */
    public String[] getReferences() {
        if (references == null) {
            return NetConstants.EMPTY_STRING_ARRAY;
        }
        return references.toArray(NetConstants.EMPTY_STRING_ARRAY);
    }

    public String getSubject() {
        return subject;
    }

    @Override
    public boolean isDummy() {
        return articleNumber == -1;
    }

    @Override
    public Threadable makeDummy() {
        return new Article();
    }

    @Override
    public String messageThreadId() {
        return articleId;
    }

    @Override
    public String[] messageThreadReferences() {
        return getReferences();
    }

    public void setArticleId(final String string) {
        articleId = string;
    }

    @Deprecated
    public void setArticleNumber(final int a) {
        articleNumber = a;
    }

    public void setArticleNumber(final long l) {
        articleNumber = l;
    }

    @Override
    public void setChild(final Threadable child) {
        this.kid = (Article) child;
        flushSubjectCache();
    }

    public void setDate(final String string) {
        date = string;
    }

    public void setFrom(final String string) {
        from = string;
    }

    @Override
    public void setNext(final Threadable next) {
        this.next = (Article) next;
        flushSubjectCache();
    }

    public void setSubject(final String string) {
        subject = string;
    }

    @Override
    public String simplifiedSubject() {
        if (simplifiedSubject == null) {
            simplifySubject();
        }
        return simplifiedSubject;
    }

    // DEPRECATED METHODS - for API compatibility only - DO NOT USE

    /**
     * Attempts to parse the subject line for some typical reply signatures, and strip them out
     */
    private void simplifySubject() {
        int start = 0;
        final String subject = getSubject();
        final int len = subject.length();

        boolean done = false;

        while (!done) {
            done = true;

            // skip whitespace
            // "Re: " breaks this
            while (start < len && subject.charAt(start) == ' ') {
                start++;
            }

            if (start < len - 2 && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R')
                    && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) {

                if (subject.charAt(start + 2) == ':') {
                    start += 3; // Skip "Re:"
                    done = false;
                } else if (start < len - 2 && (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) {

                    int i = start + 3;

                    while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9') {
                        i++;
                    }

                    if (i < len - 1 && (subject.charAt(i) == ']' || subject.charAt(i) == ')') && subject.charAt(i + 1) == ':') {
                        start = i + 2;
                        done = false;
                    }
                }
            }

            if ("(no subject)".equals(simplifiedSubject)) {
                simplifiedSubject = "";
            }

            int end = len;

            while (end > start && subject.charAt(end - 1) < ' ') {
                end--;
            }

            if (start == 0 && end == len) {
                simplifiedSubject = subject;
            } else {
                simplifiedSubject = subject.substring(start, end);
            }
        }
    }

    @Override
    public boolean subjectIsReply() {
        return isReply;
    }

    @Override
    public String toString() { // Useful for Eclipse debugging
        return articleNumber + " " + articleId + " " + subject;
    }

}