001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.schematic.document;
017
018import java.io.DataInput;
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.ObjectOutput;
022import java.io.OutputStream;
023import java.lang.ref.SoftReference;
024import java.text.DateFormat;
025import java.text.SimpleDateFormat;
026import java.util.Date;
027import java.util.GregorianCalendar;
028import java.util.List;
029import java.util.SimpleTimeZone;
030import java.util.regex.Pattern;
031import org.modeshape.schematic.internal.document.BsonReader;
032import org.modeshape.schematic.internal.document.BsonWriter;
033
034/**
035 * A utility class for working with BSON documents.
036 * 
037 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
038 */
039public class Bson {
040
041    public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
042    private static final ThreadLocal<SoftReference<DateFormat>> dateFormatter = new ThreadLocal<SoftReference<DateFormat>>();
043
044    protected static final String DATE_FORMAT_FOR_PARSING = "yyyy-MM-dd'T'HH:mm:ssz";
045    private static final ThreadLocal<SoftReference<DateFormat>> dateFormatterForParsing = new ThreadLocal<SoftReference<DateFormat>>();
046
047    /**
048     * Obtain a {@link DateFormat} object that can be used within the current thread to format {@link Date} objects.
049     * 
050     * @return the formatter; never null
051     */
052    public static DateFormat getDateFormatter() {
053        SoftReference<DateFormat> ref = dateFormatter.get();
054        DateFormat formatter = ref != null ? ref.get() : null;
055        if (formatter == null) {
056            formatter = new SimpleDateFormat(DATE_FORMAT);
057            formatter.setCalendar(new GregorianCalendar(new SimpleTimeZone(0, "GMT")));
058            dateFormatter.set(new SoftReference<DateFormat>(formatter));
059        }
060        return formatter;
061    }
062
063    public static DateFormat getDateParsingFormatter() {
064        SoftReference<DateFormat> ref = dateFormatterForParsing.get();
065        DateFormat formatter = ref != null ? ref.get() : null;
066        if (formatter == null) {
067            formatter = new SimpleDateFormat(DATE_FORMAT_FOR_PARSING);
068            formatter.setCalendar(new GregorianCalendar(new SimpleTimeZone(0, "GMT")));
069            dateFormatterForParsing.set(new SoftReference<DateFormat>(formatter));
070        }
071        return formatter;
072    }
073
074    /**
075     * Byte used for the end of a document within a BSON stream.
076     */
077    public static final byte END_OF_DOCUMENT = 0x00;
078
079    /**
080     * Byte used for the end of a string within a BSON stream.
081     */
082    public static final byte END_OF_STRING = 0x00;
083
084    /**
085     * The bytes used for the types within a BSON stream.
086     */
087    public static final class Type {
088        public static final byte DOUBLE = 0x01;
089        public static final byte STRING = 0x02;
090        public static final byte DOCUMENT = 0x03;
091        public static final byte ARRAY = 0x04;
092        public static final byte BINARY = 0x05;
093        public static final byte UNDEFINED = 0x06;
094        public static final byte OBJECTID = 0x07;
095        public static final byte BOOLEAN = 0x08;
096        public static final byte DATETIME = 0x09;
097        public static final byte NULL = 0x0A;
098        public static final byte REGEX = 0x0B;
099        public static final byte DBPOINTER = 0x0C;
100        public static final byte JAVASCRIPT = 0x0D;
101        public static final byte SYMBOL = 0x0E;
102        public static final byte JAVASCRIPT_WITH_SCOPE = 0x0F;
103        public static final byte INT32 = 0x10;
104        public static final byte TIMESTAMP = 0x11;
105        public static final byte INT64 = 0x12;
106        public static final byte MINKEY = (byte)0xFF;
107        public static final byte MAXKEY = 0x7f;
108    }
109
110    /**
111     * The bytes used for the subtypes of a binary value within a BSON stream.
112     */
113    public static final class BinaryType {
114        /**
115         * The most common binary subtype, and the one should be used as the default.
116         */
117        public static final byte GENERAL = 0x00;
118        /**
119         * The binary subtype that represents functions.
120         */
121        public static final byte FUNCTION = 0x01;
122        /**
123         * The old generic subtype. This used to be the default subtype, but was deprecated in favor of {@link #GENERAL}. Drivers
124         * and tools should be sure to handle this type appropriately.
125         * 
126         * @deprecated Use {@link #GENERAL} instead
127         */
128        @Deprecated
129        public static final byte BINARY = 0x02;
130        public static final byte UUID = 0x03;
131        public static final byte MD5 = 0x05;
132        public static final byte USER_DEFINED = (byte)0x80;
133    }
134
135    private static final BsonWriter SHARED_WRITER = new BsonWriter();
136
137    protected static BsonWriter getBsonWriter() {
138        return SHARED_WRITER;
139    }
140
141    /**
142     * Write to the supplied stream the binary BSON representation of the supplied in-memory {@link Document}.
143     * 
144     * @param bson the BSON object or BSON value; may not be null
145     * @param stream the output stream; may not be null
146     * @throws IOException if there was a problem writing to the stream
147     */
148    public static void write( Document bson,
149                              OutputStream stream ) throws IOException {
150        getBsonWriter().write(bson, stream);
151    }
152
153    /**
154     * Write to the supplied output the binary BSON representation of the supplied in-memory {@link Document}.
155     * 
156     * @param bson the BSON object or BSON value; may not be null
157     * @param output the output; may not be null
158     * @throws IOException if there was a problem writing to the ObjectOutput
159     */
160    public static void write( Document bson,
161                              ObjectOutput output ) throws IOException {
162        getBsonWriter().write(bson, output);
163    }
164
165    /**
166     * Return the array of bytes containing the standard BSON binary form of the supplied in-memory {@link Document}.
167     * 
168     * @param object the BSON object or BSON value; may not be null
169     * @return the bytes
170     * @throws IOException if there was a problem reading from the stream
171     */
172    public static byte[] write( Object object ) throws IOException {
173        return getBsonWriter().write(object);
174    }
175
176    private static final BsonReader SHARED_READER = new BsonReader();
177
178    protected static BsonReader getReader() {
179        return SHARED_READER;
180    }
181
182    /**
183     * Read the binary BSON representation from supplied input stream and construct the {@link Document} representation.
184     * 
185     * @param stream the input stream; may not be null
186     * @return the in-memory {@link Document} representation
187     * @throws IOException if there was a problem reading from the stream
188     */
189    public static Document read( InputStream stream ) throws IOException {
190        return SHARED_READER.read(stream);
191    }
192
193    /**
194     * Read the binary BSON representation from supplied data input and construct the {@link Document} representation.
195     * 
196     * @param input the data input; may not be null
197     * @return the in-memory {@link Document} representation
198     * @throws IOException if there was a problem reading from the stream
199     */
200    public static Document read( DataInput input ) throws IOException {
201        return SHARED_READER.read(input);
202    }
203
204    /**
205     * Get the {@link Type} constant that describes the type of value for the given field name.
206     * 
207     * @param value The value
208     * @return the {@link Type} constant describing the value
209     */
210    public static int getTypeForValue( Object value ) {
211        if (value == null) return Type.NULL;
212        if (value instanceof String) return Type.STRING;
213        if (value instanceof Symbol) return Type.SYMBOL;
214        if (value instanceof Integer) return Type.INT32;
215        if (value instanceof Long) return Type.INT64;
216        if (value instanceof Double) return Type.DOUBLE;
217        if (value instanceof List) return Type.ARRAY;
218        if (value instanceof Document) return Type.DOCUMENT;
219        if (value instanceof Boolean) return Type.BOOLEAN;
220        if (value instanceof Binary) return Type.BINARY;
221        if (value instanceof ObjectId) return Type.OBJECTID;
222        if (value instanceof Date) return Type.DATETIME;
223        if (value instanceof Null) return Type.NULL;
224        if (value instanceof Pattern) return Type.REGEX;
225        if (value instanceof Symbol) return Type.DBPOINTER;
226        if (value instanceof Code) return Type.JAVASCRIPT;
227        if (value instanceof CodeWithScope) return Type.JAVASCRIPT_WITH_SCOPE;
228        if (value instanceof Timestamp) return Type.TIMESTAMP;
229        if (value instanceof MinKey) return Type.MINKEY;
230        if (value instanceof MaxKey) return Type.MAXKEY;
231        return Type.UNDEFINED;
232    }
233}