KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > gnu > mail > providers > imap4 > IMAPResponseTokenizer


1 /*
2  * IMAPResponseTokenizer.java
3  * Copyright (C) 2003 Chris Burdess <dog@gnu.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * You also have permission to link it with the Sun Microsystems, Inc.
11  * JavaMail(tm) extension and run that combination.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21  */

22
23 package gnu.mail.providers.imap4;
24
25 import java.io.ByteArrayOutputStream JavaDoc;
26 import java.io.InputStream JavaDoc;
27 import java.io.IOException JavaDoc;
28 import java.net.ProtocolException JavaDoc;
29 import java.util.ArrayList JavaDoc;
30 import java.util.List JavaDoc;
31 import java.util.Stack JavaDoc;
32
33 /**
34  * An object that can parse an underlying socket stream containing IMAP
35  * protocol server responses into IMAPResponse tokens.
36  *
37  * @author <a HREF='mailto:dog@gnu.org'>Chris Burdess</a>
38  * @version 0.1
39  */

40 public class IMAPResponseTokenizer implements IMAPConstants
41 {
42
43   /**
44    * The server stream.
45    */

46   protected InputStream JavaDoc in;
47
48   private byte[] buffer;
49
50   private static final int BUFFER_SIZE = 4096; // TODO review
51
private static final String JavaDoc DEFAULT_ENCODING = "US-ASCII";
52   
53   private static final int STATE_TAG = 0;
54   private static final int STATE_COUNT = 1;
55   private static final int STATE_ID = 2;
56   private static final int STATE_MAYBE_CODE = 3;
57   private static final int STATE_CODE = 4;
58   private static final int STATE_CONTENT_LENGTH = 5;
59   private static final int STATE_CONTENT = 6;
60   private static final int STATE_TEXT = 7;
61   private static final int STATE_STATUS = 8;
62
63   /**
64    * Constructor.
65    * @param in the server socket input stream
66    */

67   public IMAPResponseTokenizer(InputStream JavaDoc in) {
68     this.in = in;
69   }
70
71   /*
72    * Reads bytes, from the underlying stream if necessary, or from the
73    * cache. If moreNeeded is specified, always performs a read to append
74    * more to the cache.
75    */

76   byte[] read(boolean moreNeeded)
77     throws IOException JavaDoc
78   {
79     if (buffer!=null && !moreNeeded && buffer.length>0)
80       return buffer;
81     int max = in.available();
82     if (max<1)
83       max = BUFFER_SIZE;
84     byte[] tmp = new byte[max];
85     int len = 0;
86     while (len==0)
87       len = in.read(tmp, 0, max);
88     if (len==-1)
89       return null; // EOF
90
int blen = (buffer==null) ? 0 : buffer.length;
91     byte[] uni = new byte[blen+len];
92     if (blen!=0)
93       System.arraycopy(buffer, 0, uni, 0, blen);
94     System.arraycopy(tmp, 0, uni, blen, len);
95     buffer = uni;
96     return buffer;
97   }
98
99   /*
100    * Invalidates the byte cache up to the specified index.
101    */

102   void mark(int index) {
103     int len = buffer.length;
104     int start = index + 1;
105     if (start<len)
106     {
107       int n = (len-start);
108       byte[] tmp = new byte[n];
109       System.arraycopy(buffer, start, tmp, 0, n);
110       buffer = tmp;
111     }
112     else
113       buffer = null;
114   }
115
116   /**
117    * Returns the next IMAPResponse.
118    */

119   public IMAPResponse next()
120     throws IOException JavaDoc
121   {
122     // Perform read
123
byte[] buf = read(false);
124     if (buf==null)
125       return null; // pass EOF back up the chain
126
int len = buf.length;
127     
128     IMAPResponse response = new IMAPResponse();
129     ByteArrayOutputStream JavaDoc genericSink = new ByteArrayOutputStream JavaDoc();
130     ByteArrayOutputStream JavaDoc contentSink = null;
131     int contentCount = 0, contentLength = -1;
132     Stack JavaDoc context = new Stack JavaDoc();
133     int state = STATE_TAG;
134     boolean inQuote = false;
135     boolean inContent = false;
136     for (int i=0; i<len; i++)
137     {
138       byte b = buf[i];
139       switch (state)
140       {
141         case STATE_TAG: // expect tag
142
if (i==0 && b==0x2a) // untagged
143
response.tag = IMAPResponse.UNTAGGED;
144           else if (i==0 && b==0x2b) // continuation
145
response.tag = IMAPResponse.CONTINUATION;
146           else if (b==0x20) // delimiter
147
{
148             if (response.tag==null)
149             {
150               byte[] tb = genericSink.toByteArray();
151               response.tag = new String JavaDoc(tb, DEFAULT_ENCODING);
152             }
153             genericSink.reset();
154             if (response.isContinuation())
155               state = STATE_TEXT;
156             else
157               state = STATE_COUNT;
158           }
159           else // tag content
160
genericSink.write(b);
161           break;
162         case STATE_COUNT: // expect count or id
163
if (b<0x30 || b>0x39)
164             state = STATE_ID;
165           if (b==0x20) // delimiter
166
{
167             byte[] cb = genericSink.toByteArray();
168             genericSink.reset();
169             String JavaDoc cs = new String JavaDoc(cb, DEFAULT_ENCODING);
170             try
171             {
172               response.count = Integer.parseInt(cs);
173             }
174             catch (NumberFormatException JavaDoc e)
175             {
176               throw new ProtocolException JavaDoc("Expecting number: "+cs);
177             }
178             state = STATE_ID;
179           }
180           else
181             genericSink.write(b);
182           break;
183         case STATE_ID: // expect id
184
if (b==0x20) // delimiter
185
{
186             byte[] ib = genericSink.toByteArray();
187             genericSink.reset();
188             response.id = new String JavaDoc(ib, DEFAULT_ENCODING).intern();
189             state = STATE_MAYBE_CODE;
190           }
191           else if (b==0x0a) // EOL
192
{
193             byte[] ib = genericSink.toByteArray();
194             genericSink.reset();
195             response.id = new String JavaDoc(ib, DEFAULT_ENCODING).intern();
196             state = STATE_TAG;
197             // mark bytes read
198
mark(i);
199             return response;
200           }
201           else if (b!=0x0d) // id content
202
genericSink.write(b);
203           break;
204         case STATE_MAYBE_CODE: // expect code or text
205
if (b==0x28 || b==0x5b)
206           {
207             List JavaDoc top = new ArrayList JavaDoc();
208             //response.code = top;
209
context.push(top);
210             state = STATE_CODE;
211           }
212           else
213           {
214             if (response.id==FETCH)
215             {
216               // We can't have text here so it must be the beginning of
217
// FETCH FLAGS. Go back to ID state.
218
genericSink.reset();
219               byte[] fetchBytes =
220                 new byte[]{0x46, 0x45, 0x54, 0x43, 0x48, 0x20};
221               genericSink.write(fetchBytes);
222               genericSink.write(b);
223               state = STATE_ID;
224             }
225             else if (response.id==STATUS)
226             {
227               // We are in the mailbox name part of the STATUS response
228
genericSink.write(b);
229               state = STATE_STATUS;
230             }
231             else
232             {
233               genericSink.write(b);
234               state = STATE_TEXT;
235             }
236           }
237           break;
238         case STATE_STATUS:
239           if (b==0x20) // delimiter
240
{
241             response.mailbox = genericSink.toString();
242             genericSink.reset();
243             state = STATE_MAYBE_CODE;
244           }
245           else
246             genericSink.write(b);
247           break;
248         case STATE_CODE: // response code inside parentheses
249
if (b==0x22) // quote delimiter
250
{
251             inQuote = !inQuote;
252             genericSink.write(b);
253           }
254           else if (inQuote)
255           {
256             genericSink.write(b);
257           }
258           else
259           {
260             if (b==0x28 || b==0x5b) // start parenthesis/bracket
261
{
262               List JavaDoc top = new ArrayList JavaDoc();
263               List JavaDoc parent = (List JavaDoc)context.peek();
264               parent.add(top);
265               context.push(top);
266             }
267             else if (b==0x29 || b==0x5d) // end parenthesis/bracket
268
{
269               List JavaDoc top = (List JavaDoc)context.pop();
270               // flush genericSink
271
byte[] tb = genericSink.toByteArray();
272               if (tb.length>0)
273               {
274                 genericSink.reset();
275                 String JavaDoc token = new String JavaDoc(tb, DEFAULT_ENCODING).intern();
276                 top.add(token);
277               }
278               if (context.size()==0) // set response code
279
response.code = top;
280             }
281             else if (b==0x7b) // content length
282
{
283               // TODO process genericSink
284
genericSink.reset();
285               state = STATE_CONTENT_LENGTH;
286             }
287             else if (b==0x20) // token delimiter
288
{
289               if (context.size()==0) // end state
290
state = STATE_TEXT;
291               else // add token
292
{
293                 List JavaDoc top = (List JavaDoc)context.peek();
294                 // flush genericSink
295
byte[] tb = genericSink.toByteArray();
296                 if (tb.length>0)
297                 {
298                   genericSink.reset();
299                   String JavaDoc token = new String JavaDoc(tb, DEFAULT_ENCODING).intern();
300                   top.add(token);
301                 }
302               }
303             }
304             else if (b==0x0a) // EOL
305
{
306               state = STATE_TAG;
307               // mark bytes read
308
mark(i);
309               return response;
310             }
311             else if (b!=0x0d) // ignore CR
312
genericSink.write(b);
313           }
314           break;
315         case STATE_CONTENT_LENGTH:
316           if (b==0x7d) // end content length
317
{
318             byte[] cb = genericSink.toByteArray();
319             genericSink.reset();
320             String JavaDoc cs = new String JavaDoc(cb, DEFAULT_ENCODING);
321             try
322             {
323               contentLength = Integer.parseInt(cs);
324             }
325             catch (NumberFormatException JavaDoc e)
326             {
327               throw new ProtocolException JavaDoc("Expecting number: "+cs);
328             }
329             state = STATE_CONTENT;
330             contentSink = new ByteArrayOutputStream JavaDoc();
331             contentCount = 0;
332           }
333           else
334             genericSink.write(b);
335           break;
336         case STATE_CONTENT:
337           if (contentCount>contentLength)
338           {
339             response.content = contentSink.toByteArray();
340             contentSink = null;
341             inContent = false;
342             state = STATE_CODE;
343           }
344           else
345           {
346             if (!inContent && b!=0x20 && b!=0x0d && b!=0x0a)
347               inContent = true;
348             if (inContent)
349               contentSink.write(b);
350             contentCount++;
351           }
352           break;
353         case STATE_TEXT: // human-readable text
354
if (b==0x0a) // delimiter
355
{
356             byte[] tb = genericSink.toByteArray();
357             genericSink.reset();
358             response.text = new String JavaDoc(tb, DEFAULT_ENCODING);
359             state = STATE_TAG;
360             // mark bytes read
361
mark(i);
362             return response;
363           }
364           else if (b!=0x0d) // ignore CR
365
genericSink.write(b);
366           break;
367       }
368     }
369     // get more bytes
370
buf = read(true);
371     return next();
372   }
373
374 }
375
Popular Tags