Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 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/ |
| 6 | |
| 7 | -module(oop). |
| 8 | |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 9 | -export([get/2, set/3, call/2, call/3, inspect/1, start_new/2, is_object/1, class/1]). |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 10 | -export([behaviour_info/1]). |
| 11 | |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 12 | -include("thrift.hrl"). |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 13 | -include("oop.hrl"). |
| 14 | |
| 15 | %%% |
| 16 | %%% behavior definition |
| 17 | %%% |
| 18 | |
| 19 | behaviour_info(callbacks) -> |
| 20 | [ |
| 21 | {attr, 4}, |
| 22 | {super, 0} |
| 23 | ]; |
| 24 | behaviour_info(_) -> |
| 25 | undefined. |
| 26 | |
| 27 | %% |
| 28 | |
| 29 | -define(TRIED, lists:reverse([TryModule|TriedRev])). |
| 30 | |
| 31 | %% no super attr defined |
| 32 | -define(NOSUPEROBJ, exit({missing_attr_super, {inspect(Obj), ?TRIED}})). |
| 33 | |
| 34 | -define(NOMETHOD, exit({missing_method, {Method, inspect(Obj), tl(Args), ?TRIED}})). |
| 35 | |
| 36 | -define(NOATTR, exit({missing_attr, {hd(tl(Args)), inspect(FirstObj), ?TRIED}})). |
| 37 | |
| 38 | -define(NOATTR_SET, exit({missing_attr, {Field, inspect(Obj), ".." %% TODO: give a backtrace |
| 39 | }})). |
| 40 | |
| 41 | |
| 42 | %%% get(Obj, Field) -> term() |
| 43 | %%% looks up Field in Obj or its ancestor objects |
| 44 | |
| 45 | get(Obj, Field) -> |
| 46 | call(Obj, attr, [get, Field, get]). |
| 47 | |
| 48 | set(Obj, Field, Value) -> %% TODO: could be tail-recursive |
| 49 | Module = ?CLASS(Obj), |
| 50 | try |
| 51 | Module:attr(Obj, set, Field, Value) |
| 52 | catch |
| 53 | error:Kind when Kind == undef; Kind == function_clause -> |
| 54 | case get_superobject(Obj) of |
| 55 | { ok, Superobj } -> |
| 56 | Super1 = set(Superobj, Field, Value), |
| 57 | try |
| 58 | Module:attr(Obj, set, super, Super1) |
| 59 | catch %% TODO(cpiro): remove check |
| 60 | X -> exit({burnsauce, X}) |
| 61 | end; |
| 62 | none -> |
| 63 | ?NOATTR_SET |
| 64 | end |
| 65 | end. |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 66 | |
| 67 | %%% C++ <-> Erlang |
| 68 | %%% classes modules |
| 69 | %%% class b : public a a:super() -> b. |
| 70 | %%% |
| 71 | |
| 72 | get_superobject(Obj) -> |
| 73 | try |
| 74 | {ok, (?CLASS(Obj)):attr(Obj, get, super, get)} |
| 75 | catch |
| 76 | error:Kind when Kind == undef; Kind == function_clause -> |
| 77 | none |
| 78 | end. |
| 79 | |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 80 | is_object(Obj) when is_tuple(Obj) -> |
| 81 | try |
| 82 | (?CLASS(Obj)):super(), %% if it's an object its first element will be a class name, and it'll have super/0 |
| 83 | true |
| 84 | catch |
| 85 | error:Kind when Kind == undef; Kind == function_clause -> |
| 86 | false |
| 87 | end; |
| 88 | is_object(_) -> |
| 89 | false. |
| 90 | |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 91 | call(Obj, Method, ArgsProper) -> |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 92 | %% error_logger:info_msg("call called: Obj=~p Method=~p ArgsProper=~p", [inspect(Obj), Method, ArgsProper]), |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 93 | Args = [Obj|ArgsProper], %% prepend This to args |
| 94 | TryModule = ?CLASS(Obj), |
| 95 | call_loop(Obj, Method, Args, TryModule, [], Obj). |
| 96 | |
| 97 | call(Obj, Method) -> |
| 98 | call(Obj, Method, []). |
| 99 | |
| 100 | call_loop(Obj, Method, Args, TryModule, TriedRev, FirstObj) -> |
| 101 | try |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 102 | %% error_logger:info_msg("call_loop~n ~p~n ~p~n ~p~n ~p", [inspect(Obj), Method, Args, TryModule]), |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 103 | apply(TryModule, Method, Args) |
| 104 | catch |
| 105 | error:Kind when Kind == undef; Kind == function_clause -> |
| 106 | case { TryModule:super(), Method } of |
| 107 | { none, attr } -> |
| 108 | ?NOATTR; |
| 109 | |
| 110 | { none, _ } -> |
| 111 | ?NOMETHOD; |
| 112 | |
| 113 | { Superclass, attr } -> |
| 114 | %% look for attrs in the "super object" |
| 115 | |
| 116 | case get_superobject(Obj) of |
| 117 | {ok, Superobj} when (TryModule == ?CLASS(Obj)) -> |
| 118 | %% replace This with Superobj |
| 119 | NewArgs = [Superobj|tl(Args)], |
| 120 | call_loop(Superobj, Method, NewArgs, |
| 121 | Superclass, [TryModule|TriedRev], FirstObj); |
| 122 | |
| 123 | {ok, _Superobj} -> % failed guard TODO(cpiro): removeme |
| 124 | exit(oh_noes); |
| 125 | |
| 126 | none -> ?NOSUPEROBJ |
| 127 | end; |
| 128 | |
| 129 | { SuperClass, _ } -> |
| 130 | call_loop(Obj, Method, Args, |
| 131 | SuperClass, [TryModule|TriedRev], FirstObj) |
| 132 | end |
| 133 | end. |
| 134 | |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 135 | class(Obj) when is_tuple(Obj) -> |
| 136 | case is_object(Obj) of |
| 137 | true -> |
| 138 | ?CLASS(Obj); |
| 139 | false -> |
| 140 | none |
| 141 | end; |
| 142 | class(_) -> |
| 143 | none. |
| 144 | |
| 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 Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 148 | inspect(Obj) -> |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 149 | try |
| 150 | case is_object(Obj) of |
| 151 | true -> |
| 152 | DeepList = inspect_loop(Obj, "#<"), |
| 153 | lists:flatten(DeepList); |
| 154 | false -> |
| 155 | thrift_utils:sformat("~p", [Obj]) |
| 156 | end |
| 157 | catch |
| 158 | _:E -> |
| 159 | thrift_utils:sformat("INSPECT_ERROR(~p) ~p", [E, Obj]) |
| 160 | |
| 161 | %% TODO(cpiro): bring this back once we're done testing: |
| 162 | %% _:E -> thrift_utils:sformat("~p", [Obj]) |
| 163 | end. |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 164 | |
| 165 | inspect_loop(Obj, Str) -> |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 166 | Class = ?CLASS(Obj), |
| 167 | Inspect = Class:inspect(Obj), |
| 168 | Current = atom_to_list(Class) ++ ": " ++ Inspect, |
| 169 | |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 170 | case get_superobject(Obj) of |
| 171 | { ok, Superobj } -> |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 172 | inspect_loop(Superobj, Str ++ Current ++ " | "); |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 173 | none -> |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 174 | Str ++ Current ++ ">" |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 175 | end. |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 176 | |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 177 | %% TODO: voids take only ok as return? |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 178 | start_new(none=Resv, _) -> |
| 179 | error_logger:format("can't instantiate ~p: class name is a reserved word", [Resv]), |
| 180 | error; |
Christopher Piro | 094823a | 2007-07-18 00:26:12 +0000 | [diff] [blame] | 181 | start_new(Class, Args) -> |
Christopher Piro | 5b3a8f7 | 2007-08-01 22:27:37 +0000 | [diff] [blame] | 182 | {ok, Pid} = gen_server:start_link(thrift_oop_server, {Class, Args}, []), |
| 183 | Pid. |