r1289@opsdev009 (orig r69911): cpiro | 2007-11-14 18:20:58 -0800
[amiethrift.git] / lib / erl / src / oop.erl
blob11be4d4e89bd5189f976c55b9c1946b9864c35ca
1 %%% Copyright (c) 2007- Facebook
2 %%% Distributed under the Thrift Software License
3 %%%
4 %%% See accompanying file LICENSE or visit the Thrift site at:
5 %%% http://developers.facebook.com/thrift/
7 %%%
8 %%% dox:
9 %%%
10 %%% C++ <-> Erlang
11 %%% classes modules
12 %%% class b : public a a:super() -> b.
13 %%%
15 -module(oop).
17 -export([start_new/2, get/2, set/3, call/2, call/3, inspect/1, class/1, is_object/1]).
18 -export([call1/3]). %% only for thrift_oop_server ... don't use it
19 -export([behaviour_info/1]).
21 -include("thrift.hrl").
22 -include("oop.hrl").
24 %% state for the call loop
25 -record(cstate, {
26 obj, %% the current object (on which we want to invoke MFA)
27 module, %% the current module we're considering
28 func, %% the method name (i.e. the function we're trying to invoke in Module)
29 args, %% the arguments, the first of which is Obj
30 tried, %% a (backwards) list of modules we've tried
31 first_obj %% the original object
32 }).
34 %%%
35 %%% behavior definition
36 %%%
38 behaviour_info(callbacks) ->
39 [ {attr, 4},
40 {super, 0}
42 behaviour_info(_) ->
43 undefined.
45 %%%
46 %%% public interface
47 %%%
49 %% TODO: voids take only ok as return?
50 start_new(none=Resv, _) ->
51 ?ERROR("can't instantiate ~p: class name is a reserved word", [Resv]),
52 error;
53 start_new(Class, Args) ->
54 {ok, Pid} = gen_server:start_link(thrift_oop_server, {Class, Args}, []),
55 Pid.
57 %% get(Obj, Field) -> term()
58 %% looks up Field in Obj or its ancestor objects
59 get(Obj, Field) ->
60 call(Obj, attr, [get, Field, get]).
62 set(Obj, Field, Value) -> %% TODO: could be tail-recursive
63 Module = ?CLASS(Obj),
64 case apply_if_defined(Module, attr, [Obj, set, Field, Value]) of
65 {ok, V} -> V;
66 undef ->
67 case get_superobject(Obj) of
68 { ok, Superobj } ->
69 Superobj1 = set(Superobj, Field, Value),
70 Module:attr(Obj, set, super, Superobj1);
71 undef ->
72 error(missing_attr_set, Field, Obj)
73 end
74 end.
77 %% ** dynamic method dispatch **
79 %% calls Module:Func(*Args) if it exists
80 %% if not, Module <- Module:super() and try again recursively
82 %% Module:attr(*Args) is handled specially:
83 %% Obj needs to be replaced with Obj's "superobject"
85 call(Obj, Func) ->
86 call(Obj, Func, []).
88 call(Obj, Func, ArgsProper) ->
89 ?INFO("oop:call called: Obj=~p Func=~p ArgsProper=~p", [inspect(Obj), Func, ArgsProper]),
90 case call1(Obj, Func, ArgsProper) of
91 {ok, Value} -> Value;
92 {error, Kind, S1} -> error(Kind, S1)
93 end.
95 call1(Obj, Func, ArgsProper) ->
96 S = #cstate{
97 obj = Obj,
98 module = ?CLASS(Obj),
99 func = Func,
100 args = [Obj|ArgsProper], %% prepend This to args
101 tried = [],
102 first_obj = Obj
104 call1(S).
106 call1(S = #cstate{obj=Obj, module=Module, func=Func, args=Args}) ->
107 %% ?INFO("call1~n obj=~p~n MFA=~p, ~p, ~p", [inspect(Obj), Module, Func, Args]),
108 %% io:format("call ~p~n", [Module]),
109 case apply_if_defined(Module, Func, Args) of
110 {ok, Value} -> {ok, Value};
111 undef -> call1_try_super(S)
112 end.
114 call1_try_super(S = #cstate{func=attr, module=Module, tried=Tried}) ->
115 case Module:super() of
116 none -> {error, missing_attr, S};
117 Superclass -> call1_try_super_attr(Superclass, S)
118 end;
119 call1_try_super(S = #cstate{func=Func, module=Module, tried=Tried}) ->
120 case Module:super() of
121 none -> {error, missing_method, S};
122 Superclass ->
123 S1 = S#cstate{
124 module = Superclass,
125 tried = [Module|Tried]
127 call1(S1)
128 end.
130 call1_try_super_attr(Superclass, S = #cstate{obj=Obj, module=Module, args=Args, tried=Tried}) ->
131 %% look for attrs in the "super object"
132 case get_superobject(Obj) of
133 undef -> {error, missing_superobj, S};
134 {ok, Superobj} when Module == ?CLASS(Obj) ->
135 %% replace This with Superobj
136 S1 = S#cstate{
137 obj = Superobj,
138 args = [Superobj|tl(Args)],
139 module = Superclass,
140 tried = [Module|Tried]
142 call1(S1)
143 end.
145 %% careful: not robust against records beginning with a class name
146 %% (note: we can't just guard with is_record(?CLASS(Obj), Obj) since we
147 %% can't/really really shouldn't require all record definitions in this file
148 inspect(Obj) ->
150 case is_object(Obj) of
151 true ->
152 DeepList = inspect1(Obj, "#<"),
153 lists:flatten(DeepList);
154 false ->
155 thrift_utils:sformat("~p", [Obj])
157 catch
158 _:E ->
159 thrift_utils:sformat("INSPECT_ERROR(~p) ~p", [E, Obj])
161 %% TODO(cpiro): bring this back once we're done testing:
162 %% _:E -> thrift_utils:sformat("~p", [Obj])
163 end.
165 inspect1(Obj, Str) ->
166 Class = ?CLASS(Obj),
167 Inspect = Class:inspect(Obj),
168 Current = atom_to_list(Class) ++ ": " ++ Inspect,
170 case get_superobject(Obj) of
171 {ok, Superobj} ->
172 inspect1(Superobj, Str ++ Current ++ " | ");
173 undef ->
174 Str ++ Current ++ ">"
175 end.
177 %% class(Obj) -> atom() = Class
178 %% | none
179 class(Obj) when is_tuple(Obj) ->
180 %% if it's an object its first element will be a class name, and it'll have super/0
181 case apply_if_defined(?CLASS(Obj), super, []) of
182 {ok, V} -> V;
183 undef -> none
184 end;
185 class(_) -> none.
187 %% is the tuple/record an object?
188 %% is_object(Obj) = bool()
189 is_object(Obj) when is_tuple(Obj) ->
190 case class(Obj) of
191 none -> false;
192 _ -> true
193 end;
194 is_object(_) -> false.
197 %%% private helpers
200 %% apply_if_defined(MFA) -> {ok, apply(MFA)}
201 %% | undef
202 %% this could be worth some money
203 apply_if_defined(M, F, A) ->
204 apply_if_defined({M,F,A}).
206 apply_if_defined({M,F,A} = MFA) ->
208 %% io:format("apply ~p ~p ~p~n", [M,F,A]),
209 {ok, apply(M, F, A)}
210 catch
211 error:Kind when Kind == undef; Kind == function_clause ->
212 case erlang:get_stacktrace() of
213 %% the first stack call should match MFA when `apply' fails because the function is undefined
214 %% they won't match if the function is currently running and an error happens in the middle
215 [MFA|_] -> undef; % trapped successfully
216 ST ->
217 io:format("DONIT THE EXIT THING ~p~n", [Kind]),
218 exit({Kind, ST}) % some unrelated error, re-exit
220 end.
222 get_superobject(Obj) ->
223 apply_if_defined(?CLASS(Obj), attr, [Obj, get, super, get]).
226 %%% errors
229 tried(S = #cstate{module=Module, tried=Tried}) ->
230 lists:reverse([Module|Tried]).
232 error(missing_superobj, S = #cstate{obj=Obj}) ->
233 exit({missing_superobj, {inspect(Obj), tried(S)}});
234 error(missing_method, S = #cstate{obj=Obj, func=Func, args=Args}) ->
235 exit({missing_method, {Func, inspect(Obj), tl(Args), tried(S)}});
236 error(missing_attr, S = #cstate{args=Args, first_obj=FirstObj}) ->
237 exit({missing_attr, {hd(tl(Args)), inspect(FirstObj), tried(S)}}).
239 error(missing_attr_set, Field, Obj) ->
240 BT = "..", %% TODO: give a backtrace
241 exit({missing_attr, {Field, inspect(Obj), BT}}).