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 }