    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.net.nntp;
    019
    020import java.io.PrintStream;
    021import java.util.ArrayList;
    022import java.util.Collections;
    023
    024import org.apache.commons.net.util.NetConstants;
    025
    026/**
    027 * This is a class that contains the basic state needed for message retrieval and threading. With thanks to Jamie Zawinski (jwz@jwz.org)
    028 */
    029public class Article implements Threadable {
    030    /**
    031     * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
    032     *
    033     * @param article the root of the article 'tree'
    034     * @since 3.4
    035     */
    036    public static void printThread(final Article article) {
    037        printThread(article, 0, System.out);
    038    }
    039
    040    /**
    041     * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
    042     *
    043     * @param article the root of the article 'tree'
    044     * @param depth   the current tree depth
    045     */
    046    public static void printThread(final Article article, final int depth) {
    047        printThread(article, depth, System.out);
    048    }
    049
    050    /**
    051     * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
    052     *
    053     * @param article the root of the article 'tree'
    054     * @param depth   the current tree depth
    055     * @param ps      the PrintStream to use
    056     * @since 3.4
    057     */
    058    public static void printThread(final Article article, final int depth, final PrintStream ps) {
    059        for (int i = 0; i < depth; ++i) {
    060            ps.print("==>");
    061        }
    062        ps.println(article.getSubject() + "\t" + article.getFrom() + "\t" + article.getArticleId());
    063        if (article.kid != null) {
    064            printThread(article.kid, depth + 1);
    065        }
    066        if (article.next != null) {
    067            printThread(article.next, depth);
    068        }
    069    }
    070
    071    /**
    072     * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out.
    073     *
    074     * @param article the root of the article 'tree'
    075     * @param ps      the PrintStream to use
    076     * @since 3.4
    077     */
    078    public static void printThread(final Article article, final PrintStream ps) {
    079        printThread(article, 0, ps);
    080    }
    081
    082    private long articleNumber;
    083    private String subject;
    084    private String date;
    085    private String articleId;
    086
    087    private String simplifiedSubject;
    088
    089    private String from;
    090    private ArrayList<String> references;
    091
    092    private boolean isReply;
    093
    094    public Article kid, next;
    095
    096    public Article() {
    097        articleNumber = -1; // isDummy
    098    }
    099
    100    @Deprecated
    101
    102    public void addHeaderField(final String name, final String val) {
    103    }
    104
    105    /**
    106     * Adds a message-id to the list of messages that this message references (i.e. replies to)
    107     *
    108     * @param msgId the message id to add
    109     */
    110    public void addReference(final String msgId) {
    111        if (msgId == null || msgId.isEmpty()) {
    112            return;
    113        }
    114        if (references == null) {
    115            references = new ArrayList<>();
    116        }
    117        isReply = true;
    118        Collections.addAll(references, msgId.split(" "));
    119    }
    120
    121    private void flushSubjectCache() {
    122        simplifiedSubject = null;
    123    }
    124
    125    public String getArticleId() {
    126        return articleId;
    127    }
    128
    129    @Deprecated
    130    public int getArticleNumber() {
    131        return (int) articleNumber;
    132    }
    133
    134    public long getArticleNumberLong() {
    135        return articleNumber;
    136    }
    137
    138    public String getDate() {
    139        return date;
    140    }
    141
    142    public String getFrom() {
    143        return from;
    144    }
    145
    146    /**
    147     * Returns the MessageId references as an array of Strings
    148     *
    149     * @return an array of message-ids
    150     */
    151    public String[] getReferences() {
    152        if (references == null) {
    153            return NetConstants.EMPTY_STRING_ARRAY;
    154        }
    155        return references.toArray(NetConstants.EMPTY_STRING_ARRAY);
    156    }
    157
    158    public String getSubject() {
    159        return subject;
    160    }
    161
    162    @Override
    163    public boolean isDummy() {
    164        return (articleNumber == -1);
    165    }
    166
    167    @Override
    168    public Threadable makeDummy() {
    169        return new Article();
    170    }
    171
    172    @Override
    173    public String messageThreadId() {
    174        return articleId;
    175    }
    176
    177    @Override
    178    public String[] messageThreadReferences() {
    179        return getReferences();
    180    }
    181
    182    public void setArticleId(final String string) {
    183        articleId = string;
    184    }
    185
    186    @Deprecated
    187    public void setArticleNumber(final int a) {
    188        articleNumber = a;
    189    }
    190
    191    public void setArticleNumber(final long l) {
    192        articleNumber = l;
    193    }
    194
    195    @Override
    196    public void setChild(final Threadable child) {
    197        this.kid = (Article) child;
    198        flushSubjectCache();
    199    }
    200
    201    public void setDate(final String string) {
    202        date = string;
    203    }
    204
    205    public void setFrom(final String string) {
    206        from = string;
    207    }
    208
    209    @Override
    210    public void setNext(final Threadable next) {
    211        this.next = (Article) next;
    212        flushSubjectCache();
    213    }
    214
    215    public void setSubject(final String string) {
    216        subject = string;
    217    }
    218
    219    @Override
    220    public String simplifiedSubject() {
    221        if (simplifiedSubject == null) {
    222            simplifySubject();
    223        }
    224        return simplifiedSubject;
    225    }
    226
    227    // DEPRECATED METHODS - for API compatibility only - DO NOT USE
    228
    229    /**
    230     * Attempts to parse the subject line for some typical reply signatures, and strip them out
    231     *
    232     */
    233    private void simplifySubject() {
    234        int start = 0;
    235        final String subject = getSubject();
    236        final int len = subject.length();
    237
    238        boolean done = false;
    239
    240        while (!done) {
    241            done = true;
    242
    243            // skip whitespace
    244            // "Re: " breaks this
    245            while (start < len && subject.charAt(start) == ' ') {
    246                start++;
    247            }
    248
    249            if (start < (len - 2) && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R')
    250                    && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) {
    251
    252                if (subject.charAt(start + 2) == ':') {
    253                    start += 3; // Skip "Re:"
    254                    done = false;
    255                } else if (start < (len - 2) && (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) {
    256
    257                    int i = start + 3;
    258
    259                    while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9') {
    260                        i++;
    261                    }
    262
    263                    if (i < (len - 1) && (subject.charAt(i) == ']' || subject.charAt(i) == ')') && subject.charAt(i + 1) == ':') {
    264                        start = i + 2;
    265                        done = false;
    266                    }
    267                }
    268            }
    269
    270            if ("(no subject)".equals(simplifiedSubject)) {
    271                simplifiedSubject = "";
    272            }
    273
    274            int end = len;
    275
    276            while (end > start && subject.charAt(end - 1) < ' ') {
    277                end--;
    278            }
    279
    280            if (start == 0 && end == len) {
    281                simplifiedSubject = subject;
    282            } else {
    283                simplifiedSubject = subject.substring(start, end);
    284            }
    285        }
    286    }
    287
    288    @Override
    289    public boolean subjectIsReply() {
    290        return isReply;
    291    }
    292
    293    @Override
    294    public String toString() { // Useful for Eclipse debugging
    295        return articleNumber + " " + articleId + " " + subject;
    296    }
    297
    298}