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