All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bson.BasicBSONEncoder Maven / Gradle / Ivy

Go to download

The MongoDB Java Driver uber-artifact, containing mongodb-driver, mongodb-driver-core, and bson

There is a newer version: 3.1.0
Show newest version
/*
 * Copyright (c) 2008-2014 MongoDB, Inc.
 *
 * 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.bson;

import com.mongodb.DBRefBase;
import org.bson.io.BasicOutputBuffer;
import org.bson.io.OutputBuffer;
import org.bson.types.BSONTimestamp;
import org.bson.types.Binary;
import org.bson.types.Code;
import org.bson.types.CodeWScope;
import org.bson.types.MaxKey;
import org.bson.types.MinKey;
import org.bson.types.ObjectId;
import org.bson.types.Symbol;

import java.lang.reflect.Array;
import java.nio.Buffer;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;

import static org.bson.BSON.ARRAY;
import static org.bson.BSON.BINARY;
import static org.bson.BSON.BOOLEAN;
import static org.bson.BSON.B_BINARY;
import static org.bson.BSON.B_GENERAL;
import static org.bson.BSON.B_UUID;
import static org.bson.BSON.CODE;
import static org.bson.BSON.CODE_W_SCOPE;
import static org.bson.BSON.DATE;
import static org.bson.BSON.EOO;
import static org.bson.BSON.MAXKEY;
import static org.bson.BSON.MINKEY;
import static org.bson.BSON.NULL;
import static org.bson.BSON.NUMBER;
import static org.bson.BSON.NUMBER_INT;
import static org.bson.BSON.NUMBER_LONG;
import static org.bson.BSON.OBJECT;
import static org.bson.BSON.OID;
import static org.bson.BSON.REGEX;
import static org.bson.BSON.STRING;
import static org.bson.BSON.SYMBOL;
import static org.bson.BSON.TIMESTAMP;
import static org.bson.BSON.UNDEFINED;
import static org.bson.BSON.regexFlags;

/**
 * This is meant to be pooled or cached. There is some per instance memory for string conversion, etc...
 */
@SuppressWarnings("unchecked")
public class BasicBSONEncoder implements BSONEncoder {
    
    static final boolean DEBUG = false;

    @Override
    public byte[] encode( BSONObject o ){
        BasicOutputBuffer buf = new BasicOutputBuffer();
        set( buf );
        putObject( o );
        done();
        return buf.toByteArray();
    }

    @Override
    public void set( OutputBuffer out ){
        if ( _buf != null )
            throw new IllegalStateException( "in the middle of something" );
        
        _buf = out;
    }

    /**
     * Gets the buffer the BSON is being encoded into.
     *
     * @return the OutputBuffer
     */
    protected OutputBuffer getOutputBuffer() {
        return _buf;
    }

    @Override
    public void done(){
        _buf = null;
    }
   
    /**
     * @return true if object was handled
     *
     * @deprecated Override {@link #putSpecial(String, Object)} if you need to you need to handle custom types.
     */
    @Deprecated
    protected boolean handleSpecialObjects( String name , BSONObject o ){
        return false;
    }

    /**
     * Special values are not encoded into documents.
     *
     * @param name the field name
     * @param o    the value
     * @return true if the operation is successful. This implementation always returns false.
     */
    protected boolean putSpecial( String name , Object o ){
        return false;
    }

    /**
     * Encodes a {@code BSONObject}. This is for the higher level api calls.
     *
     * @param o the document to encode
     * @return the number of characters in the encoding
     */
    @Override
    public int putObject( BSONObject o ){
        return putObject( null , o );
    }

    /**
     * This is really for embedded objects
     *
     * @param name the field name
     * @param o    the object
     * @return the number of characters in the encoding
     */
    protected int putObject(String name, BSONObject o) {

        if ( o == null )
            throw new NullPointerException( "can't save a null object" );

        if ( DEBUG ) System.out.println( "putObject : " + name + " [" + o.getClass() + "]" + " # keys " + o.keySet().size() );
        
        final int start = _buf.getPosition();
        
        byte myType = OBJECT;
        if ( o instanceof List )
            myType = ARRAY;

        if ( handleSpecialObjects( name , o ) )
            return _buf.getPosition() - start;
        
        if ( name != null ){
            _put( myType , name );
        }

        final int sizePos = _buf.getPosition();
        _buf.writeInt( 0 ); // leaving space for this.  set it at the end

        List transientFields = null;
        boolean rewriteID = myType == OBJECT && name == null;
        

        if ( myType == OBJECT ) {
            if ( rewriteID && o.containsField( "_id" ) )
                _putObjectField( "_id" , o.get( "_id" ) );
            
            {
                Object temp = o.get( "_transientFields" );
                if ( temp instanceof List )
                    transientFields = (List)temp;
            }
        }
        
        //TODO: reduce repeated code below.
        if ( o instanceof Map ){
	        for ( Entry e : ((Map)o).entrySet() ){
	        	
	            if ( rewriteID && e.getKey().equals( "_id" ) )
	                continue;
	            
	            if ( transientFields != null && transientFields.contains( e.getKey() ) )
	                continue;
	            
	            _putObjectField( e.getKey() , e.getValue() );
	
	        }        	
        } else {
	        for ( String s : o.keySet() ){
	
	            if ( rewriteID && s.equals( "_id" ) )
	                continue;
	            
	            if ( transientFields != null && transientFields.contains( s ) )
	                continue;
	            
	            Object val = o.get( s );
	
	            _putObjectField( s , val );
	
	        }
        }
        _buf.write( EOO );
        
        _buf.writeInt( sizePos , _buf.getPosition() - sizePos );
        return _buf.getPosition() - start;
    }

    /**
     * Encodes any Object type
     *
     * @param name the field name
     * @param val  the value to write
     */
    protected void _putObjectField( String name , Object val ){

        if ( name.equals( "_transientFields" ) )
            return;
        
        if ( DEBUG ) System.out.println( "\t put thing : " + name );

        if ( name.contains( "\0" ) )
            throw new IllegalArgumentException( "Document field names can't have a NULL character. (Bad Key: '" + name + "')" );
        
        if ( name.equals( "$where") && val instanceof String ){
            _put( CODE , name );
            _putValueString( val.toString() );
            return;
        }
        
        val = BSON.applyEncodingHooks( val );

        if ( val == null )
            putNull(name);
        else if ( val instanceof Date )
            putDate( name , (Date)val );
        else if ( val instanceof Number )
            putNumber(name, (Number)val );
        else if ( val instanceof Character )
            putString(name, val.toString() );
        else if ( val instanceof String )
            putString(name, val.toString() );
        else if ( val instanceof ObjectId )
            putObjectId(name, (ObjectId)val );
        else if ( val instanceof BSONObject )
            putObject(name, (BSONObject)val );
        else if ( val instanceof Boolean )
            putBoolean(name, (Boolean)val );
        else if ( val instanceof Pattern )
            putPattern(name, (Pattern)val );
        else if ( val instanceof Map )
            putMap( name , (Map)val );
        else if ( val instanceof Iterable)
            putIterable( name , (Iterable)val );
        else if ( val instanceof byte[] )
            putBinary( name , (byte[])val );
        else if ( val instanceof Binary )
            putBinary( name , (Binary)val );
        else if ( val instanceof UUID )
            putUUID( name , (UUID)val );
        else if ( val.getClass().isArray() )
        	putArray( name , val );

        else if (val instanceof Symbol) {
            putSymbol(name, (Symbol) val);
        }
        else if (val instanceof BSONTimestamp) {
            putTimestamp( name , (BSONTimestamp)val );
        }
        else if (val instanceof CodeWScope) {
            putCodeWScope( name , (CodeWScope)val );
        }
        else if (val instanceof Code) {
            putCode( name , (Code)val );
        }
        else if (val instanceof DBRefBase) {
            BSONObject temp = new BasicBSONObject();
            temp.put("$ref", ((DBRefBase)val).getRef());
            temp.put("$id", ((DBRefBase)val).getId());
            putObject( name, temp );
        }
        else if ( val instanceof MinKey )
            putMinKey( name );
        else if ( val instanceof MaxKey )
            putMaxKey( name );
        else if ( putSpecial( name , val ) ){
            // no-op
        }
        else {
            throw new IllegalArgumentException( "can't serialize " + val.getClass() );
        }
        
    }
	
    private void putArray( String name , Object array ) {
        _put( ARRAY , name );
        final int sizePos = _buf.getPosition();
        _buf.writeInt( 0 );
                	        
        int size = Array.getLength(array);
        for ( int i = 0; i < size; i++ )
            _putObjectField( String.valueOf( i ) , Array.get( array, i ) );

        _buf.write( EOO );
        _buf.writeInt( sizePos , _buf.getPosition() - sizePos ); 
    }
	
    private void putIterable( String name , Iterable l ){
        _put( ARRAY , name );
        final int sizePos = _buf.getPosition();
        _buf.writeInt( 0 );
        
        int i=0;
        for ( Object obj: l ) {
            _putObjectField( String.valueOf( i ) , obj );
            i++;
        }
        	

        _buf.write( EOO );
        _buf.writeInt( sizePos , _buf.getPosition() - sizePos );        
    }
    
    private void putMap( String name , Map m ){
        _put( OBJECT , name );
        final int sizePos = _buf.getPosition();
        _buf.writeInt( 0 );
        
        for ( Map.Entry entry : (Set)m.entrySet() )
            _putObjectField( entry.getKey().toString() , entry.getValue() );

        _buf.write( EOO );
        _buf.writeInt( sizePos , _buf.getPosition() - sizePos );
    }
    

    /**
     * Encodes a null value
     *
     * @param name the field name
     */
    protected void putNull( String name ){
        _put( NULL , name );
    }

    /**
     * Encodes an undefined value
     *
     * @param name the field name
     */
    protected void putUndefined(String name){
        _put(UNDEFINED, name);
    }

    /**
     * Encodes a BSON timestamp
     *
     * @param name the field name
     * @param ts   the timestamp to encode
     */
    protected void putTimestamp(String name, BSONTimestamp ts) {
        _put(TIMESTAMP, name);
        _buf.writeInt(ts.getInc());
        _buf.writeInt(ts.getTime());
    }

    /**
     * Encodes a field to a JAVASCRIPT_WITH_SCOPE value.
     *
     * @param name the field name
     * @param code the value
     */
    protected void putCodeWScope(String name, CodeWScope code) {
        _put( CODE_W_SCOPE , name );
        int temp = _buf.getPosition();
        _buf.writeInt( 0 );
        _putValueString( code.getCode() );
        putObject( code.getScope() );
        _buf.writeInt( temp , _buf.getPosition() - temp );
    }

    /**
     * Encodes a field to a JAVASCRIPT value.
     *
     * @param name the field name
     * @param code the value
     */
    protected void putCode( String name , Code code ){
        _put( CODE , name );
        int temp = _buf.getPosition();
        _putValueString( code.getCode() );
    }

    /**
     * Encodes a field with a {@code Boolean} or {@code boolean} value
     *
     * @param name  the field name
     * @param b the value
     */
    protected void putBoolean( String name , Boolean b ){
        _put( BOOLEAN , name );
        _buf.write( b ? (byte)0x1 : (byte)0x0 );
    }

    /**
     * Encodes a field with data and time value.
     *
     * @param name the field name
     * @param date the value
     */
    protected void putDate(String name, Date date) {
        _put(DATE, name);
        _buf.writeLong(date.getTime());
    }

    /**
     * Encodes any number field.
     *
     * @param name the field name
     * @param n    the number
     */
    protected void putNumber(String name, Number n) {
        if ( n instanceof Integer ||
	             n instanceof Short ||
	             n instanceof Byte ||
	             n instanceof AtomicInteger ){
		    _put( NUMBER_INT , name );
		    _buf.writeInt( n.intValue() );
		}
	    else if ( n instanceof Long || n instanceof AtomicLong ) {
	        _put( NUMBER_LONG , name );
	        _buf.writeLong( n.longValue() );
	    }
	    else if ( n instanceof Float || n instanceof Double ) {
	      _put( NUMBER , name );
	      _buf.writeDouble( n.doubleValue() );
	    }
		else {
	        throw new IllegalArgumentException( "can't serialize " + n.getClass() );
		}
    }

    /**
     * Encodes a byte array field
     *
     * @param name the field name
     * @param data the value
     */
    protected void putBinary(String name, byte[] data) {
        putBinary( name, B_GENERAL, data );
    }
    
    /**
     * Encodes a Binary field
     *
     * @param name   the field name
     * @param val the value
     */
    protected void putBinary(final String name, final Binary val) {
        putBinary( name, val.getType(), val.getData() );        
    }
    
    private void putBinary( String name , int type , byte[] data ){
        _put( BINARY , name );
        int totalLen = data.length;
        
        if (type == B_BINARY)
            totalLen += 4;
        
        _buf.writeInt( totalLen );
        _buf.write( type );
        if (type == B_BINARY)
            _buf.writeInt( totalLen -4 );
        int before = _buf.getPosition();
        _buf.write( data );
        int after = _buf.getPosition();
    }
    
    /**
     * Encodes a field with a {@link java.util.UUID} value.  This is encoded to a binary value of subtype UUID_LEGACY
     *
     * @param name the field name
     * @param val the value
     */
    protected void putUUID( String name , UUID val ){
        _put( BINARY , name );
        _buf.writeInt( 16 );
        _buf.write( B_UUID );
        _buf.writeLong( val.getMostSignificantBits());
        _buf.writeLong( val.getLeastSignificantBits());
    }

    /**
     * Encodes a Symbol field
     *
     * @param name   the field name
     * @param symbol the value
     */
    protected void putSymbol(String name, Symbol symbol) {
        _putString(name, symbol.getSymbol(), SYMBOL);
    }

    /**
     * Encodes a String field
     *
     * @param name  the field name
     * @param value the value
     */
    protected void putString(String name, String value) {
        _putString(name, value, STRING);
    }

    private void _putString( String name , String s, byte type ){
        _put( type , name );
        _putValueString( s );
    }

    /**
     * Encodes an ObjectId field to an OBJECT_ID.
     *
     * @param name the field name
     * @param oid  the value
     */
    protected void putObjectId(String name, ObjectId oid) {
        _put( OID , name );
        // according to spec, values should be stored big endian
        _buf.writeIntBE( oid._time() );
        _buf.writeIntBE( oid._machine() );
        _buf.writeIntBE( oid._inc() );
    }
    
    private void putPattern( String name, Pattern p ) {
        _put( REGEX , name );
        _buf.writeCString( p.pattern() );
        _buf.writeCString( regexFlags( p.flags() ) );
    }

    private void putMinKey( String name ) {
        _put( MINKEY , name );
    }

    private void putMaxKey( String name ) {
        _put( MAXKEY , name );
    }


    // ----------------------------------------------

    /**
     * Encodes the type and key.
     *
     * @deprecated This method is NOT a part of public API and will be dropped in 3.x versions.
     *             Access buffer directly via {@link #getOutputBuffer()} if you need to change how BSON is written.
     */
    @Deprecated
    protected void _put(byte type, String name) {
        _buf.write(type);
        _buf.writeCString(name);
    }

    /**
     * @deprecated This method is NOT a part of public API and will be dropped in 3.x versions.
     *             Access buffer directly via {@link #getOutputBuffer()} if you need to change how BSON is written.
     *             Otherwise override {@link #putString(String, String)}.
     */
    @Deprecated
    protected void _putValueString( String s ){
        _buf.writeString(s);
    }
    
    void _reset( Buffer b ){
        b.position(0);
        b.limit(b.capacity());
    }

    /**
     * Puts as utf-8 string
     *
     * @deprecated Replaced by {@code getOutputBuffer().writeCString(String)}.
     */
    @Deprecated
    protected int _put( String str ){
        return _buf.writeCString(str);
    }

    /**
     * Writes integer to underlying buffer.
     *
     * @param x the integer number
     * @deprecated Replaced by {@code getOutputBuffer().writeInt(int)}.
     */
    @Deprecated
    public void writeInt( int x ){
        _buf.writeInt( x );
    }

    /**
     * Writes long to underlying buffer.
     *
     * @param x the long number
     * @deprecated Replaced by {@code getOutputBuffer().writeLong(long)}.
     */
    @Deprecated
    public void writeLong( long x ){
        _buf.writeLong(x);
    }

    /**
     * Writes C string (null-terminated string) to underlying buffer.
     *
     * @param s the string
     * @deprecated Replaced by {@code getOutputBuffer().writeCString(String)}.
     */
    @Deprecated
    public void writeCString( String s ){
        _buf.writeCString(s);
    }

    /**
     * @deprecated Replaced by {@link #getOutputBuffer()}.
     */
    @Deprecated
    protected OutputBuffer _buf;

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy