| %%% 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/ | 
 |  | 
 | -module(oop). | 
 |  | 
 | -export([get/2, set/3, call/2, call/3, inspect/1, start_new/2, is_object/1, class/1]). | 
 | -export([behaviour_info/1]). | 
 |  | 
 | -include("thrift.hrl"). | 
 | -include("oop.hrl"). | 
 |  | 
 | %%% | 
 | %%% behavior definition | 
 | %%% | 
 |  | 
 | behaviour_info(callbacks) ->  | 
 |     [  | 
 |       {attr, 4},  | 
 |       {super, 0}  | 
 |     ]; | 
 | behaviour_info(_) ->  | 
 |     undefined. | 
 |  | 
 | %% | 
 |  | 
 | -define(TRIED, lists:reverse([TryModule|TriedRev])). | 
 |  | 
 | %% no super attr defined | 
 | -define(NOSUPEROBJ, exit({missing_attr_super, {inspect(Obj), ?TRIED}})). | 
 |  | 
 | -define(NOMETHOD, exit({missing_method, {Method, inspect(Obj), tl(Args), ?TRIED}})). | 
 |  | 
 | -define(NOATTR, exit({missing_attr, {hd(tl(Args)), inspect(FirstObj), ?TRIED}})). | 
 |  | 
 | -define(NOATTR_SET, exit({missing_attr, {Field, inspect(Obj), ".." %% TODO: give a backtrace | 
 | 			}})). | 
 |  | 
 |  | 
 | %%% 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), | 
 |     try | 
 | 	Module:attr(Obj, set, Field, Value) | 
 |     catch | 
 | 	error:Kind when Kind == undef; Kind == function_clause -> | 
 | 	    case get_superobject(Obj) of | 
 | 		{ ok, Superobj } -> | 
 | 		    Super1 = set(Superobj, Field, Value), | 
 | 		    try | 
 | 			Module:attr(Obj, set, super, Super1) | 
 | 		    catch %% TODO(cpiro): remove check | 
 | 			X -> exit({burnsauce, X}) | 
 | 		    end; | 
 | 		none -> | 
 | 		    ?NOATTR_SET | 
 | 	    end | 
 |     end. | 
 |  | 
 | %%% C++                   <-> Erlang | 
 | %%% classes                   modules | 
 | %%%   class b : public a        a:super() -> b. | 
 | %%%    | 
 |  | 
 | get_superobject(Obj) -> | 
 |     try | 
 | 	{ok, (?CLASS(Obj)):attr(Obj, get, super, get)} | 
 |     catch | 
 | 	error:Kind when Kind == undef; Kind == function_clause -> | 
 | 	    none | 
 |     end. | 
 |  | 
 | is_object(Obj) when is_tuple(Obj) -> | 
 |     try | 
 | 	(?CLASS(Obj)):super(), %% if it's an object its first element will be a class name, and it'll have super/0 | 
 | 	true | 
 |     catch | 
 | 	error:Kind when Kind == undef; Kind == function_clause -> | 
 | 	    false | 
 |     end; | 
 | is_object(_) -> | 
 |     false. | 
 |  | 
 | call(Obj, Method, ArgsProper) -> | 
 |     %% error_logger:info_msg("call called: Obj=~p Method=~p ArgsProper=~p", [inspect(Obj), Method, ArgsProper]), | 
 |     Args = [Obj|ArgsProper], %% prepend This to args | 
 |     TryModule = ?CLASS(Obj), | 
 |     call_loop(Obj, Method, Args, TryModule, [], Obj). | 
 |  | 
 | call(Obj, Method) ->  | 
 |     call(Obj, Method, []). | 
 |  | 
 | call_loop(Obj, Method, Args, TryModule, TriedRev, FirstObj) -> | 
 |     try | 
 | 	%% error_logger:info_msg("call_loop~n ~p~n ~p~n ~p~n ~p", [inspect(Obj), Method, Args, TryModule]), | 
 | 	apply(TryModule, Method, Args) | 
 |     catch | 
 | 	error:Kind when Kind == undef; Kind == function_clause -> | 
 | 	    case { TryModule:super(), Method } of  | 
 | 		{ none, attr } -> | 
 | 		    ?NOATTR; | 
 | 		 | 
 | 		{ none, _ } ->  | 
 | 		    ?NOMETHOD; | 
 | 		 | 
 | 		{ Superclass, attr } ->  | 
 | 		    %% look for attrs in the "super object" | 
 | 		     | 
 | 		    case get_superobject(Obj) of | 
 | 			{ok, Superobj} when (TryModule == ?CLASS(Obj)) ->  | 
 | 			    %% replace This with Superobj | 
 | 			    NewArgs = [Superobj|tl(Args)],  | 
 | 			    call_loop(Superobj, Method, NewArgs,  | 
 | 				      Superclass, [TryModule|TriedRev], FirstObj); | 
 | 			 | 
 | 			{ok, _Superobj} -> % failed guard TODO(cpiro): removeme | 
 | 			    exit(oh_noes); | 
 | 			 | 
 | 			none    -> ?NOSUPEROBJ | 
 | 		    end; | 
 | 		 | 
 | 		{ SuperClass, _ } -> | 
 | 		    call_loop(Obj, Method, Args,  | 
 | 			      SuperClass, [TryModule|TriedRev], FirstObj) | 
 | 	    end | 
 |     end. | 
 |      | 
 | class(Obj) when is_tuple(Obj) -> | 
 |     case is_object(Obj) of | 
 | 	true -> | 
 | 	    ?CLASS(Obj); | 
 | 	false -> | 
 | 	    none | 
 |     end; | 
 | class(_) -> | 
 |     none. | 
 |  | 
 | %% 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 = inspect_loop(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. | 
 |  | 
 | inspect_loop(Obj, Str) -> | 
 |     Class   = ?CLASS(Obj), | 
 |     Inspect = Class:inspect(Obj), | 
 |     Current = atom_to_list(Class) ++ ": " ++ Inspect, | 
 |  | 
 |     case get_superobject(Obj) of | 
 | 	{ ok, Superobj } -> | 
 | 	    inspect_loop(Superobj, Str ++ Current ++ " | "); | 
 | 	none -> | 
 | 	    Str ++ Current ++ ">" | 
 |     end. | 
 |  | 
 | %% TODO: voids take only ok as return?  | 
 | start_new(none=Resv, _) -> | 
 |     error_logger:format("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. |