1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.Nullable;
13 
14 import hunt.Functions;
15 import hunt.Exceptions;
16 import hunt.Object;
17 
18 import std.traits;
19 
20 interface INullable : IObject {
21     TypeInfo valueType();
22 
23     // const(ubyte)[] getBytes();
24 }
25 
26 /**
27 */
28 class Nullable(T) { // : INullable if(!is(T == class) && !is(T == interface))
29     
30     protected T _value;
31     private TypeInfo _valueType;
32     private bool _isNull = true;
33 
34     alias value this;
35 
36     this() {
37         _value = T.init;
38         _valueType = typeid(T);
39     }
40 
41     this(T v) {
42         _value = v;
43         _isNull = false;
44         _valueType = typeid(T);
45     }
46 
47 
48     /**
49      * Returns an {@code Nullable} describing the given non-{@code null}
50      * value.
51      *
52      * @param value the value to describe, which must be non-{@code null}
53      * @param U the type of the value
54      * @return an {@code Nullable} with the value present
55      * @throws NullPointerException if value is {@code null}
56      */
57     static Nullable!U of(U)(U value) {
58         return new Nullable!U(value);
59     }
60 
61     /**
62      * Returns an {@code Nullable} describing the given value, if
63      * non-{@code null}, otherwise returns an empty {@code Nullable}.
64      *
65      * @param value the possibly-{@code null} value to describe
66      * @param !U the type of the value
67      * @return an {@code Nullable} with a present value if the specified value
68      *         is non-{@code null}, otherwise an empty {@code Nullable}
69      */
70     static Nullable!U ofNullable(U)(U value) 
71         if(!isBasicType!U && !is(T == struct) && !is(T == enum)) {
72         return value is null ? empty!U() : of(value);
73     }
74 
75 
76     /**
77      * Returns an empty {@code Nullable} instance.  No value is present for this
78      * {@code Nullable}.
79      *
80      * @apiNote
81      * Though it may be tempting to do so, avoid testing if an object is empty
82      * by comparing with {@code ==} against instances returned by
83      * {@code Nullable.empty()}.  There is no guarantee that it is a singleton.
84      * Instead, use {@link #isPresent()}.
85      *
86      * @param U The type of the non-existent value
87      * @return an empty {@code Nullable}
88      */
89     static Nullable!U empty(U)() {
90         return new Nullable!U();
91     }
92 
93     TypeInfo valueType() {
94         return _valueType;
95     }
96 
97     T value() @trusted nothrow {
98         return _value;
99     }
100 
101     alias payload = value;
102 
103     /**
104      * If a value is present, returns the value, otherwise throws
105      * {@code NoSuchElementException}.
106      *
107      * @apiNote
108      * The preferred alternative to this method is {@link #orElseThrow()}.
109      *
110      * @return the non-{@code null} value described by this {@code Nullable}
111      * @throws NoSuchElementException if no value is present
112      */
113     T get() {
114         if (_isNull) {
115             throw new NoSuchElementException("No value present");
116         }
117         return value;
118     }
119 
120     bool isNull() {
121         return _isNull;
122     }
123 
124     /**
125      * If a value is present, returns {@code true}, otherwise {@code false}.
126      *
127      * @return {@code true} if a value is present, otherwise {@code false}
128      */
129     bool isPresent() {
130         return !_isNull;
131     }
132 
133     /**
134      * If a value is present, performs the given action with the value,
135      * otherwise does nothing.
136      *
137      * @param action the action to be performed, if a value is present
138      * @throws NullPointerException if value is present and the given action is
139      *         {@code null}
140      */
141     void ifPresent(Consumer!T action) {
142         if (!_isNull) {
143             action(value);
144         }
145     }
146 
147     // void opAssign(T v) {
148     //     _value = v;
149     //     _isNull = false;
150     // }
151 
152     // U opCast(U)() {
153     //     return cast(U)_value;
154     // }
155 
156     bool opEquals(IObject o) {
157         return opEquals(cast(Object)o);
158     }
159 
160     bool opEquals(T v) {
161         return this._value == v;
162     }
163 
164     override bool opEquals(const(Object) o) {
165         Nullable!(T) that = cast(Nullable!(T))o;
166         if(that is null)
167             return false;
168 
169         if(_isNull) return that._isNull;
170         if(that._isNull) return false;
171 
172         static if(is(T == class)) {
173             if(this._value is that._value)
174                 return true;
175         }
176 
177         return this._value == that._value;
178     }
179 
180     override string toString() {
181         import std.conv;
182         // static if(is(T == class)) {
183         //     return this._value.toString();
184         // } else static if(is(T == struct)) {
185         //     return this._value.toString();
186         // } else {
187         //     return super.toString();
188         // }
189         static if(is(T == string)) {
190             return this._value;
191         } else {
192             return to!string(_value);
193         }
194     }
195 
196     override size_t toHash() @trusted nothrow {
197         return hashOf(_value);
198     }
199 }