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.internal.document;
017
018import java.io.DataInput;
019import java.io.DataInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.util.UUID;
023import org.modeshape.schematic.annotation.ThreadSafe;
024import org.modeshape.schematic.document.Array;
025import org.modeshape.schematic.document.Bson;
026import org.modeshape.schematic.document.Bson.BinaryType;
027import org.modeshape.schematic.document.Document;
028import org.modeshape.schematic.document.MaxKey;
029import org.modeshape.schematic.document.MinKey;
030import org.modeshape.schematic.internal.io.BsonDataInput;
031
032/**
033 * A component that reads BSON representations and constructs the in-memory {@link Document} representation.
034 * 
035 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
036 */
037@ThreadSafe
038public class BsonReader {
039
040    protected static final DocumentValueFactory VALUE_FACTORY = new DefaultDocumentValueFactory();
041
042    /**
043     * Read the binary BSON representation from supplied input stream and construct the {@link Document} representation.
044     * 
045     * @param stream the input stream; may not be null
046     * @return the in-memory {@link Document} representation
047     * @throws IOException if there was a problem reading from the stream
048     */
049    public Document read( InputStream stream ) throws IOException {
050        // Create an object so that this reader is thread safe ...
051        DocumentValueFactory valueFactory = VALUE_FACTORY;
052        Reader reader = new Reader(new BsonDataInput(new DataInputStream(stream)), valueFactory);
053        reader.startDocument();
054        return reader.endDocument();
055    }
056
057    /**
058     * Read the binary BSON representation from supplied input stream and construct the {@link Document} representation.
059     * 
060     * @param input the input stream; may not be null
061     * @return the in-memory {@link Document} representation
062     * @throws IOException if there was a problem reading from the stream
063     */
064    public Document read( DataInput input ) throws IOException {
065        // Create an object so that this reader is thread safe ...
066        DocumentValueFactory valueFactory = VALUE_FACTORY;
067        Reader reader = new Reader(new BsonDataInput(input), valueFactory);
068        reader.startDocument();
069        return reader.endDocument();
070    }
071
072    /**
073     * Read the binary BSON representation from supplied input stream and construct the {@link Array} representation.
074     * 
075     * @param stream the input stream; may not be null
076     * @return the in-memory {@link Document} representation
077     * @throws IOException if there was a problem reading from the stream
078     */
079    public Array readArray( InputStream stream ) throws IOException {
080        // Create an object so that this reader is thread safe ...
081        DocumentValueFactory valueFactory = VALUE_FACTORY;
082        Reader reader = new Reader(new BsonDataInput(new DataInputStream(stream)), valueFactory);
083        reader.startArray();
084        return (Array)reader.endDocument();
085    }
086
087    /**
088     * Read the binary BSON representation from supplied input stream and construct the {@link Document} representation.
089     * 
090     * @param input the input stream; may not be null
091     * @return the in-memory {@link Document} representation
092     * @throws IOException if there was a problem reading from the stream
093     */
094    public Array readArray( DataInput input ) throws IOException {
095        // Create an object so that this reader is thread safe ...
096        DocumentValueFactory valueFactory = VALUE_FACTORY;
097        Reader reader = new Reader(new BsonDataInput(input), valueFactory);
098        reader.startArray();
099        return (Array)reader.endDocument();
100    }
101
102    protected static class Reader {
103        private final BsonDataInput data;
104        private MutableDocument object;
105        private DocumentValueFactory values;
106
107        // private final BsonEditor editor;
108
109        protected Reader( BsonDataInput data,
110                          DocumentValueFactory valueFactory ) {
111            this.data = data;
112            this.values = valueFactory;
113        }
114
115        protected void startDocument() throws IOException {
116            object = readDocument(false);
117        }
118
119        protected void startArray() throws IOException {
120            object = readDocument(true);
121        }
122
123        protected MutableDocument readDocument( boolean array ) throws IOException {
124            // Read the size int32, but we don't care about the value 'cuz it's in bytes ...
125            int length = data.readInt();
126            int startingIndex = data.getTotalBytesRead();
127            int endingIndex = startingIndex + length;
128            MutableDocument doc = array ? new BasicArray() : new BasicDocument();
129            // Read the elements ...
130            while (data.getTotalBytesRead() < endingIndex) {
131                byte type = data.readByte();
132                if (type == Bson.END_OF_DOCUMENT) break;
133                readElement(type, doc);
134            }
135            return doc;
136        }
137
138        protected void readElement( byte type,
139                                    MutableDocument bson ) throws IOException {
140            String name = readCString();
141            Object value = null;
142            switch (type) {
143                case Bson.Type.ARRAY:
144                    value = readDocument(true);
145                    break;
146                case Bson.Type.BINARY:
147                    int length = data.readInt();
148                    byte subtype = data.readByte();
149                    if (subtype == BinaryType.UUID) {
150                        long mostSig = data.readLong();
151                        long leastSig = data.readLong();
152                        value = new UUID(mostSig, leastSig);
153                    } else {
154                        byte[] bytes = new byte[length];
155                        data.readFully(bytes);
156                        value = values.createBinary(subtype, bytes);
157                    }
158                    break;
159                case Bson.Type.BOOLEAN:
160                    value = values.createBoolean(data.readBoolean());
161                    break;
162                case Bson.Type.DATETIME:
163                    value = values.createDate(data.readLong());
164                    break;
165                case Bson.Type.DBPOINTER:
166                    // Deprecated, so ignore ...
167                    break;
168                case Bson.Type.DOCUMENT:
169                    value = readDocument(false);
170                    break;
171                case Bson.Type.DOUBLE:
172                    value = values.createDouble(data.readDouble());
173                    break;
174                case Bson.Type.INT32:
175                    value = values.createInt(data.readInt());
176                    break;
177                case Bson.Type.INT64:
178                    value = values.createLong(data.readLong());
179                    break;
180                case Bson.Type.JAVASCRIPT:
181                    value = values.createCode(readString());
182                    break;
183                case Bson.Type.JAVASCRIPT_WITH_SCOPE:
184                    data.readInt(); // the length, but we don't use this
185                    String code = readString();
186                    Document scope = readDocument(false);
187                    value = values.createCode(code, scope);
188                    break;
189                case Bson.Type.MAXKEY:
190                    value = MaxKey.getInstance();
191                    break;
192                case Bson.Type.MINKEY:
193                    value = MinKey.getInstance();
194                    break;
195                case Bson.Type.NULL:
196                    value = values.createNull();
197                    break;
198                case Bson.Type.OBJECTID:
199                    byte[] objectIdBytes = new byte[12];
200                    data.readFully(objectIdBytes);
201                    value = values.createObjectId(objectIdBytes);
202                    break;
203                case Bson.Type.REGEX:
204                    value = values.createRegex(readCString(), readCString());
205                    break;
206                case Bson.Type.STRING:
207                    value = readString();
208                    break;
209                case Bson.Type.SYMBOL:
210                    value = readString();
211                    break;
212                case Bson.Type.TIMESTAMP:
213                    int inc = data.readInt();
214                    int time = data.readInt();
215                    value = values.createTimestamp(time, inc);
216                    break;
217                case Bson.Type.UNDEFINED:
218                    // ignore ...
219                    break;
220            }
221            bson.put(name, value);
222        }
223
224        protected String readCString() throws IOException {
225            return data.readUTF(-1); // this reads the zero-byte terminator
226        }
227
228        protected String readString() throws IOException {
229            int length = data.readInt();
230            String result = values.createString(data.readUTF(length - 1)); // don't read the zero-byte
231            data.readByte(); // reads the zero-byte terminator
232            return result;
233        }
234
235        protected Document endDocument() {
236            return object;
237        }
238
239    }
240
241}