Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 1 | %%% Copyright (c) 2007- Facebook |
| 2 | %%% Distributed under the Thrift Software License |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 3 | %%% |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 4 | %%% See accompanying file LICENSE or visit the Thrift site at: |
| 5 | %%% http://developers.facebook.com/thrift/ |
| 6 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 7 | %%% |
| 8 | %%% dox: |
| 9 | %%% |
| 10 | %%% C++ <-> Erlang |
| 11 | %%% classes modules |
| 12 | %%% class b : public a a:super() -> b. |
| 13 | %%% |
| 14 | |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 15 | -module(oop). |
| 16 | |
Christopher Piro | de11d85 | 2007-11-18 02:10:20 +0000 | [diff] [blame] | 17 | -export([start_new/2, get/2, set/3, call/2, call/3, inspect/1, class/1, is_object/1, is_a/2]). |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 18 | -export([call1/3]). %% only for thrift_oop_server ... don't use it |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 19 | -export([behaviour_info/1]). |
| 20 | |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 21 | -include("thrift.hrl"). |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 22 | -include("oop.hrl"). |
| 23 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 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 | }). |
| 33 | |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 34 | %%% |
| 35 | %%% behavior definition |
| 36 | %%% |
| 37 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 38 | behaviour_info(callbacks) -> |
| 39 | [ {attr, 4}, |
| 40 | {super, 0} |
| 41 | ]; |
| 42 | behaviour_info(_) -> |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 43 | undefined. |
| 44 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 45 | %%% |
| 46 | %%% public interface |
| 47 | %%% |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 48 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 49 | %% TODO: voids take only ok as return? |
| 50 | start_new(none=Resv, _) -> |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 51 | ?ERROR("can't instantiate ~p: class name is a reserved word", [Resv]), |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 52 | error; |
| 53 | start_new(Class, Args) -> |
| 54 | {ok, Pid} = gen_server:start_link(thrift_oop_server, {Class, Args}, []), |
| 55 | Pid. |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 56 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 57 | %% get(Obj, Field) -> term() |
| 58 | %% looks up Field in Obj or its ancestor objects |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 59 | get(Obj, Field) -> |
| 60 | call(Obj, attr, [get, Field, get]). |
| 61 | |
| 62 | set(Obj, Field, Value) -> %% TODO: could be tail-recursive |
| 63 | Module = ?CLASS(Obj), |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 64 | case apply_if_defined(Module, attr, [Obj, set, Field, Value]) of |
| 65 | {ok, V} -> V; |
| 66 | undef -> |
| 67 | case get_superobject(Obj) of |
Christopher Piro | de11d85 | 2007-11-18 02:10:20 +0000 | [diff] [blame] | 68 | {ok, Superobj} -> |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 69 | Superobj1 = set(Superobj, Field, Value), |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 70 | Module:attr(Obj, set, super, Superobj1); |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 71 | undef -> |
| 72 | error(missing_attr_set, Field, Obj) |
| 73 | end |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 74 | end. |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 75 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 76 | %% |
| 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 | %% |
| 85 | call(Obj, Func) -> |
| 86 | call(Obj, Func, []). |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 87 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 88 | call(Obj, Func, ArgsProper) -> |
Christopher Piro | 10522a7 | 2007-11-15 06:26:31 +0000 | [diff] [blame] | 89 | %% this is WAY too expensive |
| 90 | %% ?INFO("oop:call called: Obj=~p Func=~p ArgsProper=~p", [inspect(Obj), Func, ArgsProper]), |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 91 | case call1(Obj, Func, ArgsProper) of |
| 92 | {ok, Value} -> Value; |
| 93 | {error, Kind, S1} -> error(Kind, S1) |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 94 | end. |
| 95 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 96 | call1(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 Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 106 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 107 | call1(S = #cstate{obj=Obj, module=Module, func=Func, args=Args}) -> |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 108 | %% ?INFO("call1~n obj=~p~n MFA=~p, ~p, ~p", [inspect(Obj), Module, Func, Args]), |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 109 | %% io:format("call ~p~n", [Module]), |
| 110 | case apply_if_defined(Module, Func, Args) of |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 111 | {ok, Value} -> {ok, Value}; |
| 112 | undef -> call1_try_super(S) |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 113 | end. |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 114 | |
| 115 | call1_try_super(S = #cstate{func=attr, module=Module, tried=Tried}) -> |
| 116 | case Module:super() of |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 117 | none -> {error, missing_attr, S}; |
| 118 | Superclass -> call1_try_super_attr(Superclass, S) |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 119 | end; |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 120 | call1_try_super(S = #cstate{func=Func, module=Module, tried=Tried}) -> |
| 121 | case Module:super() of |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 122 | none -> {error, missing_method, S}; |
| 123 | Superclass -> |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 124 | S1 = S#cstate{ |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 125 | module = Superclass, |
| 126 | tried = [Module|Tried] |
| 127 | }, |
| 128 | call1(S1) |
| 129 | end. |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 130 | |
| 131 | call1_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 Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 135 | {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 Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 144 | end. |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 145 | |
| 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 Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 149 | inspect(Obj) -> |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 150 | try |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 151 | 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 Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 158 | catch |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 159 | _:E -> |
| 160 | thrift_utils:sformat("INSPECT_ERROR(~p) ~p", [E, Obj]) |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 161 | |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 162 | %% TODO(cpiro): bring this back once we're done testing: |
| 163 | %% _:E -> thrift_utils:sformat("~p", [Obj]) |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 164 | end. |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 165 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 166 | inspect1(Obj, Str) -> |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 167 | Class = ?CLASS(Obj), |
| 168 | Inspect = Class:inspect(Obj), |
| 169 | Current = atom_to_list(Class) ++ ": " ++ Inspect, |
| 170 | |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 171 | case get_superobject(Obj) of |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 172 | {ok, Superobj} -> |
| 173 | inspect1(Superobj, Str ++ Current ++ " | "); |
| 174 | undef -> |
| 175 | Str ++ Current ++ ">" |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 176 | end. |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 177 | |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 178 | %% class(Obj) -> atom() = Class |
| 179 | %% | none |
| 180 | class(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 Piro | de11d85 | 2007-11-18 02:10:20 +0000 | [diff] [blame] | 183 | {ok, _} -> ?CLASS(Obj); |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 184 | undef -> none |
| 185 | end; |
| 186 | class(_) -> none. |
| 187 | |
Christopher Piro | de11d85 | 2007-11-18 02:10:20 +0000 | [diff] [blame] | 188 | %% is_a relationship |
| 189 | is_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. |
| 197 | is_a1(Obj, Class) when Class == ?CLASS(Obj) -> |
| 198 | true; |
| 199 | is_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 Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 207 | %% is the tuple/record an object? |
| 208 | %% is_object(Obj) = bool() |
| 209 | is_object(Obj) when is_tuple(Obj) -> |
| 210 | case class(Obj) of |
| 211 | none -> false; |
| 212 | _ -> true |
| 213 | end; |
| 214 | is_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 |
| 223 | apply_if_defined(M, F, A) -> |
| 224 | apply_if_defined({M,F,A}). |
| 225 | |
| 226 | apply_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 Piro | de11d85 | 2007-11-18 02:10:20 +0000 | [diff] [blame] | 231 | _:Kind when Kind == undef; Kind == function_clause -> |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 232 | 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 Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 237 | io:format("DONIT THE EXIT THING ~p~n", [Kind]), |
| 238 | exit({Kind, ST}) % some unrelated error, re-exit |
Christopher Piro | 6c74062 | 2007-11-15 02:20:58 +0000 | [diff] [blame] | 239 | end |
Christopher Piro | b6dcd2b | 2007-10-13 01:11:46 +0000 | [diff] [blame] | 240 | end. |
| 241 | |
| 242 | get_superobject(Obj) -> |
| 243 | apply_if_defined(?CLASS(Obj), attr, [Obj, get, super, get]). |
| 244 | |
| 245 | %%% |
| 246 | %%% errors |
| 247 | %%% |
| 248 | |
| 249 | tried(S = #cstate{module=Module, tried=Tried}) -> |
| 250 | lists:reverse([Module|Tried]). |
| 251 | |
| 252 | error(missing_superobj, S = #cstate{obj=Obj}) -> |
| 253 | exit({missing_superobj, {inspect(Obj), tried(S)}}); |
| 254 | error(missing_method, S = #cstate{obj=Obj, func=Func, args=Args}) -> |
| 255 | exit({missing_method, {Func, inspect(Obj), tl(Args), tried(S)}}); |
| 256 | error(missing_attr, S = #cstate{args=Args, first_obj=FirstObj}) -> |
| 257 | exit({missing_attr, {hd(tl(Args)), inspect(FirstObj), tried(S)}}). |
| 258 | |
| 259 | error(missing_attr_set, Field, Obj) -> |
| 260 | BT = "..", %% TODO: give a backtrace |
| 261 | exit({missing_attr, {Field, inspect(Obj), BT}}). |