blob: 11be4d4e89bd5189f976c55b9c1946b9864c35ca [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 Pirob6dcd2b2007-10-13 01:11:46 +000017-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
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 Piro6c740622007-11-15 02:20:58 +000068 { ok, Superobj } ->
69 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 Piro6c740622007-11-15 02:20:58 +000089 ?INFO("oop:call called: Obj=~p Func=~p ArgsProper=~p", [inspect(Obj), Func, ArgsProper]),
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000090 case call1(Obj, Func, ArgsProper) of
91 {ok, Value} -> Value;
92 {error, Kind, S1} -> error(Kind, S1)
Christopher Piro094823a2007-07-18 00:26:12 +000093 end.
94
Christopher Pirob6dcd2b2007-10-13 01:11:46 +000095call1(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
103 },
104 call1(S).
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000105
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000106call1(S = #cstate{obj=Obj, module=Module, func=Func, args=Args}) ->
Christopher Piro6c740622007-11-15 02:20:58 +0000107 %% ?INFO("call1~n obj=~p~n MFA=~p, ~p, ~p", [inspect(Obj), Module, Func, Args]),
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000108 %% io:format("call ~p~n", [Module]),
109 case apply_if_defined(Module, Func, Args) of
Christopher Piro6c740622007-11-15 02:20:58 +0000110 {ok, Value} -> {ok, Value};
111 undef -> call1_try_super(S)
Christopher Piro094823a2007-07-18 00:26:12 +0000112 end.
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000113
114call1_try_super(S = #cstate{func=attr, module=Module, tried=Tried}) ->
115 case Module:super() of
Christopher Piro6c740622007-11-15 02:20:58 +0000116 none -> {error, missing_attr, S};
117 Superclass -> call1_try_super_attr(Superclass, S)
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000118 end;
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000119call1_try_super(S = #cstate{func=Func, module=Module, tried=Tried}) ->
120 case Module:super() of
Christopher Piro6c740622007-11-15 02:20:58 +0000121 none -> {error, missing_method, S};
122 Superclass ->
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000123 S1 = S#cstate{
Christopher Piro6c740622007-11-15 02:20:58 +0000124 module = Superclass,
125 tried = [Module|Tried]
126 },
127 call1(S1)
128 end.
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000129
130call1_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};
Christopher Piro6c740622007-11-15 02:20:58 +0000134 {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]
141 },
142 call1(S1)
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000143 end.
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000144
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
Christopher Piro094823a2007-07-18 00:26:12 +0000148inspect(Obj) ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000149 try
Christopher Piro6c740622007-11-15 02:20:58 +0000150 case is_object(Obj) of
151 true ->
152 DeepList = inspect1(Obj, "#<"),
153 lists:flatten(DeepList);
154 false ->
155 thrift_utils:sformat("~p", [Obj])
156 end
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000157 catch
Christopher Piro6c740622007-11-15 02:20:58 +0000158 _:E ->
159 thrift_utils:sformat("INSPECT_ERROR(~p) ~p", [E, Obj])
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000160
Christopher Piro6c740622007-11-15 02:20:58 +0000161 %% TODO(cpiro): bring this back once we're done testing:
162 %% _:E -> thrift_utils:sformat("~p", [Obj])
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000163 end.
Christopher Piro094823a2007-07-18 00:26:12 +0000164
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000165inspect1(Obj, Str) ->
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000166 Class = ?CLASS(Obj),
167 Inspect = Class:inspect(Obj),
168 Current = atom_to_list(Class) ++ ": " ++ Inspect,
169
Christopher Piro094823a2007-07-18 00:26:12 +0000170 case get_superobject(Obj) of
Christopher Piro6c740622007-11-15 02:20:58 +0000171 {ok, Superobj} ->
172 inspect1(Superobj, Str ++ Current ++ " | ");
173 undef ->
174 Str ++ Current ++ ">"
Christopher Piro094823a2007-07-18 00:26:12 +0000175 end.
Christopher Piro5b3a8f72007-08-01 22:27:37 +0000176
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000177%% class(Obj) -> atom() = Class
178%% | none
179class(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;
185class(_) -> none.
186
187%% is the tuple/record an object?
188%% is_object(Obj) = bool()
189is_object(Obj) when is_tuple(Obj) ->
190 case class(Obj) of
191 none -> false;
192 _ -> true
193 end;
194is_object(_) -> false.
195
196%%%
197%%% private helpers
198%%%
199
200%% apply_if_defined(MFA) -> {ok, apply(MFA)}
201%% | undef
202%% this could be worth some money
203apply_if_defined(M, F, A) ->
204 apply_if_defined({M,F,A}).
205
206apply_if_defined({M,F,A} = MFA) ->
207 try
208 %% io:format("apply ~p ~p ~p~n", [M,F,A]),
209 {ok, apply(M, F, A)}
210 catch
Christopher Piro6c740622007-11-15 02:20:58 +0000211 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 ->
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000217 io:format("DONIT THE EXIT THING ~p~n", [Kind]),
218 exit({Kind, ST}) % some unrelated error, re-exit
Christopher Piro6c740622007-11-15 02:20:58 +0000219 end
Christopher Pirob6dcd2b2007-10-13 01:11:46 +0000220 end.
221
222get_superobject(Obj) ->
223 apply_if_defined(?CLASS(Obj), attr, [Obj, get, super, get]).
224
225%%%
226%%% errors
227%%%
228
229tried(S = #cstate{module=Module, tried=Tried}) ->
230 lists:reverse([Module|Tried]).
231
232error(missing_superobj, S = #cstate{obj=Obj}) ->
233 exit({missing_superobj, {inspect(Obj), tried(S)}});
234error(missing_method, S = #cstate{obj=Obj, func=Func, args=Args}) ->
235 exit({missing_method, {Func, inspect(Obj), tl(Args), tried(S)}});
236error(missing_attr, S = #cstate{args=Args, first_obj=FirstObj}) ->
237 exit({missing_attr, {hd(tl(Args)), inspect(FirstObj), tried(S)}}).
238
239error(missing_attr_set, Field, Obj) ->
240 BT = "..", %% TODO: give a backtrace
241 exit({missing_attr, {Field, inspect(Obj), BT}}).