Thrift now a TLP - INFRA-3116

git-svn-id: https://svn.apache.org/repos/asf/thrift/branches/0.1.x@1028168 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/erl/src/thrift_protocol.erl b/lib/erl/src/thrift_protocol.erl
new file mode 100644
index 0000000..1bfb0a4
--- /dev/null
+++ b/lib/erl/src/thrift_protocol.erl
@@ -0,0 +1,356 @@
+%%
+%% 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.
+%%
+
+-module(thrift_protocol).
+
+-export([new/2,
+         write/2,
+         read/2,
+         read/3,
+         skip/2,
+         flush_transport/1,
+         close_transport/1,
+         typeid_to_atom/1
+        ]).
+
+-export([behaviour_info/1]).
+
+-include("thrift_constants.hrl").
+-include("thrift_protocol.hrl").
+
+-record(protocol, {module, data}).
+
+behaviour_info(callbacks) ->
+    [
+     {read, 2},
+     {write, 2},
+     {flush_transport, 1},
+     {close_transport, 1}
+    ];
+behaviour_info(_Else) -> undefined.
+
+new(Module, Data) when is_atom(Module) ->
+    {ok, #protocol{module = Module,
+                   data = Data}}.
+
+flush_transport(#protocol{module = Module,
+                          data = Data}) ->
+    Module:flush_transport(Data).
+
+close_transport(#protocol{module = Module,
+                          data = Data}) ->
+    Module:close_transport(Data).
+
+typeid_to_atom(?tType_STOP) -> field_stop;
+typeid_to_atom(?tType_VOID) -> void;
+typeid_to_atom(?tType_BOOL) -> bool;
+typeid_to_atom(?tType_BYTE) -> byte;
+typeid_to_atom(?tType_DOUBLE) -> double;
+typeid_to_atom(?tType_I16) -> i16;
+typeid_to_atom(?tType_I32) -> i32;
+typeid_to_atom(?tType_I64) -> i64;
+typeid_to_atom(?tType_STRING) -> string;
+typeid_to_atom(?tType_STRUCT) -> struct;
+typeid_to_atom(?tType_MAP) -> map;
+typeid_to_atom(?tType_SET) -> set;
+typeid_to_atom(?tType_LIST) -> list.
+
+term_to_typeid(void) -> ?tType_VOID;
+term_to_typeid(bool) -> ?tType_BOOL;
+term_to_typeid(byte) -> ?tType_BYTE;
+term_to_typeid(double) -> ?tType_DOUBLE;
+term_to_typeid(i16) -> ?tType_I16;
+term_to_typeid(i32) -> ?tType_I32;
+term_to_typeid(i64) -> ?tType_I64;
+term_to_typeid(string) -> ?tType_STRING;
+term_to_typeid({struct, _}) -> ?tType_STRUCT;
+term_to_typeid({map, _, _}) -> ?tType_MAP;
+term_to_typeid({set, _}) -> ?tType_SET;
+term_to_typeid({list, _}) -> ?tType_LIST.
+
+%% Structure is like:
+%%    [{Fid, Type}, ...]
+read(IProto, {struct, Structure}, Tag)
+  when is_list(Structure), is_atom(Tag) ->
+
+    % If we want a tagged tuple, we need to offset all the tuple indices
+    % by 1 to avoid overwriting the tag.
+    Offset = if Tag =/= undefined -> 1; true -> 0 end,
+    IndexList = case length(Structure) of
+                    N when N > 0 -> lists:seq(1 + Offset, N + Offset);
+                    _ -> []
+                end,
+
+    SWithIndices = [{Fid, {Type, Index}} ||
+                       {{Fid, Type}, Index} <-
+                           lists:zip(Structure, IndexList)],
+    % Fid -> {Type, Index}
+    SDict = dict:from_list(SWithIndices),
+
+    ok = read(IProto, struct_begin),
+    RTuple0 = erlang:make_tuple(length(Structure) + Offset, undefined),
+    RTuple1 = if Tag =/= undefined -> setelement(1, RTuple0, Tag);
+                 true              -> RTuple0
+              end,
+
+    RTuple2 = read_struct_loop(IProto, SDict, RTuple1),
+    {ok, RTuple2}.
+
+read(IProto, {struct, {Module, StructureName}}) when is_atom(Module),
+                                                     is_atom(StructureName) ->
+    read(IProto, Module:struct_info(StructureName), StructureName);
+
+read(IProto, S={struct, Structure}) when is_list(Structure) ->
+    read(IProto, S, undefined);
+
+read(IProto, {list, Type}) ->
+    #protocol_list_begin{etype = EType, size = Size} =
+        read(IProto, list_begin),
+    List = [Result || {ok, Result} <-
+                          [read(IProto, Type) || _X <- lists:duplicate(Size, 0)]],
+    ok = read(IProto, list_end),
+    {ok, List};
+
+read(IProto, {map, KeyType, ValType}) ->
+    #protocol_map_begin{size = Size} =
+        read(IProto, map_begin),
+
+    List = [{Key, Val} || {{ok, Key}, {ok, Val}} <-
+                              [{read(IProto, KeyType),
+                                read(IProto, ValType)} || _X <- lists:duplicate(Size, 0)]],
+    ok = read(IProto, map_end),
+    {ok, dict:from_list(List)};
+
+read(IProto, {set, Type}) ->
+    #protocol_set_begin{etype = _EType,
+                        size = Size} =
+        read(IProto, set_begin),
+    List = [Result || {ok, Result} <-
+                          [read(IProto, Type) || _X <- lists:duplicate(Size, 0)]],
+    ok = read(IProto, set_end),
+    {ok, sets:from_list(List)};
+
+read(#protocol{module = Module,
+               data = ModuleData}, ProtocolType) ->
+    Module:read(ModuleData, ProtocolType).
+
+read_struct_loop(IProto, SDict, RTuple) ->
+    #protocol_field_begin{type = FType, id = Fid, name = Name} =
+        thrift_protocol:read(IProto, field_begin),
+    case {FType, Fid} of
+        {?tType_STOP, _} ->
+            RTuple;
+        _Else ->
+            case dict:find(Fid, SDict) of
+                {ok, {Type, Index}} ->
+                    case term_to_typeid(Type) of
+                        FType ->
+                            {ok, Val} = read(IProto, Type),
+                            thrift_protocol:read(IProto, field_end),
+                            NewRTuple = setelement(Index, RTuple, Val),
+                            read_struct_loop(IProto, SDict, NewRTuple);
+                        Expected ->
+                            error_logger:info_msg(
+                              "Skipping field ~p with wrong type (~p != ~p)~n",
+                              [Fid, FType, Expected]),
+                            skip_field(FType, IProto, SDict, RTuple)
+                    end;
+                _Else2 ->
+                    error_logger:info_msg("Skipping field ~p with unknown fid~n", [Fid]),
+                    skip_field(FType, IProto, SDict, RTuple)
+            end
+    end.
+
+skip_field(FType, IProto, SDict, RTuple) ->
+    FTypeAtom = thrift_protocol:typeid_to_atom(FType),
+    thrift_protocol:skip(IProto, FTypeAtom),
+    read(IProto, field_end),
+    read_struct_loop(IProto, SDict, RTuple).
+
+
+skip(Proto, struct) ->
+    ok = read(Proto, struct_begin),
+    ok = skip_struct_loop(Proto),
+    ok = read(Proto, struct_end);
+
+skip(Proto, map) ->
+    Map = read(Proto, map_begin),
+    ok = skip_map_loop(Proto, Map),
+    ok = read(Proto, map_end);
+
+skip(Proto, set) ->
+    Set = read(Proto, set_begin),
+    ok = skip_set_loop(Proto, Set),
+    ok = read(Proto, set_end);
+
+skip(Proto, list) ->
+    List = read(Proto, list_begin),
+    ok = skip_list_loop(Proto, List),
+    ok = read(Proto, list_end);
+
+skip(Proto, Type) when is_atom(Type) ->
+    _Ignore = read(Proto, Type),
+    ok.
+
+
+skip_struct_loop(Proto) ->
+    #protocol_field_begin{type = Type} = read(Proto, field_begin),
+    case Type of
+        ?tType_STOP ->
+            ok;
+        _Else ->
+            skip(Proto, Type),
+            ok = read(Proto, field_end),
+            skip_struct_loop(Proto)
+    end.
+
+skip_map_loop(Proto, Map = #protocol_map_begin{ktype = Ktype,
+                                               vtype = Vtype,
+                                               size = Size}) ->
+    case Size of
+        N when N > 0 ->
+            skip(Proto, Ktype),
+            skip(Proto, Vtype),
+            skip_map_loop(Proto,
+                          Map#protocol_map_begin{size = Size - 1});
+        0 -> ok
+    end.
+
+skip_set_loop(Proto, Map = #protocol_set_begin{etype = Etype,
+                                               size = Size}) ->
+    case Size of
+        N when N > 0 ->
+            skip(Proto, Etype),
+            skip_set_loop(Proto,
+                          Map#protocol_set_begin{size = Size - 1});
+        0 -> ok
+    end.
+
+skip_list_loop(Proto, Map = #protocol_list_begin{etype = Etype,
+                                                 size = Size}) ->
+    case Size of
+        N when N > 0 ->
+            skip(Proto, Etype),
+            skip_list_loop(Proto,
+                           Map#protocol_list_begin{size = Size - 1});
+        0 -> ok
+    end.
+
+
+%%--------------------------------------------------------------------
+%% Function: write(OProto, {Type, Data}) -> ok
+%%
+%% Type = {struct, StructDef} |
+%%        {list, Type} |
+%%        {map, KeyType, ValType} |
+%%        {set, Type} |
+%%        BaseType
+%%
+%% Data =
+%%         tuple()  -- for struct
+%%       | list()   -- for list
+%%       | dictionary()   -- for map
+%%       | set()    -- for set
+%%       | term()   -- for base types
+%%
+%% Description:
+%%--------------------------------------------------------------------
+write(Proto, {{struct, StructDef}, Data})
+  when is_list(StructDef), is_tuple(Data), length(StructDef) == size(Data) - 1 ->
+
+    [StructName | Elems] = tuple_to_list(Data),
+    ok = write(Proto, #protocol_struct_begin{name = StructName}),
+    ok = struct_write_loop(Proto, StructDef, Elems),
+    ok = write(Proto, struct_end),
+    ok;
+
+write(Proto, {{struct, {Module, StructureName}}, Data})
+  when is_atom(Module),
+       is_atom(StructureName),
+       element(1, Data) =:= StructureName ->
+    StructType = Module:struct_info(StructureName),
+    write(Proto, {Module:struct_info(StructureName), Data});
+
+write(Proto, {{list, Type}, Data})
+  when is_list(Data) ->
+    ok = write(Proto,
+               #protocol_list_begin{
+                 etype = term_to_typeid(Type),
+                 size = length(Data)
+                }),
+    lists:foreach(fun(Elem) ->
+                          ok = write(Proto, {Type, Elem})
+                  end,
+                  Data),
+    ok = write(Proto, list_end),
+    ok;
+
+write(Proto, {{map, KeyType, ValType}, Data}) ->
+    ok = write(Proto,
+               #protocol_map_begin{
+                 ktype = term_to_typeid(KeyType),
+                 vtype = term_to_typeid(ValType),
+                 size  = dict:size(Data)
+                }),
+    dict:fold(fun(KeyData, ValData, _Acc) ->
+                      ok = write(Proto, {KeyType, KeyData}),
+                      ok = write(Proto, {ValType, ValData})
+              end,
+              _AccO = ok,
+              Data),
+    ok = write(Proto, map_end),
+    ok;
+
+write(Proto, {{set, Type}, Data}) ->
+    true = sets:is_set(Data),
+    ok = write(Proto,
+               #protocol_set_begin{
+                 etype = term_to_typeid(Type),
+                 size  = sets:size(Data)
+                }),
+    sets:fold(fun(Elem, _Acc) ->
+                      ok = write(Proto, {Type, Elem})
+              end,
+              _Acc0 = ok,
+              Data),
+    ok = write(Proto, set_end),
+    ok;
+
+write(#protocol{module = Module,
+                data = ModuleData}, Data) ->
+    Module:write(ModuleData, Data).
+
+struct_write_loop(Proto, [{Fid, Type} | RestStructDef], [Data | RestData]) ->
+    case Data of
+        undefined ->
+            % null fields are skipped in response
+            skip;
+        _ ->
+            ok = write(Proto,
+                       #protocol_field_begin{
+                         type = term_to_typeid(Type),
+                         id = Fid
+                        }),
+            ok = write(Proto, {Type, Data}),
+            ok = write(Proto, field_end)
+    end,
+    struct_write_loop(Proto, RestStructDef, RestData);
+struct_write_loop(Proto, [], []) ->
+    ok = write(Proto, field_stop),
+    ok.