| %%% 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. |