    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
    018    package org.apache.commons.net.nntp;
    019
    020    import java.util.ArrayList;
    021    import java.util.StringTokenizer;
    022
    023    /**
    024     * This is a class that contains the basic state needed for message retrieval and threading.
    025     * With thanks to Jamie  Zawinski <jwz@jwz.org>
    026     * @author rwinston <rwinston@apache.org>
    027     *
    028     */
    029    public class Article implements Threadable {
    030        private int articleNumber;
    031        private String subject;
    032        private String date;
    033        private String articleId;
    034        private String simplifiedSubject;
    035        private String from;
    036        private StringBuffer header;
    037        private StringBuffer references;
    038        private boolean isReply = false;
    039
    040        public Article kid, next;
    041
    042        public Article() {
    043            header = new StringBuffer();
    044        }
    045
    046        /**
    047         * Adds an arbitrary header key and value to this message's header.
    048         * @param name the header name
    049         * @param val the header value
    050         */
    051        public void addHeaderField(String name, String val) {
    052            header.append(name);
    053            header.append(": ");
    054            header.append(val);
    055            header.append('\n');
    056        }
    057
    058        /**
    059         * Adds a message-id to the list of messages that this message references (i.e. replies to)
    060         * @param msgId
    061         */
    062        public void addReference(String msgId) {
    063            if (references == null) {
    064                references = new StringBuffer();
    065                references.append("References: ");
    066            }
    067            references.append(msgId);
    068            references.append("\t");
    069        }
    070
    071        /**
    072         * Returns the MessageId references as an array of Strings
    073         * @return an array of message-ids
    074         */
    075        public String[] getReferences() {
    076            if (references == null)
    077                return new String[0];
    078            ArrayList<String> list = new ArrayList<String>();
    079            int terminator = references.toString().indexOf(':');
    080            StringTokenizer st =
    081                new StringTokenizer(references.substring(terminator), "\t");
    082            while (st.hasMoreTokens()) {
    083                list.add(st.nextToken());
    084            }
    085            return list.toArray(new String[list.size()]);
    086        }
    087
    088        /**
    089         * Attempts to parse the subject line for some typical reply signatures, and strip them out
    090         *
    091         */
    092        private void simplifySubject() {
    093                int start = 0;
    094                String subject = getSubject();
    095                int len = subject.length();
    096
    097                boolean done = false;
    098
    099                while (!done) {
    100                    done = true;
    101
    102                    // skip whitespace
    103                    // "Re: " breaks this
    104                    while (start < len && subject.charAt(start) == ' ') {
    105                        start++;
    106                    }
    107
    108                    if (start < (len - 2)
    109                        && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R')
    110                        && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) {
    111
    112                        if (subject.charAt(start + 2) == ':') {
    113                            start += 3; // Skip "Re:"
    114                            isReply = true;
    115                            done = false;
    116                        } else if (
    117                            start < (len - 2)
    118                            &&
    119                            (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) {
    120
    121                            int i = start + 3;
    122
    123                            while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9')
    124                                i++;
    125
    126                            if (i < (len - 1)
    127                                && (subject.charAt(i) == ']' || subject.charAt(i) == ')')
    128                                && subject.charAt(i + 1) == ':') {
    129                                start = i + 2;
    130                                isReply = true;
    131                                done = false;
    132                            }
    133                        }
    134                    }
    135
    136                    if ("(no subject)".equals(simplifiedSubject))
    137                        simplifiedSubject = "";
    138
    139                    int end = len;
    140
    141                    while (end > start && subject.charAt(end - 1) < ' ')
    142                        end--;
    143
    144                    if (start == 0 && end == len)
    145                        simplifiedSubject = subject;
    146                    else
    147                        simplifiedSubject = subject.substring(start, end);
    148                }
    149            }
    150
    151        /**
    152         * Recursive method that traverses a pre-threaded graph (or tree)
    153         * of connected Article objects and prints them out.
    154         * @param article the root of the article 'tree'
    155         * @param depth the current tree depth
    156         */
    157        public static void printThread(Article article, int depth) {
    158                for (int i = 0; i < depth; ++i)
    159                    System.out.print("==>");
    160                System.out.println(article.getSubject() + "\t" + article.getFrom());
    161                if (article.kid != null)
    162                    printThread(article.kid, depth + 1);
    163                if (article.next != null)
    164                    printThread(article.next, depth);
    165        }
    166
    167        public String getArticleId() {
    168            return articleId;
    169        }
    170
    171        public int getArticleNumber() {
    172            return articleNumber;
    173        }
    174
    175        public String getDate() {
    176            return date;
    177        }
    178
    179        public String getFrom() {
    180            return from;
    181        }
    182
    183        public String getSubject() {
    184            return subject;
    185        }
    186
    187        public void setArticleId(String string) {
    188            articleId = string;
    189        }
    190
    191        public void setArticleNumber(int i) {
    192            articleNumber = i;
    193        }
    194
    195        public void setDate(String string) {
    196            date = string;
    197        }
    198
    199        public void setFrom(String string) {
    200            from = string;
    201        }
    202
    203        public void setSubject(String string) {
    204            subject = string;
    205        }
    206
    207
    208        public boolean isDummy() {
    209            return (getSubject() == null);
    210        }
    211
    212        public String messageThreadId() {
    213            return articleId;
    214        }
    215
    216        public String[] messageThreadReferences() {
    217            return getReferences();
    218        }
    219
    220        public String simplifiedSubject() {
    221            if(simplifiedSubject == null)
    222                simplifySubject();
    223            return simplifiedSubject;
    224        }
    225
    226
    227        public boolean subjectIsReply() {
    228            if(simplifiedSubject == null)
    229                simplifySubject();
    230            return isReply;
    231        }
    232
    233
    234        public void setChild(Threadable child) {
    235            this.kid = (Article) child;
    236            flushSubjectCache();
    237        }
    238
    239        private void flushSubjectCache() {
    240            simplifiedSubject = null;
    241        }
    242
    243
    244        public void setNext(Threadable next) {
    245            this.next = (Article)next;
    246            flushSubjectCache();
    247        }
    248
    249
    250        public Threadable makeDummy() {
    251            return new Article();
    252        }
    253    }