1 ///
2 module drmi.base;
3 
4 import drmi.types;
5 import drmi.exceptions;
6 import drmi.helpers;
7 
8 ///
9 interface RMICom
10 {
11     ///
12     RMIResponse process(RMICall call);
13 }
14 
15 ///
16 class RMISkeleton(T) : RMICom
17     if (is(T == interface))
18 {
19 protected:
20     T server;
21 
22 public:
23 
24     ///
25     this(T server)
26     {
27         import std.exception : enforce;
28         this.server = enforce(server, "server is null");
29     }
30 
31     override RMIResponse process(RMICall call)
32     {
33         import std.meta;
34         import std.traits;
35 
36         template ov(string s) { alias ov = AliasSeq!(__traits(getOverloads, T, s)); }
37 
38         switch (call.func)
39         {
40             foreach (n, func; staticMap!(ov, __traits(derivedMembers, T)))
41             {
42                 case rmiFunctionName!func:
43                     try
44                     {
45                         auto params = Parameters!func.init;
46                         foreach (i, type; Parameters!func)
47                             params[i] = call.data[i].deserializeJson!type;
48                         
49                         Json resData = null;
50                         enum callstr = "server."~__traits(identifier, func)~"(params)";
51                         static if(is(ReturnType!func == void)) mixin(callstr~";");
52                         else resData = mixin(callstr).serializeToJson;
53 
54                         return RMIResponse(0, call, resData);
55                     }
56                     catch (Throwable e)
57                         return RMIResponse(2, call, e.msg.serializeToJson);
58             }
59             default:
60                 return RMIResponse(1, call, "unknown func".serializeToJson);
61         }
62     }
63 }
64 
65 ///
66 interface RMIStubCom : RMICom
67 {
68     ///
69     string caller() const @property;
70 }
71 
72 ///
73 class RMIStub(T) : T
74 {
75 protected:
76     RMIStubCom com;
77 public:
78     ///
79     this(RMIStubCom com) { this.com = com; }
80 
81     private mixin template overrideIface()
82     {
83         private enum string packCode =
84             q{
85                 enum dummy;
86                 alias self = AliasSeq!(__traits(parent, dummy))[0];
87 
88                 static fname = rmiFunctionName!self;
89 
90                 RMICall call;
91                 call.caller = com.caller;
92                 call.func = fname;
93                 call.ts = Clock.currStdTime;
94                 call.data = rmiEmptyArrayData;
95 
96                 foreach (p; ParameterIdentifierTuple!self)
97                     call.data ~= mixin(p).serializeToJson;
98 
99                 auto result = com.process(call);
100 
101                 enforce(result.status == 0, new RMIProcessException(result));
102 
103                 static if (!is(typeof(return) == void))
104                     return result.data.deserializeJson!(typeof(return));
105             };
106 
107         import std.datetime : Clock;
108         import std.exception : enforce;
109         import std.meta : staticMap;
110         import std.traits : ReturnType, AliasSeq, Parameters, ParameterIdentifierTuple,
111                             functionAttributes, FunctionAttribute;
112 
113         private mixin template impl(F...)
114         {
115             private static string trueParameters(alias FNC)()
116             {
117                 import std.conv : text;
118                 import std.string : join;
119                 string[] ret;
120                 foreach (i, param; Parameters!FNC)
121                     ret ~= `Parameters!(F[0])[`~text(i)~`] __param_` ~ text(i);
122                 return ret.join(", ");
123             }
124 
125             private static string getAttributesString(alias FNC)()
126             {
127                 import std.string : join;
128                 string[] ret;
129                 // TODO
130                 ret ~= functionAttributes!FNC & FunctionAttribute.property ? "@property" : "";
131                 return ret.join(" ");
132             }
133 
134             static if (F.length == 1)
135             {
136                 mixin("override ReturnType!(F[0]) " ~ __traits(identifier, F[0]) ~
137                       `(` ~ trueParameters!(F[0]) ~ `) ` ~ getAttributesString!(F[0]) ~
138                       ` { ` ~ packCode ~ `}`);
139             }
140             else
141             {
142                 mixin impl!(F[0..$/2]);
143                 mixin impl!(F[$/2..$]);
144             }
145         }
146 
147         private template getOverloads(string s)
148         { alias getOverloads = AliasSeq!(__traits(getOverloads, T, s)); }
149 
150         mixin impl!(staticMap!(getOverloads, __traits(derivedMembers, T)));
151     }
152 
153     mixin overrideIface;
154 }
155 
156 private version (unittest)
157 {
158     struct Point { double x, y, z; }
159 
160     interface Test
161     {
162         int foo(string abc, int xyz);
163         string foo(string str);
164         string bar(double val);
165         double len(Point pnt);
166         string state() @property;
167         void state(string s) @property;
168     }
169 
170     class Impl : Test
171     {
172         string _state;
173     override:
174         string foo(string str) { return "<" ~ str ~ ">"; }
175         int foo(string abc, int xyz) { return cast(int)(abc.length * xyz); }
176         string bar(double val) { return val > 3.14 ? "big" : "small"; }
177         double len(Point pnt)
178         {
179             import std.math;
180             return sqrt(pnt.x^^2 + pnt.y^^2 + pnt.z^^2);
181         }
182         string state() @property { return _state; }
183         void state(string s) @property { _state = s; }
184     }
185 }
186 
187 unittest
188 {
189     auto rea = new Impl;
190     auto ske = new RMISkeleton!Test(rea);
191     auto cli = new RMIStub!Test(new class RMIStubCom
192     {
193         string caller() const @property { return "fake caller"; }
194         RMIResponse process(RMICall call) { return ske.process(call); }
195     });
196 
197     assert(rea.foo("hello", 123) == cli.foo("hello", 123));
198     assert(rea.bar(2.71) == cli.bar(2.71));
199     assert(rea.bar(3.1415) == cli.bar(3.1415));
200     assert(rea.foo("okda") == cli.foo("okda"));
201     assert(rea.len(Point(1,2,3)) == cli.len(Point(1,2,3)));
202 
203     static str = "foo";
204     cli.state = str;
205     assert(rea.state == str);
206     assert(cli.state == str);
207 }