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 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.ConsoleLogger; 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.ConsoleLogger; 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 }