View Javadoc

    1   /*
    2    * Copyright 2001-2005 The Apache Software Foundation
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  package org.apache.commons.net.nntp;
    17
    18  import java.io.BufferedReader;
    19  import java.io.IOException;
    20  import java.io.Reader;
    21  import java.io.StringWriter;
    22  import java.io.Writer;
    23  import java.util.StringTokenizer;
    24  import java.util.Vector;
    25  import org.apache.commons.net.io.DotTerminatedMessageReader;
    26  import org.apache.commons.net.io.DotTerminatedMessageWriter;
    27  import org.apache.commons.net.io.Util;
    28  import org.apache.commons.net.MalformedServerReplyException;
    29
    30  /****
    31   * NNTPClient encapsulates all the functionality necessary to post and
    32   * retrieve articles from an NNTP server.  As with all classes derived
    33   * from {@link org.apache.commons.net.SocketClient},
    34   * you must first connect to the server with
    35   * {@link org.apache.commons.net.SocketClient#connect  connect }
    36   * before doing anything, and finally
    37   * {@link org.apache.commons.net.nntp.NNTP#disconnect  disconnect() }
    38   * after you're completely finished interacting with the server.
    39   * Remember that the
    40   * {@link org.apache.commons.net.nntp.NNTP#isAllowedToPost isAllowedToPost()}
    41   *  method is defined in
    42   * {@link org.apache.commons.net.nntp.NNTP}.
    43   * <p>
    44   * You should keep in mind that the NNTP server may choose to prematurely
    45   * close a connection if the client has been idle for longer than a
    46   * given time period or if the server is being shutdown by the operator or
    47   * some other reason.  The NNTP class will detect a
    48   * premature NNTP server connection closing when it receives a
    49   * {@link org.apache.commons.net.nntp.NNTPReply#SERVICE_DISCONTINUED NNTPReply.SERVICE_DISCONTINUED }
    50   *  response to a command.
    51   * When that occurs, the NNTP class method encountering that reply will throw
    52   * an {@link org.apache.commons.net.nntp.NNTPConnectionClosedException}
    53   * .
    54   * <code>NNTPConectionClosedException</code>
    55   * is a subclass of <code> IOException </code> and therefore need not be
    56   * caught separately, but if you are going to catch it separately, its
    57   * catch block must appear before the more general <code> IOException </code>
    58   * catch block.  When you encounter an
    59   * {@link org.apache.commons.net.nntp.NNTPConnectionClosedException}
    60   * , you must disconnect the connection with
    61   * {@link org.apache.commons.net.nntp.NNTP#disconnect  disconnect() }
    62   *  to properly clean up the
    63   * system resources used by NNTP.  Before disconnecting, you may check the
    64   * last reply code and text with
    65   * {@link org.apache.commons.net.nntp.NNTP#getReplyCode  getReplyCode } and
    66   * {@link org.apache.commons.net.nntp.NNTP#getReplyString  getReplyString }.
    67   * <p>
    68   * Rather than list it separately for each method, we mention here that
    69   * every method communicating with the server and throwing an IOException
    70   * can also throw a
    71   * {@link org.apache.commons.net.MalformedServerReplyException}
    72   * , which is a subclass
    73   * of IOException.  A MalformedServerReplyException will be thrown when
    74   * the reply received from the server deviates enough from the protocol
    75   * specification that it cannot be interpreted in a useful manner despite
    76   * attempts to be as lenient as possible.
    77   * <p>
    78   * <p>
    79   * @author Daniel F. Savarese
    80   * @author Rory Winston
    81   * @author Ted Wise
    82   * @see NNTP
    83   * @see NNTPConnectionClosedException
    84   * @see org.apache.commons.net.MalformedServerReplyException
    85   ***/
    86
    87  public class NNTPClient extends NNTP
    88  {
    89
    90      private void __parseArticlePointer(String reply, ArticlePointer pointer)
    91      throws MalformedServerReplyException
    92      {
    93          StringTokenizer tokenizer;
    94
    95          // Do loop is a kluge to simulate goto
    96          do
    97          {
    98              tokenizer = new StringTokenizer(reply);
    99
    100             if (tokenizer.countTokens() < 3)
    101                 break;
    102
    103             // Skip numeric response value
    104             tokenizer.nextToken();
    105             // Get article number
    106             try
    107             {
    108                 pointer.articleNumber = Integer.parseInt(tokenizer.nextToken());
    109             }
    110             catch (NumberFormatException e)
    111             {
    112                 break;
    113             }
    114
    115             // Get article id
    116             pointer.articleId = tokenizer.nextToken();
    117             return ;
    118         }
    119         while (false);
    120
    121         throw new MalformedServerReplyException(
    122             "Could not parse article pointer.\nServer reply: " + reply);
    123     }
    124
    125
    126     private void __parseGroupReply(String reply, NewsgroupInfo info)
    127     throws MalformedServerReplyException
    128     {
    129         String count, first, last;
    130         StringTokenizer tokenizer;
    131
    132         // Do loop is a kluge to simulate goto
    133         do
    134         {
    135             tokenizer = new StringTokenizer(reply);
    136
    137             if (tokenizer.countTokens() < 5)
    138                 break;
    139
    140             // Skip numeric response value
    141             tokenizer.nextToken();
    142             // Get estimated article count
    143             count = tokenizer.nextToken();
    144             // Get first article number
    145             first = tokenizer.nextToken();
    146             // Get last article number
    147             last = tokenizer.nextToken();
    148             // Get newsgroup name
    149             info._setNewsgroup(tokenizer.nextToken());
    150
    151             try
    152             {
    153                 info._setArticleCount(Integer.parseInt(count));
    154                 info._setFirstArticle(Integer.parseInt(first));
    155                 info._setLastArticle(Integer.parseInt(last));
    156             }
    157             catch (NumberFormatException e)
    158             {
    159                 break;
    160             }
    161
    162             info._setPostingPermission(NewsgroupInfo.UNKNOWN_POSTING_PERMISSION);
    163             return ;
    164         }
    165         while (false);
    166
    167         throw new MalformedServerReplyException(
    168             "Could not parse newsgroup info.\nServer reply: " + reply);
    169     }
    170
    171
    172     private NewsgroupInfo __parseNewsgroupListEntry(String entry)
    173     {
    174         NewsgroupInfo result;
    175         StringTokenizer tokenizer;
    176         int lastNum, firstNum;
    177         String last, first, permission;
    178
    179         result = new NewsgroupInfo();
    180         tokenizer = new StringTokenizer(entry);
    181
    182         if (tokenizer.countTokens() < 4)
    183             return null;
    184
    185         result._setNewsgroup(tokenizer.nextToken());
    186         last = tokenizer.nextToken();
    187         first = tokenizer.nextToken();
    188         permission = tokenizer.nextToken();
    189
    190         try
    191         {
    192             lastNum = Integer.parseInt(last);
    193             firstNum = Integer.parseInt(first);
    194             result._setFirstArticle(firstNum);
    195             result._setLastArticle(lastNum);
    196
    197 	    if((firstNum == 0) && (lastNum == 0))
    198 		    result._setArticleCount(0);
    199 	    else
    200 		    result._setArticleCount(lastNum - firstNum + 1);
    201         }
    202         catch (NumberFormatException e)
    203         {
    204             return null;
    205         }
    206
    207         switch (permission.charAt(0))
    208         {
    209         case 'y':
    210         case 'Y':
    211             result._setPostingPermission(
    212                 NewsgroupInfo.PERMITTED_POSTING_PERMISSION);
    213             break;
    214         case 'n':
    215         case 'N':
    216             result._setPostingPermission(
    217                 NewsgroupInfo.PROHIBITED_POSTING_PERMISSION);
    218             break;
    219         case 'm':
    220         case 'M':
    221             result._setPostingPermission(
    222                 NewsgroupInfo.MODERATED_POSTING_PERMISSION);
    223             break;
    224         default:
    225             result._setPostingPermission(
    226                 NewsgroupInfo.UNKNOWN_POSTING_PERMISSION);
    227             break;
    228         }
    229
    230         return result;
    231     }
    232
    233     private NewsgroupInfo[] __readNewsgroupListing() throws IOException
    234     {
    235         int size;
    236         String line;
    237         Vector list;
    238         BufferedReader reader;
    239         NewsgroupInfo tmp, info[];
    240
    241         reader = new BufferedReader(new DotTerminatedMessageReader(_reader_));
    242         // Start of with a big vector because we may be reading a very large
    243         // amount of groups.
    244         list = new Vector(2048);
    245
    246         while ((line = reader.readLine()) != null)
    247         {
    248             tmp = __parseNewsgroupListEntry(line);
    249             if (tmp != null)
    250                 list.addElement(tmp);
    251             else
    252                 throw new MalformedServerReplyException(line);
    253         }
    254
    255         if ((size = list.size()) < 1)
    256             return new NewsgroupInfo[0];
    257
    258         info = new NewsgroupInfo[size];
    259         list.copyInto(info);
    260
    261         return info;
    262     }
    263
    264
    265     private Reader __retrieve(int command,
    266                               String articleId, ArticlePointer pointer)
    267     throws IOException
    268     {
    269         Reader reader;
    270
    271         if (articleId != null)
    272         {
    273             if (!NNTPReply.isPositiveCompletion(sendCommand(command, articleId)))
    274                 return null;
    275         }
    276         else
    277         {
    278             if (!NNTPReply.isPositiveCompletion(sendCommand(command)))
    279                 return null;
    280         }
    281
    282
    283         if (pointer != null)
    284             __parseArticlePointer(getReplyString(), pointer);
    285
    286         reader = new DotTerminatedMessageReader(_reader_);
    287         return reader;
    288     }
    289
    290
    291     private Reader __retrieve(int command,
    292                               int articleNumber, ArticlePointer pointer)
    293     throws IOException
    294     {
    295         Reader reader;
    296
    297         if (!NNTPReply.isPositiveCompletion(sendCommand(command,
    298                                             Integer.toString(articleNumber))))
    299             return null;
    300
    301         if (pointer != null)
    302             __parseArticlePointer(getReplyString(), pointer);
    303
    304         reader = new DotTerminatedMessageReader(_reader_);
    305         return reader;
    306     }
    307
    308
    309
    310     /****
    311      * Retrieves an article from the NNTP server.  The article is referenced
    312      * by its unique article identifier (including the enclosing &lt and &gt).
    313      * The article number and identifier contained in the server reply
    314      * are returned through an ArticlePointer.  The <code> articleId </code>
    315      * field of the ArticlePointer cannot always be trusted because some
    316      * NNTP servers do not correctly follow the RFC 977 reply format.
    317      * <p>
    318      * A DotTerminatedMessageReader is returned from which the article can
    319      * be read.  If the article does not exist, null is returned.
    320      * <p>
    321      * You must not issue any commands to the NNTP server (i.e., call any
    322      * other methods) until you finish reading the message from the returned
    323      * Reader instance.
    324      * The NNTP protocol uses the same stream for issuing commands as it does
    325      * for returning results.  Therefore the returned Reader actually reads
    326      * directly from the NNTP connection.  After the end of message has been
    327      * reached, new commands can be executed and their replies read.  If
    328      * you do not follow these requirements, your program will not work
    329      * properly.
    330      * <p>
    331      * @param articleId  The unique article identifier of the article to
    332      *     retrieve.  If this parameter is null, the currently selected
    333      *     article is retrieved.
    334      * @param pointer    A parameter through which to return the article's
    335      *   number and unique id.  The articleId field cannot always be trusted
    336      *   because of server deviations from RFC 977 reply formats.  You may
    337      *   set this parameter to null if you do not desire to retrieve the
    338      *   returned article information.
    339      * @return A DotTerminatedMessageReader instance from which the article
    340      *         be read.  null if the article does not exist.
    341      * @exception NNTPConnectionClosedException
    342      *      If the NNTP server prematurely closes the connection as a result
    343      *      of the client being idle or some other reason causing the server
    344      *      to send NNTP reply code 400.  This exception may be caught either
    345      *      as an IOException or independently as itself.
    346      * @exception IOException  If an I/O error occurs while either sending a
    347      *      command to the server or receiving a reply from the server.
    348      ***/
    349     public Reader retrieveArticle(String articleId, ArticlePointer pointer)
    350     throws IOException
    351     {
    352         return __retrieve(NNTPCommand.ARTICLE, articleId, pointer);
    353
    354     }
    355
    356     /**** Same as <code> retrieveArticle(articleId, null) </code> ***/
    357     public Reader retrieveArticle(String articleId) throws IOException
    358     {
    359         return retrieveArticle(articleId, null);
    360     }
    361
    362     /**** Same as <code> retrieveArticle(null) </code> ***/
    363     public Reader retrieveArticle() throws IOException
    364     {
    365         return retrieveArticle(null);
    366     }
    367
    368
    369     /****
    370      * Retrieves an article from the currently selected newsgroup.  The
    371      * article is referenced by its article number.
    372      * The article number and identifier contained in the server reply
    373      * are returned through an ArticlePointer.  The <code> articleId </code>
    374      * field of the ArticlePointer cannot always be trusted because some
    375      * NNTP servers do not correctly follow the RFC 977 reply format.
    376      * <p>
    377      * A DotTerminatedMessageReader is returned from which the article can
    378      * be read.  If the article does not exist, null is returned.
    379      * <p>
    380      * You must not issue any commands to the NNTP server (i.e., call any
    381      * other methods) until you finish reading the message from the returned
    382      * Reader instance.
    383      * The NNTP protocol uses the same stream for issuing commands as it does
    384      * for returning results.  Therefore the returned Reader actually reads
    385      * directly from the NNTP connection.  After the end of message has been
    386      * reached, new commands can be executed and their replies read.  If
    387      * you do not follow these requirements, your program will not work
    388      * properly.
    389      * <p>
    390      * @param articleNumber  The number of the the article to
    391      *     retrieve.
    392      * @param pointer    A parameter through which to return the article's
    393      *   number and unique id.  The articleId field cannot always be trusted
    394      *   because of server deviations from RFC 977 reply formats.  You may
    395      *   set this parameter to null if you do not desire to retrieve the
    396      *   returned article information.
    397      * @return A DotTerminatedMessageReader instance from which the article
    398      *         be read.  null if the article does not exist.
    399      * @exception NNTPConnectionClosedException
    400      *      If the NNTP server prematurely closes the connection as a result
    401      *      of the client being idle or some other reason causing the server
    402      *      to send NNTP reply code 400.  This exception may be caught either
    403      *      as an IOException or independently as itself.
    404      * @exception IOException  If an I/O error occurs while either sending a
    405      *      command to the server or receiving a reply from the server.
    406      ***/
    407     public Reader retrieveArticle(int articleNumber, ArticlePointer pointer)
    408     throws IOException
    409     {
    410         return __retrieve(NNTPCommand.ARTICLE, articleNumber, pointer);
    411     }
    412
    413     /**** Same as <code> retrieveArticle(articleNumber, null) </code> ***/
    414     public Reader retrieveArticle(int articleNumber) throws IOException
    415     {
    416         return retrieveArticle(articleNumber, null);
    417     }
    418
    419
    420
    421     /****
    422      * Retrieves an article header from the NNTP server.  The article is
    423      * referenced
    424      * by its unique article identifier (including the enclosing &lt and &gt).
    425      * The article number and identifier contained in the server reply
    426      * are returned through an ArticlePointer.  The <code> articleId </code>
    427      * field of the ArticlePointer cannot always be trusted because some
    428      * NNTP servers do not correctly follow the RFC 977 reply format.
    429      * <p>
    430      * A DotTerminatedMessageReader is returned from which the article can
    431      * be read.  If the article does not exist, null is returned.
    432      * <p>
    433      * You must not issue any commands to the NNTP server (i.e., call any
    434      * other methods) until you finish reading the message from the returned
    435      * Reader instance.
    436      * The NNTP protocol uses the same stream for issuing commands as it does
    437      * for returning results.  Therefore the returned Reader actually reads
    438      * directly from the NNTP connection.  After the end of message has been
    439      * reached, new commands can be executed and their replies read.  If
    440      * you do not follow these requirements, your program will not work
    441      * properly.
    442      * <p>
    443      * @param articleId  The unique article identifier of the article whose
    444      *    header is being retrieved.  If this parameter is null, the
    445      *    header of the currently selected article is retrieved.
    446      * @param pointer    A parameter through which to return the article's
    447      *   number and unique id.  The articleId field cannot always be trusted
    448      *   because of server deviations from RFC 977 reply formats.  You may
    449      *   set this parameter to null if you do not desire to retrieve the
    450      *   returned article information.
    451      * @return A DotTerminatedMessageReader instance from which the article
    452      *         header can be read.  null if the article does not exist.
    453      * @exception NNTPConnectionClosedException
    454      *      If the NNTP server prematurely closes the connection as a result
    455      *      of the client being idle or some other reason causing the server
    456      *      to send NNTP reply code 400.  This exception may be caught either
    457      *      as an IOException or independently as itself.
    458      * @exception IOException  If an I/O error occurs while either sending a
    459      *      command to the server or receiving a reply from the server.
    460      ***/
    461     public Reader retrieveArticleHeader(String articleId, ArticlePointer pointer)
    462     throws IOException
    463     {
    464         return __retrieve(NNTPCommand.HEAD, articleId, pointer);
    465
    466     }
    467
    468     /**** Same as <code> retrieveArticleHeader(articleId, null) </code> ***/
    469     public Reader retrieveArticleHeader(String articleId) throws IOException
    470     {
    471         return retrieveArticleHeader(articleId, null);
    472     }
    473
    474     /**** Same as <code> retrieveArticleHeader(null) </code> ***/
    475     public Reader retrieveArticleHeader() throws IOException
    476     {
    477         return retrieveArticleHeader(null);
    478     }
    479
    480
    481     /****
    482      * Retrieves an article header from the currently selected newsgroup.  The
    483      * article is referenced by its article number.
    484      * The article number and identifier contained in the server reply
    485      * are returned through an ArticlePointer.  The <code> articleId </code>
    486      * field of the ArticlePointer cannot always be trusted because some
    487      * NNTP servers do not correctly follow the RFC 977 reply format.
    488      * <p>
    489      * A DotTerminatedMessageReader is returned from which the article can
    490      * be read.  If the article does not exist, null is returned.
    491      * <p>
    492      * You must not issue any commands to the NNTP server (i.e., call any
    493      * other methods) until you finish reading the message from the returned
    494      * Reader instance.
    495      * The NNTP protocol uses the same stream for issuing commands as it does
    496      * for returning results.  Therefore the returned Reader actually reads
    497      * directly from the NNTP connection.  After the end of message has been
    498      * reached, new commands can be executed and their replies read.  If
    499      * you do not follow these requirements, your program will not work
    500      * properly.
    501      * <p>
    502      * @param articleNumber  The number of the the article whose header is
    503      *     being retrieved.
    504      * @param pointer    A parameter through which to return the article's
    505      *   number and unique id.  The articleId field cannot always be trusted
    506      *   because of server deviations from RFC 977 reply formats.  You may
    507      *   set this parameter to null if you do not desire to retrieve the
    508      *   returned article information.
    509      * @return A DotTerminatedMessageReader instance from which the article
    510      *         header can be read.  null if the article does not exist.
    511      * @exception NNTPConnectionClosedException
    512      *      If the NNTP server prematurely closes the connection as a result
    513      *      of the client being idle or some other reason causing the server
    514      *      to send NNTP reply code 400.  This exception may be caught either
    515      *      as an IOException or independently as itself.
    516      * @exception IOException  If an I/O error occurs while either sending a
    517      *      command to the server or receiving a reply from the server.
    518      ***/
    519     public Reader retrieveArticleHeader(int articleNumber,
    520                                         ArticlePointer pointer)
    521     throws IOException
    522     {
    523         return __retrieve(NNTPCommand.HEAD, articleNumber, pointer);
    524     }
    525
    526
    527     /**** Same as <code> retrieveArticleHeader(articleNumber, null) </code> ***/
    528     public Reader retrieveArticleHeader(int articleNumber) throws IOException
    529     {
    530         return retrieveArticleHeader(articleNumber, null);
    531     }
    532
    533
    534
    535     /****
    536      * Retrieves an article body from the NNTP server.  The article is
    537      * referenced
    538      * by its unique article identifier (including the enclosing &lt and &gt).
    539      * The article number and identifier contained in the server reply
    540      * are returned through an ArticlePointer.  The <code> articleId </code>
    541      * field of the ArticlePointer cannot always be trusted because some
    542      * NNTP servers do not correctly follow the RFC 977 reply format.
    543      * <p>
    544      * A DotTerminatedMessageReader is returned from which the article can
    545      * be read.  If the article does not exist, null is returned.
    546      * <p>
    547      * You must not issue any commands to the NNTP server (i.e., call any
    548      * other methods) until you finish reading the message from the returned
    549      * Reader instance.
    550      * The NNTP protocol uses the same stream for issuing commands as it does
    551      * for returning results.  Therefore the returned Reader actually reads
    552      * directly from the NNTP connection.  After the end of message has been
    553      * reached, new commands can be executed and their replies read.  If
    554      * you do not follow these requirements, your program will not work
    555      * properly.
    556      * <p>
    557      * @param articleId  The unique article identifier of the article whose
    558      *    body is being retrieved.  If this parameter is null, the
    559      *    body of the currently selected article is retrieved.
    560      * @param pointer    A parameter through which to return the article's
    561      *   number and unique id.  The articleId field cannot always be trusted
    562      *   because of server deviations from RFC 977 reply formats.  You may
    563      *   set this parameter to null if you do not desire to retrieve the
    564      *   returned article information.
    565      * @return A DotTerminatedMessageReader instance from which the article
    566      *         body can be read.  null if the article does not exist.
    567      * @exception NNTPConnectionClosedException
    568      *      If the NNTP server prematurely closes the connection as a result
    569      *      of the client being idle or some other reason causing the server
    570      *      to send NNTP reply code 400.  This exception may be caught either
    571      *      as an IOException or independently as itself.
    572      * @exception IOException  If an I/O error occurs while either sending a
    573      *      command to the server or receiving a reply from the server.
    574      ***/
    575     public Reader retrieveArticleBody(String articleId, ArticlePointer pointer)
    576     throws IOException
    577     {
    578         return __retrieve(NNTPCommand.BODY, articleId, pointer);
    579
    580     }
    581
    582     /**** Same as <code> retrieveArticleBody(articleId, null) </code> ***/
    583     public Reader retrieveArticleBody(String articleId) throws IOException
    584     {
    585         return retrieveArticleBody(articleId, null);
    586     }
    587
    588     /**** Same as <code> retrieveArticleBody(null) </code> ***/
    589     public Reader retrieveArticleBody() throws IOException
    590     {
    591         return retrieveArticleBody(null);
    592     }
    593
    594
    595     /****
    596      * Retrieves an article body from the currently selected newsgroup.  The
    597      * article is referenced by its article number.
    598      * The article number and identifier contained in the server reply
    599      * are returned through an ArticlePointer.  The <code> articleId </code>
    600      * field of the ArticlePointer cannot always be trusted because some
    601      * NNTP servers do not correctly follow the RFC 977 reply format.
    602      * <p>
    603      * A DotTerminatedMessageReader is returned from which the article can
    604      * be read.  If the article does not exist, null is returned.
    605      * <p>
    606      * You must not issue any commands to the NNTP server (i.e., call any
    607      * other methods) until you finish reading the message from the returned
    608      * Reader instance.
    609      * The NNTP protocol uses the same stream for issuing commands as it does
    610      * for returning results.  Therefore the returned Reader actually reads
    611      * directly from the NNTP connection.  After the end of message has been
    612      * reached, new commands can be executed and their replies read.  If
    613      * you do not follow these requirements, your program will not work
    614      * properly.
    615      * <p>
    616      * @param articleNumber  The number of the the article whose body is
    617      *     being retrieved.
    618      * @param pointer    A parameter through which to return the article's
    619      *   number and unique id.  The articleId field cannot always be trusted
    620      *   because of server deviations from RFC 977 reply formats.  You may
    621      *   set this parameter to null if you do not desire to retrieve the
    622      *   returned article information.
    623      * @return A DotTerminatedMessageReader instance from which the article
    624      *         body can be read.  null if the article does not exist.
    625      * @exception NNTPConnectionClosedException
    626      *      If the NNTP server prematurely closes the connection as a result
    627      *      of the client being idle or some other reason causing the server
    628      *      to send NNTP reply code 400.  This exception may be caught either
    629      *      as an IOException or independently as itself.
    630      * @exception IOException  If an I/O error occurs while either sending a
    631      *      command to the server or receiving a reply from the server.
    632      ***/
    633     public Reader retrieveArticleBody(int articleNumber,
    634                                       ArticlePointer pointer)
    635     throws IOException
    636     {
    637         return __retrieve(NNTPCommand.BODY, articleNumber, pointer);
    638     }
    639
    640
    641     /**** Same as <code> retrieveArticleBody(articleNumber, null) </code> ***/
    642     public Reader retrieveArticleBody(int articleNumber) throws IOException
    643     {
    644         return retrieveArticleBody(articleNumber, null);
    645     }
    646
    647
    648     /****
    649      * Select the specified newsgroup to be the target of for future article
    650      * retrieval and posting operations.  Also return the newsgroup
    651      * information contained in the server reply through the info parameter.
    652      * <p>
    653      * @param newsgroup  The newsgroup to select.
    654      * @param info  A parameter through which the newsgroup information of
    655      *      the selected newsgroup contained in the server reply is returned.
    656      *      Set this to null if you do not desire this information.
    657      * @return True if the newsgroup exists and was selected, false otherwise.
    658      * @exception NNTPConnectionClosedException
    659      *      If the NNTP server prematurely closes the connection as a result
    660      *      of the client being idle or some other reason causing the server
    661      *      to send NNTP reply code 400.  This exception may be caught either
    662      *      as an IOException or independently as itself.
    663      * @exception IOException  If an I/O error occurs while either sending a
    664      *      command to the server or receiving a reply from the server.
    665      ***/
    666     public boolean selectNewsgroup(String newsgroup, NewsgroupInfo info)
    667     throws IOException
    668     {
    669         if (!NNTPReply.isPositiveCompletion(group(newsgroup)))
    670             return false;
    671
    672         if (info != null)
    673             __parseGroupReply(getReplyString(), info);
    674
    675         return true;
    676     }
    677
    678     /**** Same as <code> selectNewsgroup(newsgroup, null) </code> ***/
    679     public boolean selectNewsgroup(String newsgroup) throws IOException
    680     {
    681         return selectNewsgroup(newsgroup, null);
    682     }
    683
    684     /****
    685      * List the command help from the server.
    686      * <p>
    687      * @return The sever help information.
    688      * @exception NNTPConnectionClosedException
    689      *      If the NNTP server prematurely closes the connection as a result
    690      *      of the client being idle or some other reason causing the server
    691      *      to send NNTP reply code 400.  This exception may be caught either
    692      *      as an IOException or independently as itself.
    693      * @exception IOException  If an I/O error occurs while either sending a
    694      *      command to the server or receiving a reply from the server.
    695      ***/
    696     public String listHelp() throws IOException
    697     {
    698         StringWriter help;
    699         Reader reader;
    700
    701         if (!NNTPReply.isInformational(help()))
    702             return null;
    703
    704         help = new StringWriter();
    705         reader = new DotTerminatedMessageReader(_reader_);
    706         Util.copyReader(reader, help);
    707         reader.close();
    708         help.close();
    709         return help.toString();
    710     }
    711
    712
    713     /****
    714      * Select an article by its unique identifier (including enclosing
    715      * &lt and &gt) and return its article number and id through the
    716      * pointer parameter.  This is achieved through the STAT command.
    717      * According to RFC 977, this will NOT set the current article pointer
    718      * on the server.  To do that, you must reference the article by its
    719      * number.
    720      * <p>
    721      * @param articleId  The unique article identifier of the article that
    722      *    is being selectedd.  If this parameter is null, the
    723      *    body of the current article is selected
    724      * @param pointer    A parameter through which to return the article's
    725      *   number and unique id.  The articleId field cannot always be trusted
    726      *   because of server deviations from RFC 977 reply formats.  You may
    727      *   set this parameter to null if you do not desire to retrieve the
    728      *   returned article information.
    729      * @return True if successful, false if not.
    730      * @exception NNTPConnectionClosedException
    731      *      If the NNTP server prematurely closes the connection as a result
    732      *      of the client being idle or some other reason causing the server
    733      *      to send NNTP reply code 400.  This exception may be caught either
    734      *      as an IOException or independently as itself.
    735      * @exception IOException  If an I/O error occurs while either sending a
    736      *      command to the server or receiving a reply from the server.
    737      ***/
    738     public boolean selectArticle(String articleId, ArticlePointer pointer)
    739     throws IOException
    740     {
    741         if (articleId != null)
    742         {
    743             if (!NNTPReply.isPositiveCompletion(stat(articleId)))
    744                 return false;
    745         }
    746         else
    747         {
    748             if (!NNTPReply.isPositiveCompletion(stat()))
    749                 return false;
    750         }
    751
    752         if (pointer != null)
    753             __parseArticlePointer(getReplyString(), pointer);
    754
    755         return true;
    756     }
    757
    758     /***** Same as <code> selectArticle(articleId, null) </code> ***/
    759     public boolean selectArticle(String articleId) throws IOException
    760     {
    761         return selectArticle(articleId, null);
    762     }
    763
    764     /*****
    765      * Same as <code> selectArticle(null, articleId) </code>.  Useful
    766      * for retrieving the current article number.
    767      ***/
    768     public boolean selectArticle(ArticlePointer pointer) throws IOException
    769     {
    770         return selectArticle(null, pointer);
    771     }
    772
    773
    774     /****
    775      * Select an article in the currently selected newsgroup by its number.
    776      * and return its article number and id through the
    777      * pointer parameter.  This is achieved through the STAT command.
    778      * According to RFC 977, this WILL set the current article pointer
    779      * on the server.  Use this command to select an article before retrieving
    780      * it, or to obtain an article's unique identifier given its number.
    781      * <p>
    782      * @param articleNumber The number of the article to select from the
    783      *       currently selected newsgroup.
    784      * @param pointer    A parameter through which to return the article's
    785      *   number and unique id.  Although the articleId field cannot always
    786      *   be trusted because of server deviations from RFC 977 reply formats,
    787      *   we haven't found a server that misformats this information in response
    788      *   to this particular command.  You may set this parameter to null if
    789      *   you do not desire to retrieve the returned article information.
    790      * @return True if successful, false if not.
    791      * @exception NNTPConnectionClosedException
    792      *      If the NNTP server prematurely closes the connection as a result
    793      *      of the client being idle or some other reason causing the server
    794      *      to send NNTP reply code 400.  This exception may be caught either
    795      *      as an IOException or independently as itself.
    796      * @exception IOException  If an I/O error occurs while either sending a
    797      *      command to the server or receiving a reply from the server.
    798      ***/
    799     public boolean selectArticle(int articleNumber, ArticlePointer pointer)
    800     throws IOException
    801     {
    802         if (!NNTPReply.isPositiveCompletion(stat(articleNumber)))
    803             return false;
    804
    805         if (pointer != null)
    806             __parseArticlePointer(getReplyString(), pointer);
    807
    808         return true;
    809     }
    810
    811
    812     /**** Same as <code> selectArticle(articleNumber, null) </code> ***/
    813     public boolean selectArticle(int articleNumber) throws IOException
    814     {
    815         return selectArticle(articleNumber, null);
    816     }
    817
    818
    819     /****
    820      * Select the article preceeding the currently selected article in the
    821      * currently selected newsgroup and return its number and unique id
    822      * through the pointer parameter.  Because of deviating server
    823      * implementations, the articleId information cannot be trusted.  To
    824      * obtain the article identifier, issue a
    825      * <code> selectArticle(pointer.articleNumber, pointer) </code> immediately
    826      * afterward.
    827      * <p>
    828      * @param pointer    A parameter through which to return the article's
    829      *   number and unique id.  The articleId field cannot always be trusted
    830      *   because of server deviations from RFC 977 reply formats.  You may
    831      *   set this parameter to null if you do not desire to retrieve the
    832      *   returned article information.
    833      * @return True if successful, false if not (e.g., there is no previous
    834      *     article).
    835      * @exception NNTPConnectionClosedException
    836      *      If the NNTP server prematurely closes the connection as a result
    837      *      of the client being idle or some other reason causing the server
    838      *      to send NNTP reply code 400.  This exception may be caught either
    839      *      as an IOException or independently as itself.
    840      * @exception IOException  If an I/O error occurs while either sending a
    841      *      command to the server or receiving a reply from the server.
    842      ***/
    843     public boolean selectPreviousArticle(ArticlePointer pointer)
    844     throws IOException
    845     {
    846         if (!NNTPReply.isPositiveCompletion(last()))
    847             return false;
    848
    849         if (pointer != null)
    850             __parseArticlePointer(getReplyString(), pointer);
    851
    852         return true;
    853     }
    854
    855     /**** Same as <code> selectPreviousArticle(null) </code> ***/
    856     public boolean selectPreviousArticle() throws IOException
    857     {
    858         return selectPreviousArticle(null);
    859     }
    860
    861
    862     /****
    863      * Select the article following the currently selected article in the
    864      * currently selected newsgroup and return its number and unique id
    865      * through the pointer parameter.  Because of deviating server
    866      * implementations, the articleId information cannot be trusted.  To
    867      * obtain the article identifier, issue a
    868      * <code> selectArticle(pointer.articleNumber, pointer) </code> immediately
    869      * afterward.
    870      * <p>
    871      * @param pointer    A parameter through which to return the article's
    872      *   number and unique id.  The articleId field cannot always be trusted
    873      *   because of server deviations from RFC 977 reply formats.  You may
    874      *   set this parameter to null if you do not desire to retrieve the
    875      *   returned article information.
    876      * @return True if successful, false if not (e.g., there is no following
    877      *         article).
    878      * @exception NNTPConnectionClosedException
    879      *      If the NNTP server prematurely closes the connection as a result
    880      *      of the client being idle or some other reason causing the server
    881      *      to send NNTP reply code 400.  This exception may be caught either
    882      *      as an IOException or independently as itself.
    883      * @exception IOException  If an I/O error occurs while either sending a
    884      *      command to the server or receiving a reply from the server.
    885      ***/
    886     public boolean selectNextArticle(ArticlePointer pointer) throws IOException
    887     {
    888         if (!NNTPReply.isPositiveCompletion(next()))
    889             return false;
    890
    891         if (pointer != null)
    892             __parseArticlePointer(getReplyString(), pointer);
    893
    894         return true;
    895     }
    896
    897
    898     /**** Same as <code> selectNextArticle(null) </code> ***/
    899     public boolean selectNextArticle() throws IOException
    900     {
    901         return selectNextArticle(null);
    902     }
    903
    904
    905     /****
    906      * List all newsgroups served by the NNTP server.  If no newsgroups
    907      * are served, a zero length array will be returned.  If the command
    908      * fails, null will be returned.
    909      * <p>
    910      * @return An array of NewsgroupInfo instances containing the information
    911      *    for each newsgroup served by the NNTP server.   If no newsgroups
    912      *    are served, a zero length array will be returned.  If the command
    913      *    fails, null will be returned.
    914      * @exception NNTPConnectionClosedException
    915      *      If the NNTP server prematurely closes the connection as a result
    916      *      of the client being idle or some other reason causing the server
    917      *      to send NNTP reply code 400.  This exception may be caught either
    918      *      as an IOException or independently as itself.
    919      * @exception IOException  If an I/O error occurs while either sending a
    920      *      command to the server or receiving a reply from the server.
    921      ***/
    922     public NewsgroupInfo[] listNewsgroups() throws IOException
    923     {
    924         if (!NNTPReply.isPositiveCompletion(list()))
    925             return null;
    926
    927         return __readNewsgroupListing();
    928     }
    929
    930     /***
    931      * An overloaded listNewsgroups() command that allows us to
    932      * specify with a pattern what groups we want to list. Wraps the
    933      * LIST ACTIVE command.
    934      * <p>
    935      * @param wildmat a pseudo-regex pattern (cf. RFC 2980)
    936      * @return An array of NewsgroupInfo instances containing the information
    937      *    for each newsgroup served by the NNTP server corresponding to the
    938      *    supplied pattern.   If no such newsgroups are served, a zero length
    939      *    array will be returned.  If the command fails, null will be returned.
    940      * @throws IOException
    941      */
    942     public NewsgroupInfo[] listNewsgroups(String wildmat) throws IOException
    943     {
    944         if(!NNTPReply.isPositiveCompletion(listActive(wildmat)))
    945             return null;
    946         return __readNewsgroupListing();
    947     }
    948
    949
    950     /****
    951      * List all new newsgroups added to the NNTP server since a particular
    952      * date subject to the conditions of the specified query.  If no new
    953      * newsgroups were added, a zero length array will be returned.  If the
    954      * command fails, null will be returned.
    955      * <p>
    956      * @param query  The query restricting how to search for new newsgroups.
    957      * @return An array of NewsgroupInfo instances containing the information
    958      *    for each new newsgroup added to the NNTP server.   If no newsgroups
    959      *    were added, a zero length array will be returned.  If the command
    960      *    fails, null will be returned.
    961      * @exception NNTPConnectionClosedException
    962      *      If the NNTP server prematurely closes the connection as a result
    963      *      of the client being idle or some other reason causing the server
    964      *      to send NNTP reply code 400.  This exception may be caught either
    965      *      as an IOException or independently as itself.
    966      * @exception IOException  If an I/O error occurs while either sending a
    967      *      command to the server or receiving a reply from the server.
    968      ***/
    969     public NewsgroupInfo[] listNewNewsgroups(NewGroupsOrNewsQuery query)
    970     throws IOException
    971     {
    972         if (!NNTPReply.isPositiveCompletion(newgroups(
    973                                                 query.getDate(), query.getTime(),
    974                                                 query.isGMT(), query.getDistributions())))
    975             return null;
    976
    977         return __readNewsgroupListing();
    978     }
    979
    980
    981     /****
    982      * List all new articles added to the NNTP server since a particular
    983      * date subject to the conditions of the specified query.  If no new
    984      * new news is found, a zero length array will be returned.  If the
    985      * command fails, null will be returned.  You must add at least one
    986      * newsgroup to the query, else the command will fail.  Each String
    987      * in the returned array is a unique message identifier including the
    988      * enclosing &lt and &gt.
    989      * <p>
    990      * @param query  The query restricting how to search for new news.  You
    991      *    must add at least one newsgroup to the query.
    992      * @return An array of String instances containing the unique message
    993      *    identifiers for each new article added to the NNTP server.  If no
    994      *    new news is found, a zero length array will be returned.  If the
    995      *    command fails, null will be returned.
    996      * @exception NNTPConnectionClosedException
    997      *      If the NNTP server prematurely closes the connection as a result
    998      *      of the client being idle or some other reason causing the server
    999      *      to send NNTP reply code 400.  This exception may be caught either
    1000      *      as an IOException or independently as itself.
    1001      * @exception IOException  If an I/O error occurs while either sending a
    1002      *      command to the server or receiving a reply from the server.
    1003      ***/
    1004     public String[] listNewNews(NewGroupsOrNewsQuery query)
    1005     throws IOException
    1006     {
    1007         int size;
    1008         String line;
    1009         Vector list;
    1010         String[] result;
    1011         BufferedReader reader;
    1012
    1013         if (!NNTPReply.isPositiveCompletion(newnews(
    1014                                                 query.getNewsgroups(), query.getDate(), query.getTime(),
    1015                                                 query.isGMT(), query.getDistributions())))
    1016             return null;
    1017
    1018         list = new Vector();
    1019         reader = new BufferedReader(new DotTerminatedMessageReader(_reader_));
    1020
    1021         while ((line = reader.readLine()) != null)
    1022             list.addElement(line);
    1023
    1024         size = list.size();
    1025
    1026         if (size < 1)
    1027             return new String[0];
    1028
    1029         result = new String[size];
    1030         list.copyInto(result);
    1031
    1032         return result;
    1033     }
    1034
    1035     /****
    1036      * There are a few NNTPClient methods that do not complete the
    1037      * entire sequence of NNTP commands to complete a transaction.  These
    1038      * commands require some action by the programmer after the reception
    1039      * of a positive preliminary command.  After the programmer's code
    1040      * completes its actions, it must call this method to receive
    1041      * the completion reply from the server and verify the success of the
    1042      * entire transaction.
    1043      * <p>
    1044      * For example
    1045      * <pre>
    1046      * writer = client.postArticle();
    1047      * if(writer == null) // failure
    1048      *   return false;
    1049      * header = new SimpleNNTPHeader("foobar@foo.com", "Just testing");
    1050      * header.addNewsgroup("alt.test");
    1051      * writer.write(header.toString());
    1052      * writer.write("This is just a test");
    1053      * writer.close();
    1054      * if(!client.completePendingCommand()) // failure
    1055      *   return false;
    1056      * </pre>
    1057      * <p>
    1058      * @return True if successfully completed, false if not.
    1059      * @exception NNTPConnectionClosedException
    1060      *      If the NNTP server prematurely closes the connection as a result
    1061      *      of the client being idle or some other reason causing the server
    1062      *      to send NNTP reply code 400.  This exception may be caught either
    1063      *      as an IOException or independently as itself.
    1064      * @exception IOException  If an I/O error occurs while either sending a
    1065      *      command to the server or receiving a reply from the server.
    1066      ***/
    1067     public boolean completePendingCommand() throws IOException
    1068     {
    1069         return NNTPReply.isPositiveCompletion(getReply());
    1070     }
    1071
    1072     /****
    1073      * Post an article to the NNTP server.  This method returns a
    1074      * DotTerminatedMessageWriter instance to which the article can be
    1075      * written.  Null is returned if the posting attempt fails.  You
    1076      * should check {@link NNTP#isAllowedToPost isAllowedToPost() }
    1077      *  before trying to post.  However, a posting
    1078      * attempt can fail due to malformed headers.
    1079      * <p>
    1080      * You must not issue any commands to the NNTP server (i.e., call any
    1081      * (other methods) until you finish writing to the returned Writer
    1082      * instance and close it.  The NNTP protocol uses the same stream for
    1083      * issuing commands as it does for returning results.  Therefore the
    1084      * returned Writer actually writes directly to the NNTP connection.
    1085      * After you close the writer, you can execute new commands.  If you
    1086      * do not follow these requirements your program will not work properly.
    1087      * <p>
    1088      * Different NNTP servers will require different header formats, but
    1089      * you can use the provided
    1090      * {@link org.apache.commons.net.nntp.SimpleNNTPHeader}
    1091      * class to construct the bare minimum acceptable header for most
    1092      * news readers.  To construct more complicated headers you should
    1093      * refer to RFC 822.  When the Java Mail API is finalized, you will be
    1094      * able to use it to compose fully compliant Internet text messages.
    1095      * The DotTerminatedMessageWriter takes care of doubling line-leading
    1096      * dots and ending the message with a single dot upon closing, so all
    1097      * you have to worry about is writing the header and the message.
    1098      * <p>
    1099      * Upon closing the returned Writer, you need to call
    1100      * {@link #completePendingCommand  completePendingCommand() }
    1101      * to finalize the posting and verify its success or failure from
    1102      * the server reply.
    1103      * <p>
    1104      * @return A DotTerminatedMessageWriter to which the article (including
    1105      *      header) can be written.  Returns null if the command fails.
    1106      * @exception IOException  If an I/O error occurs while either sending a
    1107      *      command to the server or receiving a reply from the server.
    1108      ***/
    1109
    1110     public Writer postArticle() throws IOException
    1111     {
    1112         if (!NNTPReply.isPositiveIntermediate(post()))
    1113             return null;
    1114
    1115         return new DotTerminatedMessageWriter(_writer_);
    1116     }
    1117
    1118
    1119     public Writer forwardArticle(String articleId) throws IOException
    1120     {
    1121         if (!NNTPReply.isPositiveIntermediate(ihave(articleId)))
    1122             return null;
    1123
    1124         return new DotTerminatedMessageWriter(_writer_);
    1125     }
    1126
    1127
    1128     /****
    1129      * Logs out of the news server gracefully by sending the QUIT command.
    1130      * However, you must still disconnect from the server before you can open
    1131      * a new connection.
    1132      * <p>
    1133      * @return True if successfully completed, false if not.
    1134      * @exception IOException  If an I/O error occurs while either sending a
    1135      *      command to the server or receiving a reply from the server.
    1136      ***/
    1137     public boolean logout() throws IOException
    1138     {
    1139         return NNTPReply.isPositiveCompletion(quit());
    1140     }
    1141
    1142
    1143     /***
    1144      * Log into a news server by sending the AUTHINFO USER/AUTHINFO
    1145      * PASS command sequence. This is usually sent in response to a
    1146      * 480 reply code from the NNTP server.
    1147      * <p>
    1148      * @param username a valid username
    1149      * @param password the corresponding password
    1150      * @return True for successful login, false for a failure
    1151      * @throws IOException
    1152      */
    1153     public boolean authenticate(String username, String password)
    1154         throws IOException
    1155     {
    1156         int replyCode = authinfoUser(username);
    1157
    1158         if (replyCode == NNTPReply.MORE_AUTH_INFO_REQUIRED)
    1159             {
    1160                 replyCode = authinfoPass(password);
    1161
    1162                 if (replyCode == NNTPReply.AUTHENTICATION_ACCEPTED)
    1163                     {
    1164                         _isAllowedToPost = true;
    1165                         return true;
    1166                     }
    1167             }
    1168         return false;
    1169     }
    1170
    1171     /****
    1172      * Private implementation of XOVER functionality.
    1173      *
    1174      * See {@link NNTP#xover}
    1175      * for legal agument formats. Alternatively, read RFC 2980 :-)
    1176      * <p>
    1177      * @param articleRange
    1178      * @return Returns a DotTerminatedMessageReader if successful, null
    1179      *         otherwise
    1180      * @exception IOException
    1181      */
    1182     private Reader __retrieveArticleInfo(String articleRange)
    1183         throws IOException
    1184     {
    1185         if (!NNTPReply.isPositiveCompletion(xover(articleRange)))
    1186             return null;
    1187
    1188         return new DotTerminatedMessageReader(_reader_);
    1189     }
    1190
    1191     /***
    1192      * Return article headers for a specified post.
    1193      * <p>
    1194      * @param articleNumber the article to retrieve headers for
    1195      * @return a DotTerminatedReader if successful, null otherwise
    1196      * @throws IOException
    1197      */
    1198     public Reader retrieveArticleInfo(int articleNumber) throws IOException
    1199     {
    1200         return __retrieveArticleInfo(Integer.toString(articleNumber));
    1201     }
    1202
    1203     /***
    1204      * Return article headers for all articles between lowArticleNumber
    1205      * and highArticleNumber, inclusively.
    1206      * <p>
    1207      * @param lowArticleNumber
    1208      * @param highArticleNumber
    1209      * @return a DotTerminatedReader if successful, null otherwise
    1210      * @throws IOException
    1211      */
    1212     public Reader retrieveArticleInfo(int lowArticleNumber,
    1213                                       int highArticleNumber)
    1214         throws IOException
    1215     {
    1216         return
    1217             __retrieveArticleInfo(new String(lowArticleNumber + "-" +
    1218                                              highArticleNumber));
    1219     }
    1220
    1221     /****
    1222      * Private implementation of XHDR functionality.
    1223      *
    1224      * See {@link NNTP#xhdr}
    1225      * for legal agument formats. Alternatively, read RFC 1036.
    1226      * <p>
    1227      * @param header
    1228      * @param articleRange
    1229      * @return Returns a DotTerminatedMessageReader if successful, null
    1230      *         otherwise
    1231      * @exception IOException
    1232      */
    1233     private Reader __retrieveHeader(String header, String articleRange)
    1234         throws IOException
    1235     {
    1236         if (!NNTPReply.isPositiveCompletion(xhdr(header, articleRange)))
    1237             return null;
    1238
    1239         return new DotTerminatedMessageReader(_reader_);
    1240     }
    1241
    1242     /***
    1243      * Return an article header for a specified post.
    1244      * <p>
    1245      * @param header the header to retrieve
    1246      * @param articleNumber the article to retrieve the header for
    1247      * @return a DotTerminatedReader if successful, null otherwise
    1248      * @throws IOException
    1249      */
    1250     public Reader retrieveHeader(String header, int articleNumber)
    1251         throws IOException
    1252     {
    1253         return __retrieveHeader(header, Integer.toString(articleNumber));
    1254     }
    1255
    1256     /***
    1257      * Return an article header for all articles between lowArticleNumber
    1258      * and highArticleNumber, inclusively.
    1259      * <p>
    1260      * @param header
    1261      * @param lowArticleNumber
    1262      * @param highArticleNumber
    1263      * @return a DotTerminatedReader if successful, null otherwise
    1264      * @throws IOException
    1265      */
    1266     public Reader retrieveHeader(String header, int lowArticleNumber,
    1267                                  int highArticleNumber)
    1268         throws IOException
    1269     {
    1270         return
    1271             __retrieveHeader(header,
    1272                              new String(lowArticleNumber + "-" +
    1273                                         highArticleNumber));
    1274     }
    1275 }
    1276
    1277
    1278 /* Emacs configuration
    1279  * Local variables:        **
    1280  * mode:             java  **
    1281  * c-basic-offset:   4     **
    1282  * indent-tabs-mode: nil   **
    1283  * End:                    **
    1284  */