diff options
Diffstat (limited to 'apache')
76 files changed, 16308 insertions, 0 deletions
diff --git a/apache/org/apache/commons/io/IOUtils.java b/apache/org/apache/commons/io/IOUtils.java new file mode 100644 index 000000000..b41450790 --- /dev/null +++ b/apache/org/apache/commons/io/IOUtils.java @@ -0,0 +1,1202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.io; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * General IO stream manipulation utilities. + * <p> + * This class provides static utility methods for input/output operations. + * <ul> + * <li>closeQuietly - these methods close a stream ignoring nulls and exceptions + * <li>toXxx/read - these methods read data from a stream + * <li>write - these methods write data to a stream + * <li>copy - these methods copy all the data from one stream to another + * <li>contentEquals - these methods compare the content of two streams + * </ul> + * <p> + * The byte-to-char methods and char-to-byte methods involve a conversion step. + * Two methods are provided in each case, one that uses the platform default + * encoding and the other which allows you to specify an encoding. You are + * encouraged to always specify an encoding because relying on the platform + * default can lead to unexpected results, for example when moving from + * development to production. + * <p> + * All the methods in this class that read a stream are buffered internally. + * This means that there is no cause to use a <code>BufferedInputStream</code> + * or <code>BufferedReader</code>. The default buffer size of 4K has been shown + * to be efficient in tests. + * <p> + * Wherever possible, the methods in this class do <em>not</em> flush or close + * the stream. This is to avoid making non-portable assumptions about the + * streams' origin and further use. Thus the caller is still responsible for + * closing streams after use. + * <p> + * Origin of code: Excalibur. + * + * @author Peter Donald + * @author Jeff Turner + * @author Matthew Hawthorne + * @author Stephen Colebourne + * @author Gareth Davis + * @author Ian Springer + * @author Niall Pemberton + * @author Sandy McArthur + * @version $Id: IOUtils.java 481854 2006-12-03 18:30:07Z scolebourne $ + */ +public class IOUtils { + // NOTE: This class is focussed on InputStream, OutputStream, Reader and + // Writer. Each method should take at least one of these as a parameter, + // or return one of them. + + /** + * The Unix directory separator character. + */ + public static final char DIR_SEPARATOR_UNIX = '/'; + /** + * The Windows directory separator character. + */ + public static final char DIR_SEPARATOR_WINDOWS = '\\'; + /** + * The system directory separator character. + */ + public static final char DIR_SEPARATOR = File.separatorChar; + /** + * The Unix line separator string. + */ + public static final String LINE_SEPARATOR_UNIX = "\n"; + /** + * The Windows line separator string. + */ + public static final String LINE_SEPARATOR_WINDOWS = "\r\n"; + /** + * The system line separator string. + */ + public static final String LINE_SEPARATOR; + static { + // avoid security issues + StringWriter buf = new StringWriter(4); + PrintWriter out = new PrintWriter(buf); + out.println(); + LINE_SEPARATOR = buf.toString(); + } + + /** + * The default buffer size to use. + */ + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + /** + * Instances should NOT be constructed in standard programming. + */ + public IOUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + * Unconditionally close an <code>Reader</code>. + * <p> + * Equivalent to {@link Reader#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + * + * @param input the Reader to close, may be null or already closed + */ + public static void closeQuietly(Reader input) { + try { + if (input != null) { + input.close(); + } + } catch (IOException ioe) { + // ignore + } + } + + /** + * Unconditionally close a <code>Writer</code>. + * <p> + * Equivalent to {@link Writer#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + * + * @param output the Writer to close, may be null or already closed + */ + public static void closeQuietly(Writer output) { + try { + if (output != null) { + output.close(); + } + } catch (IOException ioe) { + // ignore + } + } + + /** + * Unconditionally close an <code>InputStream</code>. + * <p> + * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + * + * @param input the InputStream to close, may be null or already closed + */ + public static void closeQuietly(InputStream input) { + try { + if (input != null) { + input.close(); + } + } catch (IOException ioe) { + // ignore + } + } + + /** + * Unconditionally close an <code>OutputStream</code>. + * <p> + * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + * + * @param output the OutputStream to close, may be null or already closed + */ + public static void closeQuietly(OutputStream output) { + try { + if (output != null) { + output.close(); + } + } catch (IOException ioe) { + // ignore + } + } + + // read toByteArray + //----------------------------------------------------------------------- + /** + * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * + * @param input the <code>InputStream</code> to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(InputStream input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(input, output); + return output.toByteArray(); + } + + /** + * Get the contents of a <code>Reader</code> as a <code>byte[]</code> + * using the default character encoding of the platform. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedReader</code>. + * + * @param input the <code>Reader</code> to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(Reader input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(input, output); + return output.toByteArray(); + } + + /** + * Get the contents of a <code>Reader</code> as a <code>byte[]</code> + * using the specified character encoding. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedReader</code>. + * + * @param input the <code>Reader</code> to read from + * @param encoding the encoding to use, null means platform default + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static byte[] toByteArray(Reader input, String encoding) + throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(input, output, encoding); + return output.toByteArray(); + } + + /** + * Get the contents of a <code>String</code> as a <code>byte[]</code> + * using the default character encoding of the platform. + * <p> + * This is the same as {@link String#getBytes()}. + * + * @param input the <code>String</code> to convert + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs (never occurs) + * @deprecated Use {@link String#getBytes()} + */ + @Deprecated + public static byte[] toByteArray(String input) throws IOException { + return input.getBytes(); + } + + // read char[] + //----------------------------------------------------------------------- + /** + * Get the contents of an <code>InputStream</code> as a character array + * using the default character encoding of the platform. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * + * @param is the <code>InputStream</code> to read from + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static char[] toCharArray(InputStream is) throws IOException { + CharArrayWriter output = new CharArrayWriter(); + copy(is, output); + return output.toCharArray(); + } + + /** + * Get the contents of an <code>InputStream</code> as a character array + * using the specified character encoding. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * + * @param is the <code>InputStream</code> to read from + * @param encoding the encoding to use, null means platform default + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static char[] toCharArray(InputStream is, String encoding) + throws IOException { + CharArrayWriter output = new CharArrayWriter(); + copy(is, output, encoding); + return output.toCharArray(); + } + + /** + * Get the contents of a <code>Reader</code> as a character array. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedReader</code>. + * + * @param input the <code>Reader</code> to read from + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static char[] toCharArray(Reader input) throws IOException { + CharArrayWriter sw = new CharArrayWriter(); + copy(input, sw); + return sw.toCharArray(); + } + + // read toString + //----------------------------------------------------------------------- + /** + * Get the contents of an <code>InputStream</code> as a String + * using the default character encoding of the platform. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * + * @param input the <code>InputStream</code> to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static String toString(InputStream input) throws IOException { + StringWriter sw = new StringWriter(); + copy(input, sw); + return sw.toString(); + } + + /** + * Get the contents of an <code>InputStream</code> as a String + * using the specified character encoding. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * + * @param input the <code>InputStream</code> to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static String toString(InputStream input, String encoding) + throws IOException { + StringWriter sw = new StringWriter(); + copy(input, sw, encoding); + return sw.toString(); + } + + /** + * Get the contents of a <code>Reader</code> as a String. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedReader</code>. + * + * @param input the <code>Reader</code> to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static String toString(Reader input) throws IOException { + StringWriter sw = new StringWriter(); + copy(input, sw); + return sw.toString(); + } + + /** + * Get the contents of a <code>byte[]</code> as a String + * using the default character encoding of the platform. + * + * @param input the byte array to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs (never occurs) + * @deprecated Use {@link String#String(byte[])} + */ + @Deprecated + public static String toString(byte[] input) throws IOException { + return new String(input); + } + + /** + * Get the contents of a <code>byte[]</code> as a String + * using the specified character encoding. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * + * @param input the byte array to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs (never occurs) + * @deprecated Use {@link String#String(byte[],String)} + */ + @Deprecated + public static String toString(byte[] input, String encoding) + throws IOException { + if (encoding == null) { + return new String(input); + } else { + return new String(input, encoding); + } + } + + // readLines + //----------------------------------------------------------------------- + /** + * Get the contents of an <code>InputStream</code> as a list of Strings, + * one entry per line, using the default character encoding of the platform. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * + * @param input the <code>InputStream</code> to read from, not null + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static List<String> readLines(InputStream input) throws IOException { + InputStreamReader reader = new InputStreamReader(input); + return readLines(reader); + } + + /** + * Get the contents of an <code>InputStream</code> as a list of Strings, + * one entry per line, using the specified character encoding. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * + * @param input the <code>InputStream</code> to read from, not null + * @param encoding the encoding to use, null means platform default + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static List<String> readLines(InputStream input, String encoding) throws IOException { + if (encoding == null) { + return readLines(input); + } else { + InputStreamReader reader = new InputStreamReader(input, encoding); + return readLines(reader); + } + } + + /** + * Get the contents of a <code>Reader</code> as a list of Strings, + * one entry per line. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedReader</code>. + * + * @param input the <code>Reader</code> to read from, not null + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static List<String> readLines(Reader input) throws IOException { + BufferedReader reader = new BufferedReader(input); + List<String> list = new ArrayList<String>(); + String line = reader.readLine(); + while (line != null) { + list.add(line); + line = reader.readLine(); + } + return list; + } + + //----------------------------------------------------------------------- + /** + * Convert the specified string to an input stream, encoded as bytes + * using the default character encoding of the platform. + * + * @param input the string to convert + * @return an input stream + * @since Commons IO 1.1 + */ + public static InputStream toInputStream(String input) { + byte[] bytes = input.getBytes(); + return new ByteArrayInputStream(bytes); + } + + /** + * Convert the specified string to an input stream, encoded as bytes + * using the specified character encoding. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * + * @param input the string to convert + * @param encoding the encoding to use, null means platform default + * @throws IOException if the encoding is invalid + * @return an input stream + * @since Commons IO 1.1 + */ + public static InputStream toInputStream(String input, String encoding) throws IOException { + byte[] bytes = encoding != null ? input.getBytes(encoding) : input.getBytes(); + return new ByteArrayInputStream(bytes); + } + + // write byte[] + //----------------------------------------------------------------------- + /** + * Writes bytes from a <code>byte[]</code> to an <code>OutputStream</code>. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the <code>OutputStream</code> to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(byte[] data, OutputStream output) + throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes bytes from a <code>byte[]</code> to chars on a <code>Writer</code> + * using the default character encoding of the platform. + * <p> + * This method uses {@link String#String(byte[])}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the <code>Writer</code> to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(byte[] data, Writer output) throws IOException { + if (data != null) { + output.write(new String(data)); + } + } + + /** + * Writes bytes from a <code>byte[]</code> to chars on a <code>Writer</code> + * using the specified character encoding. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * <p> + * This method uses {@link String#String(byte[], String)}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the <code>Writer</code> to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(byte[] data, Writer output, String encoding) + throws IOException { + if (data != null) { + if (encoding == null) { + write(data, output); + } else { + output.write(new String(data, encoding)); + } + } + } + + // write char[] + //----------------------------------------------------------------------- + /** + * Writes chars from a <code>char[]</code> to a <code>Writer</code> + * using the default character encoding of the platform. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the <code>Writer</code> to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(char[] data, Writer output) throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes chars from a <code>char[]</code> to bytes on an + * <code>OutputStream</code>. + * <p> + * This method uses {@link String#String(char[])} and + * {@link String#getBytes()}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the <code>OutputStream</code> to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(char[] data, OutputStream output) + throws IOException { + if (data != null) { + output.write(new String(data).getBytes()); + } + } + + /** + * Writes chars from a <code>char[]</code> to bytes on an + * <code>OutputStream</code> using the specified character encoding. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * <p> + * This method uses {@link String#String(char[])} and + * {@link String#getBytes(String)}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the <code>OutputStream</code> to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(char[] data, OutputStream output, String encoding) + throws IOException { + if (data != null) { + if (encoding == null) { + write(data, output); + } else { + output.write(new String(data).getBytes(encoding)); + } + } + } + + // write String + //----------------------------------------------------------------------- + /** + * Writes chars from a <code>String</code> to a <code>Writer</code>. + * + * @param data the <code>String</code> to write, null ignored + * @param output the <code>Writer</code> to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(String data, Writer output) throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes chars from a <code>String</code> to bytes on an + * <code>OutputStream</code> using the default character encoding of the + * platform. + * <p> + * This method uses {@link String#getBytes()}. + * + * @param data the <code>String</code> to write, null ignored + * @param output the <code>OutputStream</code> to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(String data, OutputStream output) + throws IOException { + if (data != null) { + output.write(data.getBytes()); + } + } + + /** + * Writes chars from a <code>String</code> to bytes on an + * <code>OutputStream</code> using the specified character encoding. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * <p> + * This method uses {@link String#getBytes(String)}. + * + * @param data the <code>String</code> to write, null ignored + * @param output the <code>OutputStream</code> to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(String data, OutputStream output, String encoding) + throws IOException { + if (data != null) { + if (encoding == null) { + write(data, output); + } else { + output.write(data.getBytes(encoding)); + } + } + } + + // write StringBuffer + //----------------------------------------------------------------------- + /** + * Writes chars from a <code>StringBuffer</code> to a <code>Writer</code>. + * + * @param data the <code>StringBuffer</code> to write, null ignored + * @param output the <code>Writer</code> to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(StringBuffer data, Writer output) + throws IOException { + if (data != null) { + output.write(data.toString()); + } + } + + /** + * Writes chars from a <code>StringBuffer</code> to bytes on an + * <code>OutputStream</code> using the default character encoding of the + * platform. + * <p> + * This method uses {@link String#getBytes()}. + * + * @param data the <code>StringBuffer</code> to write, null ignored + * @param output the <code>OutputStream</code> to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(StringBuffer data, OutputStream output) + throws IOException { + if (data != null) { + output.write(data.toString().getBytes()); + } + } + + /** + * Writes chars from a <code>StringBuffer</code> to bytes on an + * <code>OutputStream</code> using the specified character encoding. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * <p> + * This method uses {@link String#getBytes(String)}. + * + * @param data the <code>StringBuffer</code> to write, null ignored + * @param output the <code>OutputStream</code> to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void write(StringBuffer data, OutputStream output, + String encoding) throws IOException { + if (data != null) { + if (encoding == null) { + write(data, output); + } else { + output.write(data.toString().getBytes(encoding)); + } + } + } + + // writeLines + //----------------------------------------------------------------------- + /** + * Writes the <code>toString()</code> value of each item in a collection to + * an <code>OutputStream</code> line by line, using the default character + * encoding of the platform and the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the <code>OutputStream</code> to write to, not null, not closed + * @throws NullPointerException if the output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void writeLines(Collection<Object> lines, String lineEnding, + OutputStream output) throws IOException { + if (lines == null) { + return; + } + if (lineEnding == null) { + lineEnding = LINE_SEPARATOR; + } + for (Iterator<Object> it = lines.iterator(); it.hasNext(); ) { + Object line = it.next(); + if (line != null) { + output.write(line.toString().getBytes()); + } + output.write(lineEnding.getBytes()); + } + } + + /** + * Writes the <code>toString()</code> value of each item in a collection to + * an <code>OutputStream</code> line by line, using the specified character + * encoding and the specified line ending. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the <code>OutputStream</code> to write to, not null, not closed + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void writeLines(Collection<Object> lines, String lineEnding, + OutputStream output, String encoding) throws IOException { + if (encoding == null) { + writeLines(lines, lineEnding, output); + } else { + if (lines == null) { + return; + } + if (lineEnding == null) { + lineEnding = LINE_SEPARATOR; + } + for (Iterator<Object> it = lines.iterator(); it.hasNext(); ) { + Object line = it.next(); + if (line != null) { + output.write(line.toString().getBytes(encoding)); + } + output.write(lineEnding.getBytes(encoding)); + } + } + } + + /** + * Writes the <code>toString()</code> value of each item in a collection to + * a <code>Writer</code> line by line, using the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param writer the <code>Writer</code> to write to, not null, not closed + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void writeLines(Collection<Object> lines, String lineEnding, + Writer writer) throws IOException { + if (lines == null) { + return; + } + if (lineEnding == null) { + lineEnding = LINE_SEPARATOR; + } + for (Iterator<Object> it = lines.iterator(); it.hasNext(); ) { + Object line = it.next(); + if (line != null) { + writer.write(line.toString()); + } + writer.write(lineEnding); + } + } + + // copy from InputStream + //----------------------------------------------------------------------- + /** + * Copy bytes from an <code>InputStream</code> to an + * <code>OutputStream</code>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * <p> + * Large streams (over 2GB) will return a bytes copied value of + * <code>-1</code> after the copy has completed since the correct + * number of bytes cannot be returned as an int. For large streams + * use the <code>copyLarge(InputStream, OutputStream)</code> method. + * + * @param input the <code>InputStream</code> to read from + * @param output the <code>OutputStream</code> to write to + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @throws ArithmeticException if the byte count is too large + * @since Commons IO 1.1 + */ + public static int copy(InputStream input, OutputStream output) throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copy bytes from a large (over 2GB) <code>InputStream</code> to an + * <code>OutputStream</code>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * + * @param input the <code>InputStream</code> to read from + * @param output the <code>OutputStream</code> to write to + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.3 + */ + public static long copyLarge(InputStream input, OutputStream output) + throws IOException { + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + long count = 0; + int n = 0; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Copy bytes from an <code>InputStream</code> to chars on a + * <code>Writer</code> using the default character encoding of the platform. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * <p> + * This method uses {@link InputStreamReader}. + * + * @param input the <code>InputStream</code> to read from + * @param output the <code>Writer</code> to write to + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void copy(InputStream input, Writer output) + throws IOException { + InputStreamReader in = new InputStreamReader(input); + copy(in, output); + } + + /** + * Copy bytes from an <code>InputStream</code> to chars on a + * <code>Writer</code> using the specified character encoding. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * <p> + * This method uses {@link InputStreamReader}. + * + * @param input the <code>InputStream</code> to read from + * @param output the <code>Writer</code> to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void copy(InputStream input, Writer output, String encoding) + throws IOException { + if (encoding == null) { + copy(input, output); + } else { + InputStreamReader in = new InputStreamReader(input, encoding); + copy(in, output); + } + } + + // copy from Reader + //----------------------------------------------------------------------- + /** + * Copy chars from a <code>Reader</code> to a <code>Writer</code>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedReader</code>. + * <p> + * Large streams (over 2GB) will return a chars copied value of + * <code>-1</code> after the copy has completed since the correct + * number of chars cannot be returned as an int. For large streams + * use the <code>copyLarge(Reader, Writer)</code> method. + * + * @param input the <code>Reader</code> to read from + * @param output the <code>Writer</code> to write to + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @throws ArithmeticException if the character count is too large + * @since Commons IO 1.1 + */ + public static int copy(Reader input, Writer output) throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copy chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedReader</code>. + * + * @param input the <code>Reader</code> to read from + * @param output the <code>Writer</code> to write to + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.3 + */ + public static long copyLarge(Reader input, Writer output) throws IOException { + char[] buffer = new char[DEFAULT_BUFFER_SIZE]; + long count = 0; + int n = 0; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Copy chars from a <code>Reader</code> to bytes on an + * <code>OutputStream</code> using the default character encoding of the + * platform, and calling flush. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedReader</code>. + * <p> + * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + * <p> + * This method uses {@link OutputStreamWriter}. + * + * @param input the <code>Reader</code> to read from + * @param output the <code>OutputStream</code> to write to + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void copy(Reader input, OutputStream output) + throws IOException { + OutputStreamWriter out = new OutputStreamWriter(output); + copy(input, out); + // XXX Unless anyone is planning on rewriting OutputStreamWriter, we + // have to flush here. + out.flush(); + } + + /** + * Copy chars from a <code>Reader</code> to bytes on an + * <code>OutputStream</code> using the specified character encoding, and + * calling flush. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedReader</code>. + * <p> + * Character encoding names can be found at + * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. + * <p> + * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + * <p> + * This method uses {@link OutputStreamWriter}. + * + * @param input the <code>Reader</code> to read from + * @param output the <code>OutputStream</code> to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static void copy(Reader input, OutputStream output, String encoding) + throws IOException { + if (encoding == null) { + copy(input, output); + } else { + OutputStreamWriter out = new OutputStreamWriter(output, encoding); + copy(input, out); + // XXX Unless anyone is planning on rewriting OutputStreamWriter, + // we have to flush here. + out.flush(); + } + } + + // content equals + //----------------------------------------------------------------------- + /** + * Compare the contents of two Streams to determine if they are equal or + * not. + * <p> + * This method buffers the input internally using + * <code>BufferedInputStream</code> if they are not already buffered. + * + * @param input1 the first stream + * @param input2 the second stream + * @return true if the content of the streams are equal or they both don't + * exist, false otherwise + * @throws NullPointerException if either input is null + * @throws IOException if an I/O error occurs + */ + public static boolean contentEquals(InputStream input1, InputStream input2) + throws IOException { + if (!(input1 instanceof BufferedInputStream)) { + input1 = new BufferedInputStream(input1); + } + if (!(input2 instanceof BufferedInputStream)) { + input2 = new BufferedInputStream(input2); + } + + int ch = input1.read(); + while (-1 != ch) { + int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + int ch2 = input2.read(); + return (ch2 == -1); + } + + /** + * Compare the contents of two Readers to determine if they are equal or + * not. + * <p> + * This method buffers the input internally using + * <code>BufferedReader</code> if they are not already buffered. + * + * @param input1 the first reader + * @param input2 the second reader + * @return true if the content of the readers are equal or they both don't + * exist, false otherwise + * @throws NullPointerException if either input is null + * @throws IOException if an I/O error occurs + * @since Commons IO 1.1 + */ + public static boolean contentEquals(Reader input1, Reader input2) + throws IOException { + if (!(input1 instanceof BufferedReader)) { + input1 = new BufferedReader(input1); + } + if (!(input2 instanceof BufferedReader)) { + input2 = new BufferedReader(input2); + } + + int ch = input1.read(); + while (-1 != ch) { + int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + int ch2 = input2.read(); + return (ch2 == -1); + } + +} diff --git a/apache/org/apache/james/mime4j/BodyDescriptor.java b/apache/org/apache/james/mime4j/BodyDescriptor.java new file mode 100644 index 000000000..867c43d86 --- /dev/null +++ b/apache/org/apache/james/mime4j/BodyDescriptor.java @@ -0,0 +1,392 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j; + +import java.util.HashMap; +import java.util.Map; + +/** + * Encapsulates the values of the MIME-specific header fields + * (which starts with <code>Content-</code>). + * + * + * @version $Id: BodyDescriptor.java,v 1.4 2005/02/11 10:08:37 ntherning Exp $ + */ +public class BodyDescriptor { + private static Log log = LogFactory.getLog(BodyDescriptor.class); + + private String mimeType = "text/plain"; + private String boundary = null; + private String charset = "us-ascii"; + private String transferEncoding = "7bit"; + private Map<String, String> parameters = new HashMap<String, String>(); + private boolean contentTypeSet = false; + private boolean contentTransferEncSet = false; + + /** + * Creates a new root <code>BodyDescriptor</code> instance. + */ + public BodyDescriptor() { + this(null); + } + + /** + * Creates a new <code>BodyDescriptor</code> instance. + * + * @param parent the descriptor of the parent or <code>null</code> if this + * is the root descriptor. + */ + public BodyDescriptor(BodyDescriptor parent) { + if (parent != null && parent.isMimeType("multipart/digest")) { + mimeType = "message/rfc822"; + } else { + mimeType = "text/plain"; + } + } + + /** + * Should be called for each <code>Content-</code> header field of + * a MIME message or part. + * + * @param name the field name. + * @param value the field value. + */ + public void addField(String name, String value) { + + name = name.trim().toLowerCase(); + + if (name.equals("content-transfer-encoding") && !contentTransferEncSet) { + contentTransferEncSet = true; + + value = value.trim().toLowerCase(); + if (value.length() > 0) { + transferEncoding = value; + } + + } else if (name.equals("content-type") && !contentTypeSet) { + contentTypeSet = true; + + value = value.trim(); + + /* + * Unfold Content-Type value + */ + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '\r' || c == '\n') { + continue; + } + sb.append(c); + } + + Map<String, String> params = getHeaderParams(sb.toString()); + + String main = params.get(""); + if (main != null) { + main = main.toLowerCase().trim(); + int index = main.indexOf('/'); + boolean valid = false; + if (index != -1) { + String type = main.substring(0, index).trim(); + String subtype = main.substring(index + 1).trim(); + if (type.length() > 0 && subtype.length() > 0) { + main = type + "/" + subtype; + valid = true; + } + } + + if (!valid) { + main = null; + } + } + String b = params.get("boundary"); + + if (main != null + && ((main.startsWith("multipart/") && b != null) + || !main.startsWith("multipart/"))) { + + mimeType = main; + } + + if (isMultipart()) { + boundary = b; + } + + String c = params.get("charset"); + if (c != null) { + c = c.trim(); + if (c.length() > 0) { + charset = c.toLowerCase(); + } + } + + /* + * Add all other parameters to parameters. + */ + parameters.putAll(params); + parameters.remove(""); + parameters.remove("boundary"); + parameters.remove("charset"); + } + } + + private Map<String, String> getHeaderParams(String headerValue) { + Map<String, String> result = new HashMap<String, String>(); + + // split main value and parameters + String main; + String rest; + if (headerValue.indexOf(";") == -1) { + main = headerValue; + rest = null; + } else { + main = headerValue.substring(0, headerValue.indexOf(";")); + rest = headerValue.substring(main.length() + 1); + } + + result.put("", main); + if (rest != null) { + char[] chars = rest.toCharArray(); + StringBuffer paramName = new StringBuffer(); + StringBuffer paramValue = new StringBuffer(); + + final byte READY_FOR_NAME = 0; + final byte IN_NAME = 1; + final byte READY_FOR_VALUE = 2; + final byte IN_VALUE = 3; + final byte IN_QUOTED_VALUE = 4; + final byte VALUE_DONE = 5; + final byte ERROR = 99; + + byte state = READY_FOR_NAME; + boolean escaped = false; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + + switch (state) { + case ERROR: + if (c == ';') + state = READY_FOR_NAME; + break; + + case READY_FOR_NAME: + if (c == '=') { + log.error("Expected header param name, got '='"); + state = ERROR; + break; + } + + paramName = new StringBuffer(); + paramValue = new StringBuffer(); + + state = IN_NAME; + // $FALL-THROUGH$ + + case IN_NAME: + if (c == '=') { + if (paramName.length() == 0) + state = ERROR; + else + state = READY_FOR_VALUE; + break; + } + + // not '='... just add to name + paramName.append(c); + break; + + case READY_FOR_VALUE: + boolean fallThrough = false; + switch (c) { + case ' ': + case '\t': + break; // ignore spaces, especially before '"' + + case '"': + state = IN_QUOTED_VALUE; + break; + + default: + state = IN_VALUE; + fallThrough = true; + break; + } + if (!fallThrough) + break; + + // $FALL-THROUGH$ + + case IN_VALUE: + fallThrough = false; + switch (c) { + case ';': + case ' ': + case '\t': + result.put( + paramName.toString().trim().toLowerCase(), + paramValue.toString().trim()); + state = VALUE_DONE; + fallThrough = true; + break; + default: + paramValue.append(c); + break; + } + if (!fallThrough) + break; + + // $FALL-THROUGH$ + + case VALUE_DONE: + switch (c) { + case ';': + state = READY_FOR_NAME; + break; + + case ' ': + case '\t': + break; + + default: + state = ERROR; + break; + } + break; + + case IN_QUOTED_VALUE: + switch (c) { + case '"': + if (!escaped) { + // don't trim quoted strings; the spaces could be intentional. + result.put( + paramName.toString().trim().toLowerCase(), + paramValue.toString()); + state = VALUE_DONE; + } else { + escaped = false; + paramValue.append(c); + } + break; + + case '\\': + if (escaped) { + paramValue.append('\\'); + } + escaped = !escaped; + break; + + default: + if (escaped) { + paramValue.append('\\'); + } + escaped = false; + paramValue.append(c); + break; + } + break; + + } + } + + // done looping. check if anything is left over. + if (state == IN_VALUE) { + result.put( + paramName.toString().trim().toLowerCase(), + paramValue.toString().trim()); + } + } + + return result; + } + + + public boolean isMimeType(String mimeType) { + return this.mimeType.equals(mimeType.toLowerCase()); + } + + /** + * Return true if the BodyDescriptor belongs to a message + */ + public boolean isMessage() { + return mimeType.equals("message/rfc822"); + } + + /** + * Return true if the BodyDescripotro belongs to a multipart + */ + public boolean isMultipart() { + return mimeType.startsWith("multipart/"); + } + + /** + * Return the MimeType + */ + public String getMimeType() { + return mimeType; + } + + /** + * Return the boundary + */ + public String getBoundary() { + return boundary; + } + + /** + * Return the charset + */ + public String getCharset() { + return charset; + } + + /** + * Return all parameters for the BodyDescriptor + */ + public Map<String, String> getParameters() { + return parameters; + } + + /** + * Return the TransferEncoding + */ + public String getTransferEncoding() { + return transferEncoding; + } + + /** + * Return true if it's base64 encoded + */ + public boolean isBase64Encoded() { + return "base64".equals(transferEncoding); + } + + /** + * Return true if it's quoted-printable + */ + public boolean isQuotedPrintableEncoded() { + return "quoted-printable".equals(transferEncoding); + } + + @Override + public String toString() { + return mimeType; + } +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/CloseShieldInputStream.java b/apache/org/apache/james/mime4j/CloseShieldInputStream.java new file mode 100644 index 000000000..d9f3b078a --- /dev/null +++ b/apache/org/apache/james/mime4j/CloseShieldInputStream.java @@ -0,0 +1,129 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j; + +import java.io.InputStream; +import java.io.IOException; + +/** + * InputStream that shields its underlying input stream from + * being closed. + * + * + * @version $Id: CloseShieldInputStream.java,v 1.2 2004/10/02 12:41:10 ntherning Exp $ + */ +public class CloseShieldInputStream extends InputStream { + + /** + * Underlying InputStream + */ + private InputStream is; + + public CloseShieldInputStream(InputStream is) { + this.is = is; + } + + public InputStream getUnderlyingStream() { + return is; + } + + /** + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + checkIfClosed(); + return is.read(); + } + + /** + * @see java.io.InputStream#available() + */ + public int available() throws IOException { + checkIfClosed(); + return is.available(); + } + + + /** + * Set the underlying InputStream to null + */ + public void close() throws IOException { + is = null; + } + + /** + * @see java.io.FilterInputStream#reset() + */ + public synchronized void reset() throws IOException { + checkIfClosed(); + is.reset(); + } + + /** + * @see java.io.FilterInputStream#markSupported() + */ + public boolean markSupported() { + if (is == null) + return false; + return is.markSupported(); + } + + /** + * @see java.io.FilterInputStream#mark(int) + */ + public synchronized void mark(int readlimit) { + if (is != null) + is.mark(readlimit); + } + + /** + * @see java.io.FilterInputStream#skip(long) + */ + public long skip(long n) throws IOException { + checkIfClosed(); + return is.skip(n); + } + + /** + * @see java.io.FilterInputStream#read(byte[]) + */ + public int read(byte b[]) throws IOException { + checkIfClosed(); + return is.read(b); + } + + /** + * @see java.io.FilterInputStream#read(byte[], int, int) + */ + public int read(byte b[], int off, int len) throws IOException { + checkIfClosed(); + return is.read(b, off, len); + } + + /** + * Check if the underlying InputStream is null. If so throw an Exception + * + * @throws IOException if the underlying InputStream is null + */ + private void checkIfClosed() throws IOException { + if (is == null) + throw new IOException("Stream is closed"); + } +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/ContentHandler.java b/apache/org/apache/james/mime4j/ContentHandler.java new file mode 100644 index 000000000..b437e739e --- /dev/null +++ b/apache/org/apache/james/mime4j/ContentHandler.java @@ -0,0 +1,177 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j; + +import java.io.IOException; +import java.io.InputStream; + +/** + * <p> + * Receives notifications of the content of a plain RFC822 or MIME message. + * Implement this interface and register an instance of that implementation + * with a <code>MimeStreamParser</code> instance using its + * {@link org.apache.james.mime4j.MimeStreamParser#setContentHandler(ContentHandler)} + * method. The parser uses the <code>ContentHandler</code> instance to report + * basic message-related events like the start and end of the body of a + * part in a multipart MIME entity. + * </p> + * <p> + * Events will be generated in the order the corresponding elements occur in + * the message stream parsed by the parser. E.g.: + * <pre> + * startMessage() + * startHeader() + * field(...) + * field(...) + * ... + * endHeader() + * startMultipart() + * preamble(...) + * startBodyPart() + * startHeader() + * field(...) + * field(...) + * ... + * endHeader() + * body() + * endBodyPart() + * startBodyPart() + * startHeader() + * field(...) + * field(...) + * ... + * endHeader() + * body() + * endBodyPart() + * epilogue(...) + * endMultipart() + * endMessage() + * </pre> + * The above shows an example of a MIME message consisting of a multipart + * body containing two body parts. + * </p> + * <p> + * See MIME RFCs 2045-2049 for more information on the structure of MIME + * messages and RFC 822 and 2822 for the general structure of Internet mail + * messages. + * </p> + * + * + * @version $Id: ContentHandler.java,v 1.3 2004/10/02 12:41:10 ntherning Exp $ + */ +public interface ContentHandler { + /** + * Called when a new message starts (a top level message or an embedded + * rfc822 message). + */ + void startMessage(); + + /** + * Called when a message ends. + */ + void endMessage(); + + /** + * Called when a new body part starts inside a + * <code>multipart/*</code> entity. + */ + void startBodyPart(); + + /** + * Called when a body part ends. + */ + void endBodyPart(); + + /** + * Called when a header (of a message or body part) is about to be parsed. + */ + void startHeader(); + + /** + * Called for each field of a header. + * + * @param fieldData the raw contents of the field + * (<code>Field-Name: field value</code>). The value will not be + * unfolded. + */ + void field(String fieldData); + + /** + * Called when there are no more header fields in a message or body part. + */ + void endHeader(); + + /** + * Called for the preamble (whatever comes before the first body part) + * of a <code>multipart/*</code> entity. + * + * @param is used to get the contents of the preamble. + * @throws IOException should be thrown on I/O errors. + */ + void preamble(InputStream is) throws IOException; + + /** + * Called for the epilogue (whatever comes after the final body part) + * of a <code>multipart/*</code> entity. + * + * @param is used to get the contents of the epilogue. + * @throws IOException should be thrown on I/O errors. + */ + void epilogue(InputStream is) throws IOException; + + /** + * Called when the body of a multipart entity is about to be parsed. + * + * @param bd encapsulates the values (either read from the + * message stream or, if not present, determined implictly + * as described in the + * MIME rfc:s) of the <code>Content-Type</code> and + * <code>Content-Transfer-Encoding</code> header fields. + */ + void startMultipart(BodyDescriptor bd); + + /** + * Called when the body of an entity has been parsed. + */ + void endMultipart(); + + /** + * Called when the body of a discrete (non-multipart) entity is about to + * be parsed. + * + * @param bd see {@link #startMultipart(BodyDescriptor)} + * @param is the contents of the body. NOTE: this is the raw body contents + * - it will not be decoded if encoded. The <code>bd</code> + * parameter should be used to determine how the stream data + * should be decoded. + * @throws IOException should be thrown on I/O errors. + */ + void body(BodyDescriptor bd, InputStream is) throws IOException; + + /** + * Called when a new entity (message or body part) starts and the + * parser is in <code>raw</code> mode. + * + * @param is the raw contents of the entity. + * @throws IOException should be thrown on I/O errors. + * @see MimeStreamParser#setRaw(boolean) + */ + void raw(InputStream is) throws IOException; +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/EOLConvertingInputStream.java b/apache/org/apache/james/mime4j/EOLConvertingInputStream.java new file mode 100644 index 000000000..d6ef706b2 --- /dev/null +++ b/apache/org/apache/james/mime4j/EOLConvertingInputStream.java @@ -0,0 +1,139 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; + +/** + * InputStream which converts <code>\r</code> + * bytes not followed by <code>\n</code> and <code>\n</code> not + * preceded by <code>\r</code> to <code>\r\n</code>. + * + * + * @version $Id: EOLConvertingInputStream.java,v 1.4 2004/11/29 13:15:42 ntherning Exp $ + */ +public class EOLConvertingInputStream extends InputStream { + /** Converts single '\r' to '\r\n' */ + public static final int CONVERT_CR = 1; + /** Converts single '\n' to '\r\n' */ + public static final int CONVERT_LF = 2; + /** Converts single '\r' and '\n' to '\r\n' */ + public static final int CONVERT_BOTH = 3; + + private PushbackInputStream in = null; + private int previous = 0; + private int flags = CONVERT_BOTH; + private int size = 0; + private int pos = 0; + private int nextTenPctPos; + private int tenPctSize; + private Callback callback; + + public interface Callback { + public void report(int bytesRead); + } + + /** + * Creates a new <code>EOLConvertingInputStream</code> + * instance converting bytes in the given <code>InputStream</code>. + * The flag <code>CONVERT_BOTH</code> is the default. + * + * @param in the <code>InputStream</code> to read from. + */ + public EOLConvertingInputStream(InputStream _in) { + super(); + in = new PushbackInputStream(_in, 2); + } + + /** + * Creates a new <code>EOLConvertingInputStream</code> + * instance converting bytes in the given <code>InputStream</code>. + * + * @param _in the <code>InputStream</code> to read from. + * @param _size the size of the input stream (need not be exact) + * @param _callback a callback reporting when each 10% of stream's size is reached + */ + public EOLConvertingInputStream(InputStream _in, int _size, Callback _callback) { + this(_in); + size = _size; + tenPctSize = size / 10; + nextTenPctPos = tenPctSize; + callback = _callback; + } + + /** + * Closes the underlying stream. + * + * @throws IOException on I/O errors. + */ + public void close() throws IOException { + in.close(); + } + + private int readByte() throws IOException { + int b = in.read(); + if (b != -1) { + if (callback != null && pos++ == nextTenPctPos) { + nextTenPctPos += tenPctSize; + if (callback != null) { + callback.report(pos); + } + } + } + return b; + } + + private void unreadByte(int c) throws IOException { + in.unread(c); + pos--; + } + + /** + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + int b = readByte(); + + if (b == -1) { + pos = size; + return -1; + } + + if ((flags & CONVERT_CR) != 0 && b == '\r') { + int c = readByte(); + if (c != -1) { + unreadByte(c); + } + if (c != '\n') { + unreadByte('\n'); + } + } else if ((flags & CONVERT_LF) != 0 && b == '\n' && previous != '\r') { + b = '\r'; + unreadByte('\n'); + } + + previous = b; + + return b; + } + +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/Log.java b/apache/org/apache/james/mime4j/Log.java new file mode 100644 index 000000000..5eeead5f3 --- /dev/null +++ b/apache/org/apache/james/mime4j/Log.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.james.mime4j; + +/** + * Empty stub for the apache logging library. + */ +public class Log { + private static final String LOG_TAG = "Email Log"; + + public Log(Class mClazz) { + } + + public boolean isDebugEnabled() { + return false; + } + + public boolean isErrorEnabled() { + return true; + } + + public boolean isFatalEnabled() { + return true; + } + + public boolean isInfoEnabled() { + return false; + } + + public boolean isTraceEnabled() { + return false; + } + + public boolean isWarnEnabled() { + return true; + } + + public void trace(Object message) { + if (!isTraceEnabled()) return; + android.util.Log.v(LOG_TAG, toString(message, null)); + } + + public void trace(Object message, Throwable t) { + if (!isTraceEnabled()) return; + android.util.Log.v(LOG_TAG, toString(message, t)); + } + + public void debug(Object message) { + if (!isDebugEnabled()) return; + android.util.Log.d(LOG_TAG, toString(message, null)); + } + + public void debug(Object message, Throwable t) { + if (!isDebugEnabled()) return; + android.util.Log.d(LOG_TAG, toString(message, t)); + } + + public void info(Object message) { + if (!isInfoEnabled()) return; + android.util.Log.i(LOG_TAG, toString(message, null)); + } + + public void info(Object message, Throwable t) { + if (!isInfoEnabled()) return; + android.util.Log.i(LOG_TAG, toString(message, t)); + } + + public void warn(Object message) { + android.util.Log.w(LOG_TAG, toString(message, null)); + } + + public void warn(Object message, Throwable t) { + android.util.Log.w(LOG_TAG, toString(message, t)); + } + + public void error(Object message) { + android.util.Log.e(LOG_TAG, toString(message, null)); + } + + public void error(Object message, Throwable t) { + android.util.Log.e(LOG_TAG, toString(message, t)); + } + + public void fatal(Object message) { + android.util.Log.e(LOG_TAG, toString(message, null)); + } + + public void fatal(Object message, Throwable t) { + android.util.Log.e(LOG_TAG, toString(message, t)); + } + + private static String toString(Object o, Throwable t) { + String m = (o == null) ? "(null)" : o.toString(); + if (t == null) { + return m; + } else { + return m + " " + t.getMessage(); + } + } +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/LogFactory.java b/apache/org/apache/james/mime4j/LogFactory.java new file mode 100644 index 000000000..ed6e3de3d --- /dev/null +++ b/apache/org/apache/james/mime4j/LogFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.james.mime4j; + +/** + * Empty stub for the apache logging library. + */ +public final class LogFactory { + private LogFactory() { + } + + public static Log getLog(Class clazz) { + return new Log(clazz); + } +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/MimeBoundaryInputStream.java b/apache/org/apache/james/mime4j/MimeBoundaryInputStream.java new file mode 100644 index 000000000..c6d6f248a --- /dev/null +++ b/apache/org/apache/james/mime4j/MimeBoundaryInputStream.java @@ -0,0 +1,184 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; + +/** + * Stream that constrains itself to a single MIME body part. + * After the stream ends (i.e. read() returns -1) {@link #hasMoreParts()} + * can be used to determine if a final boundary has been seen or not. + * If {@link #parentEOF()} is <code>true</code> an unexpected end of stream + * has been detected in the parent stream. + * + * + * + * @version $Id: MimeBoundaryInputStream.java,v 1.2 2004/11/29 13:15:42 ntherning Exp $ + */ +public class MimeBoundaryInputStream extends InputStream { + + private PushbackInputStream s = null; + private byte[] boundary = null; + private boolean first = true; + private boolean eof = false; + private boolean parenteof = false; + private boolean moreParts = true; + + /** + * Creates a new MimeBoundaryInputStream. + * @param s The underlying stream. + * @param boundary Boundary string (not including leading hyphens). + */ + public MimeBoundaryInputStream(InputStream s, String boundary) + throws IOException { + + this.s = new PushbackInputStream(s, boundary.length() + 4); + + boundary = "--" + boundary; + this.boundary = new byte[boundary.length()]; + for (int i = 0; i < this.boundary.length; i++) { + this.boundary[i] = (byte) boundary.charAt(i); + } + + /* + * By reading one byte we will update moreParts to be as expected + * before any bytes have been read. + */ + int b = read(); + if (b != -1) { + this.s.unread(b); + } + } + + /** + * Closes the underlying stream. + * + * @throws IOException on I/O errors. + */ + public void close() throws IOException { + s.close(); + } + + /** + * Determines if the underlying stream has more parts (this stream has + * not seen an end boundary). + * + * @return <code>true</code> if there are more parts in the underlying + * stream, <code>false</code> otherwise. + */ + public boolean hasMoreParts() { + return moreParts; + } + + /** + * Determines if the parent stream has reached EOF + * + * @return <code>true</code> if EOF has been reached for the parent stream, + * <code>false</code> otherwise. + */ + public boolean parentEOF() { + return parenteof; + } + + /** + * Consumes all unread bytes of this stream. After a call to this method + * this stream will have reached EOF. + * + * @throws IOException on I/O errors. + */ + public void consume() throws IOException { + while (read() != -1) { + } + } + + /** + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + if (eof) { + return -1; + } + + if (first) { + first = false; + if (matchBoundary()) { + return -1; + } + } + + int b1 = s.read(); + int b2 = s.read(); + + if (b1 == '\r' && b2 == '\n') { + if (matchBoundary()) { + return -1; + } + } + + if (b2 != -1) { + s.unread(b2); + } + + parenteof = b1 == -1; + eof = parenteof; + + return b1; + } + + private boolean matchBoundary() throws IOException { + + for (int i = 0; i < boundary.length; i++) { + int b = s.read(); + if (b != boundary[i]) { + if (b != -1) { + s.unread(b); + } + for (int j = i - 1; j >= 0; j--) { + s.unread(boundary[j]); + } + return false; + } + } + + /* + * We have a match. Is it an end boundary? + */ + int prev = s.read(); + int curr = s.read(); + moreParts = !(prev == '-' && curr == '-'); + do { + if (curr == '\n' && prev == '\r') { + break; + } + prev = curr; + } while ((curr = s.read()) != -1); + + if (curr == -1) { + moreParts = false; + parenteof = true; + } + + eof = true; + + return true; + } +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/MimeStreamParser.java b/apache/org/apache/james/mime4j/MimeStreamParser.java new file mode 100644 index 000000000..a8aad5a38 --- /dev/null +++ b/apache/org/apache/james/mime4j/MimeStreamParser.java @@ -0,0 +1,324 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j; + +import org.apache.james.mime4j.decoder.Base64InputStream; +import org.apache.james.mime4j.decoder.QuotedPrintableInputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.util.BitSet; +import java.util.LinkedList; + +/** + * <p> + * Parses MIME (or RFC822) message streams of bytes or characters and reports + * parsing events to a <code>ContentHandler</code> instance. + * </p> + * <p> + * Typical usage:<br/> + * <pre> + * ContentHandler handler = new MyHandler(); + * MimeStreamParser parser = new MimeStreamParser(); + * parser.setContentHandler(handler); + * parser.parse(new BufferedInputStream(new FileInputStream("mime.msg"))); + * </pre> + * <strong>NOTE:</strong> All lines must end with CRLF + * (<code>\r\n</code>). If you are unsure of the line endings in your stream + * you should wrap it in a {@link org.apache.james.mime4j.EOLConvertingInputStream} instance. + * + * + * @version $Id: MimeStreamParser.java,v 1.8 2005/02/11 10:12:02 ntherning Exp $ + */ +public class MimeStreamParser { + private static final Log log = LogFactory.getLog(MimeStreamParser.class); + + private static BitSet fieldChars = null; + + private RootInputStream rootStream = null; + private LinkedList<BodyDescriptor> bodyDescriptors = new LinkedList<BodyDescriptor>(); + private ContentHandler handler = null; + private boolean raw = false; + private boolean prematureEof = false; + + static { + fieldChars = new BitSet(); + for (int i = 0x21; i <= 0x39; i++) { + fieldChars.set(i); + } + for (int i = 0x3b; i <= 0x7e; i++) { + fieldChars.set(i); + } + } + + /** + * Creates a new <code>MimeStreamParser</code> instance. + */ + public MimeStreamParser() { + } + + /** + * Parses a stream of bytes containing a MIME message. + * + * @param is the stream to parse. + * @throws IOException on I/O errors. + */ + public void parse(InputStream is) throws IOException { + rootStream = new RootInputStream(is); + parseMessage(rootStream); + } + + /** + * Determines if this parser is currently in raw mode. + * + * @return <code>true</code> if in raw mode, <code>false</code> + * otherwise. + * @see #setRaw(boolean) + */ + public boolean isRaw() { + return raw; + } + + /** + * Enables or disables raw mode. In raw mode all future entities + * (messages or body parts) in the stream will be reported to the + * {@link ContentHandler#raw(InputStream)} handler method only. + * The stream will contain the entire unparsed entity contents + * including header fields and whatever is in the body. + * + * @param raw <code>true</code> enables raw mode, <code>false</code> + * disables it. + */ + public void setRaw(boolean raw) { + this.raw = raw; + } + + /** + * Finishes the parsing and stops reading lines. + * NOTE: No more lines will be parsed but the parser + * will still call + * {@link ContentHandler#endMultipart()}, + * {@link ContentHandler#endBodyPart()}, + * {@link ContentHandler#endMessage()}, etc to match previous calls + * to + * {@link ContentHandler#startMultipart(BodyDescriptor)}, + * {@link ContentHandler#startBodyPart()}, + * {@link ContentHandler#startMessage()}, etc. + */ + public void stop() { + rootStream.truncate(); + } + + /** + * Parses an entity which consists of a header followed by a body containing + * arbitrary data, body parts or an embedded message. + * + * @param is the stream to parse. + * @throws IOException on I/O errors. + */ + private void parseEntity(InputStream is) throws IOException { + BodyDescriptor bd = parseHeader(is); + + if (bd.isMultipart()) { + bodyDescriptors.addFirst(bd); + + handler.startMultipart(bd); + + MimeBoundaryInputStream tempIs = + new MimeBoundaryInputStream(is, bd.getBoundary()); + handler.preamble(new CloseShieldInputStream(tempIs)); + tempIs.consume(); + + while (tempIs.hasMoreParts()) { + tempIs = new MimeBoundaryInputStream(is, bd.getBoundary()); + parseBodyPart(tempIs); + tempIs.consume(); + if (tempIs.parentEOF()) { + prematureEof = true; +// if (log.isWarnEnabled()) { +// log.warn("Line " + rootStream.getLineNumber() +// + ": Body part ended prematurely. " +// + "Higher level boundary detected or " +// + "EOF reached."); +// } + break; + } + } + + handler.epilogue(new CloseShieldInputStream(is)); + + handler.endMultipart(); + + bodyDescriptors.removeFirst(); + + } else if (bd.isMessage()) { + if (bd.isBase64Encoded()) { + log.warn("base64 encoded message/rfc822 detected"); + is = new EOLConvertingInputStream( + new Base64InputStream(is)); + } else if (bd.isQuotedPrintableEncoded()) { + log.warn("quoted-printable encoded message/rfc822 detected"); + is = new EOLConvertingInputStream( + new QuotedPrintableInputStream(is)); + } + bodyDescriptors.addFirst(bd); + parseMessage(is); + bodyDescriptors.removeFirst(); + } else { + handler.body(bd, new CloseShieldInputStream(is)); + } + + /* + * Make sure the stream has been consumed. + */ + while (is.read() != -1) { + } + } + + private void parseMessage(InputStream is) throws IOException { + if (raw) { + handler.raw(new CloseShieldInputStream(is)); + } else { + handler.startMessage(); + parseEntity(is); + handler.endMessage(); + } + } + + public boolean getPrematureEof() { + return prematureEof; + } + + private void parseBodyPart(InputStream is) throws IOException { + if (raw) { + handler.raw(new CloseShieldInputStream(is)); + } else { + handler.startBodyPart(); + parseEntity(is); + handler.endBodyPart(); + } + } + + /** + * Parses a header. + * + * @param is the stream to parse. + * @return a <code>BodyDescriptor</code> describing the body following + * the header. + */ + private BodyDescriptor parseHeader(InputStream is) throws IOException { + BodyDescriptor bd = new BodyDescriptor(bodyDescriptors.isEmpty() + ? null : (BodyDescriptor) bodyDescriptors.getFirst()); + + handler.startHeader(); + + int lineNumber = rootStream.getLineNumber(); + + StringBuffer sb = new StringBuffer(); + int curr = 0; + int prev = 0; + while ((curr = is.read()) != -1) { + if (curr == '\n' && (prev == '\n' || prev == 0)) { + /* + * [\r]\n[\r]\n or an immediate \r\n have been seen. + */ + sb.deleteCharAt(sb.length() - 1); + break; + } + sb.append((char) curr); + prev = curr == '\r' ? prev : curr; + } + +// if (curr == -1 && log.isWarnEnabled()) { +// log.warn("Line " + rootStream.getLineNumber() +// + ": Unexpected end of headers detected. " +// + "Boundary detected in header or EOF reached."); +// } + + int start = 0; + int pos = 0; + int startLineNumber = lineNumber; + while (pos < sb.length()) { + while (pos < sb.length() && sb.charAt(pos) != '\r') { + pos++; + } + if (pos < sb.length() - 1 && sb.charAt(pos + 1) != '\n') { + pos++; + continue; + } + + if (pos >= sb.length() - 2 || fieldChars.get(sb.charAt(pos + 2))) { + + /* + * field should be the complete field data excluding the + * trailing \r\n. + */ + String field = sb.substring(start, pos); + start = pos + 2; + + /* + * Check for a valid field. + */ + int index = field.indexOf(':'); + boolean valid = false; + if (index != -1 && fieldChars.get(field.charAt(0))) { + valid = true; + String fieldName = field.substring(0, index).trim(); + for (int i = 0; i < fieldName.length(); i++) { + if (!fieldChars.get(fieldName.charAt(i))) { + valid = false; + break; + } + } + + if (valid) { + handler.field(field); + bd.addField(fieldName, field.substring(index + 1)); + } + } + + if (!valid && log.isWarnEnabled()) { + log.warn("Line " + startLineNumber + + ": Ignoring invalid field: '" + field.trim() + "'"); + } + + startLineNumber = lineNumber; + } + + pos += 2; + lineNumber++; + } + + handler.endHeader(); + + return bd; + } + + /** + * Sets the <code>ContentHandler</code> to use when reporting + * parsing events. + * + * @param h the <code>ContentHandler</code>. + */ + public void setContentHandler(ContentHandler h) { + this.handler = h; + } + +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/RootInputStream.java b/apache/org/apache/james/mime4j/RootInputStream.java new file mode 100644 index 000000000..cc8b2411c --- /dev/null +++ b/apache/org/apache/james/mime4j/RootInputStream.java @@ -0,0 +1,111 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j; + +import java.io.IOException; +import java.io.InputStream; + +/** + * <code>InputStream</code> used by the parser to wrap the original user + * supplied stream. This stream keeps track of the current line number and + * can also be truncated. When truncated the stream will appear to have + * reached end of file. This is used by the parser's + * {@link org.apache.james.mime4j.MimeStreamParser#stop()} method. + * + * + * @version $Id: RootInputStream.java,v 1.2 2004/10/02 12:41:10 ntherning Exp $ + */ +class RootInputStream extends InputStream { + private InputStream is = null; + private int lineNumber = 1; + private int prev = -1; + private boolean truncated = false; + + /** + * Creates a new <code>RootInputStream</code>. + * + * @param in the stream to read from. + */ + public RootInputStream(InputStream is) { + this.is = is; + } + + /** + * Gets the current line number starting at 1 + * (the number of <code>\r\n</code> read so far plus 1). + * + * @return the current line number. + */ + public int getLineNumber() { + return lineNumber; + } + + /** + * Truncates this <code>InputStream</code>. After this call any + * call to {@link #read()}, {@link #read(byte[]) or + * {@link #read(byte[], int, int)} will return + * -1 as if end-of-file had been reached. + */ + public void truncate() { + this.truncated = true; + } + + /** + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + if (truncated) { + return -1; + } + + int b = is.read(); + if (prev == '\r' && b == '\n') { + lineNumber++; + } + prev = b; + return b; + } + + /** + * + * @see java.io.InputStream#read(byte[], int, int) + */ + public int read(byte[] b, int off, int len) throws IOException { + if (truncated) { + return -1; + } + + int n = is.read(b, off, len); + for (int i = off; i < off + n; i++) { + if (prev == '\r' && b[i] == '\n') { + lineNumber++; + } + prev = b[i]; + } + return n; + } + + /** + * @see java.io.InputStream#read(byte[]) + */ + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/codec/EncoderUtil.java b/apache/org/apache/james/mime4j/codec/EncoderUtil.java new file mode 100644 index 000000000..6841bc998 --- /dev/null +++ b/apache/org/apache/james/mime4j/codec/EncoderUtil.java @@ -0,0 +1,630 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.codec; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.BitSet; +import java.util.Locale; + +import org.apache.james.mime4j.util.CharsetUtil; + +/** + * ANDROID: THIS CLASS IS COPIED FROM A NEWER VERSION OF MIME4J + */ + +/** + * Static methods for encoding header field values. This includes encoded-words + * as defined in <a href='http://www.faqs.org/rfcs/rfc2047.html'>RFC 2047</a> + * or display-names of an e-mail address, for example. + * + */ +public class EncoderUtil { + + // This array is a lookup table that translates 6-bit positive integer index + // values into their "Base64 Alphabet" equivalents as specified in Table 1 + // of RFC 2045. + // ANDROID: THIS TABLE IS COPIED FROM BASE64OUTPUTSTREAM + static final byte[] BASE64_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' }; + + // Byte used to pad output. + private static final byte BASE64_PAD = '='; + + private static final BitSet Q_REGULAR_CHARS = initChars("=_?"); + + private static final BitSet Q_RESTRICTED_CHARS = initChars("=_?\"#$%&'(),.:;<>@[\\]^`{|}~"); + + private static final int MAX_USED_CHARACTERS = 50; + + private static final String ENC_WORD_PREFIX = "=?"; + private static final String ENC_WORD_SUFFIX = "?="; + + private static final int ENCODED_WORD_MAX_LENGTH = 75; // RFC 2047 + + private static final BitSet TOKEN_CHARS = initChars("()<>@,;:\\\"/[]?="); + + private static final BitSet ATEXT_CHARS = initChars("()<>@.,;:\\\"[]"); + + private static BitSet initChars(String specials) { + BitSet bs = new BitSet(128); + for (char ch = 33; ch < 127; ch++) { + if (specials.indexOf(ch) == -1) { + bs.set(ch); + } + } + return bs; + } + + /** + * Selects one of the two encodings specified in RFC 2047. + */ + public enum Encoding { + /** The B encoding (identical to base64 defined in RFC 2045). */ + B, + /** The Q encoding (similar to quoted-printable defined in RFC 2045). */ + Q + } + + /** + * Indicates the intended usage of an encoded word. + */ + public enum Usage { + /** + * Encoded word is used to replace a 'text' token in any Subject or + * Comments header field. + */ + TEXT_TOKEN, + /** + * Encoded word is used to replace a 'word' entity within a 'phrase', + * for example, one that precedes an address in a From, To, or Cc + * header. + */ + WORD_ENTITY + } + + private EncoderUtil() { + } + + /** + * Encodes the display-name portion of an address. See <a + * href='http://www.faqs.org/rfcs/rfc5322.html'>RFC 5322</a> section 3.4 + * and <a href='http://www.faqs.org/rfcs/rfc2047.html'>RFC 2047</a> section + * 5.3. The specified string should not be folded. + * + * @param displayName + * display-name to encode. + * @return encoded display-name. + */ + public static String encodeAddressDisplayName(String displayName) { + // display-name = phrase + // phrase = 1*( encoded-word / word ) + // word = atom / quoted-string + // atom = [CFWS] 1*atext [CFWS] + // CFWS = comment or folding white space + + if (isAtomPhrase(displayName)) { + return displayName; + } else if (hasToBeEncoded(displayName, 0)) { + return encodeEncodedWord(displayName, Usage.WORD_ENTITY); + } else { + return quote(displayName); + } + } + + /** + * Encodes the local part of an address specification as described in RFC + * 5322 section 3.4.1. Leading and trailing CFWS should have been removed + * before calling this method. The specified string should not contain any + * illegal (control or non-ASCII) characters. + * + * @param localPart + * the local part to encode + * @return the encoded local part. + */ + public static String encodeAddressLocalPart(String localPart) { + // local-part = dot-atom / quoted-string + // dot-atom = [CFWS] dot-atom-text [CFWS] + // CFWS = comment or folding white space + + if (isDotAtomText(localPart)) { + return localPart; + } else { + return quote(localPart); + } + } + + /** + * Encodes the specified strings into a header parameter as described in RFC + * 2045 section 5.1 and RFC 2183 section 2. The specified strings should not + * contain any illegal (control or non-ASCII) characters. + * + * @param name + * parameter name. + * @param value + * parameter value. + * @return encoded result. + */ + public static String encodeHeaderParameter(String name, String value) { + name = name.toLowerCase(Locale.US); + + // value := token / quoted-string + if (isToken(value)) { + return name + "=" + value; + } else { + return name + "=" + quote(value); + } + } + + /** + * Shortcut method that encodes the specified text into an encoded-word if + * the text has to be encoded. + * + * @param text + * text to encode. + * @param usage + * whether the encoded-word is to be used to replace a text token + * or a word entity (see RFC 822). + * @param usedCharacters + * number of characters already used up (<code>0 <= usedCharacters <= 50</code>). + * @return the specified text if encoding is not necessary or an encoded + * word or a sequence of encoded words otherwise. + */ + public static String encodeIfNecessary(String text, Usage usage, + int usedCharacters) { + if (hasToBeEncoded(text, usedCharacters)) + return encodeEncodedWord(text, usage, usedCharacters); + else + return text; + } + + /** + * Determines if the specified string has to encoded into an encoded-word. + * Returns <code>true</code> if the text contains characters that don't + * fall into the printable ASCII character set or if the text contains a + * 'word' (sequence of non-whitespace characters) longer than 77 characters + * (including characters already used up in the line). + * + * @param text + * text to analyze. + * @param usedCharacters + * number of characters already used up (<code>0 <= usedCharacters <= 50</code>). + * @return <code>true</code> if the specified text has to be encoded into + * an encoded-word, <code>false</code> otherwise. + */ + public static boolean hasToBeEncoded(String text, int usedCharacters) { + if (text == null) + throw new IllegalArgumentException(); + if (usedCharacters < 0 || usedCharacters > MAX_USED_CHARACTERS) + throw new IllegalArgumentException(); + + int nonWhiteSpaceCount = usedCharacters; + + for (int idx = 0; idx < text.length(); idx++) { + char ch = text.charAt(idx); + if (ch == '\t' || ch == ' ') { + nonWhiteSpaceCount = 0; + } else { + nonWhiteSpaceCount++; + if (nonWhiteSpaceCount > 77) { + // Line cannot be folded into multiple lines with no more + // than 78 characters each. Encoding as encoded-words makes + // that possible. One character has to be reserved for + // folding white space; that leaves 77 characters. + return true; + } + + if (ch < 32 || ch >= 127) { + // non-printable ascii character has to be encoded + return true; + } + } + } + + return false; + } + + /** + * Encodes the specified text into an encoded word or a sequence of encoded + * words separated by space. The text is separated into a sequence of + * encoded words if it does not fit in a single one. + * <p> + * The charset to encode the specified text into a byte array and the + * encoding to use for the encoded-word are detected automatically. + * <p> + * This method assumes that zero characters have already been used up in the + * current line. + * + * @param text + * text to encode. + * @param usage + * whether the encoded-word is to be used to replace a text token + * or a word entity (see RFC 822). + * @return the encoded word (or sequence of encoded words if the given text + * does not fit in a single encoded word). + * @see #hasToBeEncoded(String, int) + */ + public static String encodeEncodedWord(String text, Usage usage) { + return encodeEncodedWord(text, usage, 0, null, null); + } + + /** + * Encodes the specified text into an encoded word or a sequence of encoded + * words separated by space. The text is separated into a sequence of + * encoded words if it does not fit in a single one. + * <p> + * The charset to encode the specified text into a byte array and the + * encoding to use for the encoded-word are detected automatically. + * + * @param text + * text to encode. + * @param usage + * whether the encoded-word is to be used to replace a text token + * or a word entity (see RFC 822). + * @param usedCharacters + * number of characters already used up (<code>0 <= usedCharacters <= 50</code>). + * @return the encoded word (or sequence of encoded words if the given text + * does not fit in a single encoded word). + * @see #hasToBeEncoded(String, int) + */ + public static String encodeEncodedWord(String text, Usage usage, + int usedCharacters) { + return encodeEncodedWord(text, usage, usedCharacters, null, null); + } + + /** + * Encodes the specified text into an encoded word or a sequence of encoded + * words separated by space. The text is separated into a sequence of + * encoded words if it does not fit in a single one. + * + * @param text + * text to encode. + * @param usage + * whether the encoded-word is to be used to replace a text token + * or a word entity (see RFC 822). + * @param usedCharacters + * number of characters already used up (<code>0 <= usedCharacters <= 50</code>). + * @param charset + * the Java charset that should be used to encode the specified + * string into a byte array. A suitable charset is detected + * automatically if this parameter is <code>null</code>. + * @param encoding + * the encoding to use for the encoded-word (either B or Q). A + * suitable encoding is automatically chosen if this parameter is + * <code>null</code>. + * @return the encoded word (or sequence of encoded words if the given text + * does not fit in a single encoded word). + * @see #hasToBeEncoded(String, int) + */ + public static String encodeEncodedWord(String text, Usage usage, + int usedCharacters, Charset charset, Encoding encoding) { + if (text == null) + throw new IllegalArgumentException(); + if (usedCharacters < 0 || usedCharacters > MAX_USED_CHARACTERS) + throw new IllegalArgumentException(); + + if (charset == null) + charset = determineCharset(text); + + String mimeCharset = CharsetUtil.toMimeCharset(charset.name()); + if (mimeCharset == null) { + // cannot happen if charset was originally null + throw new IllegalArgumentException("Unsupported charset"); + } + + byte[] bytes = encode(text, charset); + + if (encoding == null) + encoding = determineEncoding(bytes, usage); + + if (encoding == Encoding.B) { + String prefix = ENC_WORD_PREFIX + mimeCharset + "?B?"; + return encodeB(prefix, text, usedCharacters, charset, bytes); + } else { + String prefix = ENC_WORD_PREFIX + mimeCharset + "?Q?"; + return encodeQ(prefix, text, usage, usedCharacters, charset, bytes); + } + } + + /** + * Encodes the specified byte array using the B encoding defined in RFC + * 2047. + * + * @param bytes + * byte array to encode. + * @return encoded string. + */ + public static String encodeB(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + + int idx = 0; + final int end = bytes.length; + for (; idx < end - 2; idx += 3) { + int data = (bytes[idx] & 0xff) << 16 | (bytes[idx + 1] & 0xff) << 8 + | bytes[idx + 2] & 0xff; + sb.append((char) BASE64_TABLE[data >> 18 & 0x3f]); + sb.append((char) BASE64_TABLE[data >> 12 & 0x3f]); + sb.append((char) BASE64_TABLE[data >> 6 & 0x3f]); + sb.append((char) BASE64_TABLE[data & 0x3f]); + } + + if (idx == end - 2) { + int data = (bytes[idx] & 0xff) << 16 | (bytes[idx + 1] & 0xff) << 8; + sb.append((char) BASE64_TABLE[data >> 18 & 0x3f]); + sb.append((char) BASE64_TABLE[data >> 12 & 0x3f]); + sb.append((char) BASE64_TABLE[data >> 6 & 0x3f]); + sb.append((char) BASE64_PAD); + + } else if (idx == end - 1) { + int data = (bytes[idx] & 0xff) << 16; + sb.append((char) BASE64_TABLE[data >> 18 & 0x3f]); + sb.append((char) BASE64_TABLE[data >> 12 & 0x3f]); + sb.append((char) BASE64_PAD); + sb.append((char) BASE64_PAD); + } + + return sb.toString(); + } + + /** + * Encodes the specified byte array using the Q encoding defined in RFC + * 2047. + * + * @param bytes + * byte array to encode. + * @param usage + * whether the encoded-word is to be used to replace a text token + * or a word entity (see RFC 822). + * @return encoded string. + */ + public static String encodeQ(byte[] bytes, Usage usage) { + BitSet qChars = usage == Usage.TEXT_TOKEN ? Q_REGULAR_CHARS + : Q_RESTRICTED_CHARS; + + StringBuilder sb = new StringBuilder(); + + final int end = bytes.length; + for (int idx = 0; idx < end; idx++) { + int v = bytes[idx] & 0xff; + if (v == 32) { + sb.append('_'); + } else if (!qChars.get(v)) { + sb.append('='); + sb.append(hexDigit(v >>> 4)); + sb.append(hexDigit(v & 0xf)); + } else { + sb.append((char) v); + } + } + + return sb.toString(); + } + + /** + * Tests whether the specified string is a token as defined in RFC 2045 + * section 5.1. + * + * @param str + * string to test. + * @return <code>true</code> if the specified string is a RFC 2045 token, + * <code>false</code> otherwise. + */ + public static boolean isToken(String str) { + // token := 1*<any (US-ASCII) CHAR except SPACE, CTLs, or tspecials> + // tspecials := "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "\" / + // <"> / "/" / "[" / "]" / "?" / "=" + // CTL := 0.- 31., 127. + + final int length = str.length(); + if (length == 0) + return false; + + for (int idx = 0; idx < length; idx++) { + char ch = str.charAt(idx); + if (!TOKEN_CHARS.get(ch)) + return false; + } + + return true; + } + + private static boolean isAtomPhrase(String str) { + // atom = [CFWS] 1*atext [CFWS] + + boolean containsAText = false; + + final int length = str.length(); + for (int idx = 0; idx < length; idx++) { + char ch = str.charAt(idx); + if (ATEXT_CHARS.get(ch)) { + containsAText = true; + } else if (!CharsetUtil.isWhitespace(ch)) { + return false; + } + } + + return containsAText; + } + + // RFC 5322 section 3.2.3 + private static boolean isDotAtomText(String str) { + // dot-atom-text = 1*atext *("." 1*atext) + // atext = ALPHA / DIGIT / "!" / "#" / "$" / "%" / "&" / "'" / "*" / + // "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~" + + char prev = '.'; + + final int length = str.length(); + if (length == 0) + return false; + + for (int idx = 0; idx < length; idx++) { + char ch = str.charAt(idx); + + if (ch == '.') { + if (prev == '.' || idx == length - 1) + return false; + } else { + if (!ATEXT_CHARS.get(ch)) + return false; + } + + prev = ch; + } + + return true; + } + + // RFC 5322 section 3.2.4 + private static String quote(String str) { + // quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS] + // qcontent = qtext / quoted-pair + // qtext = %d33 / %d35-91 / %d93-126 + // quoted-pair = ("\" (VCHAR / WSP)) + // VCHAR = %x21-7E + // DQUOTE = %x22 + + String escaped = str.replaceAll("[\\\\\"]", "\\\\$0"); + return "\"" + escaped + "\""; + } + + private static String encodeB(String prefix, String text, + int usedCharacters, Charset charset, byte[] bytes) { + int encodedLength = bEncodedLength(bytes); + + int totalLength = prefix.length() + encodedLength + + ENC_WORD_SUFFIX.length(); + if (totalLength <= ENCODED_WORD_MAX_LENGTH - usedCharacters) { + return prefix + encodeB(bytes) + ENC_WORD_SUFFIX; + } else { + int splitOffset = text.offsetByCodePoints(text.length() / 2, -1); + + String part1 = text.substring(0, splitOffset); + byte[] bytes1 = encode(part1, charset); + String word1 = encodeB(prefix, part1, usedCharacters, charset, + bytes1); + + String part2 = text.substring(splitOffset); + byte[] bytes2 = encode(part2, charset); + String word2 = encodeB(prefix, part2, 0, charset, bytes2); + + return word1 + " " + word2; + } + } + + private static int bEncodedLength(byte[] bytes) { + return (bytes.length + 2) / 3 * 4; + } + + private static String encodeQ(String prefix, String text, Usage usage, + int usedCharacters, Charset charset, byte[] bytes) { + int encodedLength = qEncodedLength(bytes, usage); + + int totalLength = prefix.length() + encodedLength + + ENC_WORD_SUFFIX.length(); + if (totalLength <= ENCODED_WORD_MAX_LENGTH - usedCharacters) { + return prefix + encodeQ(bytes, usage) + ENC_WORD_SUFFIX; + } else { + int splitOffset = text.offsetByCodePoints(text.length() / 2, -1); + + String part1 = text.substring(0, splitOffset); + byte[] bytes1 = encode(part1, charset); + String word1 = encodeQ(prefix, part1, usage, usedCharacters, + charset, bytes1); + + String part2 = text.substring(splitOffset); + byte[] bytes2 = encode(part2, charset); + String word2 = encodeQ(prefix, part2, usage, 0, charset, bytes2); + + return word1 + " " + word2; + } + } + + private static int qEncodedLength(byte[] bytes, Usage usage) { + BitSet qChars = usage == Usage.TEXT_TOKEN ? Q_REGULAR_CHARS + : Q_RESTRICTED_CHARS; + + int count = 0; + + for (int idx = 0; idx < bytes.length; idx++) { + int v = bytes[idx] & 0xff; + if (v == 32) { + count++; + } else if (!qChars.get(v)) { + count += 3; + } else { + count++; + } + } + + return count; + } + + private static byte[] encode(String text, Charset charset) { + ByteBuffer buffer = charset.encode(text); + byte[] bytes = new byte[buffer.limit()]; + buffer.get(bytes); + return bytes; + } + + private static Charset determineCharset(String text) { + // it is an important property of iso-8859-1 that it directly maps + // unicode code points 0000 to 00ff to byte values 00 to ff. + boolean ascii = true; + final int len = text.length(); + for (int index = 0; index < len; index++) { + char ch = text.charAt(index); + if (ch > 0xff) { + return CharsetUtil.UTF_8; + } + if (ch > 0x7f) { + ascii = false; + } + } + return ascii ? CharsetUtil.US_ASCII : CharsetUtil.ISO_8859_1; + } + + private static Encoding determineEncoding(byte[] bytes, Usage usage) { + if (bytes.length == 0) + return Encoding.Q; + + BitSet qChars = usage == Usage.TEXT_TOKEN ? Q_REGULAR_CHARS + : Q_RESTRICTED_CHARS; + + int qEncoded = 0; + for (int i = 0; i < bytes.length; i++) { + int v = bytes[i] & 0xff; + if (v != 32 && !qChars.get(v)) { + qEncoded++; + } + } + + int percentage = qEncoded * 100 / bytes.length; + return percentage > 30 ? Encoding.B : Encoding.Q; + } + + private static char hexDigit(int i) { + return i < 10 ? (char) (i + '0') : (char) (i - 10 + 'A'); + } +} diff --git a/apache/org/apache/james/mime4j/decoder/Base64InputStream.java b/apache/org/apache/james/mime4j/decoder/Base64InputStream.java new file mode 100644 index 000000000..77f5d7d4a --- /dev/null +++ b/apache/org/apache/james/mime4j/decoder/Base64InputStream.java @@ -0,0 +1,151 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +/** + * Modified to improve efficiency by Android 21-Aug-2009 + */ + +package org.apache.james.mime4j.decoder; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Performs Base-64 decoding on an underlying stream. + * + * + * @version $Id: Base64InputStream.java,v 1.3 2004/11/29 13:15:47 ntherning Exp $ + */ +public class Base64InputStream extends InputStream { + private final InputStream s; + private int outCount = 0; + private int outIndex = 0; + private final int[] outputBuffer = new int[3]; + private final byte[] inputBuffer = new byte[4]; + private boolean done = false; + + public Base64InputStream(InputStream s) { + this.s = s; + } + + /** + * Closes the underlying stream. + * + * @throws IOException on I/O errors. + */ + @Override + public void close() throws IOException { + s.close(); + } + + @Override + public int read() throws IOException { + if (outIndex == outCount) { + fillBuffer(); + if (outIndex == outCount) { + return -1; + } + } + + return outputBuffer[outIndex++]; + } + + /** + * Retrieve data from the underlying stream, decode it, + * and put the results in the byteq. + * @throws IOException + */ + private void fillBuffer() throws IOException { + outCount = 0; + outIndex = 0; + int inCount = 0; + + int i; + // "done" is needed for the two successive '=' at the end + while (!done) { + switch (i = s.read()) { + case -1: + // No more input - just return, let outputBuffer drain out, and be done + return; + case '=': + // once we meet the first '=', avoid reading the second '=' + done = true; + decodeAndEnqueue(inCount); + return; + default: + byte sX = TRANSLATION[i]; + if (sX < 0) continue; + inputBuffer[inCount++] = sX; + if (inCount == 4) { + decodeAndEnqueue(inCount); + return; + } + break; + } + } + } + + private void decodeAndEnqueue(int len) { + int accum = 0; + accum |= inputBuffer[0] << 18; + accum |= inputBuffer[1] << 12; + accum |= inputBuffer[2] << 6; + accum |= inputBuffer[3]; + + // There's a bit of duplicated code here because we want to have straight-through operation + // for the most common case of len==4 + if (len == 4) { + outputBuffer[0] = (accum >> 16) & 0xFF; + outputBuffer[1] = (accum >> 8) & 0xFF; + outputBuffer[2] = (accum) & 0xFF; + outCount = 3; + return; + } else if (len == 3) { + outputBuffer[0] = (accum >> 16) & 0xFF; + outputBuffer[1] = (accum >> 8) & 0xFF; + outCount = 2; + return; + } else { // len == 2 + outputBuffer[0] = (accum >> 16) & 0xFF; + outCount = 1; + return; + } + } + + private static byte[] TRANSLATION = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x00 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 0x20 */ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 0x30 */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 0x40 */ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 0x50 */ + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 0x60 */ + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 0x70 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x80 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x90 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xA0 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xB0 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xC0 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xD0 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xE0 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* 0xF0 */ + }; + + +} diff --git a/apache/org/apache/james/mime4j/decoder/ByteQueue.java b/apache/org/apache/james/mime4j/decoder/ByteQueue.java new file mode 100644 index 000000000..6d7ccef52 --- /dev/null +++ b/apache/org/apache/james/mime4j/decoder/ByteQueue.java @@ -0,0 +1,62 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.decoder; + +import java.util.Iterator; + +public class ByteQueue { + + private UnboundedFifoByteBuffer buf; + private int initialCapacity = -1; + + public ByteQueue() { + buf = new UnboundedFifoByteBuffer(); + } + + public ByteQueue(int initialCapacity) { + buf = new UnboundedFifoByteBuffer(initialCapacity); + this.initialCapacity = initialCapacity; + } + + public void enqueue(byte b) { + buf.add(b); + } + + public byte dequeue() { + return buf.remove(); + } + + public int count() { + return buf.size(); + } + + public void clear() { + if (initialCapacity != -1) + buf = new UnboundedFifoByteBuffer(initialCapacity); + else + buf = new UnboundedFifoByteBuffer(); + } + + public Iterator iterator() { + return buf.iterator(); + } + + +} diff --git a/apache/org/apache/james/mime4j/decoder/DecoderUtil.java b/apache/org/apache/james/mime4j/decoder/DecoderUtil.java new file mode 100644 index 000000000..48fe07dee --- /dev/null +++ b/apache/org/apache/james/mime4j/decoder/DecoderUtil.java @@ -0,0 +1,284 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.decoder; + +//BEGIN android-changed: Stubbing out logging +import org.apache.james.mime4j.Log; +import org.apache.james.mime4j.LogFactory; +//END android-changed +import org.apache.james.mime4j.util.CharsetUtil; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +/** + * Static methods for decoding strings, byte arrays and encoded words. + * + * + * @version $Id: DecoderUtil.java,v 1.3 2005/02/07 15:33:59 ntherning Exp $ + */ +public class DecoderUtil { + private static Log log = LogFactory.getLog(DecoderUtil.class); + + /** + * Decodes a string containing quoted-printable encoded data. + * + * @param s the string to decode. + * @return the decoded bytes. + */ + public static byte[] decodeBaseQuotedPrintable(String s) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try { + byte[] bytes = s.getBytes("US-ASCII"); + + QuotedPrintableInputStream is = new QuotedPrintableInputStream( + new ByteArrayInputStream(bytes)); + + int b = 0; + while ((b = is.read()) != -1) { + baos.write(b); + } + } catch (IOException e) { + /* + * This should never happen! + */ + log.error(e); + } + + return baos.toByteArray(); + } + + /** + * Decodes a string containing base64 encoded data. + * + * @param s the string to decode. + * @return the decoded bytes. + */ + public static byte[] decodeBase64(String s) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try { + byte[] bytes = s.getBytes("US-ASCII"); + + Base64InputStream is = new Base64InputStream( + new ByteArrayInputStream(bytes)); + + int b = 0; + while ((b = is.read()) != -1) { + baos.write(b); + } + } catch (IOException e) { + /* + * This should never happen! + */ + log.error(e); + } + + return baos.toByteArray(); + } + + /** + * Decodes an encoded word encoded with the 'B' encoding (described in + * RFC 2047) found in a header field body. + * + * @param encodedWord the encoded word to decode. + * @param charset the Java charset to use. + * @return the decoded string. + * @throws UnsupportedEncodingException if the given Java charset isn't + * supported. + */ + public static String decodeB(String encodedWord, String charset) + throws UnsupportedEncodingException { + + return new String(decodeBase64(encodedWord), charset); + } + + /** + * Decodes an encoded word encoded with the 'Q' encoding (described in + * RFC 2047) found in a header field body. + * + * @param encodedWord the encoded word to decode. + * @param charset the Java charset to use. + * @return the decoded string. + * @throws UnsupportedEncodingException if the given Java charset isn't + * supported. + */ + public static String decodeQ(String encodedWord, String charset) + throws UnsupportedEncodingException { + + /* + * Replace _ with =20 + */ + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < encodedWord.length(); i++) { + char c = encodedWord.charAt(i); + if (c == '_') { + sb.append("=20"); + } else { + sb.append(c); + } + } + + return new String(decodeBaseQuotedPrintable(sb.toString()), charset); + } + + /** + * Decodes a string containing encoded words as defined by RFC 2047. + * Encoded words in have the form + * =?charset?enc?Encoded word?= where enc is either 'Q' or 'q' for + * quoted-printable and 'B' or 'b' for Base64. + * + * ANDROID: COPIED FROM A NEWER VERSION OF MIME4J + * + * @param body the string to decode. + * @return the decoded string. + */ + public static String decodeEncodedWords(String body) { + + // ANDROID: Most strings will not include "=?" so a quick test can prevent unneeded + // object creation. This could also be handled via lazy creation of the StringBuilder. + if (body.indexOf("=?") == -1) { + return body; + } + + int previousEnd = 0; + boolean previousWasEncoded = false; + + StringBuilder sb = new StringBuilder(); + + while (true) { + int begin = body.indexOf("=?", previousEnd); + + // ANDROID: The mime4j original version has an error here. It gets confused if + // the encoded string begins with an '=' (just after "?Q?"). This patch seeks forward + // to find the two '?' in the "header", before looking for the final "?=". + if (begin == -1) { + break; + } + int qm1 = body.indexOf('?', begin + 2); + if (qm1 == -1) { + break; + } + int qm2 = body.indexOf('?', qm1 + 1); + if (qm2 == -1) { + break; + } + int end = body.indexOf("?=", qm2 + 1); + if (end == -1) { + break; + } + end += 2; + + String sep = body.substring(previousEnd, begin); + + String decoded = decodeEncodedWord(body, begin, end); + if (decoded == null) { + sb.append(sep); + sb.append(body.substring(begin, end)); + } else { + if (!previousWasEncoded || !CharsetUtil.isWhitespace(sep)) { + sb.append(sep); + } + sb.append(decoded); + } + + previousEnd = end; + previousWasEncoded = decoded != null; + } + + if (previousEnd == 0) + return body; + + sb.append(body.substring(previousEnd)); + return sb.toString(); + } + + // return null on error. Begin is index of '=?' in body. + public static String decodeEncodedWord(String body, int begin, int end) { + // Skip the '?=' chars in body and scan forward from there for next '?' + int qm1 = body.indexOf('?', begin + 2); + if (qm1 == -1 || qm1 == end - 2) + return null; + + int qm2 = body.indexOf('?', qm1 + 1); + if (qm2 == -1 || qm2 == end - 2) + return null; + + String mimeCharset = body.substring(begin + 2, qm1); + String encoding = body.substring(qm1 + 1, qm2); + String encodedText = body.substring(qm2 + 1, end - 2); + + String charset = CharsetUtil.toJavaCharset(mimeCharset); + if (charset == null) { + if (log.isWarnEnabled()) { + log.warn("MIME charset '" + mimeCharset + "' in encoded word '" + + body.substring(begin, end) + "' doesn't have a " + + "corresponding Java charset"); + } + return null; + } else if (!CharsetUtil.isDecodingSupported(charset)) { + if (log.isWarnEnabled()) { + log.warn("Current JDK doesn't support decoding of charset '" + + charset + "' (MIME charset '" + mimeCharset + + "' in encoded word '" + body.substring(begin, end) + + "')"); + } + return null; + } + + if (encodedText.length() == 0) { + if (log.isWarnEnabled()) { + log.warn("Missing encoded text in encoded word: '" + + body.substring(begin, end) + "'"); + } + return null; + } + + try { + if (encoding.equalsIgnoreCase("Q")) { + return DecoderUtil.decodeQ(encodedText, charset); + } else if (encoding.equalsIgnoreCase("B")) { + return DecoderUtil.decodeB(encodedText, charset); + } else { + if (log.isWarnEnabled()) { + log.warn("Warning: Unknown encoding in encoded word '" + + body.substring(begin, end) + "'"); + } + return null; + } + } catch (UnsupportedEncodingException e) { + // should not happen because of isDecodingSupported check above + if (log.isWarnEnabled()) { + log.warn("Unsupported encoding in encoded word '" + + body.substring(begin, end) + "'", e); + } + return null; + } catch (RuntimeException e) { + if (log.isWarnEnabled()) { + log.warn("Could not decode encoded word '" + + body.substring(begin, end) + "'", e); + } + return null; + } + } +} diff --git a/apache/org/apache/james/mime4j/decoder/QuotedPrintableInputStream.java b/apache/org/apache/james/mime4j/decoder/QuotedPrintableInputStream.java new file mode 100644 index 000000000..e43f398f9 --- /dev/null +++ b/apache/org/apache/james/mime4j/decoder/QuotedPrintableInputStream.java @@ -0,0 +1,229 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.decoder; + +import java.io.IOException; +import java.io.InputStream; + +//BEGIN android-changed: Stubbing out logging +import org.apache.james.mime4j.Log; +import org.apache.james.mime4j.LogFactory; +//END android-changed + +/** + * Performs Quoted-Printable decoding on an underlying stream. + * + * + * + * @version $Id: QuotedPrintableInputStream.java,v 1.3 2004/11/29 13:15:47 ntherning Exp $ + */ +public class QuotedPrintableInputStream extends InputStream { + private static Log log = LogFactory.getLog(QuotedPrintableInputStream.class); + + private InputStream stream; + ByteQueue byteq = new ByteQueue(); + ByteQueue pushbackq = new ByteQueue(); + private byte state = 0; + + public QuotedPrintableInputStream(InputStream stream) { + this.stream = stream; + } + + /** + * Closes the underlying stream. + * + * @throws IOException on I/O errors. + */ + public void close() throws IOException { + stream.close(); + } + + public int read() throws IOException { + fillBuffer(); + if (byteq.count() == 0) + return -1; + else { + byte val = byteq.dequeue(); + if (val >= 0) + return val; + else + return val & 0xFF; + } + } + + /** + * Pulls bytes out of the underlying stream and places them in the + * pushback queue. This is necessary (vs. reading from the + * underlying stream directly) to detect and filter out "transport + * padding" whitespace, i.e., all whitespace that appears immediately + * before a CRLF. + * + * @throws IOException Underlying stream threw IOException. + */ + private void populatePushbackQueue() throws IOException { + //Debug.verify(pushbackq.count() == 0, "PopulatePushbackQueue called when pushback queue was not empty!"); + + if (pushbackq.count() != 0) + return; + + while (true) { + int i = stream.read(); + switch (i) { + case -1: + // stream is done + pushbackq.clear(); // discard any whitespace preceding EOF + return; + case ' ': + case '\t': + pushbackq.enqueue((byte)i); + break; + case '\r': + case '\n': + pushbackq.clear(); // discard any whitespace preceding EOL + pushbackq.enqueue((byte)i); + return; + default: + pushbackq.enqueue((byte)i); + return; + } + } + } + + /** + * Causes the pushback queue to get populated if it is empty, then + * consumes and decodes bytes out of it until one or more bytes are + * in the byte queue. This decoding step performs the actual QP + * decoding. + * + * @throws IOException Underlying stream threw IOException. + */ + private void fillBuffer() throws IOException { + byte msdChar = 0; // first digit of escaped num + while (byteq.count() == 0) { + if (pushbackq.count() == 0) { + populatePushbackQueue(); + if (pushbackq.count() == 0) + return; + } + + byte b = (byte)pushbackq.dequeue(); + + switch (state) { + case 0: // start state, no bytes pending + if (b != '=') { + byteq.enqueue(b); + break; // state remains 0 + } else { + state = 1; + break; + } + case 1: // encountered "=" so far + if (b == '\r') { + state = 2; + break; + } else if ((b >= '0' && b <= '9') || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f')) { + state = 3; + msdChar = b; // save until next digit encountered + break; + } else if (b == '=') { + /* + * Special case when == is encountered. + * Emit one = and stay in this state. + */ + if (log.isWarnEnabled()) { + log.warn("Malformed MIME; got =="); + } + byteq.enqueue((byte)'='); + break; + } else { + if (log.isWarnEnabled()) { + log.warn("Malformed MIME; expected \\r or " + + "[0-9A-Z], got " + b); + } + state = 0; + byteq.enqueue((byte)'='); + byteq.enqueue(b); + break; + } + case 2: // encountered "=\r" so far + if (b == '\n') { + state = 0; + break; + } else { + if (log.isWarnEnabled()) { + log.warn("Malformed MIME; expected " + + (int)'\n' + ", got " + b); + } + state = 0; + byteq.enqueue((byte)'='); + byteq.enqueue((byte)'\r'); + byteq.enqueue(b); + break; + } + case 3: // encountered =<digit> so far; expecting another <digit> to complete the octet + if ((b >= '0' && b <= '9') || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f')) { + byte msd = asciiCharToNumericValue(msdChar); + byte low = asciiCharToNumericValue(b); + state = 0; + byteq.enqueue((byte)((msd << 4) | low)); + break; + } else { + if (log.isWarnEnabled()) { + log.warn("Malformed MIME; expected " + + "[0-9A-Z], got " + b); + } + state = 0; + byteq.enqueue((byte)'='); + byteq.enqueue(msdChar); + byteq.enqueue(b); + break; + } + default: // should never happen + log.error("Illegal state: " + state); + state = 0; + byteq.enqueue(b); + break; + } + } + } + + /** + * Converts '0' => 0, 'A' => 10, etc. + * @param c ASCII character value. + * @return Numeric value of hexadecimal character. + */ + private byte asciiCharToNumericValue(byte c) { + if (c >= '0' && c <= '9') { + return (byte)(c - '0'); + } else if (c >= 'A' && c <= 'Z') { + return (byte)(0xA + (c - 'A')); + } else if (c >= 'a' && c <= 'z') { + return (byte)(0xA + (c - 'a')); + } else { + /* + * This should never happen since all calls to this method + * are preceded by a check that c is in [0-9A-Za-z] + */ + throw new IllegalArgumentException((char) c + + " is not a hexadecimal digit"); + } + } + +} diff --git a/apache/org/apache/james/mime4j/decoder/UnboundedFifoByteBuffer.java b/apache/org/apache/james/mime4j/decoder/UnboundedFifoByteBuffer.java new file mode 100644 index 000000000..f01194fd1 --- /dev/null +++ b/apache/org/apache/james/mime4j/decoder/UnboundedFifoByteBuffer.java @@ -0,0 +1,272 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.decoder; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * UnboundedFifoByteBuffer is a very efficient buffer implementation. + * According to performance testing, it exhibits a constant access time, but it + * also outperforms ArrayList when used for the same purpose. + * <p> + * The removal order of an <code>UnboundedFifoByteBuffer</code> is based on the insertion + * order; elements are removed in the same order in which they were added. + * The iteration order is the same as the removal order. + * <p> + * The {@link #remove()} and {@link #get()} operations perform in constant time. + * The {@link #add(Object)} operation performs in amortized constant time. All + * other operations perform in linear time or worse. + * <p> + * Note that this implementation is not synchronized. The following can be + * used to provide synchronized access to your <code>UnboundedFifoByteBuffer</code>: + * <pre> + * Buffer fifo = BufferUtils.synchronizedBuffer(new UnboundedFifoByteBuffer()); + * </pre> + * <p> + * This buffer prevents null objects from being added. + * + * @since Commons Collections 3.0 (previously in main package v2.1) + * @version $Revision: 1.1 $ $Date: 2004/08/24 06:52:02 $ + * + * + * + * + * + * + */ +class UnboundedFifoByteBuffer { + + protected byte[] buffer; + protected int head; + protected int tail; + + /** + * Constructs an UnboundedFifoByteBuffer with the default number of elements. + * It is exactly the same as performing the following: + * + * <pre> + * new UnboundedFifoByteBuffer(32); + * </pre> + */ + public UnboundedFifoByteBuffer() { + this(32); + } + + /** + * Constructs an UnboundedFifoByteBuffer with the specified number of elements. + * The integer must be a positive integer. + * + * @param initialSize the initial size of the buffer + * @throws IllegalArgumentException if the size is less than 1 + */ + public UnboundedFifoByteBuffer(int initialSize) { + if (initialSize <= 0) { + throw new IllegalArgumentException("The size must be greater than 0"); + } + buffer = new byte[initialSize + 1]; + head = 0; + tail = 0; + } + + /** + * Returns the number of elements stored in the buffer. + * + * @return this buffer's size + */ + public int size() { + int size = 0; + + if (tail < head) { + size = buffer.length - head + tail; + } else { + size = tail - head; + } + + return size; + } + + /** + * Returns true if this buffer is empty; false otherwise. + * + * @return true if this buffer is empty + */ + public boolean isEmpty() { + return (size() == 0); + } + + /** + * Adds the given element to this buffer. + * + * @param b the byte to add + * @return true, always + */ + public boolean add(final byte b) { + + if (size() + 1 >= buffer.length) { + byte[] tmp = new byte[((buffer.length - 1) * 2) + 1]; + + int j = 0; + for (int i = head; i != tail;) { + tmp[j] = buffer[i]; + buffer[i] = 0; + + j++; + i++; + if (i == buffer.length) { + i = 0; + } + } + + buffer = tmp; + head = 0; + tail = j; + } + + buffer[tail] = b; + tail++; + if (tail >= buffer.length) { + tail = 0; + } + return true; + } + + /** + * Returns the next object in the buffer. + * + * @return the next object in the buffer + * @throws BufferUnderflowException if this buffer is empty + */ + public byte get() { + if (isEmpty()) { + throw new IllegalStateException("The buffer is already empty"); + } + + return buffer[head]; + } + + /** + * Removes the next object from the buffer + * + * @return the removed object + * @throws BufferUnderflowException if this buffer is empty + */ + public byte remove() { + if (isEmpty()) { + throw new IllegalStateException("The buffer is already empty"); + } + + byte element = buffer[head]; + + head++; + if (head >= buffer.length) { + head = 0; + } + + return element; + } + + /** + * Increments the internal index. + * + * @param index the index to increment + * @return the updated index + */ + private int increment(int index) { + index++; + if (index >= buffer.length) { + index = 0; + } + return index; + } + + /** + * Decrements the internal index. + * + * @param index the index to decrement + * @return the updated index + */ + private int decrement(int index) { + index--; + if (index < 0) { + index = buffer.length - 1; + } + return index; + } + + /** + * Returns an iterator over this buffer's elements. + * + * @return an iterator over this buffer's elements + */ + public Iterator iterator() { + return new Iterator() { + + private int index = head; + private int lastReturnedIndex = -1; + + public boolean hasNext() { + return index != tail; + + } + + public Object next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + lastReturnedIndex = index; + index = increment(index); + return new Byte(buffer[lastReturnedIndex]); + } + + public void remove() { + if (lastReturnedIndex == -1) { + throw new IllegalStateException(); + } + + // First element can be removed quickly + if (lastReturnedIndex == head) { + UnboundedFifoByteBuffer.this.remove(); + lastReturnedIndex = -1; + return; + } + + // Other elements require us to shift the subsequent elements + int i = lastReturnedIndex + 1; + while (i != tail) { + if (i >= buffer.length) { + buffer[i - 1] = buffer[0]; + i = 0; + } else { + buffer[i - 1] = buffer[i]; + i++; + } + } + + lastReturnedIndex = -1; + tail = decrement(tail); + buffer[tail] = 0; + index = decrement(index); + } + + }; + } + +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/field/AddressListField.java b/apache/org/apache/james/mime4j/field/AddressListField.java new file mode 100644 index 000000000..df9f39835 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/AddressListField.java @@ -0,0 +1,65 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +//BEGIN android-changed: Stubbing out logging +import org.apache.james.mime4j.Log; +import org.apache.james.mime4j.LogFactory; +//END android-changed +import org.apache.james.mime4j.field.address.AddressList; +import org.apache.james.mime4j.field.address.parser.ParseException; + +public class AddressListField extends Field { + private AddressList addressList; + private ParseException parseException; + + protected AddressListField(String name, String body, String raw, AddressList addressList, ParseException parseException) { + super(name, body, raw); + this.addressList = addressList; + this.parseException = parseException; + } + + public AddressList getAddressList() { + return addressList; + } + + public ParseException getParseException() { + return parseException; + } + + public static class Parser implements FieldParser { + private static Log log = LogFactory.getLog(Parser.class); + + public Field parse(final String name, final String body, final String raw) { + AddressList addressList = null; + ParseException parseException = null; + try { + addressList = AddressList.parse(body); + } + catch (ParseException e) { + if (log.isDebugEnabled()) { + log.debug("Parsing value '" + body + "': "+ e.getMessage()); + } + parseException = e; + } + return new AddressListField(name, body, raw, addressList, parseException); + } + } +} diff --git a/apache/org/apache/james/mime4j/field/ContentTransferEncodingField.java b/apache/org/apache/james/mime4j/field/ContentTransferEncodingField.java new file mode 100644 index 000000000..73d8d2339 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/ContentTransferEncodingField.java @@ -0,0 +1,88 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + + + +/** + * Represents a <code>Content-Transfer-Encoding</code> field. + * + * + * @version $Id: ContentTransferEncodingField.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $ + */ +public class ContentTransferEncodingField extends Field { + /** + * The <code>7bit</code> encoding. + */ + public static final String ENC_7BIT = "7bit"; + /** + * The <code>8bit</code> encoding. + */ + public static final String ENC_8BIT = "8bit"; + /** + * The <code>binary</code> encoding. + */ + public static final String ENC_BINARY = "binary"; + /** + * The <code>quoted-printable</code> encoding. + */ + public static final String ENC_QUOTED_PRINTABLE = "quoted-printable"; + /** + * The <code>base64</code> encoding. + */ + public static final String ENC_BASE64 = "base64"; + + private String encoding; + + protected ContentTransferEncodingField(String name, String body, String raw, String encoding) { + super(name, body, raw); + this.encoding = encoding; + } + + /** + * Gets the encoding defined in this field. + * + * @return the encoding or an empty string if not set. + */ + public String getEncoding() { + return encoding; + } + + /** + * Gets the encoding of the given field if. Returns the default + * <code>7bit</code> if not set or if + * <code>f</code> is <code>null</code>. + * + * @return the encoding. + */ + public static String getEncoding(ContentTransferEncodingField f) { + if (f != null && f.getEncoding().length() != 0) { + return f.getEncoding(); + } + return ENC_7BIT; + } + + public static class Parser implements FieldParser { + public Field parse(final String name, final String body, final String raw) { + final String encoding = body.trim().toLowerCase(); + return new ContentTransferEncodingField(name, body, raw, encoding); + } + } +} diff --git a/apache/org/apache/james/mime4j/field/ContentTypeField.java b/apache/org/apache/james/mime4j/field/ContentTypeField.java new file mode 100644 index 000000000..ad9f7f9ac --- /dev/null +++ b/apache/org/apache/james/mime4j/field/ContentTypeField.java @@ -0,0 +1,259 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +//BEGIN android-changed: Stubbing out logging +import org.apache.james.mime4j.Log; +import org.apache.james.mime4j.LogFactory; +//END android-changed +import org.apache.james.mime4j.field.contenttype.parser.ContentTypeParser; +import org.apache.james.mime4j.field.contenttype.parser.ParseException; +import org.apache.james.mime4j.field.contenttype.parser.TokenMgrError; + +/** + * Represents a <code>Content-Type</code> field. + * + * <p>TODO: Remove dependency on Java 1.4 regexps</p> + * + * + * @version $Id: ContentTypeField.java,v 1.6 2005/01/27 14:16:31 ntherning Exp $ + */ +public class ContentTypeField extends Field { + + /** + * The prefix of all <code>multipart</code> MIME types. + */ + public static final String TYPE_MULTIPART_PREFIX = "multipart/"; + /** + * The <code>multipart/digest</code> MIME type. + */ + public static final String TYPE_MULTIPART_DIGEST = "multipart/digest"; + /** + * The <code>text/plain</code> MIME type. + */ + public static final String TYPE_TEXT_PLAIN = "text/plain"; + /** + * The <code>message/rfc822</code> MIME type. + */ + public static final String TYPE_MESSAGE_RFC822 = "message/rfc822"; + /** + * The name of the <code>boundary</code> parameter. + */ + public static final String PARAM_BOUNDARY = "boundary"; + /** + * The name of the <code>charset</code> parameter. + */ + public static final String PARAM_CHARSET = "charset"; + + private String mimeType = ""; + private Map<String, String> parameters = null; + private ParseException parseException; + + protected ContentTypeField(String name, String body, String raw, String mimeType, Map<String, String> parameters, ParseException parseException) { + super(name, body, raw); + this.mimeType = mimeType; + this.parameters = parameters; + this.parseException = parseException; + } + + /** + * Gets the exception that was raised during parsing of + * the field value, if any; otherwise, null. + */ + public ParseException getParseException() { + return parseException; + } + + /** + * Gets the MIME type defined in this Content-Type field. + * + * @return the MIME type or an empty string if not set. + */ + public String getMimeType() { + return mimeType; + } + + /** + * Gets the MIME type defined in the child's + * Content-Type field or derives a MIME type from the parent + * if child is <code>null</code> or hasn't got a MIME type value set. + * If child's MIME type is multipart but no boundary + * has been set the MIME type of child will be derived from + * the parent. + * + * @param child the child. + * @param parent the parent. + * @return the MIME type. + */ + public static String getMimeType(ContentTypeField child, + ContentTypeField parent) { + + if (child == null || child.getMimeType().length() == 0 + || child.isMultipart() && child.getBoundary() == null) { + + if (parent != null && parent.isMimeType(TYPE_MULTIPART_DIGEST)) { + return TYPE_MESSAGE_RFC822; + } else { + return TYPE_TEXT_PLAIN; + } + } + + return child.getMimeType(); + } + + /** + * Gets the value of a parameter. Parameter names are case-insensitive. + * + * @param name the name of the parameter to get. + * @return the parameter value or <code>null</code> if not set. + */ + public String getParameter(String name) { + return parameters != null + ? parameters.get(name.toLowerCase()) + : null; + } + + /** + * Gets all parameters. + * + * @return the parameters. + */ + public Map<String, String> getParameters() { + if (parameters != null) { + return Collections.unmodifiableMap(parameters); + } + return Collections.emptyMap(); + } + + /** + * Gets the value of the <code>boundary</code> parameter if set. + * + * @return the <code>boundary</code> parameter value or <code>null</code> + * if not set. + */ + public String getBoundary() { + return getParameter(PARAM_BOUNDARY); + } + + /** + * Gets the value of the <code>charset</code> parameter if set. + * + * @return the <code>charset</code> parameter value or <code>null</code> + * if not set. + */ + public String getCharset() { + return getParameter(PARAM_CHARSET); + } + + /** + * Gets the value of the <code>charset</code> parameter if set for the + * given field. Returns the default <code>us-ascii</code> if not set or if + * <code>f</code> is <code>null</code>. + * + * @return the <code>charset</code> parameter value. + */ + public static String getCharset(ContentTypeField f) { + if (f != null) { + if (f.getCharset() != null && f.getCharset().length() > 0) { + return f.getCharset(); + } + } + return "us-ascii"; + } + + /** + * Determines if the MIME type of this field matches the given one. + * + * @param mimeType the MIME type to match against. + * @return <code>true</code> if the MIME type of this field matches, + * <code>false</code> otherwise. + */ + public boolean isMimeType(String mimeType) { + return this.mimeType.equalsIgnoreCase(mimeType); + } + + /** + * Determines if the MIME type of this field is <code>multipart/*</code>. + * + * @return <code>true</code> if this field is has a <code>multipart/*</code> + * MIME type, <code>false</code> otherwise. + */ + public boolean isMultipart() { + return mimeType.startsWith(TYPE_MULTIPART_PREFIX); + } + + public static class Parser implements FieldParser { + private static Log log = LogFactory.getLog(Parser.class); + + public Field parse(final String name, final String body, final String raw) { + ParseException parseException = null; + String mimeType = ""; + Map<String, String> parameters = null; + + ContentTypeParser parser = new ContentTypeParser(new StringReader(body)); + try { + parser.parseAll(); + } + catch (ParseException e) { + if (log.isDebugEnabled()) { + log.debug("Parsing value '" + body + "': "+ e.getMessage()); + } + parseException = e; + } + catch (TokenMgrError e) { + if (log.isDebugEnabled()) { + log.debug("Parsing value '" + body + "': "+ e.getMessage()); + } + parseException = new ParseException(e.getMessage()); + } + + try { + final String type = parser.getType(); + final String subType = parser.getSubType(); + + if (type != null && subType != null) { + mimeType = (type + "/" + parser.getSubType()).toLowerCase(); + + ArrayList<String> paramNames = parser.getParamNames(); + ArrayList<String> paramValues = parser.getParamValues(); + + if (paramNames != null && paramValues != null) { + for (int i = 0; i < paramNames.size() && i < paramValues.size(); i++) { + if (parameters == null) + parameters = new HashMap<String, String>((int)(paramNames.size() * 1.3 + 1)); + String paramName = paramNames.get(i).toLowerCase(); + String paramValue = paramValues.get(i); + parameters.put(paramName, paramValue); + } + } + } + } + catch (NullPointerException npe) { + } + return new ContentTypeField(name, body, raw, mimeType, parameters, parseException); + } + } +} diff --git a/apache/org/apache/james/mime4j/field/DateTimeField.java b/apache/org/apache/james/mime4j/field/DateTimeField.java new file mode 100644 index 000000000..2336d99db --- /dev/null +++ b/apache/org/apache/james/mime4j/field/DateTimeField.java @@ -0,0 +1,96 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +//BEGIN android-changed: Stubbing out logging + +import android.text.TextUtils; +import java.util.regex.Pattern; +import org.apache.james.mime4j.Log; +import org.apache.james.mime4j.LogFactory; + +//END +import org.apache.james.mime4j.field.datetime.DateTime; +import org.apache.james.mime4j.field.datetime.parser.ParseException; + +import java.util.Date; + +public class DateTimeField extends Field { + private Date date; + private ParseException parseException; + + //BEGIN android-changed + // "GMT" + "+" or "-" + 4 digits + private static final Pattern DATE_CLEANUP_PATTERN_WRONG_TIMEZONE = + Pattern.compile("GMT([-+]\\d{4})$"); + //END android-changed + + protected DateTimeField(String name, String body, String raw, Date date, ParseException parseException) { + super(name, body, raw); + this.date = date; + this.parseException = parseException; + } + + public Date getDate() { + return date; + } + + public ParseException getParseException() { + return parseException; + } + + public static class Parser implements FieldParser { + private static Log log = LogFactory.getLog(Parser.class); + + public Field parse(final String name, String body, final String raw) { + Date date = null; + ParseException parseException = null; + //BEGIN android-changed + body = cleanUpMimeDate(body); + //END android-changed + try { + date = DateTime.parse(body).getDate(); + } + catch (ParseException e) { + if (log.isDebugEnabled()) { + log.debug("Parsing value '" + body + "': "+ e.getMessage()); + } + parseException = e; + } + return new DateTimeField(name, body, raw, date, parseException); + } + } + + //BEGIN android-changed + /** + * Try to make a date MIME(RFC 2822/5322)-compliant. + * + * <p>It fixes: - "Thu, 10 Dec 09 15:08:08 GMT-0700" to "Thu, 10 Dec 09 15:08:08 -0700" (4 digit + * zone value can't be preceded by "GMT") We got a report saying eBay sends a date in this format + */ + private static String cleanUpMimeDate(String date) { + if (TextUtils.isEmpty(date)) { + return date; + } + date = DATE_CLEANUP_PATTERN_WRONG_TIMEZONE.matcher(date).replaceFirst("$1"); + return date; + } + //END android-changed +} diff --git a/apache/org/apache/james/mime4j/field/DefaultFieldParser.java b/apache/org/apache/james/mime4j/field/DefaultFieldParser.java new file mode 100644 index 000000000..3695afe3e --- /dev/null +++ b/apache/org/apache/james/mime4j/field/DefaultFieldParser.java @@ -0,0 +1,45 @@ +/* + * Copyright 2006 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field; + +public class DefaultFieldParser extends DelegatingFieldParser { + + public DefaultFieldParser() { + setFieldParser(Field.CONTENT_TRANSFER_ENCODING, new ContentTransferEncodingField.Parser()); + setFieldParser(Field.CONTENT_TYPE, new ContentTypeField.Parser()); + + final DateTimeField.Parser dateTimeParser = new DateTimeField.Parser(); + setFieldParser(Field.DATE, dateTimeParser); + setFieldParser(Field.RESENT_DATE, dateTimeParser); + + final MailboxListField.Parser mailboxListParser = new MailboxListField.Parser(); + setFieldParser(Field.FROM, mailboxListParser); + setFieldParser(Field.RESENT_FROM, mailboxListParser); + + final MailboxField.Parser mailboxParser = new MailboxField.Parser(); + setFieldParser(Field.SENDER, mailboxParser); + setFieldParser(Field.RESENT_SENDER, mailboxParser); + + final AddressListField.Parser addressListParser = new AddressListField.Parser(); + setFieldParser(Field.TO, addressListParser); + setFieldParser(Field.RESENT_TO, addressListParser); + setFieldParser(Field.CC, addressListParser); + setFieldParser(Field.RESENT_CC, addressListParser); + setFieldParser(Field.BCC, addressListParser); + setFieldParser(Field.RESENT_BCC, addressListParser); + setFieldParser(Field.REPLY_TO, addressListParser); + } +} diff --git a/apache/org/apache/james/mime4j/field/DelegatingFieldParser.java b/apache/org/apache/james/mime4j/field/DelegatingFieldParser.java new file mode 100644 index 000000000..32b69ec13 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/DelegatingFieldParser.java @@ -0,0 +1,47 @@ +/* + * Copyright 2006 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field; + +import java.util.HashMap; +import java.util.Map; + +public class DelegatingFieldParser implements FieldParser { + + private Map<String, FieldParser> parsers = new HashMap<String, FieldParser>(); + private FieldParser defaultParser = new UnstructuredField.Parser(); + + /** + * Sets the parser used for the field named <code>name</code>. + * @param name the name of the field + * @param parser the parser for fields named <code>name</code> + */ + public void setFieldParser(final String name, final FieldParser parser) { + parsers.put(name.toLowerCase(), parser); + } + + public FieldParser getParser(final String name) { + final FieldParser field = parsers.get(name.toLowerCase()); + if(field==null) { + return defaultParser; + } + return field; + } + + public Field parse(final String name, final String body, final String raw) { + final FieldParser parser = getParser(name); + return parser.parse(name, body, raw); + } +} diff --git a/apache/org/apache/james/mime4j/field/Field.java b/apache/org/apache/james/mime4j/field/Field.java new file mode 100644 index 000000000..4dea5c5cf --- /dev/null +++ b/apache/org/apache/james/mime4j/field/Field.java @@ -0,0 +1,192 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * The base class of all field classes. + * + * + * @version $Id: Field.java,v 1.6 2004/10/25 07:26:46 ntherning Exp $ + */ +public abstract class Field { + public static final String SENDER = "Sender"; + public static final String FROM = "From"; + public static final String TO = "To"; + public static final String CC = "Cc"; + public static final String BCC = "Bcc"; + public static final String REPLY_TO = "Reply-To"; + public static final String RESENT_SENDER = "Resent-Sender"; + public static final String RESENT_FROM = "Resent-From"; + public static final String RESENT_TO = "Resent-To"; + public static final String RESENT_CC = "Resent-Cc"; + public static final String RESENT_BCC = "Resent-Bcc"; + + public static final String DATE = "Date"; + public static final String RESENT_DATE = "Resent-Date"; + + public static final String SUBJECT = "Subject"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_TRANSFER_ENCODING = + "Content-Transfer-Encoding"; + + private static final String FIELD_NAME_PATTERN = + "^([\\x21-\\x39\\x3b-\\x7e]+)[ \t]*:"; + private static final Pattern fieldNamePattern = + Pattern.compile(FIELD_NAME_PATTERN); + + private static final DefaultFieldParser parser = new DefaultFieldParser(); + + private final String name; + private final String body; + private final String raw; + + protected Field(final String name, final String body, final String raw) { + this.name = name; + this.body = body; + this.raw = raw; + } + + /** + * Parses the given string and returns an instance of the + * <code>Field</code> class. The type of the class returned depends on + * the field name: + * <table> + * <tr> + * <td><em>Field name</em></td><td><em>Class returned</em></td> + * <td>Content-Type</td><td>org.apache.james.mime4j.field.ContentTypeField</td> + * <td>other</td><td>org.apache.james.mime4j.field.UnstructuredField</td> + * </tr> + * </table> + * + * @param s the string to parse. + * @return a <code>Field</code> instance. + * @throws IllegalArgumentException on parse errors. + */ + public static Field parse(final String raw) { + + /* + * Unfold the field. + */ + final String unfolded = raw.replaceAll("\r|\n", ""); + + /* + * Split into name and value. + */ + final Matcher fieldMatcher = fieldNamePattern.matcher(unfolded); + if (!fieldMatcher.find()) { + throw new IllegalArgumentException("Invalid field in string"); + } + final String name = fieldMatcher.group(1); + + String body = unfolded.substring(fieldMatcher.end()); + if (body.length() > 0 && body.charAt(0) == ' ') { + body = body.substring(1); + } + + return parser.parse(name, body, raw); + } + + /** + * Gets the default parser used to parse fields. + * @return the default field parser + */ + public static DefaultFieldParser getParser() { + return parser; + } + + /** + * Gets the name of the field (<code>Subject</code>, + * <code>From</code>, etc). + * + * @return the field name. + */ + public String getName() { + return name; + } + + /** + * Gets the original raw field string. + * + * @return the original raw field string. + */ + public String getRaw() { + return raw; + } + + /** + * Gets the unfolded, unparsed and possibly encoded (see RFC 2047) field + * body string. + * + * @return the unfolded unparsed field body string. + */ + public String getBody() { + return body; + } + + /** + * Determines if this is a <code>Content-Type</code> field. + * + * @return <code>true</code> if this is a <code>Content-Type</code> field, + * <code>false</code> otherwise. + */ + public boolean isContentType() { + return CONTENT_TYPE.equalsIgnoreCase(name); + } + + /** + * Determines if this is a <code>Subject</code> field. + * + * @return <code>true</code> if this is a <code>Subject</code> field, + * <code>false</code> otherwise. + */ + public boolean isSubject() { + return SUBJECT.equalsIgnoreCase(name); + } + + /** + * Determines if this is a <code>From</code> field. + * + * @return <code>true</code> if this is a <code>From</code> field, + * <code>false</code> otherwise. + */ + public boolean isFrom() { + return FROM.equalsIgnoreCase(name); + } + + /** + * Determines if this is a <code>To</code> field. + * + * @return <code>true</code> if this is a <code>To</code> field, + * <code>false</code> otherwise. + */ + public boolean isTo() { + return TO.equalsIgnoreCase(name); + } + + /** + * @see #getRaw() + */ + public String toString() { + return raw; + } +} diff --git a/apache/org/apache/james/mime4j/field/FieldParser.java b/apache/org/apache/james/mime4j/field/FieldParser.java new file mode 100644 index 000000000..78aaf1334 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/FieldParser.java @@ -0,0 +1,21 @@ +/* + * Copyright 2006 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field; + +public interface FieldParser { + + Field parse(final String name, final String body, final String raw); +} diff --git a/apache/org/apache/james/mime4j/field/MailboxField.java b/apache/org/apache/james/mime4j/field/MailboxField.java new file mode 100644 index 000000000..f15980055 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/MailboxField.java @@ -0,0 +1,70 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +//BEGIN android-changed: Stubbing out logging +import org.apache.james.mime4j.Log; +import org.apache.james.mime4j.LogFactory; +//END android-changed +import org.apache.james.mime4j.field.address.AddressList; +import org.apache.james.mime4j.field.address.Mailbox; +import org.apache.james.mime4j.field.address.MailboxList; +import org.apache.james.mime4j.field.address.parser.ParseException; + +public class MailboxField extends Field { + private final Mailbox mailbox; + private final ParseException parseException; + + protected MailboxField(final String name, final String body, final String raw, final Mailbox mailbox, final ParseException parseException) { + super(name, body, raw); + this.mailbox = mailbox; + this.parseException = parseException; + } + + public Mailbox getMailbox() { + return mailbox; + } + + public ParseException getParseException() { + return parseException; + } + + public static class Parser implements FieldParser { + private static Log log = LogFactory.getLog(Parser.class); + + public Field parse(final String name, final String body, final String raw) { + Mailbox mailbox = null; + ParseException parseException = null; + try { + MailboxList mailboxList = AddressList.parse(body).flatten(); + if (mailboxList.size() > 0) { + mailbox = mailboxList.get(0); + } + } + catch (ParseException e) { + if (log.isDebugEnabled()) { + log.debug("Parsing value '" + body + "': "+ e.getMessage()); + } + parseException = e; + } + return new MailboxField(name, body, raw, mailbox, parseException); + } + } +} diff --git a/apache/org/apache/james/mime4j/field/MailboxListField.java b/apache/org/apache/james/mime4j/field/MailboxListField.java new file mode 100644 index 000000000..23378d4fa --- /dev/null +++ b/apache/org/apache/james/mime4j/field/MailboxListField.java @@ -0,0 +1,67 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +//BEGIN android-changed: Stubbing out logging +import org.apache.james.mime4j.Log; +import org.apache.james.mime4j.LogFactory; +//END android-changed +import org.apache.james.mime4j.field.address.AddressList; +import org.apache.james.mime4j.field.address.MailboxList; +import org.apache.james.mime4j.field.address.parser.ParseException; + +public class MailboxListField extends Field { + + private MailboxList mailboxList; + private ParseException parseException; + + protected MailboxListField(final String name, final String body, final String raw, final MailboxList mailboxList, final ParseException parseException) { + super(name, body, raw); + this.mailboxList = mailboxList; + this.parseException = parseException; + } + + public MailboxList getMailboxList() { + return mailboxList; + } + + public ParseException getParseException() { + return parseException; + } + + public static class Parser implements FieldParser { + private static Log log = LogFactory.getLog(Parser.class); + + public Field parse(final String name, final String body, final String raw) { + MailboxList mailboxList = null; + ParseException parseException = null; + try { + mailboxList = AddressList.parse(body).flatten(); + } + catch (ParseException e) { + if (log.isDebugEnabled()) { + log.debug("Parsing value '" + body + "': "+ e.getMessage()); + } + parseException = e; + } + return new MailboxListField(name, body, raw, mailboxList, parseException); + } + } +} diff --git a/apache/org/apache/james/mime4j/field/UnstructuredField.java b/apache/org/apache/james/mime4j/field/UnstructuredField.java new file mode 100644 index 000000000..6084e4435 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/UnstructuredField.java @@ -0,0 +1,49 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field; + +import org.apache.james.mime4j.decoder.DecoderUtil; + + +/** + * Simple unstructured field such as <code>Subject</code>. + * + * + * @version $Id: UnstructuredField.java,v 1.3 2004/10/25 07:26:46 ntherning Exp $ + */ +public class UnstructuredField extends Field { + private String value; + + protected UnstructuredField(String name, String body, String raw, String value) { + super(name, body, raw); + this.value = value; + } + + public String getValue() { + return value; + } + + public static class Parser implements FieldParser { + public Field parse(final String name, final String body, final String raw) { + final String value = DecoderUtil.decodeEncodedWords(body); + return new UnstructuredField(name, body, raw, value); + } + } +} diff --git a/apache/org/apache/james/mime4j/field/address/Address.java b/apache/org/apache/james/mime4j/field/address/Address.java new file mode 100644 index 000000000..3e24e91aa --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/Address.java @@ -0,0 +1,52 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address; + +import java.util.ArrayList; + +/** + * The abstract base for classes that represent RFC2822 addresses. + * This includes groups and mailboxes. + * + * Currently, no public methods are introduced on this class. + * + * + */ +public abstract class Address { + + /** + * Adds any mailboxes represented by this address + * into the given ArrayList. Note that this method + * has default (package) access, so a doAddMailboxesTo + * method is needed to allow the behavior to be + * overridden by subclasses. + */ + final void addMailboxesTo(ArrayList<Address> results) { + doAddMailboxesTo(results); + } + + /** + * Adds any mailboxes represented by this address + * into the given ArrayList. Must be overridden by + * concrete subclasses. + */ + protected abstract void doAddMailboxesTo(ArrayList<Address> results); + +} diff --git a/apache/org/apache/james/mime4j/field/address/AddressList.java b/apache/org/apache/james/mime4j/field/address/AddressList.java new file mode 100644 index 000000000..1829e79aa --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/AddressList.java @@ -0,0 +1,138 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address; + +import org.apache.james.mime4j.field.address.parser.AddressListParser; +import org.apache.james.mime4j.field.address.parser.ParseException; + +import java.io.StringReader; +import java.util.ArrayList; + +/** + * An immutable, random-access list of Address objects. + * + * + */ +public class AddressList { + + private ArrayList<Address> addresses; + + /** + * @param addresses An ArrayList that contains only Address objects. + * @param dontCopy true iff it is not possible for the addresses ArrayList to be modified by someone else. + */ + public AddressList(ArrayList<Address> addresses, boolean dontCopy) { + if (addresses != null) + this.addresses = (dontCopy ? addresses : new ArrayList<Address>(addresses)); + else + this.addresses = new ArrayList<Address>(0); + } + + /** + * The number of elements in this list. + */ + public int size() { + return addresses.size(); + } + + /** + * Gets an address. + */ + public Address get(int index) { + if (0 > index || size() <= index) + throw new IndexOutOfBoundsException(); + return addresses.get(index); + } + + /** + * Returns a flat list of all mailboxes represented + * in this address list. Use this if you don't care + * about grouping. + */ + public MailboxList flatten() { + // in the common case, all addresses are mailboxes + boolean groupDetected = false; + for (int i = 0; i < size(); i++) { + if (!(get(i) instanceof Mailbox)) { + groupDetected = true; + break; + } + } + + if (!groupDetected) + return new MailboxList(addresses, true); + + ArrayList<Address> results = new ArrayList<Address>(); + for (int i = 0; i < size(); i++) { + Address addr = get(i); + addr.addMailboxesTo(results); + } + + // copy-on-construct this time, because subclasses + // could have held onto a reference to the results + return new MailboxList(results, false); + } + + /** + * Dumps a representation of this address list to + * stdout, for debugging purposes. + */ + public void print() { + for (int i = 0; i < size(); i++) { + Address addr = get(i); + System.out.println(addr.toString()); + } + } + + /** + * Parse the address list string, such as the value + * of a From, To, Cc, Bcc, Sender, or Reply-To + * header. + * + * The string MUST be unfolded already. + */ + public static AddressList parse(String rawAddressList) throws ParseException { + AddressListParser parser = new AddressListParser(new StringReader(rawAddressList)); + return Builder.getInstance().buildAddressList(parser.parse()); + } + + /** + * Test console. + */ + public static void main(String[] args) throws Exception { + java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)); + while (true) { + try { + System.out.print("> "); + String line = reader.readLine(); + if (line.length() == 0 || line.toLowerCase().equals("exit") || line.toLowerCase().equals("quit")) { + System.out.println("Goodbye."); + return; + } + AddressList list = parse(line); + list.print(); + } + catch(Exception e) { + e.printStackTrace(); + Thread.sleep(300); + } + } + } +} diff --git a/apache/org/apache/james/mime4j/field/address/Builder.java b/apache/org/apache/james/mime4j/field/address/Builder.java new file mode 100644 index 000000000..3bcd15b6f --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/Builder.java @@ -0,0 +1,243 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address; + +import java.util.ArrayList; +import java.util.Iterator; + +import org.apache.james.mime4j.decoder.DecoderUtil; +import org.apache.james.mime4j.field.address.parser.ASTaddr_spec; +import org.apache.james.mime4j.field.address.parser.ASTaddress; +import org.apache.james.mime4j.field.address.parser.ASTaddress_list; +import org.apache.james.mime4j.field.address.parser.ASTangle_addr; +import org.apache.james.mime4j.field.address.parser.ASTdomain; +import org.apache.james.mime4j.field.address.parser.ASTgroup_body; +import org.apache.james.mime4j.field.address.parser.ASTlocal_part; +import org.apache.james.mime4j.field.address.parser.ASTmailbox; +import org.apache.james.mime4j.field.address.parser.ASTname_addr; +import org.apache.james.mime4j.field.address.parser.ASTphrase; +import org.apache.james.mime4j.field.address.parser.ASTroute; +import org.apache.james.mime4j.field.address.parser.Node; +import org.apache.james.mime4j.field.address.parser.SimpleNode; +import org.apache.james.mime4j.field.address.parser.Token; + +/** + * Transforms the JJTree-generated abstract syntax tree + * into a graph of org.apache.james.mime4j.field.address objects. + * + * + */ +class Builder { + + private static Builder singleton = new Builder(); + + public static Builder getInstance() { + return singleton; + } + + + + public AddressList buildAddressList(ASTaddress_list node) { + ArrayList<Address> list = new ArrayList<Address>(); + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + ASTaddress childNode = (ASTaddress) node.jjtGetChild(i); + Address address = buildAddress(childNode); + list.add(address); + } + return new AddressList(list, true); + } + + private Address buildAddress(ASTaddress node) { + ChildNodeIterator it = new ChildNodeIterator(node); + Node n = it.nextNode(); + if (n instanceof ASTaddr_spec) { + return buildAddrSpec((ASTaddr_spec)n); + } + else if (n instanceof ASTangle_addr) { + return buildAngleAddr((ASTangle_addr)n); + } + else if (n instanceof ASTphrase) { + String name = buildString((ASTphrase)n, false); + Node n2 = it.nextNode(); + if (n2 instanceof ASTgroup_body) { + return new Group(name, buildGroupBody((ASTgroup_body)n2)); + } + else if (n2 instanceof ASTangle_addr) { + name = DecoderUtil.decodeEncodedWords(name); + return new NamedMailbox(name, buildAngleAddr((ASTangle_addr)n2)); + } + else { + throw new IllegalStateException(); + } + } + else { + throw new IllegalStateException(); + } + } + + + + private MailboxList buildGroupBody(ASTgroup_body node) { + ArrayList<Address> results = new ArrayList<Address>(); + ChildNodeIterator it = new ChildNodeIterator(node); + while (it.hasNext()) { + Node n = it.nextNode(); + if (n instanceof ASTmailbox) + results.add(buildMailbox((ASTmailbox)n)); + else + throw new IllegalStateException(); + } + return new MailboxList(results, true); + } + + private Mailbox buildMailbox(ASTmailbox node) { + ChildNodeIterator it = new ChildNodeIterator(node); + Node n = it.nextNode(); + if (n instanceof ASTaddr_spec) { + return buildAddrSpec((ASTaddr_spec)n); + } + else if (n instanceof ASTangle_addr) { + return buildAngleAddr((ASTangle_addr)n); + } + else if (n instanceof ASTname_addr) { + return buildNameAddr((ASTname_addr)n); + } + else { + throw new IllegalStateException(); + } + } + + private NamedMailbox buildNameAddr(ASTname_addr node) { + ChildNodeIterator it = new ChildNodeIterator(node); + Node n = it.nextNode(); + String name; + if (n instanceof ASTphrase) { + name = buildString((ASTphrase)n, false); + } + else { + throw new IllegalStateException(); + } + + n = it.nextNode(); + if (n instanceof ASTangle_addr) { + name = DecoderUtil.decodeEncodedWords(name); + return new NamedMailbox(name, buildAngleAddr((ASTangle_addr) n)); + } + else { + throw new IllegalStateException(); + } + } + + private Mailbox buildAngleAddr(ASTangle_addr node) { + ChildNodeIterator it = new ChildNodeIterator(node); + DomainList route = null; + Node n = it.nextNode(); + if (n instanceof ASTroute) { + route = buildRoute((ASTroute)n); + n = it.nextNode(); + } + else if (n instanceof ASTaddr_spec) + ; // do nothing + else + throw new IllegalStateException(); + + if (n instanceof ASTaddr_spec) + return buildAddrSpec(route, (ASTaddr_spec)n); + else + throw new IllegalStateException(); + } + + private DomainList buildRoute(ASTroute node) { + ArrayList<String> results = new ArrayList<String>(node.jjtGetNumChildren()); + ChildNodeIterator it = new ChildNodeIterator(node); + while (it.hasNext()) { + Node n = it.nextNode(); + if (n instanceof ASTdomain) + results.add(buildString((ASTdomain)n, true)); + else + throw new IllegalStateException(); + } + return new DomainList(results, true); + } + + private Mailbox buildAddrSpec(ASTaddr_spec node) { + return buildAddrSpec(null, node); + } + private Mailbox buildAddrSpec(DomainList route, ASTaddr_spec node) { + ChildNodeIterator it = new ChildNodeIterator(node); + String localPart = buildString((ASTlocal_part)it.nextNode(), true); + String domain = buildString((ASTdomain)it.nextNode(), true); + return new Mailbox(route, localPart, domain); + } + + + private String buildString(SimpleNode node, boolean stripSpaces) { + Token head = node.firstToken; + Token tail = node.lastToken; + StringBuffer out = new StringBuffer(); + + while (head != tail) { + out.append(head.image); + head = head.next; + if (!stripSpaces) + addSpecials(out, head.specialToken); + } + out.append(tail.image); + + return out.toString(); + } + + private void addSpecials(StringBuffer out, Token specialToken) { + if (specialToken != null) { + addSpecials(out, specialToken.specialToken); + out.append(specialToken.image); + } + } + + private static class ChildNodeIterator implements Iterator<Node> { + + private SimpleNode simpleNode; + private int index; + private int len; + + public ChildNodeIterator(SimpleNode simpleNode) { + this.simpleNode = simpleNode; + this.len = simpleNode.jjtGetNumChildren(); + this.index = 0; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public boolean hasNext() { + return index < len; + } + + public Node next() { + return nextNode(); + } + + public Node nextNode() { + return simpleNode.jjtGetChild(index++); + } + + } +} diff --git a/apache/org/apache/james/mime4j/field/address/DomainList.java b/apache/org/apache/james/mime4j/field/address/DomainList.java new file mode 100644 index 000000000..49b0f3be5 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/DomainList.java @@ -0,0 +1,76 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address; + +import java.util.ArrayList; + +/** + * An immutable, random-access list of Strings (that + * are supposedly domain names or domain literals). + * + * + */ +public class DomainList { + private ArrayList<String> domains; + + /** + * @param domains An ArrayList that contains only String objects. + * @param dontCopy true iff it is not possible for the domains ArrayList to be modified by someone else. + */ + public DomainList(ArrayList<String> domains, boolean dontCopy) { + if (domains != null) + this.domains = (dontCopy ? domains : new ArrayList<String>(domains)); + else + this.domains = new ArrayList<String>(0); + } + + /** + * The number of elements in this list. + */ + public int size() { + return domains.size(); + } + + /** + * Gets the domain name or domain literal at the + * specified index. + * @throws IndexOutOfBoundsException If index is < 0 or >= size(). + */ + public String get(int index) { + if (0 > index || size() <= index) + throw new IndexOutOfBoundsException(); + return domains.get(index); + } + + /** + * Returns the list of domains formatted as a route + * string (not including the trailing ':'). + */ + public String toRouteString() { + StringBuffer out = new StringBuffer(); + for (int i = 0; i < domains.size(); i++) { + out.append("@"); + out.append(get(i)); + if (i + 1 < domains.size()) + out.append(","); + } + return out.toString(); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/Group.java b/apache/org/apache/james/mime4j/field/address/Group.java new file mode 100644 index 000000000..c0ab7f724 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/Group.java @@ -0,0 +1,75 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address; + +import java.util.ArrayList; + +/** + * A named group of zero or more mailboxes. + * + * + */ +public class Group extends Address { + private String name; + private MailboxList mailboxList; + + /** + * @param name The group name. + * @param mailboxes The mailboxes in this group. + */ + public Group(String name, MailboxList mailboxes) { + this.name = name; + this.mailboxList = mailboxes; + } + + /** + * Returns the group name. + */ + public String getName() { + return name; + } + + /** + * Returns the mailboxes in this group. + */ + public MailboxList getMailboxes() { + return mailboxList; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append(name); + buf.append(":"); + for (int i = 0; i < mailboxList.size(); i++) { + buf.append(mailboxList.get(i).toString()); + if (i + 1 < mailboxList.size()) + buf.append(","); + } + buf.append(";"); + return buf.toString(); + } + + @Override + protected void doAddMailboxesTo(ArrayList<Address> results) { + for (int i = 0; i < mailboxList.size(); i++) + results.add(mailboxList.get(i)); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/Mailbox.java b/apache/org/apache/james/mime4j/field/address/Mailbox.java new file mode 100644 index 000000000..25f2548d4 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/Mailbox.java @@ -0,0 +1,121 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address; + +import java.util.ArrayList; + +/** + * Represents a single e-mail address. + * + * + */ +public class Mailbox extends Address { + private DomainList route; + private String localPart; + private String domain; + + /** + * Creates a mailbox without a route. Routes are obsolete. + * @param localPart The part of the e-mail address to the left of the "@". + * @param domain The part of the e-mail address to the right of the "@". + */ + public Mailbox(String localPart, String domain) { + this(null, localPart, domain); + } + + /** + * Creates a mailbox with a route. Routes are obsolete. + * @param route The zero or more domains that make up the route. Can be null. + * @param localPart The part of the e-mail address to the left of the "@". + * @param domain The part of the e-mail address to the right of the "@". + */ + public Mailbox(DomainList route, String localPart, String domain) { + this.route = route; + this.localPart = localPart; + this.domain = domain; + } + + /** + * Returns the route list. + */ + public DomainList getRoute() { + return route; + } + + /** + * Returns the left part of the e-mail address + * (before "@"). + */ + public String getLocalPart() { + return localPart; + } + + /** + * Returns the right part of the e-mail address + * (after "@"). + */ + public String getDomain() { + return domain; + } + + /** + * Formats the address as a string, not including + * the route. + * + * @see #getAddressString(boolean) + */ + public String getAddressString() { + return getAddressString(false); + } + + /** + * Note that this value may not be usable + * for transport purposes, only display purposes. + * + * For example, if the unparsed address was + * + * <"Joe Cheng"@joecheng.com> + * + * this method would return + * + * <Joe Cheng@joecheng.com> + * + * which is not valid for transport; the local part + * would need to be re-quoted. + * + * @param includeRoute true if the route should be included if it exists. + */ + public String getAddressString(boolean includeRoute) { + return "<" + (!includeRoute || route == null ? "" : route.toRouteString() + ":") + + localPart + + (domain == null ? "" : "@") + + domain + ">"; + } + + @Override + protected final void doAddMailboxesTo(ArrayList<Address> results) { + results.add(this); + } + + @Override + public String toString() { + return getAddressString(); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/MailboxList.java b/apache/org/apache/james/mime4j/field/address/MailboxList.java new file mode 100644 index 000000000..2c9efb37f --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/MailboxList.java @@ -0,0 +1,71 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address; + +import java.util.ArrayList; + +/** + * An immutable, random-access list of Mailbox objects. + * + * + */ +public class MailboxList { + + private ArrayList<Address> mailboxes; + + /** + * @param mailboxes An ArrayList that contains only Mailbox objects. + * @param dontCopy true iff it is not possible for the mailboxes ArrayList to be modified by someone else. + */ + public MailboxList(ArrayList<Address> mailboxes, boolean dontCopy) { + if (mailboxes != null) + this.mailboxes = (dontCopy ? mailboxes : new ArrayList<Address>(mailboxes)); + else + this.mailboxes = new ArrayList<Address>(0); + } + + /** + * The number of elements in this list. + */ + public int size() { + return mailboxes.size(); + } + + /** + * Gets an address. + */ + public Mailbox get(int index) { + if (0 > index || size() <= index) + throw new IndexOutOfBoundsException(); + return (Mailbox)mailboxes.get(index); + } + + /** + * Dumps a representation of this mailbox list to + * stdout, for debugging purposes. + */ + public void print() { + for (int i = 0; i < size(); i++) { + Mailbox mailbox = get(i); + System.out.println(mailbox.toString()); + } + } + +} diff --git a/apache/org/apache/james/mime4j/field/address/NamedMailbox.java b/apache/org/apache/james/mime4j/field/address/NamedMailbox.java new file mode 100644 index 000000000..4b8306037 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/NamedMailbox.java @@ -0,0 +1,71 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address; + +/** + * A Mailbox that has a name/description. + * + * + */ +public class NamedMailbox extends Mailbox { + private String name; + + /** + * @see Mailbox#Mailbox(String, String) + */ + public NamedMailbox(String name, String localPart, String domain) { + super(localPart, domain); + this.name = name; + } + + /** + * @see Mailbox#Mailbox(DomainList, String, String) + */ + public NamedMailbox(String name, DomainList route, String localPart, String domain) { + super(route, localPart, domain); + this.name = name; + } + + /** + * Creates a named mailbox based on an unnamed mailbox. + */ + public NamedMailbox(String name, Mailbox baseMailbox) { + super(baseMailbox.getRoute(), baseMailbox.getLocalPart(), baseMailbox.getDomain()); + this.name = name; + } + + /** + * Returns the name of the mailbox. + */ + public String getName() { + return this.name; + } + + /** + * Same features (or problems) as Mailbox.getAddressString(boolean), + * only more so. + * + * @see Mailbox#getAddressString(boolean) + */ + @Override + public String getAddressString(boolean includeRoute) { + return (name == null ? "" : name + " ") + super.getAddressString(includeRoute); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTaddr_spec.java b/apache/org/apache/james/mime4j/field/address/parser/ASTaddr_spec.java new file mode 100644 index 000000000..4d56d000b --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTaddr_spec.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTaddr_spec.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTaddr_spec extends SimpleNode { + public ASTaddr_spec(int id) { + super(id); + } + + public ASTaddr_spec(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTaddress.java b/apache/org/apache/james/mime4j/field/address/parser/ASTaddress.java new file mode 100644 index 000000000..47bdeda8e --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTaddress.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTaddress.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTaddress extends SimpleNode { + public ASTaddress(int id) { + super(id); + } + + public ASTaddress(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTaddress_list.java b/apache/org/apache/james/mime4j/field/address/parser/ASTaddress_list.java new file mode 100644 index 000000000..737840e38 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTaddress_list.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTaddress_list.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTaddress_list extends SimpleNode { + public ASTaddress_list(int id) { + super(id); + } + + public ASTaddress_list(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTangle_addr.java b/apache/org/apache/james/mime4j/field/address/parser/ASTangle_addr.java new file mode 100644 index 000000000..8cb8f421f --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTangle_addr.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTangle_addr.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTangle_addr extends SimpleNode { + public ASTangle_addr(int id) { + super(id); + } + + public ASTangle_addr(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTdomain.java b/apache/org/apache/james/mime4j/field/address/parser/ASTdomain.java new file mode 100644 index 000000000..b52664386 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTdomain.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTdomain.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTdomain extends SimpleNode { + public ASTdomain(int id) { + super(id); + } + + public ASTdomain(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTgroup_body.java b/apache/org/apache/james/mime4j/field/address/parser/ASTgroup_body.java new file mode 100644 index 000000000..f6017b9fc --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTgroup_body.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTgroup_body.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTgroup_body extends SimpleNode { + public ASTgroup_body(int id) { + super(id); + } + + public ASTgroup_body(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTlocal_part.java b/apache/org/apache/james/mime4j/field/address/parser/ASTlocal_part.java new file mode 100644 index 000000000..5c244fa3e --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTlocal_part.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTlocal_part.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTlocal_part extends SimpleNode { + public ASTlocal_part(int id) { + super(id); + } + + public ASTlocal_part(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTmailbox.java b/apache/org/apache/james/mime4j/field/address/parser/ASTmailbox.java new file mode 100644 index 000000000..aeb469da1 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTmailbox.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTmailbox.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTmailbox extends SimpleNode { + public ASTmailbox(int id) { + super(id); + } + + public ASTmailbox(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTname_addr.java b/apache/org/apache/james/mime4j/field/address/parser/ASTname_addr.java new file mode 100644 index 000000000..846c73167 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTname_addr.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTname_addr.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTname_addr extends SimpleNode { + public ASTname_addr(int id) { + super(id); + } + + public ASTname_addr(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTphrase.java b/apache/org/apache/james/mime4j/field/address/parser/ASTphrase.java new file mode 100644 index 000000000..7d711c529 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTphrase.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTphrase.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTphrase extends SimpleNode { + public ASTphrase(int id) { + super(id); + } + + public ASTphrase(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ASTroute.java b/apache/org/apache/james/mime4j/field/address/parser/ASTroute.java new file mode 100644 index 000000000..54ea11523 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ASTroute.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. ASTroute.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class ASTroute extends SimpleNode { + public ASTroute(int id) { + super(id); + } + + public ASTroute(AddressListParser p, int id) { + super(p, id); + } + + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/AddressListParser.java b/apache/org/apache/james/mime4j/field/address/parser/AddressListParser.java new file mode 100644 index 000000000..8094df0ad --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/AddressListParser.java @@ -0,0 +1,977 @@ +/* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParser.java */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.address.parser; + +public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeConstants, AddressListParserConstants {/*@bgen(jjtree)*/ + protected JJTAddressListParserState jjtree = new JJTAddressListParserState();public static void main(String args[]) throws ParseException { + while (true) { + try { + AddressListParser parser = new AddressListParser(System.in); + parser.parseLine(); + ((SimpleNode)parser.jjtree.rootNode()).dump("> "); + } catch (Exception x) { + x.printStackTrace(); + return; + } + } + } + + private static void log(String msg) { + System.out.print(msg); + } + + public ASTaddress_list parse() throws ParseException { + try { + parseAll(); + return (ASTaddress_list)jjtree.rootNode(); + } catch (TokenMgrError tme) { + throw new ParseException(tme.getMessage()); + } + } + + + void jjtreeOpenNodeScope(Node n) { + ((SimpleNode)n).firstToken = getToken(1); + } + + void jjtreeCloseNodeScope(Node n) { + ((SimpleNode)n).lastToken = getToken(0); + } + + final public void parseLine() throws ParseException { + address_list(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 1: + jj_consume_token(1); + break; + default: + jj_la1[0] = jj_gen; + ; + } + jj_consume_token(2); + } + + final public void parseAll() throws ParseException { + address_list(); + jj_consume_token(0); + } + + final public void address_list() throws ParseException { + /*@bgen(jjtree) address_list */ + ASTaddress_list jjtn000 = new ASTaddress_list(JJTADDRESS_LIST); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 6: + case DOTATOM: + case QUOTEDSTRING: + address(); + break; + default: + jj_la1[1] = jj_gen; + ; + } + label_1: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 3: + ; + break; + default: + jj_la1[2] = jj_gen; + break label_1; + } + jj_consume_token(3); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 6: + case DOTATOM: + case QUOTEDSTRING: + address(); + break; + default: + jj_la1[3] = jj_gen; + ; + } + } + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final public void address() throws ParseException { + /*@bgen(jjtree) address */ + ASTaddress jjtn000 = new ASTaddress(JJTADDRESS); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); + try { + if (jj_2_1(2147483647)) { + addr_spec(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 6: + angle_addr(); + break; + case DOTATOM: + case QUOTEDSTRING: + phrase(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 4: + group_body(); + break; + case 6: + angle_addr(); + break; + default: + jj_la1[4] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + break; + default: + jj_la1[5] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final public void mailbox() throws ParseException { + /*@bgen(jjtree) mailbox */ + ASTmailbox jjtn000 = new ASTmailbox(JJTMAILBOX); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); + try { + if (jj_2_2(2147483647)) { + addr_spec(); + } else { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 6: + angle_addr(); + break; + case DOTATOM: + case QUOTEDSTRING: + name_addr(); + break; + default: + jj_la1[6] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final public void name_addr() throws ParseException { + /*@bgen(jjtree) name_addr */ + ASTname_addr jjtn000 = new ASTname_addr(JJTNAME_ADDR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); + try { + phrase(); + angle_addr(); + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final public void group_body() throws ParseException { + /*@bgen(jjtree) group_body */ + ASTgroup_body jjtn000 = new ASTgroup_body(JJTGROUP_BODY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); + try { + jj_consume_token(4); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 6: + case DOTATOM: + case QUOTEDSTRING: + mailbox(); + break; + default: + jj_la1[7] = jj_gen; + ; + } + label_2: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 3: + ; + break; + default: + jj_la1[8] = jj_gen; + break label_2; + } + jj_consume_token(3); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 6: + case DOTATOM: + case QUOTEDSTRING: + mailbox(); + break; + default: + jj_la1[9] = jj_gen; + ; + } + } + jj_consume_token(5); + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final public void angle_addr() throws ParseException { + /*@bgen(jjtree) angle_addr */ + ASTangle_addr jjtn000 = new ASTangle_addr(JJTANGLE_ADDR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); + try { + jj_consume_token(6); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 8: + route(); + break; + default: + jj_la1[10] = jj_gen; + ; + } + addr_spec(); + jj_consume_token(7); + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final public void route() throws ParseException { + /*@bgen(jjtree) route */ + ASTroute jjtn000 = new ASTroute(JJTROUTE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); + try { + jj_consume_token(8); + domain(); + label_3: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 3: + case 8: + ; + break; + default: + jj_la1[11] = jj_gen; + break label_3; + } + label_4: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 3: + ; + break; + default: + jj_la1[12] = jj_gen; + break label_4; + } + jj_consume_token(3); + } + jj_consume_token(8); + domain(); + } + jj_consume_token(4); + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final public void phrase() throws ParseException { + /*@bgen(jjtree) phrase */ + ASTphrase jjtn000 = new ASTphrase(JJTPHRASE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); + try { + label_5: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOTATOM: + jj_consume_token(DOTATOM); + break; + case QUOTEDSTRING: + jj_consume_token(QUOTEDSTRING); + break; + default: + jj_la1[13] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOTATOM: + case QUOTEDSTRING: + ; + break; + default: + jj_la1[14] = jj_gen; + break label_5; + } + } + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final public void addr_spec() throws ParseException { + /*@bgen(jjtree) addr_spec */ + ASTaddr_spec jjtn000 = new ASTaddr_spec(JJTADDR_SPEC); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); + try { + local_part(); + jj_consume_token(8); + domain(); + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + {if (true) throw (RuntimeException)jjte000;} + } + if (jjte000 instanceof ParseException) { + {if (true) throw (ParseException)jjte000;} + } + {if (true) throw (Error)jjte000;} + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final public void local_part() throws ParseException { + /*@bgen(jjtree) local_part */ + ASTlocal_part jjtn000 = new ASTlocal_part(JJTLOCAL_PART); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000);Token t; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOTATOM: + t = jj_consume_token(DOTATOM); + break; + case QUOTEDSTRING: + t = jj_consume_token(QUOTEDSTRING); + break; + default: + jj_la1[15] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + label_6: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 9: + case DOTATOM: + case QUOTEDSTRING: + ; + break; + default: + jj_la1[16] = jj_gen; + break label_6; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 9: + t = jj_consume_token(9); + break; + default: + jj_la1[17] = jj_gen; + ; + } + if (t.image.charAt(t.image.length() - 1) != '.' || t.kind == AddressListParserConstants.QUOTEDSTRING) + {if (true) throw new ParseException("Words in local part must be separated by '.'");} + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOTATOM: + t = jj_consume_token(DOTATOM); + break; + case QUOTEDSTRING: + t = jj_consume_token(QUOTEDSTRING); + break; + default: + jj_la1[18] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final public void domain() throws ParseException { + /*@bgen(jjtree) domain */ + ASTdomain jjtn000 = new ASTdomain(JJTDOMAIN); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000);Token t; + try { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case DOTATOM: + t = jj_consume_token(DOTATOM); + label_7: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 9: + case DOTATOM: + ; + break; + default: + jj_la1[19] = jj_gen; + break label_7; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 9: + t = jj_consume_token(9); + break; + default: + jj_la1[20] = jj_gen; + ; + } + if (t.image.charAt(t.image.length() - 1) != '.') + {if (true) throw new ParseException("Atoms in domain names must be separated by '.'");} + t = jj_consume_token(DOTATOM); + } + break; + case DOMAINLITERAL: + jj_consume_token(DOMAINLITERAL); + break; + default: + jj_la1[21] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } + } + + final private boolean jj_2_1(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_1(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(0, xla); } + } + + final private boolean jj_2_2(int xla) { + jj_la = xla; jj_lastpos = jj_scanpos = token; + try { return !jj_3_2(); } + catch(LookaheadSuccess ls) { return true; } + finally { jj_save(1, xla); } + } + + final private boolean jj_3R_11() { + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(9)) jj_scanpos = xsp; + xsp = jj_scanpos; + if (jj_scan_token(14)) { + jj_scanpos = xsp; + if (jj_scan_token(31)) return true; + } + return false; + } + + final private boolean jj_3R_13() { + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(9)) jj_scanpos = xsp; + if (jj_scan_token(DOTATOM)) return true; + return false; + } + + final private boolean jj_3R_8() { + if (jj_3R_9()) return true; + if (jj_scan_token(8)) return true; + if (jj_3R_10()) return true; + return false; + } + + final private boolean jj_3_1() { + if (jj_3R_8()) return true; + return false; + } + + final private boolean jj_3R_12() { + if (jj_scan_token(DOTATOM)) return true; + Token xsp; + while (true) { + xsp = jj_scanpos; + if (jj_3R_13()) { jj_scanpos = xsp; break; } + } + return false; + } + + final private boolean jj_3R_10() { + Token xsp; + xsp = jj_scanpos; + if (jj_3R_12()) { + jj_scanpos = xsp; + if (jj_scan_token(18)) return true; + } + return false; + } + + final private boolean jj_3_2() { + if (jj_3R_8()) return true; + return false; + } + + final private boolean jj_3R_9() { + Token xsp; + xsp = jj_scanpos; + if (jj_scan_token(14)) { + jj_scanpos = xsp; + if (jj_scan_token(31)) return true; + } + while (true) { + xsp = jj_scanpos; + if (jj_3R_11()) { jj_scanpos = xsp; break; } + } + return false; + } + + public AddressListParserTokenManager token_source; + SimpleCharStream jj_input_stream; + public Token token, jj_nt; + private int jj_ntk; + private Token jj_scanpos, jj_lastpos; + private int jj_la; + public boolean lookingAhead = false; + private boolean jj_semLA; + private int jj_gen; + final private int[] jj_la1 = new int[22]; + static private int[] jj_la1_0; + static private int[] jj_la1_1; + static { + jj_la1_0(); + jj_la1_1(); + } + private static void jj_la1_0() { + jj_la1_0 = new int[] {0x2,0x80004040,0x8,0x80004040,0x50,0x80004040,0x80004040,0x80004040,0x8,0x80004040,0x100,0x108,0x8,0x80004000,0x80004000,0x80004000,0x80004200,0x200,0x80004000,0x4200,0x200,0x44000,}; + } + private static void jj_la1_1() { + jj_la1_1 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,}; + } + final private JJCalls[] jj_2_rtns = new JJCalls[2]; + private boolean jj_rescan = false; + private int jj_gc = 0; + + public AddressListParser(java.io.InputStream stream) { + this(stream, null); + } + public AddressListParser(java.io.InputStream stream, String encoding) { + try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source = new AddressListParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 22; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + public void ReInit(java.io.InputStream stream) { + ReInit(stream, null); + } + public void ReInit(java.io.InputStream stream, String encoding) { + try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jjtree.reset(); + jj_gen = 0; + for (int i = 0; i < 22; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + public AddressListParser(java.io.Reader stream) { + jj_input_stream = new SimpleCharStream(stream, 1, 1); + token_source = new AddressListParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 22; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + public void ReInit(java.io.Reader stream) { + jj_input_stream.ReInit(stream, 1, 1); + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jjtree.reset(); + jj_gen = 0; + for (int i = 0; i < 22; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + public AddressListParser(AddressListParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 22; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + public void ReInit(AddressListParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jjtree.reset(); + jj_gen = 0; + for (int i = 0; i < 22; i++) jj_la1[i] = -1; + for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); + } + + final private Token jj_consume_token(int kind) throws ParseException { + Token oldToken; + if ((oldToken = token).next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + if (token.kind == kind) { + jj_gen++; + if (++jj_gc > 100) { + jj_gc = 0; + for (int i = 0; i < jj_2_rtns.length; i++) { + JJCalls c = jj_2_rtns[i]; + while (c != null) { + if (c.gen < jj_gen) c.first = null; + c = c.next; + } + } + } + return token; + } + token = oldToken; + jj_kind = kind; + throw generateParseException(); + } + + static private final class LookaheadSuccess extends java.lang.Error { } + final private LookaheadSuccess jj_ls = new LookaheadSuccess(); + final private boolean jj_scan_token(int kind) { + if (jj_scanpos == jj_lastpos) { + jj_la--; + if (jj_scanpos.next == null) { + jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken(); + } else { + jj_lastpos = jj_scanpos = jj_scanpos.next; + } + } else { + jj_scanpos = jj_scanpos.next; + } + if (jj_rescan) { + int i = 0; Token tok = token; + while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; } + if (tok != null) jj_add_error_token(kind, i); + } + if (jj_scanpos.kind != kind) return true; + if (jj_la == 0 && jj_scanpos == jj_lastpos) throw jj_ls; + return false; + } + + final public Token getNextToken() { + if (token.next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + jj_gen++; + return token; + } + + final public Token getToken(int index) { + Token t = lookingAhead ? jj_scanpos : token; + for (int i = 0; i < index; i++) { + if (t.next != null) t = t.next; + else t = t.next = token_source.getNextToken(); + } + return t; + } + + final private int jj_ntk() { + if ((jj_nt=token.next) == null) + return (jj_ntk = (token.next=token_source.getNextToken()).kind); + else + return (jj_ntk = jj_nt.kind); + } + + private java.util.Vector<int[]> jj_expentries = new java.util.Vector<int[]>(); + private int[] jj_expentry; + private int jj_kind = -1; + private int[] jj_lasttokens = new int[100]; + private int jj_endpos; + + private void jj_add_error_token(int kind, int pos) { + if (pos >= 100) return; + if (pos == jj_endpos + 1) { + jj_lasttokens[jj_endpos++] = kind; + } else if (jj_endpos != 0) { + jj_expentry = new int[jj_endpos]; + for (int i = 0; i < jj_endpos; i++) { + jj_expentry[i] = jj_lasttokens[i]; + } + boolean exists = false; + for (java.util.Enumeration<int[]> e = jj_expentries.elements(); e.hasMoreElements();) { + int[] oldentry = e.nextElement(); + if (oldentry.length == jj_expentry.length) { + exists = true; + for (int i = 0; i < jj_expentry.length; i++) { + if (oldentry[i] != jj_expentry[i]) { + exists = false; + break; + } + } + if (exists) break; + } + } + if (!exists) jj_expentries.addElement(jj_expentry); + if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind; + } + } + + public ParseException generateParseException() { + jj_expentries.removeAllElements(); + boolean[] la1tokens = new boolean[34]; + for (int i = 0; i < 34; i++) { + la1tokens[i] = false; + } + if (jj_kind >= 0) { + la1tokens[jj_kind] = true; + jj_kind = -1; + } + for (int i = 0; i < 22; i++) { + if (jj_la1[i] == jj_gen) { + for (int j = 0; j < 32; j++) { + if ((jj_la1_0[i] & (1<<j)) != 0) { + la1tokens[j] = true; + } + if ((jj_la1_1[i] & (1<<j)) != 0) { + la1tokens[32+j] = true; + } + } + } + } + for (int i = 0; i < 34; i++) { + if (la1tokens[i]) { + jj_expentry = new int[1]; + jj_expentry[0] = i; + jj_expentries.addElement(jj_expentry); + } + } + jj_endpos = 0; + jj_rescan_token(); + jj_add_error_token(0, 0); + int[][] exptokseq = new int[jj_expentries.size()][]; + for (int i = 0; i < jj_expentries.size(); i++) { + exptokseq[i] = jj_expentries.elementAt(i); + } + return new ParseException(token, exptokseq, tokenImage); + } + + final public void enable_tracing() { + } + + final public void disable_tracing() { + } + + final private void jj_rescan_token() { + jj_rescan = true; + for (int i = 0; i < 2; i++) { + try { + JJCalls p = jj_2_rtns[i]; + do { + if (p.gen > jj_gen) { + jj_la = p.arg; jj_lastpos = jj_scanpos = p.first; + switch (i) { + case 0: jj_3_1(); break; + case 1: jj_3_2(); break; + } + } + p = p.next; + } while (p != null); + } catch(LookaheadSuccess ls) { } + } + jj_rescan = false; + } + + final private void jj_save(int index, int xla) { + JJCalls p = jj_2_rtns[index]; + while (p.gen > jj_gen) { + if (p.next == null) { p = p.next = new JJCalls(); break; } + p = p.next; + } + p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla; + } + + static final class JJCalls { + int gen; + Token first; + int arg; + JJCalls next; + } + +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/AddressListParser.jj b/apache/org/apache/james/mime4j/field/address/parser/AddressListParser.jj new file mode 100644 index 000000000..c14277bc6 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/AddressListParser.jj @@ -0,0 +1,595 @@ +/*@bgen(jjtree) Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParser.jj */ +/*@egen*//**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + + +/** + * RFC2822 address list parser. + * + * Created 9/17/2004 + * by Joe Cheng <code@joecheng.com> + */ + +options { + STATIC=false; + LOOKAHEAD=1; + //DEBUG_PARSER=true; + //DEBUG_TOKEN_MANAGER=true; +} + +PARSER_BEGIN(AddressListParser) +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.address.parser; + +public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeConstants/*@egen*/ {/*@bgen(jjtree)*/ + protected JJTAddressListParserState jjtree = new JJTAddressListParserState(); + +/*@egen*/ + public static void main(String args[]) throws ParseException { + while (true) { + try { + AddressListParser parser = new AddressListParser(System.in); + parser.parseLine(); + ((SimpleNode)parser.jjtree.rootNode()).dump("> "); + } catch (Exception x) { + x.printStackTrace(); + return; + } + } + } + + private static void log(String msg) { + System.out.print(msg); + } + + public ASTaddress_list parse() throws ParseException { + try { + parseAll(); + return (ASTaddress_list)jjtree.rootNode(); + } catch (TokenMgrError tme) { + throw new ParseException(tme.getMessage()); + } + } + + + void jjtreeOpenNodeScope(Node n) { + ((SimpleNode)n).firstToken = getToken(1); + } + + void jjtreeCloseNodeScope(Node n) { + ((SimpleNode)n).lastToken = getToken(0); + } +} + +PARSER_END(AddressListParser) + +void parseLine() : +{} +{ + address_list() ["\r"] "\n" +} + +void parseAll() : +{} +{ + address_list() <EOF> +} + +void address_list() : +{/*@bgen(jjtree) address_list */ + ASTaddress_list jjtn000 = new ASTaddress_list(JJTADDRESS_LIST); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/} +{/*@bgen(jjtree) address_list */ + try { +/*@egen*/ + [ address() ] + ( + "," + [ address() ] + )*/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } +/*@egen*/ +} + +void address() : +{/*@bgen(jjtree) address */ + ASTaddress jjtn000 = new ASTaddress(JJTADDRESS); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/} +{/*@bgen(jjtree) address */ + try { +/*@egen*/ + LOOKAHEAD(2147483647) + addr_spec() +| angle_addr() +| ( phrase() (group_body() | angle_addr()) )/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } +/*@egen*/ +} + +void mailbox() : +{/*@bgen(jjtree) mailbox */ + ASTmailbox jjtn000 = new ASTmailbox(JJTMAILBOX); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/} +{/*@bgen(jjtree) mailbox */ + try { +/*@egen*/ + LOOKAHEAD(2147483647) + addr_spec() +| angle_addr() +| name_addr()/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } +/*@egen*/ +} + +void name_addr() : +{/*@bgen(jjtree) name_addr */ + ASTname_addr jjtn000 = new ASTname_addr(JJTNAME_ADDR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/} +{/*@bgen(jjtree) name_addr */ + try { +/*@egen*/ + phrase() angle_addr()/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } +/*@egen*/ +} + +void group_body() : +{/*@bgen(jjtree) group_body */ + ASTgroup_body jjtn000 = new ASTgroup_body(JJTGROUP_BODY); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/} +{/*@bgen(jjtree) group_body */ + try { +/*@egen*/ + ":" + [ mailbox() ] + ( + "," + [ mailbox() ] + )* + ";"/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } +/*@egen*/ +} + +void angle_addr() : +{/*@bgen(jjtree) angle_addr */ + ASTangle_addr jjtn000 = new ASTangle_addr(JJTANGLE_ADDR); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/} +{/*@bgen(jjtree) angle_addr */ + try { +/*@egen*/ + "<" [ route() ] addr_spec() ">"/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } +/*@egen*/ +} + +void route() : +{/*@bgen(jjtree) route */ + ASTroute jjtn000 = new ASTroute(JJTROUTE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/} +{/*@bgen(jjtree) route */ + try { +/*@egen*/ + "@" domain() ( (",")* "@" domain() )* ":"/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } +/*@egen*/ +} + +void phrase() : +{/*@bgen(jjtree) phrase */ + ASTphrase jjtn000 = new ASTphrase(JJTPHRASE); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/} +{/*@bgen(jjtree) phrase */ +try { +/*@egen*/ +( <DOTATOM> +| <QUOTEDSTRING> +)+/*@bgen(jjtree)*/ +} finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } +} +/*@egen*/ +} + +void addr_spec() : +{/*@bgen(jjtree) addr_spec */ + ASTaddr_spec jjtn000 = new ASTaddr_spec(JJTADDR_SPEC); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/} +{/*@bgen(jjtree) addr_spec */ + try { +/*@egen*/ + ( local_part() "@" domain() )/*@bgen(jjtree)*/ + } catch (Throwable jjte000) { + if (jjtc000) { + jjtree.clearNodeScope(jjtn000); + jjtc000 = false; + } else { + jjtree.popNode(); + } + if (jjte000 instanceof RuntimeException) { + throw (RuntimeException)jjte000; + } + if (jjte000 instanceof ParseException) { + throw (ParseException)jjte000; + } + throw (Error)jjte000; + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } +/*@egen*/ +} + +void local_part() : +{/*@bgen(jjtree) local_part */ + ASTlocal_part jjtn000 = new ASTlocal_part(JJTLOCAL_PART); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/ Token t; } +{/*@bgen(jjtree) local_part */ + try { +/*@egen*/ + ( t=<DOTATOM> | t=<QUOTEDSTRING> ) + ( [t="."] + { + if (t.image.charAt(t.image.length() - 1) != '.' || t.kind == AddressListParserConstants.QUOTEDSTRING) + throw new ParseException("Words in local part must be separated by '.'"); + } + ( t=<DOTATOM> | t=<QUOTEDSTRING> ) + )*/*@bgen(jjtree)*/ + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } +/*@egen*/ +} + +void domain() : +{/*@bgen(jjtree) domain */ + ASTdomain jjtn000 = new ASTdomain(JJTDOMAIN); + boolean jjtc000 = true; + jjtree.openNodeScope(jjtn000); + jjtreeOpenNodeScope(jjtn000); +/*@egen*/ Token t; } +{/*@bgen(jjtree) domain */ + try { +/*@egen*/ + ( t=<DOTATOM> + ( [t="."] + { + if (t.image.charAt(t.image.length() - 1) != '.') + throw new ParseException("Atoms in domain names must be separated by '.'"); + } + t=<DOTATOM> + )* + ) +| <DOMAINLITERAL>/*@bgen(jjtree)*/ + } finally { + if (jjtc000) { + jjtree.closeNodeScope(jjtn000, true); + jjtreeCloseNodeScope(jjtn000); + } + } +/*@egen*/ +} + +SPECIAL_TOKEN : +{ + < WS: ( [" ", "\t"] )+ > +} + +TOKEN : +{ + < #ALPHA: ["a" - "z", "A" - "Z"] > +| < #DIGIT: ["0" - "9"] > +| < #ATEXT: ( <ALPHA> | <DIGIT> + | "!" | "#" | "$" | "%" + | "&" | "'" | "*" | "+" + | "-" | "/" | "=" | "?" + | "^" | "_" | "`" | "{" + | "|" | "}" | "~" + )> +| < DOTATOM: <ATEXT> ( <ATEXT> | "." )* > +} + +TOKEN_MGR_DECLS : +{ + // Keeps track of how many levels of comment nesting + // we've encountered. This is only used when the 2nd + // level is reached, for example ((this)), not (this). + // This is because the outermost level must be treated + // specially anyway, because the outermost ")" has a + // different token type than inner ")" instances. + static int commentNest; +} + +MORE : +{ + // domain literal + "[" : INDOMAINLITERAL +} + +<INDOMAINLITERAL> +MORE : +{ + < <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); } +| < ~["[", "]", "\\"] > +} + +<INDOMAINLITERAL> +TOKEN : +{ + < DOMAINLITERAL: "]" > { matchedToken.image = image.toString(); }: DEFAULT +} + +MORE : +{ + // starts a comment + "(" : INCOMMENT +} + +<INCOMMENT> +SKIP : +{ + // ends a comment + < COMMENT: ")" > : DEFAULT + // if this is ever changed to not be a SKIP, need + // to make sure matchedToken.token = token.toString() + // is called. +} + +<INCOMMENT> +MORE : +{ + < <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); } +| "(" { commentNest = 1; } : NESTED_COMMENT +| < <ANY>> +} + +<NESTED_COMMENT> +MORE : +{ + < <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); } +| "(" { ++commentNest; } +| ")" { --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); } +| < <ANY>> +} + + +// QUOTED STRINGS + +MORE : +{ + "\"" { image.deleteCharAt(image.length() - 1); } : INQUOTEDSTRING +} + +<INQUOTEDSTRING> +MORE : +{ + < <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); } +| < (~["\"", "\\"])+ > +} + +<INQUOTEDSTRING> +TOKEN : +{ + < QUOTEDSTRING: "\"" > { matchedToken.image = image.substring(0, image.length() - 1); } : DEFAULT +} + +// GLOBALS + +<*> +TOKEN : +{ + < #QUOTEDPAIR: "\\" <ANY> > +| < #ANY: ~[] > +} + +// ERROR! +/* + +<*> +TOKEN : +{ + < UNEXPECTED_CHAR: <ANY> > +} + +*/
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/field/address/parser/AddressListParserConstants.java b/apache/org/apache/james/mime4j/field/address/parser/AddressListParserConstants.java new file mode 100644 index 000000000..006a082c1 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/AddressListParserConstants.java @@ -0,0 +1,76 @@ +/* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParserConstants.java */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.address.parser; + +public interface AddressListParserConstants { + + int EOF = 0; + int WS = 10; + int ALPHA = 11; + int DIGIT = 12; + int ATEXT = 13; + int DOTATOM = 14; + int DOMAINLITERAL = 18; + int COMMENT = 20; + int QUOTEDSTRING = 31; + int QUOTEDPAIR = 32; + int ANY = 33; + + int DEFAULT = 0; + int INDOMAINLITERAL = 1; + int INCOMMENT = 2; + int NESTED_COMMENT = 3; + int INQUOTEDSTRING = 4; + + String[] tokenImage = { + "<EOF>", + "\"\\r\"", + "\"\\n\"", + "\",\"", + "\":\"", + "\";\"", + "\"<\"", + "\">\"", + "\"@\"", + "\".\"", + "<WS>", + "<ALPHA>", + "<DIGIT>", + "<ATEXT>", + "<DOTATOM>", + "\"[\"", + "<token of kind 16>", + "<token of kind 17>", + "\"]\"", + "\"(\"", + "\")\"", + "<token of kind 21>", + "\"(\"", + "<token of kind 23>", + "<token of kind 24>", + "\"(\"", + "\")\"", + "<token of kind 27>", + "\"\\\"\"", + "<token of kind 29>", + "<token of kind 30>", + "\"\\\"\"", + "<QUOTEDPAIR>", + "<ANY>", + }; + +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/AddressListParserTokenManager.java b/apache/org/apache/james/mime4j/field/address/parser/AddressListParserTokenManager.java new file mode 100644 index 000000000..d2dd88dd3 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/AddressListParserTokenManager.java @@ -0,0 +1,1009 @@ +/* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParserTokenManager.java */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.address.parser; + +public class AddressListParserTokenManager implements AddressListParserConstants +{ + // Keeps track of how many levels of comment nesting + // we've encountered. This is only used when the 2nd + // level is reached, for example ((this)), not (this). + // This is because the outermost level must be treated + // specially anyway, because the outermost ")" has a + // different token type than inner ")" instances. + static int commentNest; + public java.io.PrintStream debugStream = System.out; + public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } +private final int jjStopStringLiteralDfa_0(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_0(int pos, long active0) +{ + return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1); +} +private final int jjStopAtPos(int pos, int kind) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + return pos + 1; +} +private final int jjStartNfaWithStates_0(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_0(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_0() +{ + switch(curChar) + { + case 10: + return jjStopAtPos(0, 2); + case 13: + return jjStopAtPos(0, 1); + case 34: + return jjStopAtPos(0, 28); + case 40: + return jjStopAtPos(0, 19); + case 44: + return jjStopAtPos(0, 3); + case 46: + return jjStopAtPos(0, 9); + case 58: + return jjStopAtPos(0, 4); + case 59: + return jjStopAtPos(0, 5); + case 60: + return jjStopAtPos(0, 6); + case 62: + return jjStopAtPos(0, 7); + case 64: + return jjStopAtPos(0, 8); + case 91: + return jjStopAtPos(0, 15); + default : + return jjMoveNfa_0(1, 0); + } +} +private final void jjCheckNAdd(int state) +{ + if (jjrounds[state] != jjround) + { + jjstateSet[jjnewStateCnt++] = state; + jjrounds[state] = jjround; + } +} +private final void jjAddStates(int start, int end) +{ + do { + jjstateSet[jjnewStateCnt++] = jjnextStates[start]; + } while (start++ != end); +} +private final void jjCheckNAddTwoStates(int state1, int state2) +{ + jjCheckNAdd(state1); + jjCheckNAdd(state2); +} +private final void jjCheckNAddStates(int start, int end) +{ + do { + jjCheckNAdd(jjnextStates[start]); + } while (start++ != end); +} +private final void jjCheckNAddStates(int start) +{ + jjCheckNAdd(jjnextStates[start]); + jjCheckNAdd(jjnextStates[start + 1]); +} +private final int jjMoveNfa_0(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 1: + if ((0xa3ffacfa00000000L & l) != 0L) + { + if (kind > 14) + kind = 14; + jjCheckNAdd(2); + } + else if ((0x100000200L & l) != 0L) + { + if (kind > 10) + kind = 10; + jjCheckNAdd(0); + } + break; + case 0: + if ((0x100000200L & l) == 0L) + break; + kind = 10; + jjCheckNAdd(0); + break; + case 2: + if ((0xa3ffecfa00000000L & l) == 0L) + break; + if (kind > 14) + kind = 14; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 1: + case 2: + if ((0x7fffffffc7fffffeL & l) == 0L) + break; + if (kind > 14) + kind = 14; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_2(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_2(int pos, long active0) +{ + return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1); +} +private final int jjStartNfaWithStates_2(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_2(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_2() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 22); + case 41: + return jjStopAtPos(0, 20); + default : + return jjMoveNfa_2(0, 0); + } +} +static final long[] jjbitVec0 = { + 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL +}; +private final int jjMoveNfa_2(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 23) + kind = 23; + break; + case 1: + if (kind > 21) + kind = 21; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 23) + kind = 23; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 21) + kind = 21; + break; + case 2: + if (kind > 23) + kind = 23; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 23) + kind = 23; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 21) + kind = 21; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_4(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_4(int pos, long active0) +{ + return jjMoveNfa_4(jjStopStringLiteralDfa_4(pos, active0), pos + 1); +} +private final int jjStartNfaWithStates_4(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_4(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_4() +{ + switch(curChar) + { + case 34: + return jjStopAtPos(0, 31); + default : + return jjMoveNfa_4(0, 0); + } +} +private final int jjMoveNfa_4(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((0xfffffffbffffffffL & l) == 0L) + break; + if (kind > 30) + kind = 30; + jjCheckNAdd(2); + break; + case 1: + if (kind > 29) + kind = 29; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0xffffffffefffffffL & l) != 0L) + { + if (kind > 30) + kind = 30; + jjCheckNAdd(2); + } + else if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 29) + kind = 29; + break; + case 2: + if ((0xffffffffefffffffL & l) == 0L) + break; + if (kind > 30) + kind = 30; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 30) + kind = 30; + jjCheckNAdd(2); + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 29) + kind = 29; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_3(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_3(int pos, long active0) +{ + return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1); +} +private final int jjStartNfaWithStates_3(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_3(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_3() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 25); + case 41: + return jjStopAtPos(0, 26); + default : + return jjMoveNfa_3(0, 0); + } +} +private final int jjMoveNfa_3(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 27) + kind = 27; + break; + case 1: + if (kind > 24) + kind = 24; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 27) + kind = 27; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 24) + kind = 24; + break; + case 2: + if (kind > 27) + kind = 27; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 27) + kind = 27; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 24) + kind = 24; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_1(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_1(int pos, long active0) +{ + return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1); +} +private final int jjStartNfaWithStates_1(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_1(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_1() +{ + switch(curChar) + { + case 93: + return jjStopAtPos(0, 18); + default : + return jjMoveNfa_1(0, 0); + } +} +private final int jjMoveNfa_1(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 17) + kind = 17; + break; + case 1: + if (kind > 16) + kind = 16; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0xffffffffc7ffffffL & l) != 0L) + { + if (kind > 17) + kind = 17; + } + else if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 16) + kind = 16; + break; + case 2: + if ((0xffffffffc7ffffffL & l) != 0L && kind > 17) + kind = 17; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 17) + kind = 17; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 16) + kind = 16; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +static final int[] jjnextStates = { +}; +public static final String[] jjstrLiteralImages = { +"", "\15", "\12", "\54", "\72", "\73", "\74", "\76", "\100", "\56", null, null, +null, null, null, null, null, null, null, null, null, null, null, null, null, null, +null, null, null, null, null, null, null, null, }; +public static final String[] lexStateNames = { + "DEFAULT", + "INDOMAINLITERAL", + "INCOMMENT", + "NESTED_COMMENT", + "INQUOTEDSTRING", +}; +public static final int[] jjnewLexState = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 0, 2, 0, -1, 3, -1, -1, + -1, -1, -1, 4, -1, -1, 0, -1, -1, +}; +static final long[] jjtoToken = { + 0x800443ffL, +}; +static final long[] jjtoSkip = { + 0x100400L, +}; +static final long[] jjtoSpecial = { + 0x400L, +}; +static final long[] jjtoMore = { + 0x7feb8000L, +}; +protected SimpleCharStream input_stream; +private final int[] jjrounds = new int[3]; +private final int[] jjstateSet = new int[6]; +StringBuffer image; +int jjimageLen; +int lengthOfMatch; +protected char curChar; +public AddressListParserTokenManager(SimpleCharStream stream){ + if (SimpleCharStream.staticFlag) + throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); + input_stream = stream; +} +public AddressListParserTokenManager(SimpleCharStream stream, int lexState){ + this(stream); + SwitchTo(lexState); +} +public void ReInit(SimpleCharStream stream) +{ + jjmatchedPos = jjnewStateCnt = 0; + curLexState = defaultLexState; + input_stream = stream; + ReInitRounds(); +} +private final void ReInitRounds() +{ + int i; + jjround = 0x80000001; + for (i = 3; i-- > 0;) + jjrounds[i] = 0x80000000; +} +public void ReInit(SimpleCharStream stream, int lexState) +{ + ReInit(stream); + SwitchTo(lexState); +} +public void SwitchTo(int lexState) +{ + if (lexState >= 5 || lexState < 0) + throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE); + else + curLexState = lexState; +} + +protected Token jjFillToken() +{ + Token t = Token.newToken(jjmatchedKind); + t.kind = jjmatchedKind; + String im = jjstrLiteralImages[jjmatchedKind]; + t.image = (im == null) ? input_stream.GetImage() : im; + t.beginLine = input_stream.getBeginLine(); + t.beginColumn = input_stream.getBeginColumn(); + t.endLine = input_stream.getEndLine(); + t.endColumn = input_stream.getEndColumn(); + return t; +} + +int curLexState = 0; +int defaultLexState = 0; +int jjnewStateCnt; +int jjround; +int jjmatchedPos; +int jjmatchedKind; + +public Token getNextToken() +{ + int kind; + Token specialToken = null; + Token matchedToken; + int curPos = 0; + + EOFLoop : + for (;;) + { + try + { + curChar = input_stream.BeginToken(); + } + catch(java.io.IOException e) + { + jjmatchedKind = 0; + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + return matchedToken; + } + image = null; + jjimageLen = 0; + + for (;;) + { + switch(curLexState) + { + case 0: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_0(); + break; + case 1: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_1(); + break; + case 2: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_2(); + break; + case 3: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_3(); + break; + case 4: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_4(); + break; + } + if (jjmatchedKind != 0x7fffffff) + { + if (jjmatchedPos + 1 < curPos) + input_stream.backup(curPos - jjmatchedPos - 1); + if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + TokenLexicalActions(matchedToken); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + return matchedToken; + } + else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + if (specialToken == null) + specialToken = matchedToken; + else + { + matchedToken.specialToken = specialToken; + specialToken = (specialToken.next = matchedToken); + } + } + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + continue EOFLoop; + } + MoreLexicalActions(); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + curPos = 0; + jjmatchedKind = 0x7fffffff; + try { + curChar = input_stream.readChar(); + continue; + } + catch (java.io.IOException e1) { } + } + int error_line = input_stream.getEndLine(); + int error_column = input_stream.getEndColumn(); + String error_after = null; + boolean EOFSeen = false; + try { input_stream.readChar(); input_stream.backup(1); } + catch (java.io.IOException e1) { + EOFSeen = true; + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + if (curChar == '\n' || curChar == '\r') { + error_line++; + error_column = 0; + } + else + error_column++; + } + if (!EOFSeen) { + input_stream.backup(1); + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + } + throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR); + } + } +} + +void MoreLexicalActions() +{ + jjimageLen += (lengthOfMatch = jjmatchedPos + 1); + switch(jjmatchedKind) + { + case 16 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 21 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 22 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + commentNest = 1; + break; + case 24 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 25 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + ++commentNest; + break; + case 26 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); + break; + case 28 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 1); + break; + case 29 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + default : + break; + } +} +void TokenLexicalActions(Token matchedToken) +{ + switch(jjmatchedKind) + { + case 18 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + matchedToken.image = image.toString(); + break; + case 31 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + matchedToken.image = image.substring(0, image.length() - 1); + break; + default : + break; + } +} +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java b/apache/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java new file mode 100644 index 000000000..5987f19d8 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java @@ -0,0 +1,35 @@ +/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java */ + +package org.apache.james.mime4j.field.address.parser; + +public interface AddressListParserTreeConstants +{ + public int JJTVOID = 0; + public int JJTADDRESS_LIST = 1; + public int JJTADDRESS = 2; + public int JJTMAILBOX = 3; + public int JJTNAME_ADDR = 4; + public int JJTGROUP_BODY = 5; + public int JJTANGLE_ADDR = 6; + public int JJTROUTE = 7; + public int JJTPHRASE = 8; + public int JJTADDR_SPEC = 9; + public int JJTLOCAL_PART = 10; + public int JJTDOMAIN = 11; + + + public String[] jjtNodeName = { + "void", + "address_list", + "address", + "mailbox", + "name_addr", + "group_body", + "angle_addr", + "route", + "phrase", + "addr_spec", + "local_part", + "domain", + }; +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java b/apache/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java new file mode 100644 index 000000000..8ec2fe7d2 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java @@ -0,0 +1,19 @@ +/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java */ + +package org.apache.james.mime4j.field.address.parser; + +public interface AddressListParserVisitor +{ + public Object visit(SimpleNode node, Object data); + public Object visit(ASTaddress_list node, Object data); + public Object visit(ASTaddress node, Object data); + public Object visit(ASTmailbox node, Object data); + public Object visit(ASTname_addr node, Object data); + public Object visit(ASTgroup_body node, Object data); + public Object visit(ASTangle_addr node, Object data); + public Object visit(ASTroute node, Object data); + public Object visit(ASTphrase node, Object data); + public Object visit(ASTaddr_spec node, Object data); + public Object visit(ASTlocal_part node, Object data); + public Object visit(ASTdomain node, Object data); +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/BaseNode.java b/apache/org/apache/james/mime4j/field/address/parser/BaseNode.java new file mode 100644 index 000000000..780974616 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/BaseNode.java @@ -0,0 +1,30 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.address.parser; + +import org.apache.james.mime4j.field.address.parser.Node; +import org.apache.james.mime4j.field.address.parser.Token; + +public abstract class BaseNode implements Node { + + public Token firstToken; + public Token lastToken; + +}
\ No newline at end of file diff --git a/apache/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java b/apache/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java new file mode 100644 index 000000000..08b5c5bef --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java @@ -0,0 +1,123 @@ +/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java */ + +package org.apache.james.mime4j.field.address.parser; + +class JJTAddressListParserState { + private java.util.Stack<Node> nodes; + private java.util.Stack<Integer> marks; + + private int sp; // number of nodes on stack + private int mk; // current mark + private boolean node_created; + + JJTAddressListParserState() { + nodes = new java.util.Stack<Node>(); + marks = new java.util.Stack<Integer>(); + sp = 0; + mk = 0; + } + + /* Determines whether the current node was actually closed and + pushed. This should only be called in the final user action of a + node scope. */ + boolean nodeCreated() { + return node_created; + } + + /* Call this to reinitialize the node stack. It is called + automatically by the parser's ReInit() method. */ + void reset() { + nodes.removeAllElements(); + marks.removeAllElements(); + sp = 0; + mk = 0; + } + + /* Returns the root node of the AST. It only makes sense to call + this after a successful parse. */ + Node rootNode() { + return nodes.elementAt(0); + } + + /* Pushes a node on to the stack. */ + void pushNode(Node n) { + nodes.push(n); + ++sp; + } + + /* Returns the node on the top of the stack, and remove it from the + stack. */ + Node popNode() { + if (--sp < mk) { + mk = marks.pop().intValue(); + } + return nodes.pop(); + } + + /* Returns the node currently on the top of the stack. */ + Node peekNode() { + return nodes.peek(); + } + + /* Returns the number of children on the stack in the current node + scope. */ + int nodeArity() { + return sp - mk; + } + + + void clearNodeScope(Node n) { + while (sp > mk) { + popNode(); + } + mk = marks.pop().intValue(); + } + + + void openNodeScope(Node n) { + marks.push(new Integer(mk)); + mk = sp; + n.jjtOpen(); + } + + + /* A definite node is constructed from a specified number of + children. That number of nodes are popped from the stack and + made the children of the definite node. Then the definite node + is pushed on to the stack. */ + void closeNodeScope(Node n, int num) { + mk = marks.pop().intValue(); + while (num-- > 0) { + Node c = popNode(); + c.jjtSetParent(n); + n.jjtAddChild(c, num); + } + n.jjtClose(); + pushNode(n); + node_created = true; + } + + + /* A conditional node is constructed if its condition is true. All + the nodes that have been pushed since the node was opened are + made children of the the conditional node, which is then pushed + on to the stack. If the condition is false the node is not + constructed and they are left on the stack. */ + void closeNodeScope(Node n, boolean condition) { + if (condition) { + int a = nodeArity(); + mk = marks.pop().intValue(); + while (a-- > 0) { + Node c = popNode(); + c.jjtSetParent(n); + n.jjtAddChild(c, a); + } + n.jjtClose(); + pushNode(n); + node_created = true; + } else { + mk = marks.pop().intValue(); + node_created = false; + } + } +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/Node.java b/apache/org/apache/james/mime4j/field/address/parser/Node.java new file mode 100644 index 000000000..158892016 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/Node.java @@ -0,0 +1,37 @@ +/* Generated By:JJTree: Do not edit this line. Node.java */ + +package org.apache.james.mime4j.field.address.parser; + +/* All AST nodes must implement this interface. It provides basic + machinery for constructing the parent and child relationships + between nodes. */ + +public interface Node { + + /** This method is called after the node has been made the current + node. It indicates that child nodes can now be added to it. */ + public void jjtOpen(); + + /** This method is called after all the child nodes have been + added. */ + public void jjtClose(); + + /** This pair of methods are used to inform the node of its + parent. */ + public void jjtSetParent(Node n); + public Node jjtGetParent(); + + /** This method tells the node to add its argument to the node's + list of children. */ + public void jjtAddChild(Node n, int i); + + /** This method returns a child node. The children are numbered + from zero, left to right. */ + public Node jjtGetChild(int i); + + /** Return the number of children the node has. */ + public int jjtGetNumChildren(); + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data); +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/ParseException.java b/apache/org/apache/james/mime4j/field/address/parser/ParseException.java new file mode 100644 index 000000000..e20146fb6 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/ParseException.java @@ -0,0 +1,207 @@ +/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.address.parser; + +/** + * This exception is thrown when parse errors are encountered. + * You can explicitly create objects of this exception type by + * calling the method generateParseException in the generated + * parser. + * + * You can modify this class to customize your error reporting + * mechanisms so long as you retain the public fields. + */ +public class ParseException extends Exception { + + /** + * This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form: + * ParseException: <result of getMessage> + */ + public ParseException(Token currentTokenVal, + int[][] expectedTokenSequencesVal, + String[] tokenImageVal + ) + { + super(""); + specialConstructor = true; + currentToken = currentTokenVal; + expectedTokenSequences = expectedTokenSequencesVal; + tokenImage = tokenImageVal; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + */ + + public ParseException() { + super(); + specialConstructor = false; + } + + public ParseException(String message) { + super(message); + specialConstructor = false; + } + + /** + * This variable determines which constructor was used to create + * this object and thereby affects the semantics of the + * "getMessage" method (see below). + */ + protected boolean specialConstructor; + + /** + * This is the last token that has been consumed successfully. If + * this object has been created due to a parse error, the token + * followng this token will (therefore) be the first error token. + */ + public Token currentToken; + + /** + * Each entry in this array is an array of integers. Each array + * of integers represents a sequence of tokens (by their ordinal + * values) that is expected at this point of the parse. + */ + public int[][] expectedTokenSequences; + + /** + * This is a reference to the "tokenImage" array of the generated + * parser within which the parse error occurred. This array is + * defined in the generated ...Constants interface. + */ + public String[] tokenImage; + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + */ + public String getMessage() { + if (!specialConstructor) { + return super.getMessage(); + } + StringBuffer expected = new StringBuffer(); + int maxSize = 0; + for (int i = 0; i < expectedTokenSequences.length; i++) { + if (maxSize < expectedTokenSequences[i].length) { + maxSize = expectedTokenSequences[i].length; + } + for (int j = 0; j < expectedTokenSequences[i].length; j++) { + expected.append(tokenImage[expectedTokenSequences[i][j]]).append(" "); + } + if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) { + expected.append("..."); + } + expected.append(eol).append(" "); + } + String retval = "Encountered \""; + Token tok = currentToken.next; + for (int i = 0; i < maxSize; i++) { + if (i != 0) retval += " "; + if (tok.kind == 0) { + retval += tokenImage[0]; + break; + } + retval += add_escapes(tok.image); + tok = tok.next; + } + retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn; + retval += "." + eol; + if (expectedTokenSequences.length == 1) { + retval += "Was expecting:" + eol + " "; + } else { + retval += "Was expecting one of:" + eol + " "; + } + retval += expected.toString(); + return retval; + } + + /** + * The end of line string for this machine. + */ + protected String eol = System.getProperty("line.separator", "\n"); + + /** + * Used to convert raw characters to their escaped version + * when these raw version cannot be used as part of an ASCII + * string literal. + */ + protected String add_escapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/SimpleCharStream.java b/apache/org/apache/james/mime4j/field/address/parser/SimpleCharStream.java new file mode 100644 index 000000000..c9ba0b444 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/SimpleCharStream.java @@ -0,0 +1,454 @@ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 4.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.address.parser; + +/** + * An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (without unicode processing). + */ + +public class SimpleCharStream +{ + public static final boolean staticFlag = false; + int bufsize; + int available; + int tokenBegin; + public int bufpos = -1; + protected int bufline[]; + protected int bufcolumn[]; + + protected int column = 0; + protected int line = 1; + + protected boolean prevCharIsCR = false; + protected boolean prevCharIsLF = false; + + protected java.io.Reader inputStream; + + protected char[] buffer; + protected int maxNextCharInd = 0; + protected int inBuf = 0; + protected int tabSize = 8; + + protected void setTabSize(int i) { tabSize = i; } + protected int getTabSize(int i) { return tabSize; } + + + protected void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, + bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } + + + bufsize += 2048; + available = bufsize; + tokenBegin = 0; + } + + protected void FillBuff() throws java.io.IOException + { + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) + { + bufpos = maxNextCharInd = 0; + available = tokenBegin; + } + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; + else + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } + + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, + available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } + } + + public char BeginToken() throws java.io.IOException + { + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; + + return c; + } + + protected void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + line += (column = 1); + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + + public char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + return buffer[bufpos]; + } + + if (++bufpos >= maxNextCharInd) + FillBuff(); + + char c = buffer[bufpos]; + + UpdateLineColumn(c); + return (c); + } + + /** + * @deprecated + * @see #getEndColumn + */ + @Deprecated + public int getColumn() { + return bufcolumn[bufpos]; + } + + /** + * @deprecated + * @see #getEndLine + */ + @Deprecated + public int getLine() { + return bufline[bufpos]; + } + + public int getEndColumn() { + return bufcolumn[bufpos]; + } + + public int getEndLine() { + return bufline[bufpos]; + } + + public int getBeginColumn() { + return bufcolumn[tokenBegin]; + } + + public int getBeginLine() { + return bufline[tokenBegin]; + } + + public void backup(int amount) { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.Reader dstream) + { + this(dstream, 1, 1, 4096); + } + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + bufpos = -1; + } + + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + + public void ReInit(java.io.Reader dstream) + { + ReInit(dstream, 1, 1, 4096); + } + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, 1, 1, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream) + { + this(dstream, 1, 1, 4096); + } + + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, 1, 1, 4096); + } + + public void ReInit(java.io.InputStream dstream) + { + ReInit(dstream, 1, 1, 4096); + } + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, startline, startcolumn, 4096); + } + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + public String GetImage() + { + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + + public char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + public void Done() + { + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token. + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && + bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/SimpleNode.java b/apache/org/apache/james/mime4j/field/address/parser/SimpleNode.java new file mode 100644 index 000000000..9bf537e60 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/SimpleNode.java @@ -0,0 +1,87 @@ +/* Generated By:JJTree: Do not edit this line. SimpleNode.java */ + +package org.apache.james.mime4j.field.address.parser; + +public class SimpleNode extends org.apache.james.mime4j.field.address.parser.BaseNode implements Node { + protected Node parent; + protected Node[] children; + protected int id; + protected AddressListParser parser; + + public SimpleNode(int i) { + id = i; + } + + public SimpleNode(AddressListParser p, int i) { + this(i); + parser = p; + } + + public void jjtOpen() { + } + + public void jjtClose() { + } + + public void jjtSetParent(Node n) { parent = n; } + public Node jjtGetParent() { return parent; } + + public void jjtAddChild(Node n, int i) { + if (children == null) { + children = new Node[i + 1]; + } else if (i >= children.length) { + Node c[] = new Node[i + 1]; + System.arraycopy(children, 0, c, 0, children.length); + children = c; + } + children[i] = n; + } + + public Node jjtGetChild(int i) { + return children[i]; + } + + public int jjtGetNumChildren() { + return (children == null) ? 0 : children.length; + } + + /** Accept the visitor. **/ + public Object jjtAccept(AddressListParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + /** Accept the visitor. **/ + public Object childrenAccept(AddressListParserVisitor visitor, Object data) { + if (children != null) { + for (int i = 0; i < children.length; ++i) { + children[i].jjtAccept(visitor, data); + } + } + return data; + } + + /* You can override these two methods in subclasses of SimpleNode to + customize the way the node appears when the tree is dumped. If + your output uses more than one line you should override + toString(String), otherwise overriding toString() is probably all + you need to do. */ + + public String toString() { return AddressListParserTreeConstants.jjtNodeName[id]; } + public String toString(String prefix) { return prefix + toString(); } + + /* Override this method if you want to customize how the node dumps + out its children. */ + + public void dump(String prefix) { + System.out.println(toString(prefix)); + if (children != null) { + for (int i = 0; i < children.length; ++i) { + SimpleNode n = (SimpleNode)children[i]; + if (n != null) { + n.dump(prefix + " "); + } + } + } + } +} + diff --git a/apache/org/apache/james/mime4j/field/address/parser/Token.java b/apache/org/apache/james/mime4j/field/address/parser/Token.java new file mode 100644 index 000000000..2382e8e92 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/Token.java @@ -0,0 +1,96 @@ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.address.parser; + +/** + * Describes the input token stream. + */ + +public class Token { + + /** + * An integer that describes the kind of this token. This numbering + * system is determined by JavaCCParser, and a table of these numbers is + * stored in the file ...Constants.java. + */ + public int kind; + + /** + * beginLine and beginColumn describe the position of the first character + * of this token; endLine and endColumn describe the position of the + * last character of this token. + */ + public int beginLine, beginColumn, endLine, endColumn; + + /** + * The string image of the token. + */ + public String image; + + /** + * A reference to the next regular (non-special) token from the input + * stream. If this is the last token from the input stream, or if the + * token manager has not read tokens beyond this one, this field is + * set to null. This is true only if this token is also a regular + * token. Otherwise, see below for a description of the contents of + * this field. + */ + public Token next; + + /** + * This field is used to access special tokens that occur prior to this + * token, but after the immediately preceding regular (non-special) token. + * If there are no such special tokens, this field is set to null. + * When there are more than one such special token, this field refers + * to the last of these special tokens, which in turn refers to the next + * previous special token through its specialToken field, and so on + * until the first special token (whose specialToken field is null). + * The next fields of special tokens refer to other special tokens that + * immediately follow it (without an intervening regular token). If there + * is no such token, this field is null. + */ + public Token specialToken; + + /** + * Returns the image. + */ + public String toString() + { + return image; + } + + /** + * Returns a new Token object, by default. However, if you want, you + * can create and return subclass objects based on the value of ofKind. + * Simply add the cases to the switch for all those special cases. + * For example, if you have a subclass of Token called IDToken that + * you want to create if ofKind is ID, simlpy add something like : + * + * case MyParserConstants.ID : return new IDToken(); + * + * to the following switch statement. Then you can cast matchedToken + * variable to the appropriate type and use it in your lexical actions. + */ + public static final Token newToken(int ofKind) + { + switch(ofKind) + { + default : return new Token(); + } + } + +} diff --git a/apache/org/apache/james/mime4j/field/address/parser/TokenMgrError.java b/apache/org/apache/james/mime4j/field/address/parser/TokenMgrError.java new file mode 100644 index 000000000..0299c8523 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/address/parser/TokenMgrError.java @@ -0,0 +1,148 @@ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.address.parser; + +public class TokenMgrError extends Error +{ + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ + + /** + * Lexical error occured. + */ + static final int LEXICAL_ERROR = 0; + + /** + * An attempt wass made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; + + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; + + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their espaced (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexicl error + * curLexState : lexical state in which this error occured + * errorLine : line number when the error occured + * errorColumn : column number when the error occured + * errorAfter : prefix that was seen before this error occured + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } + + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } + + /* + * Constructors of various flavors follow. + */ + + public TokenMgrError() { + } + + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } + + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } +} diff --git a/apache/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParser.java b/apache/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParser.java new file mode 100644 index 000000000..cacf3af21 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParser.java @@ -0,0 +1,268 @@ +/* Generated By:JavaCC: Do not edit this line. ContentTypeParser.java */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.contenttype.parser; + +import java.util.ArrayList; +import java.util.Vector; + +public class ContentTypeParser implements ContentTypeParserConstants { + + private String type; + private String subtype; + private ArrayList<String> paramNames = new ArrayList<String>(); + private ArrayList<String> paramValues = new ArrayList<String>(); + + public String getType() { return type; } + public String getSubType() { return subtype; } + public ArrayList<String> getParamNames() { return paramNames; } + public ArrayList<String> getParamValues() { return paramValues; } + + public static void main(String args[]) throws ParseException { + while (true) { + try { + ContentTypeParser parser = new ContentTypeParser(System.in); + parser.parseLine(); + } catch (Exception x) { + x.printStackTrace(); + return; + } + } + } + + final public void parseLine() throws ParseException { + parse(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 1: + jj_consume_token(1); + break; + default: + jj_la1[0] = jj_gen; + ; + } + jj_consume_token(2); + } + + final public void parseAll() throws ParseException { + parse(); + jj_consume_token(0); + } + + final public void parse() throws ParseException { + Token type; + Token subtype; + type = jj_consume_token(ATOKEN); + jj_consume_token(3); + subtype = jj_consume_token(ATOKEN); + this.type = type.image; + this.subtype = subtype.image; + label_1: + while (true) { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 4: + ; + break; + default: + jj_la1[1] = jj_gen; + break label_1; + } + jj_consume_token(4); + parameter(); + } + } + + final public void parameter() throws ParseException { + Token attrib; + String val; + attrib = jj_consume_token(ATOKEN); + jj_consume_token(5); + val = value(); + paramNames.add(attrib.image); + paramValues.add(val); + } + + final public String value() throws ParseException { + Token t; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case ATOKEN: + t = jj_consume_token(ATOKEN); + break; + case QUOTEDSTRING: + t = jj_consume_token(QUOTEDSTRING); + break; + default: + jj_la1[2] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + {if (true) return t.image;} + throw new Error("Missing return statement in function"); + } + + public ContentTypeParserTokenManager token_source; + SimpleCharStream jj_input_stream; + public Token token, jj_nt; + private int jj_ntk; + private int jj_gen; + final private int[] jj_la1 = new int[3]; + static private int[] jj_la1_0; + static { + jj_la1_0(); + } + private static void jj_la1_0() { + jj_la1_0 = new int[] {0x2,0x10,0x280000,}; + } + + public ContentTypeParser(java.io.InputStream stream) { + this(stream, null); + } + public ContentTypeParser(java.io.InputStream stream, String encoding) { + try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source = new ContentTypeParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + public void ReInit(java.io.InputStream stream) { + ReInit(stream, null); + } + public void ReInit(java.io.InputStream stream, String encoding) { + try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + public ContentTypeParser(java.io.Reader stream) { + jj_input_stream = new SimpleCharStream(stream, 1, 1); + token_source = new ContentTypeParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + public void ReInit(java.io.Reader stream) { + jj_input_stream.ReInit(stream, 1, 1); + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + public ContentTypeParser(ContentTypeParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + public void ReInit(ContentTypeParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 3; i++) jj_la1[i] = -1; + } + + final private Token jj_consume_token(int kind) throws ParseException { + Token oldToken; + if ((oldToken = token).next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + if (token.kind == kind) { + jj_gen++; + return token; + } + token = oldToken; + jj_kind = kind; + throw generateParseException(); + } + + final public Token getNextToken() { + if (token.next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + jj_gen++; + return token; + } + + final public Token getToken(int index) { + Token t = token; + for (int i = 0; i < index; i++) { + if (t.next != null) t = t.next; + else t = t.next = token_source.getNextToken(); + } + return t; + } + + final private int jj_ntk() { + if ((jj_nt=token.next) == null) + return (jj_ntk = (token.next=token_source.getNextToken()).kind); + else + return (jj_ntk = jj_nt.kind); + } + + private Vector<int[]> jj_expentries = new Vector<int[]>(); + private int[] jj_expentry; + private int jj_kind = -1; + + public ParseException generateParseException() { + jj_expentries.removeAllElements(); + boolean[] la1tokens = new boolean[24]; + for (int i = 0; i < 24; i++) { + la1tokens[i] = false; + } + if (jj_kind >= 0) { + la1tokens[jj_kind] = true; + jj_kind = -1; + } + for (int i = 0; i < 3; i++) { + if (jj_la1[i] == jj_gen) { + for (int j = 0; j < 32; j++) { + if ((jj_la1_0[i] & (1<<j)) != 0) { + la1tokens[j] = true; + } + } + } + } + for (int i = 0; i < 24; i++) { + if (la1tokens[i]) { + jj_expentry = new int[1]; + jj_expentry[0] = i; + jj_expentries.addElement(jj_expentry); + } + } + int[][] exptokseq = new int[jj_expentries.size()][]; + for (int i = 0; i < jj_expentries.size(); i++) { + exptokseq[i] = jj_expentries.elementAt(i); + } + return new ParseException(token, exptokseq, tokenImage); + } + + final public void enable_tracing() { + } + + final public void disable_tracing() { + } + +} diff --git a/apache/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserConstants.java b/apache/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserConstants.java new file mode 100644 index 000000000..d933d800d --- /dev/null +++ b/apache/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserConstants.java @@ -0,0 +1,62 @@ +/* Generated By:JavaCC: Do not edit this line. ContentTypeParserConstants.java */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.contenttype.parser; + +public interface ContentTypeParserConstants { + + int EOF = 0; + int WS = 6; + int COMMENT = 8; + int QUOTEDSTRING = 19; + int DIGITS = 20; + int ATOKEN = 21; + int QUOTEDPAIR = 22; + int ANY = 23; + + int DEFAULT = 0; + int INCOMMENT = 1; + int NESTED_COMMENT = 2; + int INQUOTEDSTRING = 3; + + String[] tokenImage = { + "<EOF>", + "\"\\r\"", + "\"\\n\"", + "\"/\"", + "\";\"", + "\"=\"", + "<WS>", + "\"(\"", + "\")\"", + "<token of kind 9>", + "\"(\"", + "<token of kind 11>", + "<token of kind 12>", + "\"(\"", + "\")\"", + "<token of kind 15>", + "\"\\\"\"", + "<token of kind 17>", + "<token of kind 18>", + "\"\\\"\"", + "<DIGITS>", + "<ATOKEN>", + "<QUOTEDPAIR>", + "<ANY>", + }; + +} diff --git a/apache/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserTokenManager.java b/apache/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserTokenManager.java new file mode 100644 index 000000000..25b7abafa --- /dev/null +++ b/apache/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserTokenManager.java @@ -0,0 +1,877 @@ +/* Generated By:JavaCC: Do not edit this line. ContentTypeParserTokenManager.java */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.contenttype.parser; +import java.util.ArrayList; + +public class ContentTypeParserTokenManager implements ContentTypeParserConstants +{ + // Keeps track of how many levels of comment nesting + // we've encountered. This is only used when the 2nd + // level is reached, for example ((this)), not (this). + // This is because the outermost level must be treated + // specially anyway, because the outermost ")" has a + // different token type than inner ")" instances. + static int commentNest; + public java.io.PrintStream debugStream = System.out; + public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } +private final int jjStopStringLiteralDfa_0(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_0(int pos, long active0) +{ + return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1); +} +private final int jjStopAtPos(int pos, int kind) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + return pos + 1; +} +private final int jjStartNfaWithStates_0(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_0(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_0() +{ + switch(curChar) + { + case 10: + return jjStartNfaWithStates_0(0, 2, 2); + case 13: + return jjStartNfaWithStates_0(0, 1, 2); + case 34: + return jjStopAtPos(0, 16); + case 40: + return jjStopAtPos(0, 7); + case 47: + return jjStopAtPos(0, 3); + case 59: + return jjStopAtPos(0, 4); + case 61: + return jjStopAtPos(0, 5); + default : + return jjMoveNfa_0(3, 0); + } +} +private final void jjCheckNAdd(int state) +{ + if (jjrounds[state] != jjround) + { + jjstateSet[jjnewStateCnt++] = state; + jjrounds[state] = jjround; + } +} +private final void jjAddStates(int start, int end) +{ + do { + jjstateSet[jjnewStateCnt++] = jjnextStates[start]; + } while (start++ != end); +} +private final void jjCheckNAddTwoStates(int state1, int state2) +{ + jjCheckNAdd(state1); + jjCheckNAdd(state2); +} +private final void jjCheckNAddStates(int start, int end) +{ + do { + jjCheckNAdd(jjnextStates[start]); + } while (start++ != end); +} +private final void jjCheckNAddStates(int start) +{ + jjCheckNAdd(jjnextStates[start]); + jjCheckNAdd(jjnextStates[start + 1]); +} +static final long[] jjbitVec0 = { + 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL +}; +private final int jjMoveNfa_0(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 3: + if ((0x3ff6cfafffffdffL & l) != 0L) + { + if (kind > 21) + kind = 21; + jjCheckNAdd(2); + } + else if ((0x100000200L & l) != 0L) + { + if (kind > 6) + kind = 6; + jjCheckNAdd(0); + } + if ((0x3ff000000000000L & l) != 0L) + { + if (kind > 20) + kind = 20; + jjCheckNAdd(1); + } + break; + case 0: + if ((0x100000200L & l) == 0L) + break; + kind = 6; + jjCheckNAdd(0); + break; + case 1: + if ((0x3ff000000000000L & l) == 0L) + break; + if (kind > 20) + kind = 20; + jjCheckNAdd(1); + break; + case 2: + if ((0x3ff6cfafffffdffL & l) == 0L) + break; + if (kind > 21) + kind = 21; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 3: + case 2: + if ((0xffffffffc7fffffeL & l) == 0L) + break; + kind = 21; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 3: + case 2: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 21) + kind = 21; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_1(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_1(int pos, long active0) +{ + return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1); +} +private final int jjStartNfaWithStates_1(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_1(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_1() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 10); + case 41: + return jjStopAtPos(0, 8); + default : + return jjMoveNfa_1(0, 0); + } +} +private final int jjMoveNfa_1(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 11) + kind = 11; + break; + case 1: + if (kind > 9) + kind = 9; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 11) + kind = 11; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 9) + kind = 9; + break; + case 2: + if (kind > 11) + kind = 11; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 11) + kind = 11; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 9) + kind = 9; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_3(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_3(int pos, long active0) +{ + return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1); +} +private final int jjStartNfaWithStates_3(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_3(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_3() +{ + switch(curChar) + { + case 34: + return jjStopAtPos(0, 19); + default : + return jjMoveNfa_3(0, 0); + } +} +private final int jjMoveNfa_3(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((0xfffffffbffffffffL & l) == 0L) + break; + if (kind > 18) + kind = 18; + jjCheckNAdd(2); + break; + case 1: + if (kind > 17) + kind = 17; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0xffffffffefffffffL & l) != 0L) + { + if (kind > 18) + kind = 18; + jjCheckNAdd(2); + } + else if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 17) + kind = 17; + break; + case 2: + if ((0xffffffffefffffffL & l) == 0L) + break; + if (kind > 18) + kind = 18; + jjCheckNAdd(2); + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + case 2: + if ((jjbitVec0[i2] & l2) == 0L) + break; + if (kind > 18) + kind = 18; + jjCheckNAdd(2); + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 17) + kind = 17; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_2(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_2(int pos, long active0) +{ + return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1); +} +private final int jjStartNfaWithStates_2(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_2(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_2() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 13); + case 41: + return jjStopAtPos(0, 14); + default : + return jjMoveNfa_2(0, 0); + } +} +private final int jjMoveNfa_2(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 15) + kind = 15; + break; + case 1: + if (kind > 12) + kind = 12; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 15) + kind = 15; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 12) + kind = 12; + break; + case 2: + if (kind > 15) + kind = 15; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 15) + kind = 15; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 12) + kind = 12; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +static final int[] jjnextStates = { +}; +public static final String[] jjstrLiteralImages = { +"", "\15", "\12", "\57", "\73", "\75", null, null, null, null, null, null, +null, null, null, null, null, null, null, null, null, null, null, null, }; +public static final String[] lexStateNames = { + "DEFAULT", + "INCOMMENT", + "NESTED_COMMENT", + "INQUOTEDSTRING", +}; +public static final int[] jjnewLexState = { + -1, -1, -1, -1, -1, -1, -1, 1, 0, -1, 2, -1, -1, -1, -1, -1, 3, -1, -1, 0, -1, -1, -1, -1, +}; +static final long[] jjtoToken = { + 0x38003fL, +}; +static final long[] jjtoSkip = { + 0x140L, +}; +static final long[] jjtoSpecial = { + 0x40L, +}; +static final long[] jjtoMore = { + 0x7fe80L, +}; +protected SimpleCharStream input_stream; +private final int[] jjrounds = new int[3]; +private final int[] jjstateSet = new int[6]; +StringBuffer image; +int jjimageLen; +int lengthOfMatch; +protected char curChar; +public ContentTypeParserTokenManager(SimpleCharStream stream){ + if (SimpleCharStream.staticFlag) + throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); + input_stream = stream; +} +public ContentTypeParserTokenManager(SimpleCharStream stream, int lexState){ + this(stream); + SwitchTo(lexState); +} +public void ReInit(SimpleCharStream stream) +{ + jjmatchedPos = jjnewStateCnt = 0; + curLexState = defaultLexState; + input_stream = stream; + ReInitRounds(); +} +private final void ReInitRounds() +{ + int i; + jjround = 0x80000001; + for (i = 3; i-- > 0;) + jjrounds[i] = 0x80000000; +} +public void ReInit(SimpleCharStream stream, int lexState) +{ + ReInit(stream); + SwitchTo(lexState); +} +public void SwitchTo(int lexState) +{ + if (lexState >= 4 || lexState < 0) + throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE); + else + curLexState = lexState; +} + +protected Token jjFillToken() +{ + Token t = Token.newToken(jjmatchedKind); + t.kind = jjmatchedKind; + String im = jjstrLiteralImages[jjmatchedKind]; + t.image = (im == null) ? input_stream.GetImage() : im; + t.beginLine = input_stream.getBeginLine(); + t.beginColumn = input_stream.getBeginColumn(); + t.endLine = input_stream.getEndLine(); + t.endColumn = input_stream.getEndColumn(); + return t; +} + +int curLexState = 0; +int defaultLexState = 0; +int jjnewStateCnt; +int jjround; +int jjmatchedPos; +int jjmatchedKind; + +public Token getNextToken() +{ + int kind; + Token specialToken = null; + Token matchedToken; + int curPos = 0; + + EOFLoop : + for (;;) + { + try + { + curChar = input_stream.BeginToken(); + } + catch(java.io.IOException e) + { + jjmatchedKind = 0; + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + return matchedToken; + } + image = null; + jjimageLen = 0; + + for (;;) + { + switch(curLexState) + { + case 0: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_0(); + break; + case 1: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_1(); + break; + case 2: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_2(); + break; + case 3: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_3(); + break; + } + if (jjmatchedKind != 0x7fffffff) + { + if (jjmatchedPos + 1 < curPos) + input_stream.backup(curPos - jjmatchedPos - 1); + if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + TokenLexicalActions(matchedToken); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + return matchedToken; + } + else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + if (specialToken == null) + specialToken = matchedToken; + else + { + matchedToken.specialToken = specialToken; + specialToken = (specialToken.next = matchedToken); + } + } + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + continue EOFLoop; + } + MoreLexicalActions(); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + curPos = 0; + jjmatchedKind = 0x7fffffff; + try { + curChar = input_stream.readChar(); + continue; + } + catch (java.io.IOException e1) { } + } + int error_line = input_stream.getEndLine(); + int error_column = input_stream.getEndColumn(); + String error_after = null; + boolean EOFSeen = false; + try { input_stream.readChar(); input_stream.backup(1); } + catch (java.io.IOException e1) { + EOFSeen = true; + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + if (curChar == '\n' || curChar == '\r') { + error_line++; + error_column = 0; + } + else + error_column++; + } + if (!EOFSeen) { + input_stream.backup(1); + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + } + throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR); + } + } +} + +void MoreLexicalActions() +{ + jjimageLen += (lengthOfMatch = jjmatchedPos + 1); + switch(jjmatchedKind) + { + case 9 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 10 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + commentNest = 1; + break; + case 12 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 13 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + ++commentNest; + break; + case 14 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); + break; + case 16 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 1); + break; + case 17 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + default : + break; + } +} +void TokenLexicalActions(Token matchedToken) +{ + switch(jjmatchedKind) + { + case 19 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1))); + matchedToken.image = image.substring(0, image.length() - 1); + break; + default : + break; + } +} +} diff --git a/apache/org/apache/james/mime4j/field/contenttype/parser/ParseException.java b/apache/org/apache/james/mime4j/field/contenttype/parser/ParseException.java new file mode 100644 index 000000000..d9b69b25c --- /dev/null +++ b/apache/org/apache/james/mime4j/field/contenttype/parser/ParseException.java @@ -0,0 +1,207 @@ +/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.contenttype.parser; + +/** + * This exception is thrown when parse errors are encountered. + * You can explicitly create objects of this exception type by + * calling the method generateParseException in the generated + * parser. + * + * You can modify this class to customize your error reporting + * mechanisms so long as you retain the public fields. + */ +public class ParseException extends Exception { + + /** + * This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form: + * ParseException: <result of getMessage> + */ + public ParseException(Token currentTokenVal, + int[][] expectedTokenSequencesVal, + String[] tokenImageVal + ) + { + super(""); + specialConstructor = true; + currentToken = currentTokenVal; + expectedTokenSequences = expectedTokenSequencesVal; + tokenImage = tokenImageVal; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + */ + + public ParseException() { + super(); + specialConstructor = false; + } + + public ParseException(String message) { + super(message); + specialConstructor = false; + } + + /** + * This variable determines which constructor was used to create + * this object and thereby affects the semantics of the + * "getMessage" method (see below). + */ + protected boolean specialConstructor; + + /** + * This is the last token that has been consumed successfully. If + * this object has been created due to a parse error, the token + * followng this token will (therefore) be the first error token. + */ + public Token currentToken; + + /** + * Each entry in this array is an array of integers. Each array + * of integers represents a sequence of tokens (by their ordinal + * values) that is expected at this point of the parse. + */ + public int[][] expectedTokenSequences; + + /** + * This is a reference to the "tokenImage" array of the generated + * parser within which the parse error occurred. This array is + * defined in the generated ...Constants interface. + */ + public String[] tokenImage; + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + */ + public String getMessage() { + if (!specialConstructor) { + return super.getMessage(); + } + StringBuffer expected = new StringBuffer(); + int maxSize = 0; + for (int i = 0; i < expectedTokenSequences.length; i++) { + if (maxSize < expectedTokenSequences[i].length) { + maxSize = expectedTokenSequences[i].length; + } + for (int j = 0; j < expectedTokenSequences[i].length; j++) { + expected.append(tokenImage[expectedTokenSequences[i][j]]).append(" "); + } + if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) { + expected.append("..."); + } + expected.append(eol).append(" "); + } + String retval = "Encountered \""; + Token tok = currentToken.next; + for (int i = 0; i < maxSize; i++) { + if (i != 0) retval += " "; + if (tok.kind == 0) { + retval += tokenImage[0]; + break; + } + retval += add_escapes(tok.image); + tok = tok.next; + } + retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn; + retval += "." + eol; + if (expectedTokenSequences.length == 1) { + retval += "Was expecting:" + eol + " "; + } else { + retval += "Was expecting one of:" + eol + " "; + } + retval += expected.toString(); + return retval; + } + + /** + * The end of line string for this machine. + */ + protected String eol = System.getProperty("line.separator", "\n"); + + /** + * Used to convert raw characters to their escaped version + * when these raw version cannot be used as part of an ASCII + * string literal. + */ + protected String add_escapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + +} diff --git a/apache/org/apache/james/mime4j/field/contenttype/parser/SimpleCharStream.java b/apache/org/apache/james/mime4j/field/contenttype/parser/SimpleCharStream.java new file mode 100644 index 000000000..ae035b717 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/contenttype/parser/SimpleCharStream.java @@ -0,0 +1,454 @@ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 4.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.contenttype.parser; + +/** + * An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (without unicode processing). + */ + +public class SimpleCharStream +{ + public static final boolean staticFlag = false; + int bufsize; + int available; + int tokenBegin; + public int bufpos = -1; + protected int bufline[]; + protected int bufcolumn[]; + + protected int column = 0; + protected int line = 1; + + protected boolean prevCharIsCR = false; + protected boolean prevCharIsLF = false; + + protected java.io.Reader inputStream; + + protected char[] buffer; + protected int maxNextCharInd = 0; + protected int inBuf = 0; + protected int tabSize = 8; + + protected void setTabSize(int i) { tabSize = i; } + protected int getTabSize(int i) { return tabSize; } + + + protected void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, + bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } + + + bufsize += 2048; + available = bufsize; + tokenBegin = 0; + } + + protected void FillBuff() throws java.io.IOException + { + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) + { + bufpos = maxNextCharInd = 0; + available = tokenBegin; + } + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; + else + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } + + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, + available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } + } + + public char BeginToken() throws java.io.IOException + { + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; + + return c; + } + + protected void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + line += (column = 1); + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + + public char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + return buffer[bufpos]; + } + + if (++bufpos >= maxNextCharInd) + FillBuff(); + + char c = buffer[bufpos]; + + UpdateLineColumn(c); + return (c); + } + + /** + * @deprecated + * @see #getEndColumn + */ + @Deprecated + public int getColumn() { + return bufcolumn[bufpos]; + } + + /** + * @deprecated + * @see #getEndLine + */ + @Deprecated + public int getLine() { + return bufline[bufpos]; + } + + public int getEndColumn() { + return bufcolumn[bufpos]; + } + + public int getEndLine() { + return bufline[bufpos]; + } + + public int getBeginColumn() { + return bufcolumn[tokenBegin]; + } + + public int getBeginLine() { + return bufline[tokenBegin]; + } + + public void backup(int amount) { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.Reader dstream) + { + this(dstream, 1, 1, 4096); + } + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + bufpos = -1; + } + + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + + public void ReInit(java.io.Reader dstream) + { + ReInit(dstream, 1, 1, 4096); + } + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, 1, 1, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream) + { + this(dstream, 1, 1, 4096); + } + + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, 1, 1, 4096); + } + + public void ReInit(java.io.InputStream dstream) + { + ReInit(dstream, 1, 1, 4096); + } + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, startline, startcolumn, 4096); + } + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + public String GetImage() + { + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + + public char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + public void Done() + { + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token. + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && + bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + +} diff --git a/apache/org/apache/james/mime4j/field/contenttype/parser/Token.java b/apache/org/apache/james/mime4j/field/contenttype/parser/Token.java new file mode 100644 index 000000000..34e65eec0 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/contenttype/parser/Token.java @@ -0,0 +1,96 @@ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.contenttype.parser; + +/** + * Describes the input token stream. + */ + +public class Token { + + /** + * An integer that describes the kind of this token. This numbering + * system is determined by JavaCCParser, and a table of these numbers is + * stored in the file ...Constants.java. + */ + public int kind; + + /** + * beginLine and beginColumn describe the position of the first character + * of this token; endLine and endColumn describe the position of the + * last character of this token. + */ + public int beginLine, beginColumn, endLine, endColumn; + + /** + * The string image of the token. + */ + public String image; + + /** + * A reference to the next regular (non-special) token from the input + * stream. If this is the last token from the input stream, or if the + * token manager has not read tokens beyond this one, this field is + * set to null. This is true only if this token is also a regular + * token. Otherwise, see below for a description of the contents of + * this field. + */ + public Token next; + + /** + * This field is used to access special tokens that occur prior to this + * token, but after the immediately preceding regular (non-special) token. + * If there are no such special tokens, this field is set to null. + * When there are more than one such special token, this field refers + * to the last of these special tokens, which in turn refers to the next + * previous special token through its specialToken field, and so on + * until the first special token (whose specialToken field is null). + * The next fields of special tokens refer to other special tokens that + * immediately follow it (without an intervening regular token). If there + * is no such token, this field is null. + */ + public Token specialToken; + + /** + * Returns the image. + */ + public String toString() + { + return image; + } + + /** + * Returns a new Token object, by default. However, if you want, you + * can create and return subclass objects based on the value of ofKind. + * Simply add the cases to the switch for all those special cases. + * For example, if you have a subclass of Token called IDToken that + * you want to create if ofKind is ID, simlpy add something like : + * + * case MyParserConstants.ID : return new IDToken(); + * + * to the following switch statement. Then you can cast matchedToken + * variable to the appropriate type and use it in your lexical actions. + */ + public static final Token newToken(int ofKind) + { + switch(ofKind) + { + default : return new Token(); + } + } + +} diff --git a/apache/org/apache/james/mime4j/field/contenttype/parser/TokenMgrError.java b/apache/org/apache/james/mime4j/field/contenttype/parser/TokenMgrError.java new file mode 100644 index 000000000..ea5a7826e --- /dev/null +++ b/apache/org/apache/james/mime4j/field/contenttype/parser/TokenMgrError.java @@ -0,0 +1,148 @@ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.contenttype.parser; + +public class TokenMgrError extends Error +{ + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ + + /** + * Lexical error occured. + */ + static final int LEXICAL_ERROR = 0; + + /** + * An attempt wass made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; + + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; + + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their espaced (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexicl error + * curLexState : lexical state in which this error occured + * errorLine : line number when the error occured + * errorColumn : column number when the error occured + * errorAfter : prefix that was seen before this error occured + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } + + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } + + /* + * Constructors of various flavors follow. + */ + + public TokenMgrError() { + } + + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } + + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } +} diff --git a/apache/org/apache/james/mime4j/field/datetime/DateTime.java b/apache/org/apache/james/mime4j/field/datetime/DateTime.java new file mode 100644 index 000000000..506ff54e5 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/datetime/DateTime.java @@ -0,0 +1,127 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.field.datetime; + +import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; +import org.apache.james.mime4j.field.datetime.parser.ParseException; +import org.apache.james.mime4j.field.datetime.parser.TokenMgrError; + +import java.util.Date; +import java.util.Calendar; +import java.util.TimeZone; +import java.util.GregorianCalendar; +import java.io.StringReader; + +public class DateTime { + private final Date date; + private final int year; + private final int month; + private final int day; + private final int hour; + private final int minute; + private final int second; + private final int timeZone; + + public DateTime(String yearString, int month, int day, int hour, int minute, int second, int timeZone) { + this.year = convertToYear(yearString); + this.date = convertToDate(year, month, day, hour, minute, second, timeZone); + this.month = month; + this.day = day; + this.hour = hour; + this.minute = minute; + this.second = second; + this.timeZone = timeZone; + } + + private int convertToYear(String yearString) { + int year = Integer.parseInt(yearString); + switch (yearString.length()) { + case 1: + case 2: + if (year >= 0 && year < 50) + return 2000 + year; + else + return 1900 + year; + case 3: + return 1900 + year; + default: + return year; + } + } + + public static Date convertToDate(int year, int month, int day, int hour, int minute, int second, int timeZone) { + Calendar c = new GregorianCalendar(TimeZone.getTimeZone("GMT+0")); + c.set(year, month - 1, day, hour, minute, second); + c.set(Calendar.MILLISECOND, 0); + + if (timeZone != Integer.MIN_VALUE) { + int minutes = ((timeZone / 100) * 60) + timeZone % 100; + c.add(Calendar.MINUTE, -1 * minutes); + } + + return c.getTime(); + } + + public Date getDate() { + return date; + } + + public int getYear() { + return year; + } + + public int getMonth() { + return month; + } + + public int getDay() { + return day; + } + + public int getHour() { + return hour; + } + + public int getMinute() { + return minute; + } + + public int getSecond() { + return second; + } + + public int getTimeZone() { + return timeZone; + } + + public void print() { + System.out.println(getYear() + " " + getMonth() + " " + getDay() + "; " + getHour() + " " + getMinute() + " " + getSecond() + " " + getTimeZone()); + } + + + public static DateTime parse(String dateString) throws ParseException { + try { + return new DateTimeParser(new StringReader(dateString)).parseAll(); + } + catch (TokenMgrError err) { + throw new ParseException(err.getMessage()); + } + } +} diff --git a/apache/org/apache/james/mime4j/field/datetime/parser/DateTimeParser.java b/apache/org/apache/james/mime4j/field/datetime/parser/DateTimeParser.java new file mode 100644 index 000000000..43edebb5c --- /dev/null +++ b/apache/org/apache/james/mime4j/field/datetime/parser/DateTimeParser.java @@ -0,0 +1,570 @@ +/* Generated By:JavaCC: Do not edit this line. DateTimeParser.java */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.datetime.parser; + +import org.apache.james.mime4j.field.datetime.DateTime; + +import java.util.Vector; + +public class DateTimeParser implements DateTimeParserConstants { + private static final boolean ignoreMilitaryZoneOffset = true; + + public static void main(String args[]) throws ParseException { + while (true) { + try { + DateTimeParser parser = new DateTimeParser(System.in); + parser.parseLine(); + } catch (Exception x) { + x.printStackTrace(); + return; + } + } + } + + private static int parseDigits(Token token) { + return Integer.parseInt(token.image, 10); + } + + private static int getMilitaryZoneOffset(char c) { + if (ignoreMilitaryZoneOffset) + return 0; + + c = Character.toUpperCase(c); + + switch (c) { + case 'A': return 1; + case 'B': return 2; + case 'C': return 3; + case 'D': return 4; + case 'E': return 5; + case 'F': return 6; + case 'G': return 7; + case 'H': return 8; + case 'I': return 9; + case 'K': return 10; + case 'L': return 11; + case 'M': return 12; + + case 'N': return -1; + case 'O': return -2; + case 'P': return -3; + case 'Q': return -4; + case 'R': return -5; + case 'S': return -6; + case 'T': return -7; + case 'U': return -8; + case 'V': return -9; + case 'W': return -10; + case 'X': return -11; + case 'Y': return -12; + + case 'Z': return 0; + default: return 0; + } + } + + private static class Time { + private int hour; + private int minute; + private int second; + private int zone; + + public Time(int hour, int minute, int second, int zone) { + this.hour = hour; + this.minute = minute; + this.second = second; + this.zone = zone; + } + + public int getHour() { return hour; } + public int getMinute() { return minute; } + public int getSecond() { return second; } + public int getZone() { return zone; } + } + + private static class Date { + private String year; + private int month; + private int day; + + public Date(String year, int month, int day) { + this.year = year; + this.month = month; + this.day = day; + } + + public String getYear() { return year; } + public int getMonth() { return month; } + public int getDay() { return day; } + } + + final public DateTime parseLine() throws ParseException { + DateTime dt; + dt = date_time(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 1: + jj_consume_token(1); + break; + default: + jj_la1[0] = jj_gen; + ; + } + jj_consume_token(2); + {if (true) return dt;} + throw new Error("Missing return statement in function"); + } + + final public DateTime parseAll() throws ParseException { + DateTime dt; + dt = date_time(); + jj_consume_token(0); + {if (true) return dt;} + throw new Error("Missing return statement in function"); + } + + final public DateTime date_time() throws ParseException { + Date d; Time t; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + day_of_week(); + jj_consume_token(3); + break; + default: + jj_la1[1] = jj_gen; + ; + } + d = date(); + t = time(); + {if (true) return new DateTime( + d.getYear(), + d.getMonth(), + d.getDay(), + t.getHour(), + t.getMinute(), + t.getSecond(), + t.getZone());} // time zone offset + + throw new Error("Missing return statement in function"); + } + + final public String day_of_week() throws ParseException { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 4: + jj_consume_token(4); + break; + case 5: + jj_consume_token(5); + break; + case 6: + jj_consume_token(6); + break; + case 7: + jj_consume_token(7); + break; + case 8: + jj_consume_token(8); + break; + case 9: + jj_consume_token(9); + break; + case 10: + jj_consume_token(10); + break; + default: + jj_la1[2] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + {if (true) return token.image;} + throw new Error("Missing return statement in function"); + } + + final public Date date() throws ParseException { + int d, m; String y; + d = day(); + m = month(); + y = year(); + {if (true) return new Date(y, m, d);} + throw new Error("Missing return statement in function"); + } + + final public int day() throws ParseException { + Token t; + t = jj_consume_token(DIGITS); + {if (true) return parseDigits(t);} + throw new Error("Missing return statement in function"); + } + + final public int month() throws ParseException { + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 11: + jj_consume_token(11); + {if (true) return 1;} + break; + case 12: + jj_consume_token(12); + {if (true) return 2;} + break; + case 13: + jj_consume_token(13); + {if (true) return 3;} + break; + case 14: + jj_consume_token(14); + {if (true) return 4;} + break; + case 15: + jj_consume_token(15); + {if (true) return 5;} + break; + case 16: + jj_consume_token(16); + {if (true) return 6;} + break; + case 17: + jj_consume_token(17); + {if (true) return 7;} + break; + case 18: + jj_consume_token(18); + {if (true) return 8;} + break; + case 19: + jj_consume_token(19); + {if (true) return 9;} + break; + case 20: + jj_consume_token(20); + {if (true) return 10;} + break; + case 21: + jj_consume_token(21); + {if (true) return 11;} + break; + case 22: + jj_consume_token(22); + {if (true) return 12;} + break; + default: + jj_la1[3] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + throw new Error("Missing return statement in function"); + } + + final public String year() throws ParseException { + Token t; + t = jj_consume_token(DIGITS); + {if (true) return t.image;} + throw new Error("Missing return statement in function"); + } + + final public Time time() throws ParseException { + int h, m, s=0, z; + h = hour(); + jj_consume_token(23); + m = minute(); + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 23: + jj_consume_token(23); + s = second(); + break; + default: + jj_la1[4] = jj_gen; + ; + } + z = zone(); + {if (true) return new Time(h, m, s, z);} + throw new Error("Missing return statement in function"); + } + + final public int hour() throws ParseException { + Token t; + t = jj_consume_token(DIGITS); + {if (true) return parseDigits(t);} + throw new Error("Missing return statement in function"); + } + + final public int minute() throws ParseException { + Token t; + t = jj_consume_token(DIGITS); + {if (true) return parseDigits(t);} + throw new Error("Missing return statement in function"); + } + + final public int second() throws ParseException { + Token t; + t = jj_consume_token(DIGITS); + {if (true) return parseDigits(t);} + throw new Error("Missing return statement in function"); + } + + final public int zone() throws ParseException { + Token t, u; int z; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case OFFSETDIR: + t = jj_consume_token(OFFSETDIR); + u = jj_consume_token(DIGITS); + z=parseDigits(u)*(t.image.equals("-") ? -1 : 1); + break; + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + case 33: + case 34: + case MILITARY_ZONE: + z = obs_zone(); + break; + default: + jj_la1[5] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + {if (true) return z;} + throw new Error("Missing return statement in function"); + } + + final public int obs_zone() throws ParseException { + Token t; int z; + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case 25: + jj_consume_token(25); + z=0; + break; + case 26: + jj_consume_token(26); + z=0; + break; + case 27: + jj_consume_token(27); + z=-5; + break; + case 28: + jj_consume_token(28); + z=-4; + break; + case 29: + jj_consume_token(29); + z=-6; + break; + case 30: + jj_consume_token(30); + z=-5; + break; + case 31: + jj_consume_token(31); + z=-7; + break; + case 32: + jj_consume_token(32); + z=-6; + break; + case 33: + jj_consume_token(33); + z=-8; + break; + case 34: + jj_consume_token(34); + z=-7; + break; + case MILITARY_ZONE: + t = jj_consume_token(MILITARY_ZONE); + z=getMilitaryZoneOffset(t.image.charAt(0)); + break; + default: + jj_la1[6] = jj_gen; + jj_consume_token(-1); + throw new ParseException(); + } + {if (true) return z * 100;} + throw new Error("Missing return statement in function"); + } + + public DateTimeParserTokenManager token_source; + SimpleCharStream jj_input_stream; + public Token token, jj_nt; + private int jj_ntk; + private int jj_gen; + final private int[] jj_la1 = new int[7]; + static private int[] jj_la1_0; + static private int[] jj_la1_1; + static { + jj_la1_0(); + jj_la1_1(); + } + private static void jj_la1_0() { + jj_la1_0 = new int[] {0x2,0x7f0,0x7f0,0x7ff800,0x800000,0xff000000,0xfe000000,}; + } + private static void jj_la1_1() { + jj_la1_1 = new int[] {0x0,0x0,0x0,0x0,0x0,0xf,0xf,}; + } + + public DateTimeParser(java.io.InputStream stream) { + this(stream, null); + } + public DateTimeParser(java.io.InputStream stream, String encoding) { + try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source = new DateTimeParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 7; i++) jj_la1[i] = -1; + } + + public void ReInit(java.io.InputStream stream) { + ReInit(stream, null); + } + public void ReInit(java.io.InputStream stream, String encoding) { + try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 7; i++) jj_la1[i] = -1; + } + + public DateTimeParser(java.io.Reader stream) { + jj_input_stream = new SimpleCharStream(stream, 1, 1); + token_source = new DateTimeParserTokenManager(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 7; i++) jj_la1[i] = -1; + } + + public void ReInit(java.io.Reader stream) { + jj_input_stream.ReInit(stream, 1, 1); + token_source.ReInit(jj_input_stream); + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 7; i++) jj_la1[i] = -1; + } + + public DateTimeParser(DateTimeParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 7; i++) jj_la1[i] = -1; + } + + public void ReInit(DateTimeParserTokenManager tm) { + token_source = tm; + token = new Token(); + jj_ntk = -1; + jj_gen = 0; + for (int i = 0; i < 7; i++) jj_la1[i] = -1; + } + + final private Token jj_consume_token(int kind) throws ParseException { + Token oldToken; + if ((oldToken = token).next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + if (token.kind == kind) { + jj_gen++; + return token; + } + token = oldToken; + jj_kind = kind; + throw generateParseException(); + } + + final public Token getNextToken() { + if (token.next != null) token = token.next; + else token = token.next = token_source.getNextToken(); + jj_ntk = -1; + jj_gen++; + return token; + } + + final public Token getToken(int index) { + Token t = token; + for (int i = 0; i < index; i++) { + if (t.next != null) t = t.next; + else t = t.next = token_source.getNextToken(); + } + return t; + } + + final private int jj_ntk() { + if ((jj_nt=token.next) == null) + return (jj_ntk = (token.next=token_source.getNextToken()).kind); + else + return (jj_ntk = jj_nt.kind); + } + + private Vector<int[]> jj_expentries = new Vector<int[]>(); + private int[] jj_expentry; + private int jj_kind = -1; + + public ParseException generateParseException() { + jj_expentries.removeAllElements(); + boolean[] la1tokens = new boolean[49]; + for (int i = 0; i < 49; i++) { + la1tokens[i] = false; + } + if (jj_kind >= 0) { + la1tokens[jj_kind] = true; + jj_kind = -1; + } + for (int i = 0; i < 7; i++) { + if (jj_la1[i] == jj_gen) { + for (int j = 0; j < 32; j++) { + if ((jj_la1_0[i] & (1<<j)) != 0) { + la1tokens[j] = true; + } + if ((jj_la1_1[i] & (1<<j)) != 0) { + la1tokens[32+j] = true; + } + } + } + } + for (int i = 0; i < 49; i++) { + if (la1tokens[i]) { + jj_expentry = new int[1]; + jj_expentry[0] = i; + jj_expentries.addElement(jj_expentry); + } + } + int[][] exptokseq = new int[jj_expentries.size()][]; + for (int i = 0; i < jj_expentries.size(); i++) { + exptokseq[i] = jj_expentries.elementAt(i); + } + return new ParseException(token, exptokseq, tokenImage); + } + + final public void enable_tracing() { + } + + final public void disable_tracing() { + } + +} diff --git a/apache/org/apache/james/mime4j/field/datetime/parser/DateTimeParserConstants.java b/apache/org/apache/james/mime4j/field/datetime/parser/DateTimeParserConstants.java new file mode 100644 index 000000000..2c203db2e --- /dev/null +++ b/apache/org/apache/james/mime4j/field/datetime/parser/DateTimeParserConstants.java @@ -0,0 +1,86 @@ +/* Generated By:JavaCC: Do not edit this line. DateTimeParserConstants.java */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.datetime.parser; + +public interface DateTimeParserConstants { + + int EOF = 0; + int OFFSETDIR = 24; + int MILITARY_ZONE = 35; + int WS = 36; + int COMMENT = 38; + int DIGITS = 46; + int QUOTEDPAIR = 47; + int ANY = 48; + + int DEFAULT = 0; + int INCOMMENT = 1; + int NESTED_COMMENT = 2; + + String[] tokenImage = { + "<EOF>", + "\"\\r\"", + "\"\\n\"", + "\",\"", + "\"Mon\"", + "\"Tue\"", + "\"Wed\"", + "\"Thu\"", + "\"Fri\"", + "\"Sat\"", + "\"Sun\"", + "\"Jan\"", + "\"Feb\"", + "\"Mar\"", + "\"Apr\"", + "\"May\"", + "\"Jun\"", + "\"Jul\"", + "\"Aug\"", + "\"Sep\"", + "\"Oct\"", + "\"Nov\"", + "\"Dec\"", + "\":\"", + "<OFFSETDIR>", + "\"UT\"", + "\"GMT\"", + "\"EST\"", + "\"EDT\"", + "\"CST\"", + "\"CDT\"", + "\"MST\"", + "\"MDT\"", + "\"PST\"", + "\"PDT\"", + "<MILITARY_ZONE>", + "<WS>", + "\"(\"", + "\")\"", + "<token of kind 39>", + "\"(\"", + "<token of kind 41>", + "<token of kind 42>", + "\"(\"", + "\")\"", + "<token of kind 45>", + "<DIGITS>", + "<QUOTEDPAIR>", + "<ANY>", + }; + +} diff --git a/apache/org/apache/james/mime4j/field/datetime/parser/DateTimeParserTokenManager.java b/apache/org/apache/james/mime4j/field/datetime/parser/DateTimeParserTokenManager.java new file mode 100644 index 000000000..4b2d2fd95 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/datetime/parser/DateTimeParserTokenManager.java @@ -0,0 +1,882 @@ +/* Generated By:JavaCC: Do not edit this line. DateTimeParserTokenManager.java */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.datetime.parser; +import org.apache.james.mime4j.field.datetime.DateTime; +import java.util.Calendar; + +public class DateTimeParserTokenManager implements DateTimeParserConstants +{ + // Keeps track of how many levels of comment nesting + // we've encountered. This is only used when the 2nd + // level is reached, for example ((this)), not (this). + // This is because the outermost level must be treated + // specially anyway, because the outermost ")" has a + // different token type than inner ")" instances. + static int commentNest; + public java.io.PrintStream debugStream = System.out; + public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; } +private final int jjStopStringLiteralDfa_0(int pos, long active0) +{ + switch (pos) + { + case 0: + if ((active0 & 0x7fe7cf7f0L) != 0L) + { + jjmatchedKind = 35; + return -1; + } + return -1; + case 1: + if ((active0 & 0x7fe7cf7f0L) != 0L) + { + if (jjmatchedPos == 0) + { + jjmatchedKind = 35; + jjmatchedPos = 0; + } + return -1; + } + return -1; + default : + return -1; + } +} +private final int jjStartNfa_0(int pos, long active0) +{ + return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1); +} +private final int jjStopAtPos(int pos, int kind) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + return pos + 1; +} +private final int jjStartNfaWithStates_0(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_0(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_0() +{ + switch(curChar) + { + case 10: + return jjStopAtPos(0, 2); + case 13: + return jjStopAtPos(0, 1); + case 40: + return jjStopAtPos(0, 37); + case 44: + return jjStopAtPos(0, 3); + case 58: + return jjStopAtPos(0, 23); + case 65: + return jjMoveStringLiteralDfa1_0(0x44000L); + case 67: + return jjMoveStringLiteralDfa1_0(0x60000000L); + case 68: + return jjMoveStringLiteralDfa1_0(0x400000L); + case 69: + return jjMoveStringLiteralDfa1_0(0x18000000L); + case 70: + return jjMoveStringLiteralDfa1_0(0x1100L); + case 71: + return jjMoveStringLiteralDfa1_0(0x4000000L); + case 74: + return jjMoveStringLiteralDfa1_0(0x30800L); + case 77: + return jjMoveStringLiteralDfa1_0(0x18000a010L); + case 78: + return jjMoveStringLiteralDfa1_0(0x200000L); + case 79: + return jjMoveStringLiteralDfa1_0(0x100000L); + case 80: + return jjMoveStringLiteralDfa1_0(0x600000000L); + case 83: + return jjMoveStringLiteralDfa1_0(0x80600L); + case 84: + return jjMoveStringLiteralDfa1_0(0xa0L); + case 85: + return jjMoveStringLiteralDfa1_0(0x2000000L); + case 87: + return jjMoveStringLiteralDfa1_0(0x40L); + default : + return jjMoveNfa_0(0, 0); + } +} +private final int jjMoveStringLiteralDfa1_0(long active0) +{ + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { + jjStopStringLiteralDfa_0(0, active0); + return 1; + } + switch(curChar) + { + case 68: + return jjMoveStringLiteralDfa2_0(active0, 0x550000000L); + case 77: + return jjMoveStringLiteralDfa2_0(active0, 0x4000000L); + case 83: + return jjMoveStringLiteralDfa2_0(active0, 0x2a8000000L); + case 84: + if ((active0 & 0x2000000L) != 0L) + return jjStopAtPos(1, 25); + break; + case 97: + return jjMoveStringLiteralDfa2_0(active0, 0xaa00L); + case 99: + return jjMoveStringLiteralDfa2_0(active0, 0x100000L); + case 101: + return jjMoveStringLiteralDfa2_0(active0, 0x481040L); + case 104: + return jjMoveStringLiteralDfa2_0(active0, 0x80L); + case 111: + return jjMoveStringLiteralDfa2_0(active0, 0x200010L); + case 112: + return jjMoveStringLiteralDfa2_0(active0, 0x4000L); + case 114: + return jjMoveStringLiteralDfa2_0(active0, 0x100L); + case 117: + return jjMoveStringLiteralDfa2_0(active0, 0x70420L); + default : + break; + } + return jjStartNfa_0(0, active0); +} +private final int jjMoveStringLiteralDfa2_0(long old0, long active0) +{ + if (((active0 &= old0)) == 0L) + return jjStartNfa_0(0, old0); + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { + jjStopStringLiteralDfa_0(1, active0); + return 2; + } + switch(curChar) + { + case 84: + if ((active0 & 0x4000000L) != 0L) + return jjStopAtPos(2, 26); + else if ((active0 & 0x8000000L) != 0L) + return jjStopAtPos(2, 27); + else if ((active0 & 0x10000000L) != 0L) + return jjStopAtPos(2, 28); + else if ((active0 & 0x20000000L) != 0L) + return jjStopAtPos(2, 29); + else if ((active0 & 0x40000000L) != 0L) + return jjStopAtPos(2, 30); + else if ((active0 & 0x80000000L) != 0L) + return jjStopAtPos(2, 31); + else if ((active0 & 0x100000000L) != 0L) + return jjStopAtPos(2, 32); + else if ((active0 & 0x200000000L) != 0L) + return jjStopAtPos(2, 33); + else if ((active0 & 0x400000000L) != 0L) + return jjStopAtPos(2, 34); + break; + case 98: + if ((active0 & 0x1000L) != 0L) + return jjStopAtPos(2, 12); + break; + case 99: + if ((active0 & 0x400000L) != 0L) + return jjStopAtPos(2, 22); + break; + case 100: + if ((active0 & 0x40L) != 0L) + return jjStopAtPos(2, 6); + break; + case 101: + if ((active0 & 0x20L) != 0L) + return jjStopAtPos(2, 5); + break; + case 103: + if ((active0 & 0x40000L) != 0L) + return jjStopAtPos(2, 18); + break; + case 105: + if ((active0 & 0x100L) != 0L) + return jjStopAtPos(2, 8); + break; + case 108: + if ((active0 & 0x20000L) != 0L) + return jjStopAtPos(2, 17); + break; + case 110: + if ((active0 & 0x10L) != 0L) + return jjStopAtPos(2, 4); + else if ((active0 & 0x400L) != 0L) + return jjStopAtPos(2, 10); + else if ((active0 & 0x800L) != 0L) + return jjStopAtPos(2, 11); + else if ((active0 & 0x10000L) != 0L) + return jjStopAtPos(2, 16); + break; + case 112: + if ((active0 & 0x80000L) != 0L) + return jjStopAtPos(2, 19); + break; + case 114: + if ((active0 & 0x2000L) != 0L) + return jjStopAtPos(2, 13); + else if ((active0 & 0x4000L) != 0L) + return jjStopAtPos(2, 14); + break; + case 116: + if ((active0 & 0x200L) != 0L) + return jjStopAtPos(2, 9); + else if ((active0 & 0x100000L) != 0L) + return jjStopAtPos(2, 20); + break; + case 117: + if ((active0 & 0x80L) != 0L) + return jjStopAtPos(2, 7); + break; + case 118: + if ((active0 & 0x200000L) != 0L) + return jjStopAtPos(2, 21); + break; + case 121: + if ((active0 & 0x8000L) != 0L) + return jjStopAtPos(2, 15); + break; + default : + break; + } + return jjStartNfa_0(1, active0); +} +private final void jjCheckNAdd(int state) +{ + if (jjrounds[state] != jjround) + { + jjstateSet[jjnewStateCnt++] = state; + jjrounds[state] = jjround; + } +} +private final void jjAddStates(int start, int end) +{ + do { + jjstateSet[jjnewStateCnt++] = jjnextStates[start]; + } while (start++ != end); +} +private final void jjCheckNAddTwoStates(int state1, int state2) +{ + jjCheckNAdd(state1); + jjCheckNAdd(state2); +} +private final void jjCheckNAddStates(int start, int end) +{ + do { + jjCheckNAdd(jjnextStates[start]); + } while (start++ != end); +} +private final void jjCheckNAddStates(int start) +{ + jjCheckNAdd(jjnextStates[start]); + jjCheckNAdd(jjnextStates[start + 1]); +} +private final int jjMoveNfa_0(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 4; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0x3ff000000000000L & l) != 0L) + { + if (kind > 46) + kind = 46; + jjCheckNAdd(3); + } + else if ((0x100000200L & l) != 0L) + { + if (kind > 36) + kind = 36; + jjCheckNAdd(2); + } + else if ((0x280000000000L & l) != 0L) + { + if (kind > 24) + kind = 24; + } + break; + case 2: + if ((0x100000200L & l) == 0L) + break; + kind = 36; + jjCheckNAdd(2); + break; + case 3: + if ((0x3ff000000000000L & l) == 0L) + break; + kind = 46; + jjCheckNAdd(3); + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((0x7fffbfe07fffbfeL & l) != 0L) + kind = 35; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 4 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_1(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_1(int pos, long active0) +{ + return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1); +} +private final int jjStartNfaWithStates_1(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_1(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_1() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 40); + case 41: + return jjStopAtPos(0, 38); + default : + return jjMoveNfa_1(0, 0); + } +} +static final long[] jjbitVec0 = { + 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL +}; +private final int jjMoveNfa_1(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 41) + kind = 41; + break; + case 1: + if (kind > 39) + kind = 39; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 41) + kind = 41; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 39) + kind = 39; + break; + case 2: + if (kind > 41) + kind = 41; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 41) + kind = 41; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 39) + kind = 39; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +private final int jjStopStringLiteralDfa_2(int pos, long active0) +{ + switch (pos) + { + default : + return -1; + } +} +private final int jjStartNfa_2(int pos, long active0) +{ + return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1); +} +private final int jjStartNfaWithStates_2(int pos, int kind, int state) +{ + jjmatchedKind = kind; + jjmatchedPos = pos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return pos + 1; } + return jjMoveNfa_2(state, pos + 1); +} +private final int jjMoveStringLiteralDfa0_2() +{ + switch(curChar) + { + case 40: + return jjStopAtPos(0, 43); + case 41: + return jjStopAtPos(0, 44); + default : + return jjMoveNfa_2(0, 0); + } +} +private final int jjMoveNfa_2(int startState, int curPos) +{ + int[] nextStates; + int startsAt = 0; + jjnewStateCnt = 3; + int i = 1; + jjstateSet[0] = startState; + int j, kind = 0x7fffffff; + for (;;) + { + if (++jjround == 0x7fffffff) + ReInitRounds(); + if (curChar < 64) + { + long l = 1L << curChar; + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 45) + kind = 45; + break; + case 1: + if (kind > 42) + kind = 42; + break; + default : break; + } + } while(i != startsAt); + } + else if (curChar < 128) + { + long l = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if (kind > 45) + kind = 45; + if (curChar == 92) + jjstateSet[jjnewStateCnt++] = 1; + break; + case 1: + if (kind > 42) + kind = 42; + break; + case 2: + if (kind > 45) + kind = 45; + break; + default : break; + } + } while(i != startsAt); + } + else + { + int i2 = (curChar & 0xff) >> 6; + long l2 = 1L << (curChar & 077); + MatchLoop: do + { + switch(jjstateSet[--i]) + { + case 0: + if ((jjbitVec0[i2] & l2) != 0L && kind > 45) + kind = 45; + break; + case 1: + if ((jjbitVec0[i2] & l2) != 0L && kind > 42) + kind = 42; + break; + default : break; + } + } while(i != startsAt); + } + if (kind != 0x7fffffff) + { + jjmatchedKind = kind; + jjmatchedPos = curPos; + kind = 0x7fffffff; + } + ++curPos; + if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt))) + return curPos; + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return curPos; } + } +} +static final int[] jjnextStates = { +}; +public static final String[] jjstrLiteralImages = { +"", "\15", "\12", "\54", "\115\157\156", "\124\165\145", "\127\145\144", +"\124\150\165", "\106\162\151", "\123\141\164", "\123\165\156", "\112\141\156", +"\106\145\142", "\115\141\162", "\101\160\162", "\115\141\171", "\112\165\156", +"\112\165\154", "\101\165\147", "\123\145\160", "\117\143\164", "\116\157\166", +"\104\145\143", "\72", null, "\125\124", "\107\115\124", "\105\123\124", "\105\104\124", +"\103\123\124", "\103\104\124", "\115\123\124", "\115\104\124", "\120\123\124", +"\120\104\124", null, null, null, null, null, null, null, null, null, null, null, null, null, +null, }; +public static final String[] lexStateNames = { + "DEFAULT", + "INCOMMENT", + "NESTED_COMMENT", +}; +public static final int[] jjnewLexState = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 0, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, +}; +static final long[] jjtoToken = { + 0x400fffffffffL, +}; +static final long[] jjtoSkip = { + 0x5000000000L, +}; +static final long[] jjtoSpecial = { + 0x1000000000L, +}; +static final long[] jjtoMore = { + 0x3fa000000000L, +}; +protected SimpleCharStream input_stream; +private final int[] jjrounds = new int[4]; +private final int[] jjstateSet = new int[8]; +StringBuffer image; +int jjimageLen; +int lengthOfMatch; +protected char curChar; +public DateTimeParserTokenManager(SimpleCharStream stream){ + if (SimpleCharStream.staticFlag) + throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer."); + input_stream = stream; +} +public DateTimeParserTokenManager(SimpleCharStream stream, int lexState){ + this(stream); + SwitchTo(lexState); +} +public void ReInit(SimpleCharStream stream) +{ + jjmatchedPos = jjnewStateCnt = 0; + curLexState = defaultLexState; + input_stream = stream; + ReInitRounds(); +} +private final void ReInitRounds() +{ + int i; + jjround = 0x80000001; + for (i = 4; i-- > 0;) + jjrounds[i] = 0x80000000; +} +public void ReInit(SimpleCharStream stream, int lexState) +{ + ReInit(stream); + SwitchTo(lexState); +} +public void SwitchTo(int lexState) +{ + if (lexState >= 3 || lexState < 0) + throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE); + else + curLexState = lexState; +} + +protected Token jjFillToken() +{ + Token t = Token.newToken(jjmatchedKind); + t.kind = jjmatchedKind; + String im = jjstrLiteralImages[jjmatchedKind]; + t.image = (im == null) ? input_stream.GetImage() : im; + t.beginLine = input_stream.getBeginLine(); + t.beginColumn = input_stream.getBeginColumn(); + t.endLine = input_stream.getEndLine(); + t.endColumn = input_stream.getEndColumn(); + return t; +} + +int curLexState = 0; +int defaultLexState = 0; +int jjnewStateCnt; +int jjround; +int jjmatchedPos; +int jjmatchedKind; + +public Token getNextToken() +{ + int kind; + Token specialToken = null; + Token matchedToken; + int curPos = 0; + + EOFLoop : + for (;;) + { + try + { + curChar = input_stream.BeginToken(); + } + catch(java.io.IOException e) + { + jjmatchedKind = 0; + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + return matchedToken; + } + image = null; + jjimageLen = 0; + + for (;;) + { + switch(curLexState) + { + case 0: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_0(); + break; + case 1: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_1(); + break; + case 2: + jjmatchedKind = 0x7fffffff; + jjmatchedPos = 0; + curPos = jjMoveStringLiteralDfa0_2(); + break; + } + if (jjmatchedKind != 0x7fffffff) + { + if (jjmatchedPos + 1 < curPos) + input_stream.backup(curPos - jjmatchedPos - 1); + if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + matchedToken.specialToken = specialToken; + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + return matchedToken; + } + else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L) + { + matchedToken = jjFillToken(); + if (specialToken == null) + specialToken = matchedToken; + else + { + matchedToken.specialToken = specialToken; + specialToken = (specialToken.next = matchedToken); + } + } + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + continue EOFLoop; + } + MoreLexicalActions(); + if (jjnewLexState[jjmatchedKind] != -1) + curLexState = jjnewLexState[jjmatchedKind]; + curPos = 0; + jjmatchedKind = 0x7fffffff; + try { + curChar = input_stream.readChar(); + continue; + } + catch (java.io.IOException e1) { } + } + int error_line = input_stream.getEndLine(); + int error_column = input_stream.getEndColumn(); + String error_after = null; + boolean EOFSeen = false; + try { input_stream.readChar(); input_stream.backup(1); } + catch (java.io.IOException e1) { + EOFSeen = true; + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + if (curChar == '\n' || curChar == '\r') { + error_line++; + error_column = 0; + } + else + error_column++; + } + if (!EOFSeen) { + input_stream.backup(1); + error_after = curPos <= 1 ? "" : input_stream.GetImage(); + } + throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR); + } + } +} + +void MoreLexicalActions() +{ + jjimageLen += (lengthOfMatch = jjmatchedPos + 1); + switch(jjmatchedKind) + { + case 39 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 40 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + commentNest = 1; + break; + case 42 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + image.deleteCharAt(image.length() - 2); + break; + case 43 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + ++commentNest; + break; + case 44 : + if (image == null) + image = new StringBuffer(); + image.append(input_stream.GetSuffix(jjimageLen)); + jjimageLen = 0; + --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); + break; + default : + break; + } +} +} diff --git a/apache/org/apache/james/mime4j/field/datetime/parser/ParseException.java b/apache/org/apache/james/mime4j/field/datetime/parser/ParseException.java new file mode 100644 index 000000000..13b3ff097 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/datetime/parser/ParseException.java @@ -0,0 +1,207 @@ +/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.datetime.parser; + +/** + * This exception is thrown when parse errors are encountered. + * You can explicitly create objects of this exception type by + * calling the method generateParseException in the generated + * parser. + * + * You can modify this class to customize your error reporting + * mechanisms so long as you retain the public fields. + */ +public class ParseException extends Exception { + + /** + * This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form: + * ParseException: <result of getMessage> + */ + public ParseException(Token currentTokenVal, + int[][] expectedTokenSequencesVal, + String[] tokenImageVal + ) + { + super(""); + specialConstructor = true; + currentToken = currentTokenVal; + expectedTokenSequences = expectedTokenSequencesVal; + tokenImage = tokenImageVal; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + */ + + public ParseException() { + super(); + specialConstructor = false; + } + + public ParseException(String message) { + super(message); + specialConstructor = false; + } + + /** + * This variable determines which constructor was used to create + * this object and thereby affects the semantics of the + * "getMessage" method (see below). + */ + protected boolean specialConstructor; + + /** + * This is the last token that has been consumed successfully. If + * this object has been created due to a parse error, the token + * followng this token will (therefore) be the first error token. + */ + public Token currentToken; + + /** + * Each entry in this array is an array of integers. Each array + * of integers represents a sequence of tokens (by their ordinal + * values) that is expected at this point of the parse. + */ + public int[][] expectedTokenSequences; + + /** + * This is a reference to the "tokenImage" array of the generated + * parser within which the parse error occurred. This array is + * defined in the generated ...Constants interface. + */ + public String[] tokenImage; + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + */ + public String getMessage() { + if (!specialConstructor) { + return super.getMessage(); + } + StringBuffer expected = new StringBuffer(); + int maxSize = 0; + for (int i = 0; i < expectedTokenSequences.length; i++) { + if (maxSize < expectedTokenSequences[i].length) { + maxSize = expectedTokenSequences[i].length; + } + for (int j = 0; j < expectedTokenSequences[i].length; j++) { + expected.append(tokenImage[expectedTokenSequences[i][j]]).append(" "); + } + if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) { + expected.append("..."); + } + expected.append(eol).append(" "); + } + String retval = "Encountered \""; + Token tok = currentToken.next; + for (int i = 0; i < maxSize; i++) { + if (i != 0) retval += " "; + if (tok.kind == 0) { + retval += tokenImage[0]; + break; + } + retval += add_escapes(tok.image); + tok = tok.next; + } + retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn; + retval += "." + eol; + if (expectedTokenSequences.length == 1) { + retval += "Was expecting:" + eol + " "; + } else { + retval += "Was expecting one of:" + eol + " "; + } + retval += expected.toString(); + return retval; + } + + /** + * The end of line string for this machine. + */ + protected String eol = System.getProperty("line.separator", "\n"); + + /** + * Used to convert raw characters to their escaped version + * when these raw version cannot be used as part of an ASCII + * string literal. + */ + protected String add_escapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + +} diff --git a/apache/org/apache/james/mime4j/field/datetime/parser/SimpleCharStream.java b/apache/org/apache/james/mime4j/field/datetime/parser/SimpleCharStream.java new file mode 100644 index 000000000..2724529f7 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/datetime/parser/SimpleCharStream.java @@ -0,0 +1,454 @@ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 4.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.datetime.parser; + +/** + * An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (without unicode processing). + */ + +public class SimpleCharStream +{ + public static final boolean staticFlag = false; + int bufsize; + int available; + int tokenBegin; + public int bufpos = -1; + protected int bufline[]; + protected int bufcolumn[]; + + protected int column = 0; + protected int line = 1; + + protected boolean prevCharIsCR = false; + protected boolean prevCharIsLF = false; + + protected java.io.Reader inputStream; + + protected char[] buffer; + protected int maxNextCharInd = 0; + protected int inBuf = 0; + protected int tabSize = 8; + + protected void setTabSize(int i) { tabSize = i; } + protected int getTabSize(int i) { return tabSize; } + + + protected void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, + bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } + + + bufsize += 2048; + available = bufsize; + tokenBegin = 0; + } + + protected void FillBuff() throws java.io.IOException + { + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) + { + bufpos = maxNextCharInd = 0; + available = tokenBegin; + } + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; + else + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } + + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, + available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } + } + + public char BeginToken() throws java.io.IOException + { + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; + + return c; + } + + protected void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + line += (column = 1); + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (tabSize - (column % tabSize)); + break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + + public char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + return buffer[bufpos]; + } + + if (++bufpos >= maxNextCharInd) + FillBuff(); + + char c = buffer[bufpos]; + + UpdateLineColumn(c); + return (c); + } + + /** + * @deprecated + * @see #getEndColumn + */ + @Deprecated + public int getColumn() { + return bufcolumn[bufpos]; + } + + /** + * @deprecated + * @see #getEndLine + */ + @Deprecated + public int getLine() { + return bufline[bufpos]; + } + + public int getEndColumn() { + return bufcolumn[bufpos]; + } + + public int getEndLine() { + return bufline[bufpos]; + } + + public int getBeginColumn() { + return bufcolumn[tokenBegin]; + } + + public int getBeginLine() { + return bufline[tokenBegin]; + } + + public void backup(int amount) { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.Reader dstream) + { + this(dstream, 1, 1, 4096); + } + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + bufpos = -1; + } + + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + + public void ReInit(java.io.Reader dstream) + { + ReInit(dstream, 1, 1, 4096); + } + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + this(dstream, encoding, 1, 1, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream) + { + this(dstream, 1, 1, 4096); + } + + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException + { + ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + } + + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, 1, 1, 4096); + } + + public void ReInit(java.io.InputStream dstream) + { + ReInit(dstream, 1, 1, 4096); + } + public void ReInit(java.io.InputStream dstream, String encoding, int startline, + int startcolumn) throws java.io.UnsupportedEncodingException + { + ReInit(dstream, encoding, startline, startcolumn, 4096); + } + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + public String GetImage() + { + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + + public char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + public void Done() + { + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token. + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && + bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + +} diff --git a/apache/org/apache/james/mime4j/field/datetime/parser/Token.java b/apache/org/apache/james/mime4j/field/datetime/parser/Token.java new file mode 100644 index 000000000..0927a0921 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/datetime/parser/Token.java @@ -0,0 +1,96 @@ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.datetime.parser; + +/** + * Describes the input token stream. + */ + +public class Token { + + /** + * An integer that describes the kind of this token. This numbering + * system is determined by JavaCCParser, and a table of these numbers is + * stored in the file ...Constants.java. + */ + public int kind; + + /** + * beginLine and beginColumn describe the position of the first character + * of this token; endLine and endColumn describe the position of the + * last character of this token. + */ + public int beginLine, beginColumn, endLine, endColumn; + + /** + * The string image of the token. + */ + public String image; + + /** + * A reference to the next regular (non-special) token from the input + * stream. If this is the last token from the input stream, or if the + * token manager has not read tokens beyond this one, this field is + * set to null. This is true only if this token is also a regular + * token. Otherwise, see below for a description of the contents of + * this field. + */ + public Token next; + + /** + * This field is used to access special tokens that occur prior to this + * token, but after the immediately preceding regular (non-special) token. + * If there are no such special tokens, this field is set to null. + * When there are more than one such special token, this field refers + * to the last of these special tokens, which in turn refers to the next + * previous special token through its specialToken field, and so on + * until the first special token (whose specialToken field is null). + * The next fields of special tokens refer to other special tokens that + * immediately follow it (without an intervening regular token). If there + * is no such token, this field is null. + */ + public Token specialToken; + + /** + * Returns the image. + */ + public String toString() + { + return image; + } + + /** + * Returns a new Token object, by default. However, if you want, you + * can create and return subclass objects based on the value of ofKind. + * Simply add the cases to the switch for all those special cases. + * For example, if you have a subclass of Token called IDToken that + * you want to create if ofKind is ID, simlpy add something like : + * + * case MyParserConstants.ID : return new IDToken(); + * + * to the following switch statement. Then you can cast matchedToken + * variable to the appropriate type and use it in your lexical actions. + */ + public static final Token newToken(int ofKind) + { + switch(ofKind) + { + default : return new Token(); + } + } + +} diff --git a/apache/org/apache/james/mime4j/field/datetime/parser/TokenMgrError.java b/apache/org/apache/james/mime4j/field/datetime/parser/TokenMgrError.java new file mode 100644 index 000000000..e7043c1b7 --- /dev/null +++ b/apache/org/apache/james/mime4j/field/datetime/parser/TokenMgrError.java @@ -0,0 +1,148 @@ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */ +/* + * Copyright 2004 the mime4j project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.james.mime4j.field.datetime.parser; + +public class TokenMgrError extends Error +{ + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ + + /** + * Lexical error occured. + */ + static final int LEXICAL_ERROR = 0; + + /** + * An attempt wass made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; + + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; + + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their espaced (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexicl error + * curLexState : lexical state in which this error occured + * errorLine : line number when the error occured + * errorColumn : column number when the error occured + * errorAfter : prefix that was seen before this error occured + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } + + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } + + /* + * Constructors of various flavors follow. + */ + + public TokenMgrError() { + } + + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } + + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } +} diff --git a/apache/org/apache/james/mime4j/util/CharsetUtil.java b/apache/org/apache/james/mime4j/util/CharsetUtil.java new file mode 100644 index 000000000..4e712fcdd --- /dev/null +++ b/apache/org/apache/james/mime4j/util/CharsetUtil.java @@ -0,0 +1,1249 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.util; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.HashMap; +import java.util.Locale; +import java.util.TreeSet; + +//BEGIN android-changed: Stubbing out logging +import org.apache.james.mime4j.Log; +import org.apache.james.mime4j.LogFactory; +//END android-changed + +/** + * Utility class for working with character sets. It is somewhat similar to + * the Java 1.4 <code>java.nio.charset.Charset</code> class but knows many + * more aliases and is compatible with Java 1.3. It will use a simple detection + * mechanism to detect what character sets the current VM supports. This will + * be a sub-set of the character sets listed in the + * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html"> + * Java 1.5 (J2SE5.0) Supported Encodings</a> document. + * <p> + * The <a href="http://www.iana.org/assignments/character-sets"> + * IANA Character Sets</a> document has been used to determine the preferred + * MIME character set names and to get a list of known aliases. + * <p> + * This is a complete list of the character sets known to this class: + * <table> + * <tr> + * <td>Canonical (Java) name</td> + * <td>MIME preferred</td> + * <td>Aliases</td> + * </tr> + * <tr> + * <td>ASCII</td> + * <td>US-ASCII</td> + * <td>ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ISO646-US us IBM367 cp367 csASCII ascii7 646 iso_646.irv:1983 </td> + * </tr> + * <tr> + * <td>Big5</td> + * <td>Big5</td> + * <td>csBig5 CN-Big5 BIG-FIVE BIGFIVE </td> + * </tr> + * <tr> + * <td>Big5_HKSCS</td> + * <td>Big5-HKSCS</td> + * <td>big5hkscs </td> + * </tr> + * <tr> + * <td>Big5_Solaris</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp037</td> + * <td>IBM037</td> + * <td>ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl csIBM037 </td> + * </tr> + * <tr> + * <td>Cp1006</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1025</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1026</td> + * <td>IBM1026</td> + * <td>csIBM1026 </td> + * </tr> + * <tr> + * <td>Cp1046</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1047</td> + * <td>IBM1047</td> + * <td>IBM-1047 </td> + * </tr> + * <tr> + * <td>Cp1097</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1098</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1112</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1122</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1123</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1124</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1140</td> + * <td>IBM01140</td> + * <td>CCSID01140 CP01140 ebcdic-us-37+euro </td> + * </tr> + * <tr> + * <td>Cp1141</td> + * <td>IBM01141</td> + * <td>CCSID01141 CP01141 ebcdic-de-273+euro </td> + * </tr> + * <tr> + * <td>Cp1142</td> + * <td>IBM01142</td> + * <td>CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro </td> + * </tr> + * <tr> + * <td>Cp1143</td> + * <td>IBM01143</td> + * <td>CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro </td> + * </tr> + * <tr> + * <td>Cp1144</td> + * <td>IBM01144</td> + * <td>CCSID01144 CP01144 ebcdic-it-280+euro </td> + * </tr> + * <tr> + * <td>Cp1145</td> + * <td>IBM01145</td> + * <td>CCSID01145 CP01145 ebcdic-es-284+euro </td> + * </tr> + * <tr> + * <td>Cp1146</td> + * <td>IBM01146</td> + * <td>CCSID01146 CP01146 ebcdic-gb-285+euro </td> + * </tr> + * <tr> + * <td>Cp1147</td> + * <td>IBM01147</td> + * <td>CCSID01147 CP01147 ebcdic-fr-297+euro </td> + * </tr> + * <tr> + * <td>Cp1148</td> + * <td>IBM01148</td> + * <td>CCSID01148 CP01148 ebcdic-international-500+euro </td> + * </tr> + * <tr> + * <td>Cp1149</td> + * <td>IBM01149</td> + * <td>CCSID01149 CP01149 ebcdic-is-871+euro </td> + * </tr> + * <tr> + * <td>Cp1250</td> + * <td>windows-1250</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1251</td> + * <td>windows-1251</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1252</td> + * <td>windows-1252</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1253</td> + * <td>windows-1253</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1254</td> + * <td>windows-1254</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1255</td> + * <td>windows-1255</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1256</td> + * <td>windows-1256</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1257</td> + * <td>windows-1257</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1258</td> + * <td>windows-1258</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1381</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp1383</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp273</td> + * <td>IBM273</td> + * <td>csIBM273 </td> + * </tr> + * <tr> + * <td>Cp277</td> + * <td>IBM277</td> + * <td>EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 </td> + * </tr> + * <tr> + * <td>Cp278</td> + * <td>IBM278</td> + * <td>CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 </td> + * </tr> + * <tr> + * <td>Cp280</td> + * <td>IBM280</td> + * <td>ebcdic-cp-it csIBM280 </td> + * </tr> + * <tr> + * <td>Cp284</td> + * <td>IBM284</td> + * <td>ebcdic-cp-es csIBM284 </td> + * </tr> + * <tr> + * <td>Cp285</td> + * <td>IBM285</td> + * <td>ebcdic-cp-gb csIBM285 </td> + * </tr> + * <tr> + * <td>Cp297</td> + * <td>IBM297</td> + * <td>ebcdic-cp-fr csIBM297 </td> + * </tr> + * <tr> + * <td>Cp33722</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp420</td> + * <td>IBM420</td> + * <td>ebcdic-cp-ar1 csIBM420 </td> + * </tr> + * <tr> + * <td>Cp424</td> + * <td>IBM424</td> + * <td>ebcdic-cp-he csIBM424 </td> + * </tr> + * <tr> + * <td>Cp437</td> + * <td>IBM437</td> + * <td>437 csPC8CodePage437 </td> + * </tr> + * <tr> + * <td>Cp500</td> + * <td>IBM500</td> + * <td>ebcdic-cp-be ebcdic-cp-ch csIBM500 </td> + * </tr> + * <tr> + * <td>Cp737</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp775</td> + * <td>IBM775</td> + * <td>csPC775Baltic </td> + * </tr> + * <tr> + * <td>Cp838</td> + * <td>IBM-Thai</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp850</td> + * <td>IBM850</td> + * <td>850 csPC850Multilingual </td> + * </tr> + * <tr> + * <td>Cp852</td> + * <td>IBM852</td> + * <td>852 csPCp852 </td> + * </tr> + * <tr> + * <td>Cp855</td> + * <td>IBM855</td> + * <td>855 csIBM855 </td> + * </tr> + * <tr> + * <td>Cp856</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp857</td> + * <td>IBM857</td> + * <td>857 csIBM857 </td> + * </tr> + * <tr> + * <td>Cp858</td> + * <td>IBM00858</td> + * <td>CCSID00858 CP00858 PC-Multilingual-850+euro </td> + * </tr> + * <tr> + * <td>Cp860</td> + * <td>IBM860</td> + * <td>860 csIBM860 </td> + * </tr> + * <tr> + * <td>Cp861</td> + * <td>IBM861</td> + * <td>861 cp-is csIBM861 </td> + * </tr> + * <tr> + * <td>Cp862</td> + * <td>IBM862</td> + * <td>862 csPC862LatinHebrew </td> + * </tr> + * <tr> + * <td>Cp863</td> + * <td>IBM863</td> + * <td>863 csIBM863 </td> + * </tr> + * <tr> + * <td>Cp864</td> + * <td>IBM864</td> + * <td>cp864 csIBM864 </td> + * </tr> + * <tr> + * <td>Cp865</td> + * <td>IBM865</td> + * <td>865 csIBM865 </td> + * </tr> + * <tr> + * <td>Cp866</td> + * <td>IBM866</td> + * <td>866 csIBM866 </td> + * </tr> + * <tr> + * <td>Cp868</td> + * <td>IBM868</td> + * <td>cp-ar csIBM868 </td> + * </tr> + * <tr> + * <td>Cp869</td> + * <td>IBM869</td> + * <td>cp-gr csIBM869 </td> + * </tr> + * <tr> + * <td>Cp870</td> + * <td>IBM870</td> + * <td>ebcdic-cp-roece ebcdic-cp-yu csIBM870 </td> + * </tr> + * <tr> + * <td>Cp871</td> + * <td>IBM871</td> + * <td>ebcdic-cp-is csIBM871 </td> + * </tr> + * <tr> + * <td>Cp875</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp918</td> + * <td>IBM918</td> + * <td>ebcdic-cp-ar2 csIBM918 </td> + * </tr> + * <tr> + * <td>Cp921</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp922</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp930</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp933</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp935</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp937</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp939</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp942</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp942C</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp943</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp943C</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp948</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp949</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp949C</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp950</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp964</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>Cp970</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>EUC_CN</td> + * <td>GB2312</td> + * <td>x-EUC-CN csGB2312 euccn euc-cn gb2312-80 gb2312-1980 CN-GB CN-GB-ISOIR165 </td> + * </tr> + * <tr> + * <td>EUC_JP</td> + * <td>EUC-JP</td> + * <td>csEUCPkdFmtJapanese Extended_UNIX_Code_Packed_Format_for_Japanese eucjis x-eucjp eucjp x-euc-jp </td> + * </tr> + * <tr> + * <td>EUC_JP_LINUX</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>EUC_JP_Solaris</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>EUC_KR</td> + * <td>EUC-KR</td> + * <td>csEUCKR ksc5601 5601 ksc5601_1987 ksc_5601 ksc5601-1987 ks_c_5601-1987 euckr </td> + * </tr> + * <tr> + * <td>EUC_TW</td> + * <td>EUC-TW</td> + * <td>x-EUC-TW cns11643 euctw </td> + * </tr> + * <tr> + * <td>GB18030</td> + * <td>GB18030</td> + * <td>gb18030-2000 </td> + * </tr> + * <tr> + * <td>GBK</td> + * <td>windows-936</td> + * <td>CP936 MS936 ms_936 x-mswin-936 </td> + * </tr> + * <tr> + * <td>ISCII91</td> + * <td>?</td> + * <td>x-ISCII91 iscii </td> + * </tr> + * <tr> + * <td>ISO2022CN</td> + * <td>ISO-2022-CN</td> + * <td></td> + * </tr> + * <tr> + * <td>ISO2022JP</td> + * <td>ISO-2022-JP</td> + * <td>csISO2022JP JIS jis_encoding csjisencoding </td> + * </tr> + * <tr> + * <td>ISO2022KR</td> + * <td>ISO-2022-KR</td> + * <td>csISO2022KR </td> + * </tr> + * <tr> + * <td>ISO2022_CN_CNS</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>ISO2022_CN_GB</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>ISO8859_1</td> + * <td>ISO-8859-1</td> + * <td>ISO_8859-1:1987 iso-ir-100 ISO_8859-1 latin1 l1 IBM819 CP819 csISOLatin1 8859_1 819 IBM-819 ISO8859-1 ISO_8859_1 </td> + * </tr> + * <tr> + * <td>ISO8859_13</td> + * <td>ISO-8859-13</td> + * <td></td> + * </tr> + * <tr> + * <td>ISO8859_15</td> + * <td>ISO-8859-15</td> + * <td>ISO_8859-15 Latin-9 8859_15 csISOlatin9 IBM923 cp923 923 L9 IBM-923 ISO8859-15 LATIN9 LATIN0 csISOlatin0 ISO8859_15_FDIS </td> + * </tr> + * <tr> + * <td>ISO8859_2</td> + * <td>ISO-8859-2</td> + * <td>ISO_8859-2:1987 iso-ir-101 ISO_8859-2 latin2 l2 csISOLatin2 8859_2 iso8859_2 </td> + * </tr> + * <tr> + * <td>ISO8859_3</td> + * <td>ISO-8859-3</td> + * <td>ISO_8859-3:1988 iso-ir-109 ISO_8859-3 latin3 l3 csISOLatin3 8859_3 </td> + * </tr> + * <tr> + * <td>ISO8859_4</td> + * <td>ISO-8859-4</td> + * <td>ISO_8859-4:1988 iso-ir-110 ISO_8859-4 latin4 l4 csISOLatin4 8859_4 </td> + * </tr> + * <tr> + * <td>ISO8859_5</td> + * <td>ISO-8859-5</td> + * <td>ISO_8859-5:1988 iso-ir-144 ISO_8859-5 cyrillic csISOLatinCyrillic 8859_5 </td> + * </tr> + * <tr> + * <td>ISO8859_6</td> + * <td>ISO-8859-6</td> + * <td>ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ECMA-114 ASMO-708 arabic csISOLatinArabic 8859_6 </td> + * </tr> + * <tr> + * <td>ISO8859_7</td> + * <td>ISO-8859-7</td> + * <td>ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ELOT_928 ECMA-118 greek greek8 csISOLatinGreek 8859_7 sun_eu_greek </td> + * </tr> + * <tr> + * <td>ISO8859_8</td> + * <td>ISO-8859-8</td> + * <td>ISO_8859-8:1988 iso-ir-138 ISO_8859-8 hebrew csISOLatinHebrew 8859_8 </td> + * </tr> + * <tr> + * <td>ISO8859_9</td> + * <td>ISO-8859-9</td> + * <td>ISO_8859-9:1989 iso-ir-148 ISO_8859-9 latin5 l5 csISOLatin5 8859_9 </td> + * </tr> + * <tr> + * <td>JISAutoDetect</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>JIS_C6626-1983</td> + * <td>JIS_C6626-1983</td> + * <td>x-JIS0208 JIS0208 csISO87JISX0208 x0208 JIS_X0208-1983 iso-ir-87 </td> + * </tr> + * <tr> + * <td>JIS_X0201</td> + * <td>JIS_X0201</td> + * <td>X0201 JIS0201 csHalfWidthKatakana </td> + * </tr> + * <tr> + * <td>JIS_X0212-1990</td> + * <td>JIS_X0212-1990</td> + * <td>iso-ir-159 x0212 JIS0212 csISO159JISX02121990 </td> + * </tr> + * <tr> + * <td>KOI8_R</td> + * <td>KOI8-R</td> + * <td>csKOI8R koi8 </td> + * </tr> + * <tr> + * <td>MS874</td> + * <td>windows-874</td> + * <td>cp874 </td> + * </tr> + * <tr> + * <td>MS932</td> + * <td>Windows-31J</td> + * <td>windows-932 csWindows31J x-ms-cp932 </td> + * </tr> + * <tr> + * <td>MS949</td> + * <td>windows-949</td> + * <td>windows949 ms_949 x-windows-949 </td> + * </tr> + * <tr> + * <td>MS950</td> + * <td>windows-950</td> + * <td>x-windows-950 </td> + * </tr> + * <tr> + * <td>MS950_HKSCS</td> + * <td></td> + * <td></td> + * </tr> + * <tr> + * <td>MacArabic</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacCentralEurope</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacCroatian</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacCyrillic</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacDingbat</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacGreek</td> + * <td>MacGreek</td> + * <td></td> + * </tr> + * <tr> + * <td>MacHebrew</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacIceland</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacRoman</td> + * <td>MacRoman</td> + * <td>Macintosh MAC csMacintosh </td> + * </tr> + * <tr> + * <td>MacRomania</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacSymbol</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacThai</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacTurkish</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>MacUkraine</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>SJIS</td> + * <td>Shift_JIS</td> + * <td>MS_Kanji csShiftJIS shift-jis x-sjis pck </td> + * </tr> + * <tr> + * <td>TIS620</td> + * <td>TIS-620</td> + * <td></td> + * </tr> + * <tr> + * <td>UTF-16</td> + * <td>UTF-16</td> + * <td>UTF_16 </td> + * </tr> + * <tr> + * <td>UTF8</td> + * <td>UTF-8</td> + * <td></td> + * </tr> + * <tr> + * <td>UnicodeBig</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>UnicodeBigUnmarked</td> + * <td>UTF-16BE</td> + * <td>X-UTF-16BE UTF_16BE ISO-10646-UCS-2 </td> + * </tr> + * <tr> + * <td>UnicodeLittle</td> + * <td>?</td> + * <td></td> + * </tr> + * <tr> + * <td>UnicodeLittleUnmarked</td> + * <td>UTF-16LE</td> + * <td>UTF_16LE X-UTF-16LE </td> + * </tr> + * <tr> + * <td>x-Johab</td> + * <td>johab</td> + * <td>johab cp1361 ms1361 ksc5601-1992 ksc5601_1992 </td> + * </tr> + * <tr> + * <td>x-iso-8859-11</td> + * <td>?</td> + * <td></td> + * </tr> + * </table> + * + * + * @version $Id: CharsetUtil.java,v 1.1 2004/10/25 07:26:46 ntherning Exp $ + */ +public class CharsetUtil { + private static Log log = LogFactory.getLog(CharsetUtil.class); + + private static class Charset implements Comparable<Charset> { + private String canonical = null; + private String mime = null; + private String[] aliases = null; + + private Charset(String canonical, String mime, String[] aliases) { + this.canonical = canonical; + this.mime = mime; + this.aliases = aliases; + } + + public int compareTo(Charset c) { + return this.canonical.compareTo(c.canonical); + } + } + + private static Charset[] JAVA_CHARSETS = { + new Charset("ISO8859_1", "ISO-8859-1", + new String[] {"ISO_8859-1:1987", "iso-ir-100", "ISO_8859-1", + "latin1", "l1", "IBM819", "CP819", + "csISOLatin1", "8859_1", "819", "IBM-819", + "ISO8859-1", "ISO_8859_1"}), + new Charset("ISO8859_2", "ISO-8859-2", + new String[] {"ISO_8859-2:1987", "iso-ir-101", "ISO_8859-2", + "latin2", "l2", "csISOLatin2", "8859_2", + "iso8859_2"}), + new Charset("ISO8859_3", "ISO-8859-3", new String[] {"ISO_8859-3:1988", "iso-ir-109", "ISO_8859-3", "latin3", "l3", "csISOLatin3", "8859_3"}), + new Charset("ISO8859_4", "ISO-8859-4", + new String[] {"ISO_8859-4:1988", "iso-ir-110", "ISO_8859-4", + "latin4", "l4", "csISOLatin4", "8859_4"}), + new Charset("ISO8859_5", "ISO-8859-5", + new String[] {"ISO_8859-5:1988", "iso-ir-144", "ISO_8859-5", + "cyrillic", "csISOLatinCyrillic", "8859_5"}), + new Charset("ISO8859_6", "ISO-8859-6", new String[] {"ISO_8859-6:1987", "iso-ir-127", "ISO_8859-6", "ECMA-114", "ASMO-708", "arabic", "csISOLatinArabic", "8859_6"}), + new Charset("ISO8859_7", "ISO-8859-7", + new String[] {"ISO_8859-7:1987", "iso-ir-126", "ISO_8859-7", + "ELOT_928", "ECMA-118", "greek", "greek8", + "csISOLatinGreek", "8859_7", "sun_eu_greek"}), + new Charset("ISO8859_8", "ISO-8859-8", new String[] {"ISO_8859-8:1988", "iso-ir-138", "ISO_8859-8", "hebrew", "csISOLatinHebrew", "8859_8"}), + new Charset("ISO8859_9", "ISO-8859-9", + new String[] {"ISO_8859-9:1989", "iso-ir-148", "ISO_8859-9", + "latin5", "l5", "csISOLatin5", "8859_9"}), + + new Charset("ISO8859_13", "ISO-8859-13", new String[] {}), + new Charset("ISO8859_15", "ISO-8859-15", + new String[] {"ISO_8859-15", "Latin-9", "8859_15", + "csISOlatin9", "IBM923", "cp923", "923", "L9", + "IBM-923", "ISO8859-15", "LATIN9", "LATIN0", + "csISOlatin0", "ISO8859_15_FDIS"}), + new Charset("KOI8_R", "KOI8-R", new String[] {"csKOI8R", "koi8"}), + new Charset("ASCII", "US-ASCII", + new String[] {"ANSI_X3.4-1968", "iso-ir-6", + "ANSI_X3.4-1986", "ISO_646.irv:1991", + "ISO646-US", "us", "IBM367", "cp367", + "csASCII", "ascii7", "646", "iso_646.irv:1983"}), + new Charset("UTF8", "UTF-8", new String[] {}), + new Charset("UTF-16", "UTF-16", new String[] {"UTF_16"}), + new Charset("UnicodeBigUnmarked", "UTF-16BE", new String[] {"X-UTF-16BE", "UTF_16BE", "ISO-10646-UCS-2"}), + new Charset("UnicodeLittleUnmarked", "UTF-16LE", new String[] {"UTF_16LE", "X-UTF-16LE"}), + new Charset("Big5", "Big5", new String[] {"csBig5", "CN-Big5", "BIG-FIVE", "BIGFIVE"}), + new Charset("Big5_HKSCS", "Big5-HKSCS", new String[] {"big5hkscs"}), + new Charset("EUC_JP", "EUC-JP", + new String[] {"csEUCPkdFmtJapanese", + "Extended_UNIX_Code_Packed_Format_for_Japanese", + "eucjis", "x-eucjp", "eucjp", "x-euc-jp"}), + new Charset("EUC_KR", "EUC-KR", + new String[] {"csEUCKR", "ksc5601", "5601", "ksc5601_1987", + "ksc_5601", "ksc5601-1987", "ks_c_5601-1987", + "euckr"}), + new Charset("GB18030", "GB18030", new String[] {"gb18030-2000"}), + new Charset("EUC_CN", "GB2312", new String[] {"x-EUC-CN", "csGB2312", "euccn", "euc-cn", "gb2312-80", "gb2312-1980", "CN-GB", "CN-GB-ISOIR165"}), + new Charset("GBK", "windows-936", new String[] {"CP936", "MS936", "ms_936", "x-mswin-936"}), + + new Charset("Cp037", "IBM037", new String[] {"ebcdic-cp-us", "ebcdic-cp-ca", "ebcdic-cp-wt", "ebcdic-cp-nl", "csIBM037"}), + new Charset("Cp273", "IBM273", new String[] {"csIBM273"}), + new Charset("Cp277", "IBM277", new String[] {"EBCDIC-CP-DK", "EBCDIC-CP-NO", "csIBM277"}), + new Charset("Cp278", "IBM278", new String[] {"CP278", "ebcdic-cp-fi", "ebcdic-cp-se", "csIBM278"}), + new Charset("Cp280", "IBM280", new String[] {"ebcdic-cp-it", "csIBM280"}), + new Charset("Cp284", "IBM284", new String[] {"ebcdic-cp-es", "csIBM284"}), + new Charset("Cp285", "IBM285", new String[] {"ebcdic-cp-gb", "csIBM285"}), + new Charset("Cp297", "IBM297", new String[] {"ebcdic-cp-fr", "csIBM297"}), + new Charset("Cp420", "IBM420", new String[] {"ebcdic-cp-ar1", "csIBM420"}), + new Charset("Cp424", "IBM424", new String[] {"ebcdic-cp-he", "csIBM424"}), + new Charset("Cp437", "IBM437", new String[] {"437", "csPC8CodePage437"}), + new Charset("Cp500", "IBM500", new String[] {"ebcdic-cp-be", "ebcdic-cp-ch", "csIBM500"}), + new Charset("Cp775", "IBM775", new String[] {"csPC775Baltic"}), + new Charset("Cp838", "IBM-Thai", new String[] {}), + new Charset("Cp850", "IBM850", new String[] {"850", "csPC850Multilingual"}), + new Charset("Cp852", "IBM852", new String[] {"852", "csPCp852"}), + new Charset("Cp855", "IBM855", new String[] {"855", "csIBM855"}), + new Charset("Cp857", "IBM857", new String[] {"857", "csIBM857"}), + new Charset("Cp858", "IBM00858", + new String[] {"CCSID00858", "CP00858", + "PC-Multilingual-850+euro"}), + new Charset("Cp860", "IBM860", new String[] {"860", "csIBM860"}), + new Charset("Cp861", "IBM861", new String[] {"861", "cp-is", "csIBM861"}), + new Charset("Cp862", "IBM862", new String[] {"862", "csPC862LatinHebrew"}), + new Charset("Cp863", "IBM863", new String[] {"863", "csIBM863"}), + new Charset("Cp864", "IBM864", new String[] {"cp864", "csIBM864"}), + new Charset("Cp865", "IBM865", new String[] {"865", "csIBM865"}), + new Charset("Cp866", "IBM866", new String[] {"866", "csIBM866"}), + new Charset("Cp868", "IBM868", new String[] {"cp-ar", "csIBM868"}), + new Charset("Cp869", "IBM869", new String[] {"cp-gr", "csIBM869"}), + new Charset("Cp870", "IBM870", new String[] {"ebcdic-cp-roece", "ebcdic-cp-yu", "csIBM870"}), + new Charset("Cp871", "IBM871", new String[] {"ebcdic-cp-is", "csIBM871"}), + new Charset("Cp918", "IBM918", new String[] {"ebcdic-cp-ar2", "csIBM918"}), + new Charset("Cp1026", "IBM1026", new String[] {"csIBM1026"}), + new Charset("Cp1047", "IBM1047", new String[] {"IBM-1047"}), + new Charset("Cp1140", "IBM01140", + new String[] {"CCSID01140", "CP01140", + "ebcdic-us-37+euro"}), + new Charset("Cp1141", "IBM01141", + new String[] {"CCSID01141", "CP01141", + "ebcdic-de-273+euro"}), + new Charset("Cp1142", "IBM01142", new String[] {"CCSID01142", "CP01142", "ebcdic-dk-277+euro", "ebcdic-no-277+euro"}), + new Charset("Cp1143", "IBM01143", new String[] {"CCSID01143", "CP01143", "ebcdic-fi-278+euro", "ebcdic-se-278+euro"}), + new Charset("Cp1144", "IBM01144", new String[] {"CCSID01144", "CP01144", "ebcdic-it-280+euro"}), + new Charset("Cp1145", "IBM01145", new String[] {"CCSID01145", "CP01145", "ebcdic-es-284+euro"}), + new Charset("Cp1146", "IBM01146", new String[] {"CCSID01146", "CP01146", "ebcdic-gb-285+euro"}), + new Charset("Cp1147", "IBM01147", new String[] {"CCSID01147", "CP01147", "ebcdic-fr-297+euro"}), + new Charset("Cp1148", "IBM01148", new String[] {"CCSID01148", "CP01148", "ebcdic-international-500+euro"}), + new Charset("Cp1149", "IBM01149", new String[] {"CCSID01149", "CP01149", "ebcdic-is-871+euro"}), + new Charset("Cp1250", "windows-1250", new String[] {}), + new Charset("Cp1251", "windows-1251", new String[] {}), + new Charset("Cp1252", "windows-1252", new String[] {}), + new Charset("Cp1253", "windows-1253", new String[] {}), + new Charset("Cp1254", "windows-1254", new String[] {}), + new Charset("Cp1255", "windows-1255", new String[] {}), + new Charset("Cp1256", "windows-1256", new String[] {}), + new Charset("Cp1257", "windows-1257", new String[] {}), + new Charset("Cp1258", "windows-1258", new String[] {}), + new Charset("ISO2022CN", "ISO-2022-CN", new String[] {}), + new Charset("ISO2022JP", "ISO-2022-JP", new String[] {"csISO2022JP", "JIS", "jis_encoding", "csjisencoding"}), + new Charset("ISO2022KR", "ISO-2022-KR", new String[] {"csISO2022KR"}), + new Charset("JIS_X0201", "JIS_X0201", new String[] {"X0201", "JIS0201", "csHalfWidthKatakana"}), + new Charset("JIS_X0212-1990", "JIS_X0212-1990", new String[] {"iso-ir-159", "x0212", "JIS0212", "csISO159JISX02121990"}), + new Charset("JIS_C6626-1983", "JIS_C6626-1983", new String[] {"x-JIS0208", "JIS0208", "csISO87JISX0208", "x0208", "JIS_X0208-1983", "iso-ir-87"}), + new Charset("SJIS", "Shift_JIS", new String[] {"MS_Kanji", "csShiftJIS", "shift-jis", "x-sjis", "pck"}), + new Charset("TIS620", "TIS-620", new String[] {}), + new Charset("MS932", "Windows-31J", new String[] {"windows-932", "csWindows31J", "x-ms-cp932"}), + new Charset("EUC_TW", "EUC-TW", new String[] {"x-EUC-TW", "cns11643", "euctw"}), + new Charset("x-Johab", "johab", new String[] {"johab", "cp1361", "ms1361", "ksc5601-1992", "ksc5601_1992"}), + new Charset("MS950_HKSCS", "", new String[] {}), + new Charset("MS874", "windows-874", new String[] {"cp874"}), + new Charset("MS949", "windows-949", new String[] {"windows949", "ms_949", "x-windows-949"}), + new Charset("MS950", "windows-950", new String[] {"x-windows-950"}), + + new Charset("Cp737", null, new String[] {}), + new Charset("Cp856", null, new String[] {}), + new Charset("Cp875", null, new String[] {}), + new Charset("Cp921", null, new String[] {}), + new Charset("Cp922", null, new String[] {}), + new Charset("Cp930", null, new String[] {}), + new Charset("Cp933", null, new String[] {}), + new Charset("Cp935", null, new String[] {}), + new Charset("Cp937", null, new String[] {}), + new Charset("Cp939", null, new String[] {}), + new Charset("Cp942", null, new String[] {}), + new Charset("Cp942C", null, new String[] {}), + new Charset("Cp943", null, new String[] {}), + new Charset("Cp943C", null, new String[] {}), + new Charset("Cp948", null, new String[] {}), + new Charset("Cp949", null, new String[] {}), + new Charset("Cp949C", null, new String[] {}), + new Charset("Cp950", null, new String[] {}), + new Charset("Cp964", null, new String[] {}), + new Charset("Cp970", null, new String[] {}), + new Charset("Cp1006", null, new String[] {}), + new Charset("Cp1025", null, new String[] {}), + new Charset("Cp1046", null, new String[] {}), + new Charset("Cp1097", null, new String[] {}), + new Charset("Cp1098", null, new String[] {}), + new Charset("Cp1112", null, new String[] {}), + new Charset("Cp1122", null, new String[] {}), + new Charset("Cp1123", null, new String[] {}), + new Charset("Cp1124", null, new String[] {}), + new Charset("Cp1381", null, new String[] {}), + new Charset("Cp1383", null, new String[] {}), + new Charset("Cp33722", null, new String[] {}), + new Charset("Big5_Solaris", null, new String[] {}), + new Charset("EUC_JP_LINUX", null, new String[] {}), + new Charset("EUC_JP_Solaris", null, new String[] {}), + new Charset("ISCII91", null, new String[] {"x-ISCII91", "iscii"}), + new Charset("ISO2022_CN_CNS", null, new String[] {}), + new Charset("ISO2022_CN_GB", null, new String[] {}), + new Charset("x-iso-8859-11", null, new String[] {}), + new Charset("JISAutoDetect", null, new String[] {}), + new Charset("MacArabic", null, new String[] {}), + new Charset("MacCentralEurope", null, new String[] {}), + new Charset("MacCroatian", null, new String[] {}), + new Charset("MacCyrillic", null, new String[] {}), + new Charset("MacDingbat", null, new String[] {}), + new Charset("MacGreek", "MacGreek", new String[] {}), + new Charset("MacHebrew", null, new String[] {}), + new Charset("MacIceland", null, new String[] {}), + new Charset("MacRoman", "MacRoman", new String[] {"Macintosh", "MAC", "csMacintosh"}), + new Charset("MacRomania", null, new String[] {}), + new Charset("MacSymbol", null, new String[] {}), + new Charset("MacThai", null, new String[] {}), + new Charset("MacTurkish", null, new String[] {}), + new Charset("MacUkraine", null, new String[] {}), + new Charset("UnicodeBig", null, new String[] {}), + new Charset("UnicodeLittle", null, new String[] {}) + }; + + /** + * Contains the canonical names of character sets which can be used to + * decode bytes into Java chars. + */ + private static TreeSet<String> decodingSupported = null; + + /** + * Contains the canonical names of character sets which can be used to + * encode Java chars into bytes. + */ + private static TreeSet<String> encodingSupported = null; + + /** + * Maps character set names to Charset objects. All possible names of + * a charset will be mapped to the Charset. + */ + private static HashMap<String, Charset> charsetMap = null; + + static { + decodingSupported = new TreeSet<String>(); + encodingSupported = new TreeSet<String>(); + byte[] dummy = new byte[] {'d', 'u', 'm', 'm', 'y'}; + for (int i = 0; i < JAVA_CHARSETS.length; i++) { + try { + String s = new String(dummy, JAVA_CHARSETS[i].canonical); + decodingSupported.add(JAVA_CHARSETS[i].canonical.toLowerCase(Locale.US)); + } catch (UnsupportedOperationException e) { + } catch (UnsupportedEncodingException e) { + } + try { + "dummy".getBytes(JAVA_CHARSETS[i].canonical); + encodingSupported.add(JAVA_CHARSETS[i].canonical.toLowerCase(Locale.US)); + } catch (UnsupportedOperationException e) { + } catch (UnsupportedEncodingException e) { + } + } + + charsetMap = new HashMap<String, Charset>(); + for (int i = 0; i < JAVA_CHARSETS.length; i++) { + Charset c = JAVA_CHARSETS[i]; + charsetMap.put(c.canonical.toLowerCase(Locale.US), c); + if (c.mime != null) { + charsetMap.put(c.mime.toLowerCase(Locale.US), c); + } + if (c.aliases != null) { + for (int j = 0; j < c.aliases.length; j++) { + charsetMap.put(c.aliases[j].toLowerCase(Locale.US), c); + } + } + } + + if (log.isDebugEnabled()) { + log.debug("Character sets which support decoding: " + + decodingSupported); + log.debug("Character sets which support encoding: " + + encodingSupported); + } + } + + /** + * ANDROID: THE FOLLOWING SET OF STATIC STRINGS ARE COPIED FROM A NEWER VERSION OF MIME4J + */ + + /** carriage return - line feed sequence */ + public static final String CRLF = "\r\n"; + + /** US-ASCII CR, carriage return (13) */ + public static final int CR = '\r'; + + /** US-ASCII LF, line feed (10) */ + public static final int LF = '\n'; + + /** US-ASCII SP, space (32) */ + public static final int SP = ' '; + + /** US-ASCII HT, horizontal-tab (9)*/ + public static final int HT = '\t'; + + public static final java.nio.charset.Charset US_ASCII = java.nio.charset.Charset + .forName("US-ASCII"); + + public static final java.nio.charset.Charset ISO_8859_1 = java.nio.charset.Charset + .forName("ISO-8859-1"); + + public static final java.nio.charset.Charset UTF_8 = java.nio.charset.Charset + .forName("UTF-8"); + + /** + * Returns <code>true</code> if the specified character is a whitespace + * character (CR, LF, SP or HT). + * + * ANDROID: COPIED FROM A NEWER VERSION OF MIME4J + * + * @param ch + * character to test. + * @return <code>true</code> if the specified character is a whitespace + * character, <code>false</code> otherwise. + */ + public static boolean isWhitespace(char ch) { + return ch == SP || ch == HT || ch == CR || ch == LF; + } + + /** + * Returns <code>true</code> if the specified string consists entirely of + * whitespace characters. + * + * ANDROID: COPIED FROM A NEWER VERSION OF MIME4J + * + * @param s + * string to test. + * @return <code>true</code> if the specified string consists entirely of + * whitespace characters, <code>false</code> otherwise. + */ + public static boolean isWhitespace(final String s) { + if (s == null) { + throw new IllegalArgumentException("String may not be null"); + } + final int len = s.length(); + for (int i = 0; i < len; i++) { + if (!isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } + + /** + * Determines if the VM supports encoding (chars to bytes) the + * specified character set. NOTE: the given character set name may + * not be known to the VM even if this method returns <code>true</code>. + * Use {@link #toJavaCharset(String)} to get the canonical Java character + * set name. + * + * @param charsetName the characters set name. + * @return <code>true</code> if encoding is supported, <code>false</code> + * otherwise. + */ + public static boolean isEncodingSupported(String charsetName) { + return encodingSupported.contains(charsetName.toLowerCase(Locale.US)); + } + + /** + * Determines if the VM supports decoding (bytes to chars) the + * specified character set. NOTE: the given character set name may + * not be known to the VM even if this method returns <code>true</code>. + * Use {@link #toJavaCharset(String)} to get the canonical Java character + * set name. + * + * @param charsetName the characters set name. + * @return <code>true</code> if decoding is supported, <code>false</code> + * otherwise. + */ + public static boolean isDecodingSupported(String charsetName) { + return decodingSupported.contains(charsetName.toLowerCase(Locale.US)); + } + + /** + * Gets the preferred MIME character set name for the specified + * character set or <code>null</code> if not known. + * + * @param charsetName the character set name to look for. + * @return the MIME preferred name or <code>null</code> if not known. + */ + public static String toMimeCharset(String charsetName) { + Charset c = charsetMap.get(charsetName.toLowerCase(Locale.US)); + if (c != null) { + return c.mime; + } + return null; + } + + /** + * Gets the canonical Java character set name for the specified + * character set or <code>null</code> if not known. This should be + * called before doing any conversions using the Java API. NOTE: + * you must use {@link #isEncodingSupported(String)} or + * {@link #isDecodingSupported(String)} to make sure the returned + * Java character set is supported by the current VM. + * + * @param charsetName the character set name to look for. + * @return the canonical Java name or <code>null</code> if not known. + */ + public static String toJavaCharset(String charsetName) { + Charset c = charsetMap.get(charsetName.toLowerCase(Locale.US)); + if (c != null) { + return c.canonical; + } + return null; + } + + public static java.nio.charset.Charset getCharset(String charsetName) { + String defaultCharset = "ISO-8859-1"; + + // Use the default chareset if given charset is null + if(charsetName == null) charsetName = defaultCharset; + + try { + return java.nio.charset.Charset.forName(charsetName); + } catch (IllegalCharsetNameException e) { + log.info("Illegal charset " + charsetName + ", fallback to " + + defaultCharset + ": " + e); + // Use default charset on exception + return java.nio.charset.Charset.forName(defaultCharset); + } catch (UnsupportedCharsetException ex) { + log.info("Unsupported charset " + charsetName + ", fallback to " + + defaultCharset + ": " + ex); + // Use default charset on exception + return java.nio.charset.Charset.forName(defaultCharset); + } + + } + /* + * Uncomment the code below and run the main method to regenerate the + * Javadoc table above when the known charsets change. + */ + + /* + private static String dumpHtmlTable() { + LinkedList l = new LinkedList(Arrays.asList(JAVA_CHARSETS)); + Collections.sort(l); + StringBuffer sb = new StringBuffer(); + sb.append(" * <table>\n"); + sb.append(" * <tr>\n"); + sb.append(" * <td>Canonical (Java) name</td>\n"); + sb.append(" * <td>MIME preferred</td>\n"); + sb.append(" * <td>Aliases</td>\n"); + sb.append(" * </tr>\n"); + + for (Iterator it = l.iterator(); it.hasNext();) { + Charset c = (Charset) it.next(); + sb.append(" * <tr>\n"); + sb.append(" * <td>" + c.canonical + "</td>\n"); + sb.append(" * <td>" + (c.mime == null ? "?" : c.mime)+ "</td>\n"); + sb.append(" * <td>"); + for (int i = 0; c.aliases != null && i < c.aliases.length; i++) { + sb.append(c.aliases[i] + " "); + } + sb.append("</td>\n"); + sb.append(" * </tr>\n"); + } + sb.append(" * </table>\n"); + return sb.toString(); + } + + public static void main(String[] args) { + System.out.println(dumpHtmlTable()); + }*/ +}
\ No newline at end of file |