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.util.ObjectUtils;
13 
14 import hunt.Exceptions;
15 import hunt.logging.ConsoleLogger;
16 import hunt.util.Common;
17 
18 import std.algorithm : canFind;
19 import std.array;
20 import std.conv : to;
21 import std.format;
22 import std.meta;
23 import std.string;
24 import std.traits;
25 import std.typecons;
26 
27 
28 /**
29  * <p>
30  * The root class from which all event state objects shall be derived.
31  * <p>
32  * All Events are constructed with a reference to the object, the "source",
33  * that is logically deemed to be the object upon which the Event in question
34  * initially occurred upon.
35  */
36 class EventObject {
37 
38     /**
39      * The object on which the Event initially occurred.
40      */
41     protected Object  source;
42 
43     /**
44      * Constructs a prototypical Event.
45      *
46      * @param    source    The object on which the Event initially occurred.
47      * @exception  IllegalArgumentException  if source is null.
48      */
49     this(Object source) {
50         if (source is null)
51             throw new IllegalArgumentException("null source");
52 
53         this.source = source;
54     }
55 
56     /**
57      * The object on which the Event initially occurred.
58      *
59      * @return   The object on which the Event initially occurred.
60      */
61     Object getSource() {
62         return source;
63     }
64 
65     /**
66      * Returns a string representation of this EventObject.
67      *
68      * @return  A a string representation of this EventObject.
69      */
70     override
71     string toString() {
72         return typeid(this).name ~ "[source=" ~ source.toString() ~ "]";
73     }
74 }
75 
76 
77 /**
78 */
79 class ObjectUtils {
80 
81     private enum int INITIAL_HASH = 7;
82 	private enum int MULTIPLIER = 31;
83 
84 	private enum string EMPTY_STRING = "";
85 	private enum string NULL_STRING = "null";
86 	private enum string ARRAY_START = "{";
87 	private enum string ARRAY_END = "}";
88 	private enum string EMPTY_ARRAY = ARRAY_START ~ ARRAY_END;
89 	private enum string ARRAY_ELEMENT_SEPARATOR = ", ";
90     
91     /**
92 	 * Return a string representation of an object's overall identity.
93 	 * @param obj the object (may be {@code null})
94 	 * @return the object's identity as string representation,
95 	 * or an empty string if the object was {@code null}
96 	 */
97 	static string identityToString(Object obj) {
98 		if (obj is null) {
99 			return EMPTY_STRING;
100 		}
101 		return typeid(obj).name ~ "@" ~ getIdentityHexString(obj);
102 	}
103 
104 
105 
106 	/**
107 	 * Return a hex String form of an object's identity hash code.
108 	 * @param obj the object
109 	 * @return the object's identity code in hex notation
110 	 */
111 	static string getIdentityHexString(Object obj) {
112 		return format("%s", cast(void*)obj);
113 	}
114 
115 
116 	//---------------------------------------------------------------------
117 	// Convenience methods for content-based equality/hash-code handling
118 	//---------------------------------------------------------------------
119 
120 	/**
121 	 * Determine if the given objects are equal, returning {@code true} if
122 	 * both are {@code null} or {@code false} if only one is {@code null}.
123 	 * <p>Compares arrays with {@code Arrays.equals}, performing an equality
124 	 * check based on the array elements rather than the array reference.
125 	 * @param o1 first Object to compare
126 	 * @param o2 second Object to compare
127 	 * @return whether the given objects are equal
128 	 * @see Object#equals(Object)
129 	 * @see java.util.Arrays#equals
130 	 */
131 	static bool nullSafeEquals(Object o1, Object o2) {
132 		if (o1 is o2) {
133 			return true;
134 		}
135 		if (o1 is null || o2 is null) {
136 			return false;
137 		}
138 		if (o1 == o2) {
139 			return true;
140 		}
141 		// if (o1.getClass().isArray() && o2.getClass().isArray()) {
142 		// 	return arrayEquals(o1, o2);
143 		// }
144 		return false;
145 	}
146 }
147 
148 
149 bool isInstanceOf(T, S)(S obj) if(is(S == class) || is(S == interface)) {
150 	T t = cast(T)obj;
151 	return t !is null;
152 }
153 
154 
155 mixin template ValuesMemberTempate(T) if (is(T == struct) || is(T == class)) {
156     import std.concurrency : initOnce;
157     import std.traits;
158     
159 	static T[] values() {
160 		__gshared T[] inst;
161         
162         return initOnce!inst({
163             T[] r;
164             enum s = __getValues!(r.stringof, T)();
165             // pragma(msg, s);
166             mixin(s);
167             return r;
168         }());
169 	}
170 
171 	private static string __getValues(string name, T)() {
172 		string str;
173 
174 		foreach (string memberName; __traits(derivedMembers, typeof(this))) {
175 			alias memberType = typeof(__traits(getMember, typeof(this), memberName));
176 			static if (is(memberType : T)) {
177 				str ~= name ~ " ~= " ~ memberName ~ ";\r\n";
178 			}
179 		}
180 
181 		return str;
182 	}
183 
184 	static T[string] namedValues() {
185 		__gshared T[string] inst;
186         
187         return initOnce!inst({
188             T[string] r;
189             enum s = __getNamedValues!(r.stringof, T)();
190             // pragma(msg, s);
191             mixin(s);
192             return r;
193         }());
194 	}
195 
196 
197 	private static string __getNamedValues(string name, T)() {
198 		string str;
199 
200 		foreach (string memberName; __traits(derivedMembers, typeof(this))) {
201 			alias memberType = typeof(__traits(getMember, typeof(this), memberName));
202 			static if (is(memberType : T)) {
203 				str ~= name ~ "[\"" ~ memberName ~ "\"] = " ~ memberName ~ ";\r\n";
204 			}
205 		}
206 
207 		return str;
208 	}
209 }
210 
211 
212 deprecated("Using ValuesMemberTempate instead.")
213 mixin template GetConstantValues(T) if (is(T == struct) || is(T == class)) {
214 	static T[] values() {
215 		T[] r;
216 		enum s = __getValues!(r.stringof, T)();
217 		// pragma(msg, s);
218 		mixin(s);
219 		return r;
220 	}
221 
222 	private static string __getValues(string name, T)() {
223 		string str;
224 
225 		foreach (string memberName; __traits(derivedMembers, T)) {
226 			// enum member = __traits(getMember, T, memberName);
227 			alias memberType = typeof(__traits(getMember, T, memberName));
228 			static if (is(memberType : T)) {
229 				str ~= name ~ " ~= " ~ memberName ~ ";\r\n";
230 			}
231 		}
232 
233 		return str;
234 	}
235 }
236 
237 // alias Helper(alias T) = T;
238 
239 // template Pointer(T) {
240 // 	static if (is(T == class) || is(T == interface)) {
241 // 		alias Pointer = T;
242 // 	} else {
243 // 		alias Pointer = T*;
244 // 	}
245 // }
246 
247 enum string[] FixedObjectMembers = ["toString", "opCmp", "opEquals", "Monitor", "factory"];
248 
249 
250 alias TopLevel = Flag!"TopLevel";
251 
252 static if (CompilerHelper.isGreaterThan(2086)) {
253 	
254 /**
255 */
256 mixin template CloneMemberTemplate(T, TopLevel topLevel = TopLevel.no, alias onCloned = null) 	{
257 	import std.traits;
258 	version(HUNT_DEBUG) import hunt.logging.ConsoleLogger;
259 	alias baseClasses = BaseClassesTuple!T;
260 
261 	static if(baseClasses.length == 1 && is(baseClasses[0] == Object) 
262 			|| topLevel == TopLevel.yes) {
263 		T clone() {
264 			T copy = cast(T)typeid(this).create();
265 			__copyFieldsTo(copy);
266 			return copy;
267 		}
268 	} else {
269 		override T clone() {
270 			T copy = cast(T)super.clone();
271 			__copyFieldsTo(copy);
272 			return copy;
273 		}
274 	}
275 
276 	private void __copyFieldsTo(T copy) {
277 		if(copy is null) {
278 			version(HUNT_DEBUG) warningf("Can't create an instance for %s", T.stringof);
279 			throw new Exception("Can't create an instance for " ~ T.stringof);
280 		}
281 
282 		// debug(HUNT_DEBUG_MORE) pragma(msg, "========\n clone type: " ~ T.stringof);
283 		static foreach (string fieldName; FieldNameTuple!T) {
284 			debug(HUNT_DEBUG_MORE) {
285 				// pragma(msg, "clone field=" ~ fieldName);
286 				import hunt.logging.ConsoleLogger;
287 				tracef("cloning: name=%s, value=%s", fieldName, __traits(getMember, this, fieldName));
288 			}
289 			__traits(getMember, copy, fieldName) = __traits(getMember, this, fieldName);
290 		}
291 
292 		static if(onCloned !is null) {
293 			onCloned(this, copy);
294 		}
295 	}
296 }
297 
298 
299 /**
300 */
301 string getAllFieldValues(T, string separator1 = "=", string separator2 = ", ")(T obj) 
302 	if (is(T == class) || is(T == struct)) {
303 
304 	Appender!string sb;
305 	bool isFirst = true;
306 	alias baseClasses = BaseClassesTuple!T;
307 
308 	static if(baseClasses.length == 1 && is(baseClasses[0] == Object)) {
309 	} else {
310 		string s = getAllFieldValues!(baseClasses[0], separator1, separator2)(obj);
311 		sb.put(s);
312 		isFirst = false; 
313 	}
314 
315 	static foreach (string fieldName; FieldNameTuple!T) {
316 		if(isFirst) 
317 			isFirst = false; 
318 		else 
319 			sb.put(separator2);
320 		sb.put(fieldName);
321 		sb.put(separator1);
322 		sb.put(to!string(__traits(getMember, obj, fieldName)));
323 	}
324 
325 	return sb.data;
326 }
327 
328 /**
329 */
330 U mappingToObject(U, T)(T t) if(is(U == struct)) {
331 	U u;
332 
333 	mappingObject(t, u);
334 
335 	return u;
336 }
337 
338 /**
339 */
340 U mappingToObject(U, T)(T t) 
341 	if(is(T == struct) && is(U == class) && is(typeof(new U()))) {
342 
343 	U u = new U();
344 	mappingObject(t, u);
345 	return u;
346 }
347 
348 /**
349 */
350 U mappingToObject(U, T)(T t) 
351 	if(is(T == class) && is(U == class) && is(typeof(new U()))) {
352 
353 	U u = new U();
354 	mappingObject(t, u);
355 	return u;
356 }
357 
358 /**
359 */
360 void mappingObject(T, U)(T src, ref U dst) if(is(U == struct)) {
361 
362 	// super fields
363 	static if(is(T == class)) {
364 	alias baseClasses = BaseClassesTuple!T;
365 	static if(baseClasses.length >= 1) {
366 		alias BaseType = baseClasses[0];
367 		static if(!is(BaseType == Object)) {
368 			mappingObject!(BaseType, U)(src, dst);
369 		}
370 	}
371 	}
372 
373 	foreach (targetMemberName; FieldNameTuple!U) {		
374 		foreach (sourceMemberName; FieldNameTuple!T) {
375 			static if(targetMemberName == sourceMemberName) {
376 				debug(HUNT_DEBUG_MORE) {
377 					tracef("mapping: name=%s, value=%s", targetMemberName, __traits(getMember, src, sourceMemberName));
378 				}
379 				__traits(getMember, dst, targetMemberName) = __traits(getMember, src, sourceMemberName);
380 			}
381 		}
382 	}
383 
384 }
385 
386 /**
387 */
388 void mappingObject(T, U)(T src, U dst) 
389 		if((is(T == class) || is(T == struct)) && is(U == class)) {
390 	foreach (targetMemberName; FieldNameTuple!U) {		
391 		foreach (sourceMemberName; FieldNameTuple!T) {
392 			static if(targetMemberName == sourceMemberName) {
393 				debug(HUNT_DEBUG_MORE) {
394 					tracef("mapping: name=%s, value=%s", targetMemberName, __traits(getMember, src, sourceMemberName));
395 				}
396 				__traits(getMember, dst, targetMemberName) = __traits(getMember, src, sourceMemberName);
397 			}
398 		}
399 	}
400 
401 
402 	// super fields
403 	alias baseClasses = BaseClassesTuple!U;
404 	static if(baseClasses.length >= 1) {
405 		alias BaseType = baseClasses[0];
406 		static if(!is(BaseType == Object)) {
407 			static if(is(T : BaseType)) {
408 				mappingObject!(BaseType, BaseType)(src, dst);
409 			} else {
410 				mappingObject!(T, BaseType)(src, dst);
411 			}
412 		}
413 	}
414 }
415 
416 }
417 
418 /**
419 * Params
420 *	name = xXX will map to T's memeber function void setXXX()
421 */
422 bool setProperty(T, Args...)(ref T p, string name, Args value) {
423 	enum string MethodPrefix = "set";
424 	// pragma(msg, "Args: " ~ Args.stringof);
425 
426 	if (name.empty) {
427 		throw new Exception("The name can't be empty");
428 	}
429 
430 	enum PrefixLength = MethodPrefix.length;
431 	string currentMember = MethodPrefix ~ toUpper(name[0 .. 1]) ~ name[1 .. $];
432 	bool isSuccess = false;
433 
434 	foreach (memberName; __traits(allMembers, T)) {
435 		// pragma(msg, "Member: " ~ memberName);
436 
437 		static if (is(T == class) && FixedObjectMembers.canFind(memberName)) {
438 			// pragma(msg, "skipping fixed Object member: " ~ memberName);
439 		} else static if (memberName.length > PrefixLength
440 				&& memberName[0 .. PrefixLength] == MethodPrefix) {
441 			// tracef("checking: %s", memberName);
442 
443 			if (!isSuccess && currentMember == memberName) {
444 				static if (is(typeof(__traits(getMember, T, memberName)) == function)) {
445 					// pragma(msg, "Function: " ~ memberName);
446 
447 					foreach (PT; __traits(getOverloads, T, memberName)) {
448 						// pragma(msg, "overload function: " ~ memberName);
449 
450 						enum memberParams = ParameterIdentifierTuple!PT;
451 						static if (Args.length == memberParams.length) {
452 							alias memberParamTypes = Parameters!PT;
453 							isSuccess = true;
454 
455 							// tracef("try to execute method %s, with value: %s", memberName, value.stringof);
456 							// foreach (i, s; value) {
457 							// 	tracef("value[%d] type: %s, actual value: %s", i, typeid(s), s);
458 							// }
459 
460 							static if (__traits(isSame, memberParamTypes, Args)) {
461 								__traits(getMember, p, memberName)(value);
462 							} else {
463 								enum string str = generateSetter!(PT,
464 											p.stringof ~ "." ~ memberName, value.stringof, Args)();
465 								// pragma(msg, "== code == " ~ str);
466 
467 								static if (str.length > 0) {
468 									mixin(str);
469 								}
470 							}
471 						}
472 					}
473 
474 					if (!isSuccess) {
475 						warningf("Mismatch member %s in %s for parameter size %d",
476 								currentMember, typeid(T), Args.length);
477 					} else {
478 						return true;
479 					}
480 				}
481 			}
482 		} else {
483 			// pragma(msg, "skipping: " ~ memberName);
484 		}
485 	}
486 
487 	if (!isSuccess) {
488 		warningf("Failed to set member %s in %s", currentMember, typeid(T));
489 		// assert(false, T.stringof ~ " has no member " ~ currentMember);
490 	}
491 	return isSuccess;
492 }
493 
494 /**
495 */
496 private string generateSetter(alias T, string callerName, string parameterName, argumentTypes...)() {
497 	string str;
498 	import std.conv;
499 
500 	enum memberParams = ParameterIdentifierTuple!T;
501 	str ~= callerName ~ "(";
502 	alias memberParamTypes = Parameters!T;
503 
504 	bool isFirst = true;
505 
506 	static foreach (int i; 0 .. memberParams.length) {
507 		if (isFirst)
508 			isFirst = false;
509 		else
510 			str ~= ", ";
511 
512 		static if (is(memberParamTypes[i] == argumentTypes[i])) {
513 			str ~= parameterName ~ "[" ~ to!string(i) ~ "]";
514 		} else {
515 			str ~= "to!(" ~ memberParamTypes[i].stringof ~ ")(" ~ parameterName ~ "[" ~ to!string(
516 					i) ~ "])";
517 		}
518 
519 	}
520 	str ~= ");";
521 	return str;
522 }
523 
524 unittest {
525 
526 	struct Foo {
527 		string name = "dog";
528 		int bar = 42;
529 		int baz = 31337;
530 
531 		void setBar(int value) {
532 			bar = value;
533 		}
534 
535 		void setBar(string name, int value) {
536 			this.name = name;
537 			this.bar = value;
538 		}
539 
540 		int getBar() {
541 			return bar;
542 		}
543 	}
544 
545 	Foo foo;
546 
547 	setProperty(foo, "bar", 12);
548 	assert(foo.bar == 12);
549 	setProperty(foo, "bar", "112");
550 	assert(foo.bar == 112);
551 
552 	foo.setProperty("bar", "age", 16);
553 	assert(foo.name == "age");
554 	assert(foo.bar == 16);
555 	foo.setProperty("bar", "age", "36");
556 	assert(foo.name == "age");
557 	assert(foo.bar == 36);
558 }