001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: /*
038: * @(#)SearchSequence.java 1.16 07/05/04
039: */
040:
041: package com.sun.mail.imap.protocol;
042:
043: import java.util.*;
044: import java.io.IOException;
045:
046: import javax.mail.*;
047: import javax.mail.search.*;
048: import com.sun.mail.iap.*;
049:
050: /**
051: * This class traverses a search-tree and generates the
052: * corresponding IMAP search sequence.
053: *
054: * @author John Mani
055: */
056: class SearchSequence {
057:
058: /**
059: * Generate the IMAP search sequence for the given search expression.
060: */
061: static Argument generateSequence(SearchTerm term, String charset)
062: throws SearchException, IOException {
063: /* Call the appropriate handler depending on the type of
064: * the search-term ...
065: */
066: if (term instanceof AndTerm) // AND
067: return and((AndTerm) term, charset);
068: else if (term instanceof OrTerm) // OR
069: return or((OrTerm) term, charset);
070: else if (term instanceof NotTerm) // NOT
071: return not((NotTerm) term, charset);
072: else if (term instanceof HeaderTerm) // HEADER
073: return header((HeaderTerm) term, charset);
074: else if (term instanceof FlagTerm) // FLAG
075: return flag((FlagTerm) term);
076: else if (term instanceof FromTerm) { // FROM
077: FromTerm fterm = (FromTerm) term;
078: return from(fterm.getAddress().toString(), charset);
079: } else if (term instanceof FromStringTerm) { // FROM
080: FromStringTerm fterm = (FromStringTerm) term;
081: return from(fterm.getPattern(), charset);
082: } else if (term instanceof RecipientTerm) { // RECIPIENT
083: RecipientTerm rterm = (RecipientTerm) term;
084: return recipient(rterm.getRecipientType(), rterm
085: .getAddress().toString(), charset);
086: } else if (term instanceof RecipientStringTerm) { // RECIPIENT
087: RecipientStringTerm rterm = (RecipientStringTerm) term;
088: return recipient(rterm.getRecipientType(), rterm
089: .getPattern(), charset);
090: } else if (term instanceof SubjectTerm) // SUBJECT
091: return subject((SubjectTerm) term, charset);
092: else if (term instanceof BodyTerm) // BODY
093: return body((BodyTerm) term, charset);
094: else if (term instanceof SizeTerm) // SIZE
095: return size((SizeTerm) term);
096: else if (term instanceof SentDateTerm) // SENTDATE
097: return sentdate((SentDateTerm) term);
098: else if (term instanceof ReceivedDateTerm) // INTERNALDATE
099: return receiveddate((ReceivedDateTerm) term);
100: else if (term instanceof MessageIDTerm) // MessageID
101: return messageid((MessageIDTerm) term, charset);
102: else
103: throw new SearchException("Search too complex");
104: }
105:
106: /*
107: * Check if the "text" terms in the given SearchTerm contain
108: * non US-ASCII characters.
109: */
110: static boolean isAscii(SearchTerm term) {
111: if (term instanceof AndTerm || term instanceof OrTerm) {
112: SearchTerm[] terms;
113: if (term instanceof AndTerm)
114: terms = ((AndTerm) term).getTerms();
115: else
116: terms = ((OrTerm) term).getTerms();
117:
118: for (int i = 0; i < terms.length; i++)
119: if (!isAscii(terms[i])) // outta here !
120: return false;
121: } else if (term instanceof NotTerm)
122: return isAscii(((NotTerm) term).getTerm());
123: else if (term instanceof StringTerm)
124: return isAscii(((StringTerm) term).getPattern());
125: else if (term instanceof AddressTerm)
126: return isAscii(((AddressTerm) term).getAddress().toString());
127:
128: // Any other term returns true.
129: return true;
130: }
131:
132: private static boolean isAscii(String s) {
133: int l = s.length();
134:
135: for (int i = 0; i < l; i++) {
136: if ((int) s.charAt(i) > 0177) // non-ascii
137: return false;
138: }
139: return true;
140: }
141:
142: private static Argument and(AndTerm term, String charset)
143: throws SearchException, IOException {
144: // Combine the sequences for both terms
145: SearchTerm[] terms = term.getTerms();
146: // Generate the search sequence for the first term
147: Argument result = generateSequence(terms[0], charset);
148: // Append other terms
149: for (int i = 1; i < terms.length; i++)
150: result.append(generateSequence(terms[i], charset));
151: return result;
152: }
153:
154: private static Argument or(OrTerm term, String charset)
155: throws SearchException, IOException {
156: SearchTerm[] terms = term.getTerms();
157:
158: /* The IMAP OR operator takes only two operands. So if
159: * we have more than 2 operands, group them into 2-operand
160: * OR Terms.
161: */
162: if (terms.length > 2) {
163: SearchTerm t = terms[0];
164:
165: // Include rest of the terms
166: for (int i = 1; i < terms.length; i++)
167: t = new OrTerm(t, terms[i]);
168:
169: term = (OrTerm) t; // set 'term' to the new jumbo OrTerm we
170: // just created
171: terms = term.getTerms();
172: }
173:
174: // 'term' now has only two operands
175: Argument result = new Argument();
176:
177: // Add the OR search-key, if more than one term
178: if (terms.length > 1)
179: result.writeAtom("OR");
180:
181: /* If this term is an AND expression, we need to enclose it
182: * within paranthesis.
183: *
184: * AND expressions are either AndTerms or FlagTerms
185: */
186: if (terms[0] instanceof AndTerm || terms[0] instanceof FlagTerm)
187: result.writeArgument(generateSequence(terms[0], charset));
188: else
189: result.append(generateSequence(terms[0], charset));
190:
191: // Repeat the above for the second term, if there is one
192: if (terms.length > 1) {
193: if (terms[1] instanceof AndTerm
194: || terms[1] instanceof FlagTerm)
195: result
196: .writeArgument(generateSequence(terms[1],
197: charset));
198: else
199: result.append(generateSequence(terms[1], charset));
200: }
201:
202: return result;
203: }
204:
205: private static Argument not(NotTerm term, String charset)
206: throws SearchException, IOException {
207: Argument result = new Argument();
208:
209: // Add the NOT search-key
210: result.writeAtom("NOT");
211:
212: /* If this term is an AND expression, we need to enclose it
213: * within paranthesis.
214: *
215: * AND expressions are either AndTerms or FlagTerms
216: */
217: SearchTerm nterm = term.getTerm();
218: if (nterm instanceof AndTerm || nterm instanceof FlagTerm)
219: result.writeArgument(generateSequence(nterm, charset));
220: else
221: result.append(generateSequence(nterm, charset));
222:
223: return result;
224: }
225:
226: private static Argument header(HeaderTerm term, String charset)
227: throws SearchException, IOException {
228: Argument result = new Argument();
229: result.writeAtom("HEADER");
230: result.writeString(term.getHeaderName());
231: result.writeString(term.getPattern(), charset);
232: return result;
233: }
234:
235: private static Argument messageid(MessageIDTerm term, String charset)
236: throws SearchException, IOException {
237: Argument result = new Argument();
238: result.writeAtom("HEADER");
239: result.writeString("Message-ID");
240: // XXX confirm that charset conversion ought to be done
241: result.writeString(term.getPattern(), charset);
242: return result;
243: }
244:
245: private static Argument flag(FlagTerm term) throws SearchException {
246: boolean set = term.getTestSet();
247:
248: Argument result = new Argument();
249:
250: Flags flags = term.getFlags();
251: Flags.Flag[] sf = flags.getSystemFlags();
252: String[] uf = flags.getUserFlags();
253: if (sf.length == 0 && uf.length == 0)
254: throw new SearchException("Invalid FlagTerm");
255:
256: for (int i = 0; i < sf.length; i++) {
257: if (sf[i] == Flags.Flag.DELETED)
258: result.writeAtom(set ? "DELETED" : "UNDELETED");
259: else if (sf[i] == Flags.Flag.ANSWERED)
260: result.writeAtom(set ? "ANSWERED" : "UNANSWERED");
261: else if (sf[i] == Flags.Flag.DRAFT)
262: result.writeAtom(set ? "DRAFT" : "UNDRAFT");
263: else if (sf[i] == Flags.Flag.FLAGGED)
264: result.writeAtom(set ? "FLAGGED" : "UNFLAGGED");
265: else if (sf[i] == Flags.Flag.RECENT)
266: result.writeAtom(set ? "RECENT" : "OLD");
267: else if (sf[i] == Flags.Flag.SEEN)
268: result.writeAtom(set ? "SEEN" : "UNSEEN");
269: }
270:
271: for (int i = 0; i < uf.length; i++) {
272: result.writeAtom(set ? "KEYWORD" : "UNKEYWORD");
273: result.writeAtom(uf[i]);
274: }
275:
276: return result;
277: }
278:
279: private static Argument from(String address, String charset)
280: throws SearchException, IOException {
281: Argument result = new Argument();
282: result.writeAtom("FROM");
283: result.writeString(address, charset);
284: return result;
285: }
286:
287: private static Argument recipient(Message.RecipientType type,
288: String address, String charset) throws SearchException,
289: IOException {
290: Argument result = new Argument();
291:
292: if (type == Message.RecipientType.TO)
293: result.writeAtom("TO");
294: else if (type == Message.RecipientType.CC)
295: result.writeAtom("CC");
296: else if (type == Message.RecipientType.BCC)
297: result.writeAtom("BCC");
298: else
299: throw new SearchException("Illegal Recipient type");
300:
301: result.writeString(address, charset);
302: return result;
303: }
304:
305: private static Argument subject(SubjectTerm term, String charset)
306: throws SearchException, IOException {
307: Argument result = new Argument();
308:
309: result.writeAtom("SUBJECT");
310: result.writeString(term.getPattern(), charset);
311: return result;
312: }
313:
314: private static Argument body(BodyTerm term, String charset)
315: throws SearchException, IOException {
316: Argument result = new Argument();
317:
318: result.writeAtom("BODY");
319: result.writeString(term.getPattern(), charset);
320: return result;
321: }
322:
323: private static Argument size(SizeTerm term) throws SearchException {
324: Argument result = new Argument();
325:
326: switch (term.getComparison()) {
327: case ComparisonTerm.GT:
328: result.writeAtom("LARGER");
329: break;
330: case ComparisonTerm.LT:
331: result.writeAtom("SMALLER");
332: break;
333: default:
334: // GT and LT is all we get from IMAP for size
335: throw new SearchException("Cannot handle Comparison");
336: }
337:
338: result.writeNumber(term.getNumber());
339: return result;
340: }
341:
342: // Date SEARCH stuff ...
343:
344: /**
345: * Print an IMAP Date string, that is suitable for the Date
346: * SEARCH commands.
347: *
348: * The IMAP Date string is :
349: * date ::= date_day "-" date_month "-" date_year
350: *
351: * Note that this format does not contain the TimeZone
352: */
353: private static String monthTable[] = { "Jan", "Feb", "Mar", "Apr",
354: "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
355:
356: // A GregorianCalendar object in the current timezone
357: private static Calendar cal = new GregorianCalendar();
358:
359: private static String toIMAPDate(Date date) {
360: StringBuffer s = new StringBuffer();
361: cal.setTime(date);
362:
363: s.append(cal.get(Calendar.DATE)).append("-");
364: s.append(monthTable[cal.get(Calendar.MONTH)]).append('-');
365: s.append(cal.get(Calendar.YEAR));
366:
367: return s.toString();
368: }
369:
370: private static Argument sentdate(DateTerm term)
371: throws SearchException {
372: Argument result = new Argument();
373: String date = toIMAPDate(term.getDate());
374:
375: switch (term.getComparison()) {
376: case ComparisonTerm.GT:
377: result.writeAtom("SENTSINCE " + date);
378: break;
379: case ComparisonTerm.EQ:
380: result.writeAtom("SENTON " + date);
381: break;
382: case ComparisonTerm.LT:
383: result.writeAtom("SENTBEFORE " + date);
384: break;
385: case ComparisonTerm.GE:
386: result
387: .writeAtom("OR SENTSINCE " + date + " SENTON "
388: + date);
389: break;
390: case ComparisonTerm.LE:
391: result.writeAtom("OR SENTBEFORE " + date + " SENTON "
392: + date);
393: break;
394: case ComparisonTerm.NE:
395: result.writeAtom("NOT SENTON " + date);
396: break;
397: default:
398: throw new SearchException("Cannot handle Date Comparison");
399: }
400:
401: return result;
402: }
403:
404: private static Argument receiveddate(DateTerm term)
405: throws SearchException {
406: Argument result = new Argument();
407: String date = toIMAPDate(term.getDate());
408:
409: switch (term.getComparison()) {
410: case ComparisonTerm.GT:
411: result.writeAtom("SINCE " + date);
412: break;
413: case ComparisonTerm.EQ:
414: result.writeAtom("ON " + date);
415: break;
416: case ComparisonTerm.LT:
417: result.writeAtom("BEFORE " + date);
418: break;
419: case ComparisonTerm.GE:
420: result.writeAtom("OR SINCE " + date + " ON " + date);
421: break;
422: case ComparisonTerm.LE:
423: result.writeAtom("OR BEFORE " + date + " ON " + date);
424: break;
425: case ComparisonTerm.NE:
426: result.writeAtom("NOT ON " + date);
427: break;
428: default:
429: throw new SearchException("Cannot handle Date Comparison");
430: }
431:
432: return result;
433: }
434: }
|