| %%% Copyright (c) 2007- Facebook | 
 | %%% Distributed under the Thrift Software License | 
 | %%% | 
 | %%% See accompanying file LICENSE or visit the Thrift site at: | 
 | %%% http://developers.facebook.com/thrift/ | 
 |  | 
 | %%% | 
 | %%% dox: | 
 | %%% | 
 | %%% C++                   <-> Erlang | 
 | %%% classes                   modules | 
 | %%%   class b : public a        a:super() -> b. | 
 | %%% | 
 |  | 
 | -module(oop). | 
 |  | 
 | -export([start_new/2, get/2, set/3, call/2, call/3, inspect/1, class/1, is_object/1]). | 
 | -export([call1/3]). %% only for thrift_oop_server ... don't use it | 
 | -export([behaviour_info/1]). | 
 |  | 
 | -include("thrift.hrl"). | 
 | -include("oop.hrl"). | 
 |  | 
 | %% state for the call loop | 
 | -record(cstate, { | 
 |           obj,      %% the current object (on which we want to invoke MFA) | 
 |           module,   %% the current module we're considering | 
 |           func,     %% the method name (i.e. the function we're trying to invoke in Module) | 
 |           args,     %% the arguments, the first of which is Obj | 
 |           tried,    %% a (backwards) list of modules we've tried | 
 |           first_obj %% the original object | 
 |          }). | 
 |  | 
 | %%% | 
 | %%% behavior definition | 
 | %%% | 
 |  | 
 | behaviour_info(callbacks) -> | 
 |     [ {attr, 4}, | 
 |       {super, 0} | 
 |      ]; | 
 | behaviour_info(_) -> | 
 |     undefined. | 
 |  | 
 | %%% | 
 | %%% public interface | 
 | %%% | 
 |  | 
 | %% TODO: voids take only ok as return? | 
 | start_new(none=Resv, _) -> | 
 |     ?ERROR("can't instantiate ~p: class name is a reserved word", [Resv]), | 
 |     error; | 
 | start_new(Class, Args) -> | 
 |     {ok, Pid} = gen_server:start_link(thrift_oop_server, {Class, Args}, []), | 
 |     Pid. | 
 |  | 
 | %% get(Obj, Field) -> term() | 
 | %% looks up Field in Obj or its ancestor objects | 
 | get(Obj, Field) -> | 
 |     call(Obj, attr, [get, Field, get]). | 
 |  | 
 | set(Obj, Field, Value) -> %% TODO: could be tail-recursive | 
 |     Module = ?CLASS(Obj), | 
 |     case apply_if_defined(Module, attr, [Obj, set, Field, Value]) of | 
 |         {ok, V} -> V; | 
 |         undef   -> | 
 |             case get_superobject(Obj) of | 
 |                 { ok, Superobj } -> | 
 |                     Superobj1 = set(Superobj, Field, Value), | 
 |                     Module:attr(Obj, set, super, Superobj1); | 
 |                 undef -> | 
 |                     error(missing_attr_set, Field, Obj) | 
 |             end | 
 |     end. | 
 |  | 
 | %% | 
 | %% ** dynamic method dispatch ** | 
 | %% | 
 | %% calls Module:Func(*Args) if it exists | 
 | %% if not, Module <- Module:super() and try again recursively | 
 | %% | 
 | %% Module:attr(*Args) is handled specially: | 
 | %% Obj needs to be replaced with Obj's "superobject" | 
 | %% | 
 | call(Obj, Func) -> | 
 |     call(Obj, Func, []). | 
 |  | 
 | call(Obj, Func, ArgsProper) -> | 
 |     %% this is WAY too expensive | 
 |     %% ?INFO("oop:call called: Obj=~p Func=~p ArgsProper=~p", [inspect(Obj), Func, ArgsProper]), | 
 |     case call1(Obj, Func, ArgsProper) of | 
 |         {ok, Value}       -> Value; | 
 |         {error, Kind, S1} -> error(Kind, S1) | 
 |     end. | 
 |  | 
 | call1(Obj, Func, ArgsProper) -> | 
 |     S = #cstate{ | 
 |       obj       = Obj, | 
 |       module    = ?CLASS(Obj), | 
 |       func      = Func, | 
 |       args      = [Obj|ArgsProper], %% prepend This to args | 
 |       tried     = [], | 
 |       first_obj = Obj | 
 |      }, | 
 |     call1(S). | 
 |  | 
 | call1(S = #cstate{obj=Obj, module=Module, func=Func, args=Args}) -> | 
 |     %% ?INFO("call1~n obj=~p~n MFA=~p, ~p, ~p", [inspect(Obj), Module, Func, Args]), | 
 |     %% io:format("call ~p~n", [Module]), | 
 |     case apply_if_defined(Module, Func, Args) of | 
 |         {ok, Value} -> {ok, Value}; | 
 |         undef       -> call1_try_super(S) | 
 |     end. | 
 |  | 
 | call1_try_super(S = #cstate{func=attr, module=Module, tried=Tried}) -> | 
 |     case Module:super() of | 
 |         none       -> {error, missing_attr, S}; | 
 |         Superclass -> call1_try_super_attr(Superclass, S) | 
 |     end; | 
 | call1_try_super(S = #cstate{func=Func, module=Module, tried=Tried}) -> | 
 |     case Module:super() of | 
 |         none       -> {error, missing_method, S}; | 
 |         Superclass -> | 
 |             S1 = S#cstate{ | 
 |                    module = Superclass, | 
 |                    tried  = [Module|Tried] | 
 |                   }, | 
 |             call1(S1) | 
 |     end. | 
 |  | 
 | call1_try_super_attr(Superclass, S = #cstate{obj=Obj, module=Module, args=Args, tried=Tried}) -> | 
 |     %% look for attrs in the "super object" | 
 |     case get_superobject(Obj) of | 
 |         undef -> {error, missing_superobj, S}; | 
 |         {ok, Superobj} when Module == ?CLASS(Obj) -> | 
 |             %% replace This with Superobj | 
 |             S1 = S#cstate{ | 
 |                    obj    = Superobj, | 
 |                    args   = [Superobj|tl(Args)], | 
 |                    module = Superclass, | 
 |                    tried  = [Module|Tried] | 
 |                   }, | 
 |             call1(S1) | 
 |     end. | 
 |  | 
 | %% careful: not robust against records beginning with a class name | 
 | %% (note: we can't just guard with is_record(?CLASS(Obj), Obj) since we | 
 | %% can't/really really shouldn't require all record definitions in this file | 
 | inspect(Obj) -> | 
 |     try | 
 |         case is_object(Obj) of | 
 |             true -> | 
 |                 DeepList = inspect1(Obj, "#<"), | 
 |                 lists:flatten(DeepList); | 
 |             false -> | 
 |                 thrift_utils:sformat("~p", [Obj]) | 
 |         end | 
 |     catch | 
 |         _:E -> | 
 |             thrift_utils:sformat("INSPECT_ERROR(~p) ~p", [E, Obj]) | 
 |  | 
 |             %% TODO(cpiro): bring this back once we're done testing: | 
 |             %% _:E -> thrift_utils:sformat("~p", [Obj]) | 
 |     end. | 
 |  | 
 | inspect1(Obj, Str) -> | 
 |     Class   = ?CLASS(Obj), | 
 |     Inspect = Class:inspect(Obj), | 
 |     Current = atom_to_list(Class) ++ ": " ++ Inspect, | 
 |  | 
 |     case get_superobject(Obj) of | 
 |         {ok, Superobj} -> | 
 |             inspect1(Superobj, Str ++ Current ++ " | "); | 
 |         undef -> | 
 |             Str ++ Current ++ ">" | 
 |     end. | 
 |  | 
 | %% class(Obj) -> atom() = Class | 
 | %%             | none | 
 | class(Obj) when is_tuple(Obj) -> | 
 |     %% if it's an object its first element will be a class name, and it'll have super/0 | 
 |     case apply_if_defined(?CLASS(Obj), super, []) of | 
 |         {ok, V} -> V; | 
 |         undef   -> none | 
 |     end; | 
 | class(_)    -> none. | 
 |  | 
 | %% is the tuple/record an object? | 
 | %% is_object(Obj) = bool() | 
 | is_object(Obj) when is_tuple(Obj) -> | 
 |     case class(Obj) of | 
 |         none -> false; | 
 |         _    -> true | 
 |     end; | 
 | is_object(_) -> false. | 
 |  | 
 | %%% | 
 | %%% private helpers | 
 | %%% | 
 |  | 
 | %% apply_if_defined(MFA) -> {ok, apply(MFA)} | 
 | %%                        | undef | 
 | %% this could be worth some money | 
 | apply_if_defined(M, F, A) -> | 
 |     apply_if_defined({M,F,A}). | 
 |  | 
 | apply_if_defined({M,F,A} = MFA) -> | 
 |     try | 
 |         %% io:format("apply ~p ~p ~p~n", [M,F,A]), | 
 |         {ok, apply(M, F, A)} | 
 |     catch | 
 |         error:Kind when Kind == undef; Kind == function_clause -> | 
 |             case erlang:get_stacktrace() of | 
 |                 %% the first stack call should match MFA when `apply' fails because the function is undefined | 
 |                 %% they won't match if the function is currently running and an error happens in the middle | 
 |                 [MFA|_] -> undef;           % trapped successfully | 
 |                 ST -> | 
 |                     io:format("DONIT THE EXIT THING ~p~n", [Kind]), | 
 |                     exit({Kind, ST}) % some unrelated error, re-exit | 
 |             end | 
 |     end. | 
 |  | 
 | get_superobject(Obj) -> | 
 |     apply_if_defined(?CLASS(Obj), attr, [Obj, get, super, get]). | 
 |  | 
 | %%% | 
 | %%% errors | 
 | %%% | 
 |  | 
 | tried(S = #cstate{module=Module, tried=Tried}) -> | 
 |     lists:reverse([Module|Tried]). | 
 |  | 
 | error(missing_superobj, S = #cstate{obj=Obj}) -> | 
 |     exit({missing_superobj, {inspect(Obj), tried(S)}}); | 
 | error(missing_method, S = #cstate{obj=Obj, func=Func, args=Args}) -> | 
 |     exit({missing_method, {Func, inspect(Obj), tl(Args), tried(S)}}); | 
 | error(missing_attr, S = #cstate{args=Args, first_obj=FirstObj}) -> | 
 |     exit({missing_attr, {hd(tl(Args)), inspect(FirstObj), tried(S)}}). | 
 |  | 
 | error(missing_attr_set, Field, Obj) -> | 
 |     BT = "..", %% TODO: give a backtrace | 
 |     exit({missing_attr, {Field, inspect(Obj), BT}}). |