blob: 418f0f284d1e51bb106e12ff67b35014dd060d1f [file] [log] [blame]
Christopher Piro094823a2007-07-18 00:26:12 +00001%%% Copyright (c) 2007- Facebook
2%%% Distributed under the Thrift Software License
Christopher Piro6c740622007-11-15 02:20:58 +00003%%%
Christopher Piro094823a2007-07-18 00:26:12 +00004%%% See accompanying file LICENSE or visit the Thrift site at:
5%%% http://developers.facebook.com/thrift/
6
Christopher Pirob6dcd2b2007-10-13 01:11:46 +00007%%%
8%%% dox:
9%%%
10%%% C++ <-> Erlang
11%%% classes modules
12%%% class b : public a a:super() -> b.
13%%%
14
Christopher Piro094823a2007-07-18 00:26:12 +000015-module(oop).
16
Christopher Pirode11d852007-11-18 02:10:20 +000017-export([start_new/2, get/2, set/3, call/2, call/3, inspect/1, class/1, is_object/1, is_a/2]).
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000018-export([call1/3]). %% only for thrift_oop_server ... don't use it
Christopher Piro094823a2007-07-18 00:26:12 +000019-export([behaviour_info/1]).
20
Christopher Piro5b3a8f72007-08-01 22:27:37 +000021-include("thrift.hrl").
Christopher Piro094823a2007-07-18 00:26:12 +000022-include("oop.hrl").
23
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000024%% 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 }).
33
Christopher Piro094823a2007-07-18 00:26:12 +000034%%%
35%%% behavior definition
36%%%
37
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000038behaviour_info(callbacks) ->
39 [ {attr, 4},
40 {super, 0}
41 ];
42behaviour_info(_) ->
Christopher Piro094823a2007-07-18 00:26:12 +000043 undefined.
44
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000045%%%
46%%% public interface
47%%%
Christopher Piro094823a2007-07-18 00:26:12 +000048
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000049%% TODO: voids take only ok as return?
50start_new(none=Resv, _) ->
Christopher Piro6c740622007-11-15 02:20:58 +000051 ?ERROR("can't instantiate ~p: class name is a reserved word", [Resv]),
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000052 error;
53start_new(Class, Args) ->
54 {ok, Pid} = gen_server:start_link(thrift_oop_server, {Class, Args}, []),
55 Pid.
Christopher Piro094823a2007-07-18 00:26:12 +000056
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000057%% get(Obj, Field) -> term()
58%% looks up Field in Obj or its ancestor objects
Christopher Piro094823a2007-07-18 00:26:12 +000059get(Obj, Field) ->
60 call(Obj, attr, [get, Field, get]).
61
62set(Obj, Field, Value) -> %% TODO: could be tail-recursive
63 Module = ?CLASS(Obj),
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000064 case apply_if_defined(Module, attr, [Obj, set, Field, Value]) of
65 {ok, V} -> V;
66 undef ->
67 case get_superobject(Obj) of
Christopher Pirode11d852007-11-18 02:10:20 +000068 {ok, Superobj} ->
Christopher Piro6c740622007-11-15 02:20:58 +000069 Superobj1 = set(Superobj, Field, Value),
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000070 Module:attr(Obj, set, super, Superobj1);
Christopher Piro6c740622007-11-15 02:20:58 +000071 undef ->
72 error(missing_attr_set, Field, Obj)
73 end
Christopher Piro094823a2007-07-18 00:26:12 +000074 end.
Christopher Piro094823a2007-07-18 00:26:12 +000075
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000076%%
77%% ** dynamic method dispatch **
78%%
79%% calls Module:Func(*Args) if it exists
80%% if not, Module <- Module:super() and try again recursively
81%%
82%% Module:attr(*Args) is handled specially:
83%% Obj needs to be replaced with Obj's "superobject"
84%%
85call(Obj, Func) ->
86 call(Obj, Func, []).
Christopher Piro094823a2007-07-18 00:26:12 +000087
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000088call(Obj, Func, ArgsProper) ->
Christopher Piro10522a72007-11-15 06:26:31 +000089 %% this is WAY too expensive
90 %% ?INFO("oop:call called: Obj=~p Func=~p ArgsProper=~p", [inspect(Obj), Func, ArgsProper]),
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000091 case call1(Obj, Func, ArgsProper) of
92 {ok, Value} -> Value;
93 {error, Kind, S1} -> error(Kind, S1)
Christopher Piro094823a2007-07-18 00:26:12 +000094 end.
95
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000096call1(Obj, Func, ArgsProper) ->
97 S = #cstate{
98 obj = Obj,
99 module = ?CLASS(Obj),
100 func = Func,
101 args = [Obj|ArgsProper], %% prepend This to args
102 tried = [],
103 first_obj = Obj
104 },
105 call1(S).
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000106
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000107call1(S = #cstate{obj=Obj, module=Module, func=Func, args=Args}) ->
Christopher Piro6c740622007-11-15 02:20:58 +0000108 %% ?INFO("call1~n obj=~p~n MFA=~p, ~p, ~p", [inspect(Obj), Module, Func, Args]),
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000109 %% io:format("call ~p~n", [Module]),
110 case apply_if_defined(Module, Func, Args) of
Christopher Piro6c740622007-11-15 02:20:58 +0000111 {ok, Value} -> {ok, Value};
112 undef -> call1_try_super(S)
Christopher Piro094823a2007-07-18 00:26:12 +0000113 end.
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000114
115call1_try_super(S = #cstate{func=attr, module=Module, tried=Tried}) ->
116 case Module:super() of
Christopher Piro6c740622007-11-15 02:20:58 +0000117 none -> {error, missing_attr, S};
118 Superclass -> call1_try_super_attr(Superclass, S)
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000119 end;
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000120call1_try_super(S = #cstate{func=Func, module=Module, tried=Tried}) ->
121 case Module:super() of
Christopher Piro6c740622007-11-15 02:20:58 +0000122 none -> {error, missing_method, S};
123 Superclass ->
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000124 S1 = S#cstate{
Christopher Piro6c740622007-11-15 02:20:58 +0000125 module = Superclass,
126 tried = [Module|Tried]
127 },
128 call1(S1)
129 end.
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000130
131call1_try_super_attr(Superclass, S = #cstate{obj=Obj, module=Module, args=Args, tried=Tried}) ->
132 %% look for attrs in the "super object"
133 case get_superobject(Obj) of
134 undef -> {error, missing_superobj, S};
Christopher Piro6c740622007-11-15 02:20:58 +0000135 {ok, Superobj} when Module == ?CLASS(Obj) ->
136 %% replace This with Superobj
137 S1 = S#cstate{
138 obj = Superobj,
139 args = [Superobj|tl(Args)],
140 module = Superclass,
141 tried = [Module|Tried]
142 },
143 call1(S1)
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000144 end.
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000145
146%% careful: not robust against records beginning with a class name
147%% (note: we can't just guard with is_record(?CLASS(Obj), Obj) since we
148%% can't/really really shouldn't require all record definitions in this file
Christopher Piro094823a2007-07-18 00:26:12 +0000149inspect(Obj) ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000150 try
Christopher Piro6c740622007-11-15 02:20:58 +0000151 case is_object(Obj) of
152 true ->
153 DeepList = inspect1(Obj, "#<"),
154 lists:flatten(DeepList);
155 false ->
156 thrift_utils:sformat("~p", [Obj])
157 end
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000158 catch
Christopher Piro6c740622007-11-15 02:20:58 +0000159 _:E ->
160 thrift_utils:sformat("INSPECT_ERROR(~p) ~p", [E, Obj])
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000161
Christopher Piro6c740622007-11-15 02:20:58 +0000162 %% TODO(cpiro): bring this back once we're done testing:
163 %% _:E -> thrift_utils:sformat("~p", [Obj])
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000164 end.
Christopher Piro094823a2007-07-18 00:26:12 +0000165
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000166inspect1(Obj, Str) ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000167 Class = ?CLASS(Obj),
168 Inspect = Class:inspect(Obj),
169 Current = atom_to_list(Class) ++ ": " ++ Inspect,
170
Christopher Piro094823a2007-07-18 00:26:12 +0000171 case get_superobject(Obj) of
Christopher Piro6c740622007-11-15 02:20:58 +0000172 {ok, Superobj} ->
173 inspect1(Superobj, Str ++ Current ++ " | ");
174 undef ->
175 Str ++ Current ++ ">"
Christopher Piro094823a2007-07-18 00:26:12 +0000176 end.
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000177
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000178%% class(Obj) -> atom() = Class
179%% | none
180class(Obj) when is_tuple(Obj) ->
181 %% if it's an object its first element will be a class name, and it'll have super/0
182 case apply_if_defined(?CLASS(Obj), super, []) of
Christopher Pirode11d852007-11-18 02:10:20 +0000183 {ok, _} -> ?CLASS(Obj);
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000184 undef -> none
185 end;
186class(_) -> none.
187
Christopher Pirode11d852007-11-18 02:10:20 +0000188%% is_a relationship
189is_a(Obj, Class) ->
190 %% ?INFO("is_a ~p ~p", [Obj, Class]),
191 case is_object(Obj) of
192 true ->
193 is_a1(Obj, Class);
194 false ->
195 false
196 end.
197is_a1(Obj, Class) when Class == ?CLASS(Obj) ->
198 true;
199is_a1(Obj, Class) ->
200 case get_superobject(Obj) of
201 undef ->
202 false;
203 {ok, SuperObj} ->
204 is_a1(SuperObj, Class)
205 end.
206
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000207%% is the tuple/record an object?
208%% is_object(Obj) = bool()
209is_object(Obj) when is_tuple(Obj) ->
210 case class(Obj) of
211 none -> false;
212 _ -> true
213 end;
214is_object(_) -> false.
215
216%%%
217%%% private helpers
218%%%
219
220%% apply_if_defined(MFA) -> {ok, apply(MFA)}
221%% | undef
222%% this could be worth some money
223apply_if_defined(M, F, A) ->
224 apply_if_defined({M,F,A}).
225
226apply_if_defined({M,F,A} = MFA) ->
227 try
228 %% io:format("apply ~p ~p ~p~n", [M,F,A]),
229 {ok, apply(M, F, A)}
230 catch
Christopher Pirode11d852007-11-18 02:10:20 +0000231 _:Kind when Kind == undef; Kind == function_clause ->
Christopher Piro6c740622007-11-15 02:20:58 +0000232 case erlang:get_stacktrace() of
233 %% the first stack call should match MFA when `apply' fails because the function is undefined
234 %% they won't match if the function is currently running and an error happens in the middle
235 [MFA|_] -> undef; % trapped successfully
236 ST ->
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000237 io:format("DONIT THE EXIT THING ~p~n", [Kind]),
238 exit({Kind, ST}) % some unrelated error, re-exit
Christopher Piro6c740622007-11-15 02:20:58 +0000239 end
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000240 end.
241
242get_superobject(Obj) ->
243 apply_if_defined(?CLASS(Obj), attr, [Obj, get, super, get]).
244
245%%%
246%%% errors
247%%%
248
249tried(S = #cstate{module=Module, tried=Tried}) ->
250 lists:reverse([Module|Tried]).
251
252error(missing_superobj, S = #cstate{obj=Obj}) ->
253 exit({missing_superobj, {inspect(Obj), tried(S)}});
254error(missing_method, S = #cstate{obj=Obj, func=Func, args=Args}) ->
255 exit({missing_method, {Func, inspect(Obj), tl(Args), tried(S)}});
256error(missing_attr, S = #cstate{args=Args, first_obj=FirstObj}) ->
257 exit({missing_attr, {hd(tl(Args)), inspect(FirstObj), tried(S)}}).
258
259error(missing_attr_set, Field, Obj) ->
260 BT = "..", %% TODO: give a backtrace
261 exit({missing_attr, {Field, inspect(Obj), BT}}).