1 /**
2  * Look up table system for storing and retrieving references
3  * from an unique identifier.
4  */
5 module iz.referencable;
6 
7 import iz.containers;
8 
9 /**
10  * interface for a class reference.
11  */
12 interface Referenced
13 {
14     /// the ID, as set when added as reference.
15     string refID();
16     /// the type, as registered in the ReferenceMan ( typeString!typeof(this) )
17     string refType();
18 }
19 
20 private template isReferenceType(T)
21 {
22     enum isReferenceType = is(T == class) || is(T == interface);
23 }
24 
25 /**
26  * Associates an pointer (a reference) to an unique ID (ulong).
27  */
28 version(none) private alias ItemsById = void*[char[]];
29 else alias ItemsById = HashMap_AB!(const(char)[], void*);
30 
31 /**
32  * itemsById for a type (identified by a string).
33  */
34 version(none) private alias ItemsByIdByType = ItemsById[string];
35 else private alias ItemsByIdByType = HashMap_AB!(const(char)[], ItemsById);
36 
37 /**
38  * The Referencable manager associates variables of a particular type to
39  * an unique identifier.
40  *
41  * This manager is mostly used by iz.classes and iz.serializer.
42  * For example, in a setting file, it allows to store the unique identifier
43  * associated to a class instance, rather than storing all its properties, as
44  * the instance settings may be saved elsewhere.
45  * It also allow to serialize fat pointers, such as delegates.
46  */
47 static struct ReferenceMan
48 {
49 
50 private:
51 
52     __gshared ItemsByIdByType _store;
53 
54     shared static this()
55     {
56         _store.reserve(128);
57     }
58 
59 public:
60 
61 // Helpers --------------------------------------------------------------------+
62 
63     /**
64      * Indicates if a type is referenced.
65      *
66      * Params:
67      *      RT = The type to test.
68      *
69      * Returns:
70      *      True if the type is referenced otherwise false.
71      */
72     static bool isTypeStored(RT)()
73     {
74         return ((RT.stringof in _store) !is null);
75     }
76 
77     /**
78      * Indicates if a variable is referenced.
79      *
80      * Params:
81      *      RT = a referencable type. Optional, likely to be infered.
82      *      aReference = a pointer to a RT.
83      *
84      * Returns:
85      *      True if the variable is referenced otherwise false.
86      */
87     static bool isReferenced(RT)(RT* aReference)
88     if (!isReferenceType!RT)
89     {
90         return (referenceID!RT(aReference) != "");
91     }
92 
93     static bool isReferenced(RT)(RT aReference)
94     if (isReferenceType!RT)
95     {
96         return (referenceID!RT(aReference) != "");
97     }
98 
99     /**
100      * Support for the in operator.
101      * Evaluates to true if the variable is referenced otherwise false.
102      */
103     static bool opBinaryRight(string op : "in", RT)(RT* aReference)
104     if (!isReferenceType!RT)
105     {
106         return (referenceID!RT(aReference) != "");
107     }
108 
109     static bool opBinaryRight(string op : "in", RT)(RT aReference)
110     if (isReferenceType!RT)
111     {
112         return (referenceID!RT(aReference) != "");
113     }
114 
115     /**
116      * Empties the references and the types.
117      */
118     static void reset()
119     {
120         _store.clear;
121     }
122 // -----------------------------------------------------------------------------
123 // Add stuff ------------------------------------------------------------------+
124 
125     /**
126      * Stores a type. This is a convenience function since
127      * storeReference() automatically stores a type when needed.
128      *
129      * Params:
130      *      RT = A type to reference.
131      */
132     static void storeType(RT)()
133     {
134         _store[RT.stringof, ""] = null;
135     }
136 
137     /**
138      * Proposes an unique ID for a reference. This is a convenience function
139      * that will not return the same values for each software session.
140      *
141      * Params:
142      *      RT = A referencable type. Optional, likely to be infered.
143      *      aReference = A pointer to a RT.
144      *
145      * Returns:
146      *      The unique string used to identify the reference.
147      */
148     static const(char)[] getIDProposal(RT)(RT* aReference)
149     {
150         // already stored ? returns current ID
151         const string ID = referenceID(aReference);
152         if (ID != "") return ID;
153 
154         // not stored ? returns 1
155         if (!isTypeStored)
156         {
157             storeType!RT;
158             return "entry_1";
159         }
160 
161         // try to get an available ID in the existing range
162         foreach(immutable i; 0 .. _store[RT.stringof].length)
163         {
164             import std..string: format;
165             if (_store[RT.stringof, i] == null)
166                 return format("entry_%d", i);
167         }
168 
169         // otherwise returns the next ID after the current range.
170         foreach(immutable i; 0 .. ulong.max)
171         {
172             import std..string: format;
173             if (i > _store[RT.stringof].length)
174                 return format("entry_%d", i);
175         }
176 
177         assert(0, "ReferenceMan is full for this type");
178     }
179 
180     /**
181      * Tries to store a reference.
182      *
183      * Params:
184      *      RT = the type of the reference.
185      *      aReference = a pointer to a RT. Optional, likely to be infered.
186      *      anID = the unique identifier for this reference.
187      *
188      * Returns:
189      *      true if the reference is added otherwise false.
190      */
191     static bool storeReference(RT)(RT* aReference, const(char)[] anID)
192     if (!isReferenceType!RT)
193     {
194         if (anID == "") return false;
195         // what's already there ?
196         const RT* curr = reference!RT(anID);
197         if (curr == aReference) return true;
198         if (curr != null) return false;
199         //
200         _store[RT.stringof, anID] = aReference;
201         return true;
202     }
203 
204     /// ditto
205     static bool storeReference(RT)(RT aReference, const(char)[] anID)
206     if (isReferenceType!RT)
207     {
208         if (anID == "") return false;
209         // what's already there ?
210         auto curr = reference!RT(anID);
211         if (curr == aReference) return true;
212         if (curr !is null) return false;
213         //
214         _store[RT.stringof, anID] = cast(RT*)aReference;
215         return true;
216     }
217 
218 // -----------------------------------------------------------------------------
219 // Remove stuff ---------------------------------------------------------------+
220 
221 
222     /**
223      * Removes all the references for a type.
224      *
225      * Params:
226      *      RT = The type of the references to remove.
227      */
228     static void removeReferences(RT)()
229     {
230         if (auto t = RT.stringof in _store)
231             t.clear;
232     }
233 
234     /**
235      * Empties the storage.
236      */
237     static void clear()
238     {
239         foreach(k; _store.byKey)
240             _store[k].clear;
241         _store.clear;
242     }
243 
244     /**
245      * Tries to remove the reference matching to an ID.
246      *
247      * Params:
248      *      RT = The type of the reference to remove.
249      *      anID = The string that identifies the reference to remove.
250      *
251      * Returns:
252      *      The reference if it's found otherwise null.
253      */
254     static auto removeReference(RT)(const(char)[] anID)
255     {
256         auto result = reference!RT(anID);
257         if (result) _store[RT.stringof, anID] = null;
258         return result;
259     }
260 
261     /**
262      * Removes a reference.
263      *
264      * Params:
265      *      RT = The type of the reference to remove. Optional, likely to be infered.
266      *      aReference = The pointer to the RT to be removed.
267      */
268     static void removeReference(RT)(RT* aReference)
269     if (!isReferenceType!RT)
270     {
271         if (auto id = referenceID!RT(aReference))
272             _store[RT.stringof, id] = null;
273     }
274 
275     /// ditto
276     static void removeReference(RT)(RT aReference)
277     if (isReferenceType!RT)
278     {
279         if (auto id = referenceID!RT(aReference))
280             _store[RT.stringof, id] = null;
281     }
282 
283 // -----------------------------------------------------------------------------
284 // Query stuff ----------------------------------------------------------------+
285 
286     /**
287      * Indicates the reference ID of a variable.
288      *
289      * Params:
290      *      RT = The type of the reference. Optional, likely to be infered.
291      *      aReference = A pointer to a RT or a RT.
292      *
293      * Returns:
294      *      A non empty string if the variable is referenced.
295      */
296     static const(char)[] referenceID(RT)(RT* aReference)
297     if (!isReferenceType!RT)
298     {
299         if (!isTypeStored!RT) return "";
300         foreach (k; _store[RT.stringof].byKey)
301         {
302             static if (!is(RT == delegate))
303             {
304                 if (_store[RT.stringof][k] == aReference)
305                     return k;
306             }
307             else
308             {
309                 struct Dg {void* a,b;}
310                 auto stored = *cast(Dg*) _store[RT.stringof][k];
311                 auto passed = *cast(Dg*) aReference;
312                 if (stored.a == passed.a && stored.b == passed.b)
313                     return k;
314             }
315         }
316         return "";
317     }
318 
319     /// ditto
320     static const(char)[] referenceID(RT)(RT aReference)
321     if (isReferenceType!RT)
322     {
323         if (!isTypeStored!RT) return "";
324         foreach (k; _store[RT.stringof].byKey)
325         {
326             if (_store[RT.stringof][k] == cast(void*)aReference)
327                 return k;
328         }
329         return "";
330     }
331 
332     /**
333      * Indicates the reference ID of a variable, without using its static type.
334      *
335      * Params:
336      *      dg = indicates if the reference to find points to a delegate.
337      *      type = The $(D .stringof) of the type of the reference.
338      *      aReference = A pointer to a variable
339      *
340      * Returns:
341      *      A non empty string if the variable is referenced.
342      */
343     static const(char)[] referenceID(bool dg = false)(const(char)[] type, void* aReference)
344     {
345         if (type == "") return "";
346         if (type !in _store) return "";
347         foreach (k; _store[type].byKey)
348         {
349             static if (!dg)
350             {
351                 if (_store[type][k] == aReference)
352                     return k;
353             }
354             else
355             {
356                 struct Dg {void* a,b;}
357                 auto stored = *cast(Dg*) _store[type][k];
358                 auto passed = *cast(Dg*) aReference;
359                 if (stored.a == passed.a && stored.b == passed.b)
360                     return k;
361             }
362         }
363         return "";
364     }
365 
366     /**
367      * Retrieves a reference.
368      *
369      * Params:
370      *      RT = The type of the reference to retrieve.
371      *      anID = The unique identifier of the reference to retrieve.
372      *
373      * Returns:
374      *      Null if the operation fails otherwise a pointer to a RT, or
375      *      a RT if RT is a reference type.
376      */
377     static RT* reference(RT)(const(char)[] anID)
378     if (!isReferenceType!RT)
379     {
380         if (anID == "") return null;
381         if (!isTypeStored!RT) return null;
382         if (void** result = anID in _store[RT.stringof])
383             return *cast(RT**) result;
384         else
385             return null;
386     }
387 
388     static RT reference(RT)(const(char)[] anID)
389     if (isReferenceType!RT)
390     {
391         if (anID == "") return null;
392         if (!isTypeStored!RT) return null;
393         if (void* result = anID in _store[RT.stringof])
394             return *cast(RT*) result;
395         else
396             return null;
397     }
398 
399     /**
400      * Retrieves a reference without the static type
401      *
402      * Params:
403      *      type = A string that represents the type of the reference.
404      *      anID = The unique identifier of the reference to retrieve.
405      *
406      * Returns:
407      *      Null if the operation fails otherwise a raw pointer.
408      */
409     static void* reference(const(char)[] type, const(char)[] anID)
410     {
411         import std.stdio;
412         if (anID == "") return null;
413         if (type !in _store) return null;
414         if (void** result = anID in _store[type])
415             return *result;
416         else
417             return null;
418     }
419 // -----------------------------------------------------------------------------        
420 
421 }
422 
423 unittest
424 {
425     import iz.memory: construct, destructEach;
426     
427     alias delegate1 = ubyte delegate(long param);
428     alias delegate2 = short delegate(uint param);
429     class Foo{int aMember;}
430 
431     assert( !ReferenceMan.isTypeStored!delegate1 );
432     assert( !ReferenceMan.isTypeStored!delegate2 );
433     assert( !ReferenceMan.isTypeStored!Foo );
434 
435     ReferenceMan.storeType!delegate1;
436     ReferenceMan.storeType!delegate2;
437     ReferenceMan.storeType!Foo;
438 
439     assert( ReferenceMan.isTypeStored!delegate1 );
440     assert( ReferenceMan.isTypeStored!delegate2 );
441     assert( ReferenceMan.isTypeStored!Foo );
442 
443     auto f1 = construct!Foo;
444     auto f2 = construct!Foo;
445     auto f3 = construct!Foo;
446     scope(exit) destructEach(f1,f2,f3);
447 
448     assert( !ReferenceMan.isReferenced(f1) );
449     assert( !ReferenceMan.isReferenced(f2) );
450     assert( !ReferenceMan.isReferenced(f3) );
451 
452     assert( ReferenceMan.referenceID(f1) == "");
453     assert( ReferenceMan.referenceID(f2) == "");
454     assert( ReferenceMan.referenceID(f3) == "");
455 
456     ReferenceMan.storeReference( f1, "a.f1" );
457     ReferenceMan.storeReference( f2, "a.f2" );
458     ReferenceMan.storeReference( f3, "a.f3" );
459 
460     assert( ReferenceMan.reference!Foo("a.f1") == f1);
461     assert( ReferenceMan.reference!Foo("a.f2") == f2);
462     assert( ReferenceMan.reference!Foo("a.f3") == f3);
463 
464     assert( ReferenceMan.referenceID(f1) == "a.f1");
465     assert( ReferenceMan.referenceID(f2) == "a.f2");
466     assert( ReferenceMan.referenceID(f3) == "a.f3");
467 
468     assert( ReferenceMan.isReferenced(f1) );
469     assert( ReferenceMan.isReferenced(f2) );
470     assert( ReferenceMan.isReferenced(f3) );
471     assert( f3 in ReferenceMan );
472 
473     auto f11 = f1;
474     assert( ReferenceMan.referenceID!Foo(f11) == "a.f1");
475     assert( ReferenceMan.reference("Foo","a.f1") != null);
476 
477     ReferenceMan.removeReference(f1);
478     ReferenceMan.removeReference(f2);
479     ReferenceMan.removeReference!Foo("a.f3");
480 
481     assert( !ReferenceMan.isReferenced(f1) );
482     assert( !ReferenceMan.isReferenced(f2) );
483     assert( !ReferenceMan.isReferenced(f3) );
484 
485     ReferenceMan.removeReference!Foo("a.f1");
486     ReferenceMan.removeReference(f2);
487     ReferenceMan.removeReference!Foo("a.f3");
488 
489     ReferenceMan.reset;
490     assert( !ReferenceMan.isTypeStored!Foo );
491 
492     ReferenceMan.storeReference( f1, "a.f1" );
493     assert( ReferenceMan.isTypeStored!Foo );
494 
495     ReferenceMan.clear;
496     assert( !ReferenceMan.isTypeStored!Foo );
497 }
498 
499 unittest
500 {
501     struct Foo
502     {
503         this(bool){adg = &a;}
504         void a(){}
505         void delegate() adg;
506     }
507     Foo foo = Foo(false);
508     Foo bar = Foo(false);
509     auto dg1 = foo.adg;
510     ReferenceMan.storeReference(&foo.adg, "foo.adg");
511     assert(ReferenceMan.isReferenced(&foo.adg));
512     assert(ReferenceMan.referenceID(&foo.adg) == "foo.adg");
513     assert(ReferenceMan.reference!(typeof(dg1))("foo.adg") );
514     assert(*ReferenceMan.reference!(typeof(dg1))("foo.adg") == foo.adg);
515     assert(!ReferenceMan.isReferenced(&bar.adg));
516     assert(ReferenceMan.referenceID(&bar.adg) == "");
517     assert( ReferenceMan.referenceID(&dg1) == "foo.adg");
518 }
519