// BSON.java /** * Copyright (C) 2008 10gen 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 java.nio.charset.Charset; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Logger; import java.util.regex.Pattern; import org.bson.util.ClassMap; public class BSON { static final Logger LOGGER = Logger.getLogger( "org.bson.BSON" ); // ---- basics ---- public static final byte EOO = 0; public static final byte NUMBER = 1; public static final byte STRING = 2; public static final byte OBJECT = 3; public static final byte ARRAY = 4; public static final byte BINARY = 5; public static final byte UNDEFINED = 6; public static final byte OID = 7; public static final byte BOOLEAN = 8; public static final byte DATE = 9; public static final byte NULL = 10; public static final byte REGEX = 11; public static final byte REF = 12; public static final byte CODE = 13; public static final byte SYMBOL = 14; public static final byte CODE_W_SCOPE = 15; public static final byte NUMBER_INT = 16; public static final byte TIMESTAMP = 17; public static final byte NUMBER_LONG = 18; public static final byte MINKEY = -1; public static final byte MAXKEY = 127; // --- binary types /* these are binary types so the format would look like <BINARY><name><BINARY_TYPE><...> */ public static final byte B_GENERAL = 0; public static final byte B_FUNC = 1; public static final byte B_BINARY = 2; public static final byte B_UUID = 3; // ---- regular expression handling ---- /** Converts a string of regular expression flags from the database in Java regular * expression flags. * @param flags flags from database * @return the Java flags */ public static int regexFlags( String flags ){ int fint = 0; if ( flags == null || flags.length() == 0 ) return fint; flags = flags.toLowerCase(); for( int i=0; i<flags.length(); i++ ) { RegexFlag flag = RegexFlag.getByCharacter( flags.charAt( i ) ); if( flag != null ) { fint |= flag.javaFlag; if( flag.unsupported != null ) _warnUnsupportedRegex( flag.unsupported ); } else { throw new IllegalArgumentException( "unrecognized flag ["+flags.charAt( i ) + "] " + (int)flags.charAt(i) ); } } return fint; } public static int regexFlag( char c ){ RegexFlag flag = RegexFlag.getByCharacter( c ); if ( flag == null ) throw new IllegalArgumentException( "unrecognized flag [" + c + "]" ); if ( flag.unsupported != null ){ _warnUnsupportedRegex( flag.unsupported ); return 0; } return flag.javaFlag; } /** Converts Java regular expression flags into a string of flags for the database * @param flags Java flags * @return the flags for the database */ public static String regexFlags( int flags ){ StringBuilder buf = new StringBuilder(); for( RegexFlag flag : RegexFlag.values() ) { if( ( flags & flag.javaFlag ) > 0 ) { buf.append( flag.flagChar ); flags -= flag.javaFlag; } } if( flags > 0 ) throw new IllegalArgumentException( "some flags could not be recognized." ); return buf.toString(); } private static enum RegexFlag { CANON_EQ( Pattern.CANON_EQ, 'c', "Pattern.CANON_EQ" ), UNIX_LINES(Pattern.UNIX_LINES, 'd', "Pattern.UNIX_LINES" ), GLOBAL( GLOBAL_FLAG, 'g', null ), CASE_INSENSITIVE( Pattern.CASE_INSENSITIVE, 'i', null ), MULTILINE(Pattern.MULTILINE, 'm', null ), DOTALL( Pattern.DOTALL, 's', "Pattern.DOTALL" ), LITERAL( Pattern.LITERAL, 't', "Pattern.LITERAL" ), UNICODE_CASE( Pattern.UNICODE_CASE, 'u', "Pattern.UNICODE_CASE" ), COMMENTS( Pattern.COMMENTS, 'x', null ); private static final Map<Character, RegexFlag> byCharacter = new HashMap<Character, RegexFlag>(); static { for (RegexFlag flag : values()) { byCharacter.put(flag.flagChar, flag); } } public static RegexFlag getByCharacter(char ch) { return byCharacter.get(ch); } public final int javaFlag; public final char flagChar; public final String unsupported; RegexFlag( int f, char ch, String u ) { javaFlag = f; flagChar = ch; unsupported = u; } } private static void _warnUnsupportedRegex( String flag ) { LOGGER.info( "flag " + flag + " not supported by db." ); } private static final int GLOBAL_FLAG = 256; // --- (en|de)coding hooks ----- public static boolean hasDecodeHooks() { return _decodeHooks; } public static void addEncodingHook( Class c , Transformer t ){ _encodeHooks = true; List<Transformer> l = _encodingHooks.get( c ); if ( l == null ){ l = new CopyOnWriteArrayList<Transformer>(); _encodingHooks.put( c , l ); } l.add( t ); } public static void addDecodingHook( Class c , Transformer t ){ _decodeHooks = true; List<Transformer> l = _decodingHooks.get( c ); if ( l == null ){ l = new CopyOnWriteArrayList<Transformer>(); _decodingHooks.put( c , l ); } l.add( t ); } public static Object applyEncodingHooks( Object o ){ if ( ! _anyHooks() ) return o; if ( _encodingHooks.size() == 0 || o == null ) return o; List<Transformer> l = _encodingHooks.get( o.getClass() ); if ( l != null ) for ( Transformer t : l ) o = t.transform( o ); return o; } public static Object applyDecodingHooks( Object o ){ if ( ! _anyHooks() || o == null ) return o; List<Transformer> l = _decodingHooks.get( o.getClass() ); if ( l != null ) for ( Transformer t : l ) o = t.transform( o ); return o; } /** * Returns the encoding hook(s) associated with the specified class * */ public static List<Transformer> getEncodingHooks( Class c ){ return _encodingHooks.get( c ); } /** * Clears *all* encoding hooks. */ public static void clearEncodingHooks(){ _encodeHooks = false; _encodingHooks.clear(); } /** * Remove all encoding hooks for a specific class. */ public static void removeEncodingHooks( Class c ){ _encodingHooks.remove( c ); } /** * Remove a specific encoding hook for a specific class. */ public static void removeEncodingHook( Class c , Transformer t ){ getEncodingHooks( c ).remove( t ); } /** * Returns the decoding hook(s) associated with the specific class */ public static List<Transformer> getDecodingHooks( Class c ){ return _decodingHooks.get( c ); } /** * Clears *all* decoding hooks. */ public static void clearDecodingHooks(){ _decodeHooks = false; _decodingHooks.clear(); } /** * Remove all decoding hooks for a specific class. */ public static void removeDecodingHooks( Class c ){ _decodingHooks.remove( c ); } /** * Remove a specific encoding hook for a specific class. */ public static void removeDecodingHook( Class c , Transformer t ){ getDecodingHooks( c ).remove( t ); } public static void clearAllHooks(){ clearEncodingHooks(); clearDecodingHooks(); } /** * Returns true if any encoding or decoding hooks are loaded. */ private static boolean _anyHooks(){ return _encodeHooks || _decodeHooks; } private static boolean _encodeHooks = false; private static boolean _decodeHooks = false; static ClassMap<List<Transformer>> _encodingHooks = new ClassMap<List<Transformer>>(); static ClassMap<List<Transformer>> _decodingHooks = new ClassMap<List<Transformer>>(); static protected Charset _utf8 = Charset.forName( "UTF-8" ); // ----- static encode/decode ----- public static byte[] encode( BSONObject o ){ BSONEncoder e = _staticEncoder.get(); try { return e.encode( o ); } finally { e.done(); } } public static BSONObject decode( byte[] b ){ BSONDecoder d = _staticDecoder.get(); return d.readObject( b ); } static ThreadLocal<BSONEncoder> _staticEncoder = new ThreadLocal<BSONEncoder>(){ protected BSONEncoder initialValue(){ return new BasicBSONEncoder(); } }; static ThreadLocal<BSONDecoder> _staticDecoder = new ThreadLocal<BSONDecoder>(){ protected BSONDecoder initialValue(){ return new BasicBSONDecoder(); } }; // --- coercing --- public static int toInt( Object o ){ if ( o == null ) throw new NullPointerException( "can't be null" ); if ( o instanceof Number ) return ((Number)o).intValue(); if ( o instanceof Boolean ) return ((Boolean)o) ? 1 : 0; throw new IllegalArgumentException( "can't convert: " + o.getClass().getName() + " to int" ); } }