001/******************************************************************************* 002The MIT License (MIT) 003 004Copyright (c) 2024 KILLCODING.COM 005 006Permission is hereby granted, free of charge, to any person obtaining a copy 007of this software and associated documentation files (the "Software"), to deal 008in the Software without restriction, including without limitation the rights 009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010copies of the Software, and to permit persons to whom the Software is 011furnished to do so, subject to the following conditions: 012 013The above copyright notice and this permission notice shall be included in 014all copies or substantial portions of the Software. 015 016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 022THE SOFTWARE. 023*****************************************************************************/ 024package com.killcoding.tool; 025 026import java.util.Properties; 027import java.util.regex.Pattern; 028import java.util.regex.Matcher; 029import java.util.Set; 030import java.util.Map; 031import java.util.Enumeration; 032import java.util.HashMap; 033import java.util.List; 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.Arrays; 037import com.killcoding.log.Logger; 038import java.io.StringReader; 039import java.io.Reader; 040import java.io.IOException; 041import java.io.InputStream; 042import java.io.InputStreamReader; 043import java.io.File; 044import java.io.FileInputStream; 045import java.util.Collection; 046import java.util.concurrent.ExecutorService; 047import java.util.concurrent.Executors; 048import java.nio.file.Files; 049import java.nio.file.Path; 050import java.nio.file.LinkOption; 051import java.nio.file.Paths; 052import java.net.URI; 053import java.text.SimpleDateFormat; 054import java.util.Date; 055import java.text.ParseException; 056import com.killcoding.file.BaseFile; 057import java.util.concurrent.CopyOnWriteArrayList; 058import com.killcoding.file.DiskFile; 059import com.killcoding.tool.CipherTools; 060import java.io.ByteArrayInputStream; 061 062public class ConfigProperties extends Properties { 063 064 public static final String ENV_VAR_REGEX = "[\\$%#]{1}\\{?([\\.\\w-]+)(:{1}([^\\}]+)){0,1}\\}"; 065 066 public static String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; 067 068 public static String Config_Cipher_AesKey = null; 069 070 public static Long AUTO_LOAD_TIMER = 5000L; 071 private static final List<Runnable> AUTO_LOAD_CP = new CopyOnWriteArrayList<Runnable>(); 072 073 private static ConfigCustomLoad CUSTOMLOAD = null; 074 075 protected static ExecutorService executor = null; 076 077 private String parseContentRaw = null; 078 079 public ConfigProperties(int arg0) { 080 super(arg0); 081 } 082 083 public ConfigProperties(Properties arg0) { 084 super(arg0); 085 } 086 087 public ConfigProperties() { 088 super(); 089 } 090 091 private void addAutoLoad(Runnable cpRun) { 092 AUTO_LOAD_CP.add(cpRun); 093 startAutoLoad(); 094 } 095 096 private static synchronized void startAutoLoad() { 097 if (executor == null) { 098 executor = Executors.newFixedThreadPool(1); 099 executor.execute(new Runnable() { 100 @Override 101 public void run() { 102 while(!Thread.currentThread().isInterrupted()){ 103 Thread.currentThread().setName("ConfigProperties.startAutoLoad"); 104 for (Runnable cpRun : AUTO_LOAD_CP) { 105 cpRun.run(); 106 } 107 try { 108 Thread.sleep(getAutoLoadTimer()); 109 } catch (InterruptedException e) { 110 Logger.systemError(ConfigProperties.class, e.getMessage(), e); 111 break; 112 } 113 } 114 } 115 }); 116 } 117 } 118 119 private static Long getAutoLoadTimer(){ 120 return AUTO_LOAD_TIMER; 121 } 122 123 public static void setConfigCustom(ConfigCustomLoad customLoad){ 124 ConfigProperties.CUSTOMLOAD = customLoad; 125 } 126 127 private static boolean loadConfigCustom(File file,ConfigProperties target){ 128 if(ConfigProperties.CUSTOMLOAD != null){ 129 ConfigProperties.CUSTOMLOAD.load(file,target); 130 return true; 131 } 132 return false; 133 } 134 135 public synchronized void load(File file, Runnable updatedRunnable) throws IOException { 136 this.load(file); 137 final ConfigProperties the = this; 138 final Runnable cpRun = new Runnable() { 139 @Override 140 public void run() { 141 Thread.currentThread().setName(String.format("AutoLoad-%s", file.getName())); 142 try { 143 ConfigProperties target = new ConfigProperties(); 144 if(Files.exists(Paths.get(file.getAbsolutePath()))){ 145 target.load(file); 146 boolean isUseCustom = false; 147 boolean haveCustomLoad = loadConfigCustom(file,the); 148 if(haveCustomLoad){ 149 isUseCustom = ConfigProperties.CUSTOMLOAD.isUseCustom(); 150 boolean isUpdated = ConfigProperties.CUSTOMLOAD.isUpdated(); 151 if(isUpdated){ 152 Logger.systemMark(ConfigProperties.class, "CustomUpdated."); 153 if (updatedRunnable != null) 154 updatedRunnable.run(); 155 } 156 } 157 if(!isUseCustom){ 158 boolean same = ConfigProperties.compareProperties(the, target); 159 if (!same) { 160 the.load(file); 161 Logger.systemMark(ConfigProperties.class, "Updated"); 162 Logger.systemInfo(ConfigProperties.class, the.parseContentRaw); 163 164 if (updatedRunnable != null) 165 updatedRunnable.run(); 166 } 167 } 168 } 169 } catch (Exception e) { 170 Logger.systemError(ConfigProperties.class, e.getMessage(), e); 171 } 172 } 173 }; 174 addAutoLoad(cpRun); 175 } 176 177 public void load(File file) throws IOException { 178 if(CommonTools.isBlank(Config_Cipher_AesKey)){ 179 load(new FileInputStream(file)); 180 }else{ 181 try{ 182 DiskFile df = new DiskFile(file); 183 byte[] encryptBytes = df.readAllBytes(); 184 byte[] decryptBytes = CipherTools.AESDecrypt(Config_Cipher_AesKey,encryptBytes); 185 load(new ByteArrayInputStream(decryptBytes)); 186 }catch(Exception e){ 187 throw new IOException(String.format("The file '%s' decryption failed.",file),e); 188 } 189 } 190 } 191 192 @Override 193 public void load(InputStream inputStream) throws IOException { 194 try { 195 Reader reader = new InputStreamReader(inputStream,BaseFile.CHARSET); 196 load(reader); 197 } finally { 198 if (inputStream != null) 199 inputStream.close(); 200 } 201 } 202 203 @Override 204 public void load(Reader reader) throws IOException { 205 try { 206 int readChar; 207 StringBuffer sbf = new StringBuffer(); 208 while ((readChar = reader.read()) != -1) { 209 sbf.append((char) readChar); 210 } 211 this.parseContentRaw = parseContent(sbf.toString()); 212 super.load(new StringReader(this.parseContentRaw)); 213 } finally { 214 if (reader != null) 215 reader.close(); 216 } 217 } 218 219 public ConfigProperties putAndReturnSlef(String key, Object value) { 220 if (isBlank(value)) { 221 put(key, ""); 222 } else if (value instanceof Map) { 223 Map<String, Object> subMap = conver(key, (Map<String, Object>) value); 224 putAllAndReturnSlef(subMap); 225 } else if (value instanceof List) { 226 List<Object> list = (List<Object>) value; 227 int size = list.size(); 228 for (int i = 0; i < size; i++) { 229 Object item = list.get(i); 230 String newKey = String.format("%s[%s]", key, i); 231 putAndReturnSlef(newKey, item); 232 } 233 } else if (value instanceof Object[]) { 234 List<Object> list = Arrays.asList((Object[]) value); 235 int size = list.size(); 236 for (int i = 0; i < size; i++) { 237 Object item = list.get(i); 238 String newKey = String.format("%s[%s]", key, i); 239 putAndReturnSlef(newKey, item); 240 } 241 } else if (value instanceof String) { 242 put(key, value); 243 } else { 244 put(key, value.toString()); 245 } 246 return this; 247 } 248 249 public ConfigProperties removeAndReturnSlef(String key) { 250 this.remove(key); 251 return this; 252 } 253 254 public ConfigProperties putAllAndReturnSlef(Map<String, Object> map) { 255 Set<String> keys = map.keySet(); 256 for (String key : keys) { 257 Object value = map.get(key); 258 putAndReturnSlef(key, value); 259 } 260 return this; 261 } 262 263 public void removeAllNullValues() { 264 Set<Object> keys = this.keySet(); 265 for (Object key : keys) { 266 Object value = get(key); 267 boolean blank = isBlank(value); 268 if (blank) { 269 this.remove(key); 270 continue; 271 } 272 } 273 } 274 275 private Map<String, Object> conver(String parentKey, Map<String, Object> map) { 276 Map<String, Object> subMap = new HashMap<String, Object>(); 277 Set<String> keys = map.keySet(); 278 for (String key : keys) { 279 String newKey = String.format("%s.%s", parentKey, key); 280 Object value = map.get(key); 281 subMap.put(newKey, value); 282 } 283 return subMap; 284 } 285 286 public Boolean getBoolean(String key) { 287 return getBoolean(key, null); 288 } 289 290 public Boolean getBoolean(String key, Boolean defaultValue) { 291 Object originValue = get(key); 292 293 if(originValue == null) return defaultValue; 294 295 if(originValue instanceof Boolean) return (Boolean)originValue; 296 297 String o = getString(key); 298 if (isBlank(o)) { 299 return defaultValue; 300 } 301 return Boolean.parseBoolean(o.trim()); 302 } 303 304 public List<String> getArray(String key) { 305 List<String> defaultValue = null; 306 List<String> v = getArray(key, defaultValue); 307 if (v == null) { 308 String sv = getString(key); 309 if (sv == null) 310 return new ArrayList<String>(); 311 312 return new ArrayList<String>(Arrays.asList(new String[] { sv })); 313 } else { 314 return v; 315 } 316 } 317 318 public List<String> getArray(String key, String[] defaultValue) { 319 return getArray(key, Arrays.asList(defaultValue)); 320 } 321 322 public List<String> getArray(String key, List<String> defaultValue) { 323 String[] list = null; 324 Map<Integer, String> listMap = new HashMap<Integer, String>(); 325 List<Integer> listIndex = new ArrayList<Integer>(); 326 Set<Object> keys = keySet(); 327 for (Object _key : keys) { 328 String format = String.format("^%s\\[(\\w+)\\]$", key); 329 Pattern pattern = Pattern.compile(format); 330 Matcher matcher = pattern.matcher(_key.toString()); 331 if (matcher.find()) { 332 Integer index = Integer.parseInt(matcher.group(1)); 333 String value = getString(_key.toString()); 334 listMap.put(index, value); 335 listIndex.add(index); 336 } 337 } 338 int listIndexSize = listIndex.size(); 339 if (listIndexSize > 0) { 340 Collections.sort(listIndex); 341 list = new String[listIndex.get(listIndexSize - 1) + 1]; 342 for (int i = 0; i < listIndexSize; i++) { 343 int index = listIndex.get(i); 344 String value = listMap.get(index); 345 list[index] = isBlank(value) ? null : value; 346 } 347 return Arrays.asList(list); 348 } else { 349 return defaultValue; 350 } 351 352 } 353 354 public String getString(String key) { 355 return getString(key, null); 356 } 357 358 public String getString(String key, String defaultValue) { 359 String o = getProperty(key); 360 if (isBlank(o)) { 361 return defaultValue; 362 } 363 return o.trim(); 364 } 365 366 // public String getString(String key, String defaultValue) { 367 // String o = getProperty(key); 368 // if(o == null) 369 // return o; 370 371 // String originValue = o.trim(); 372 // Pattern p = Pattern.compile(ENV_VAR_REGEX); 373 // Matcher m = p.matcher(originValue); 374 // boolean findEnvFormat = m.find(); 375 // if(findEnvFormat){ 376 // String envVar = m.group(1); 377 // String envDefValue = m.group(3); 378 // String envValue = getSysEnv(envVar); 379 // if (!isBlank(envValue)) { 380 // return envValue; 381 // } 382 // if (!isBlank(envDefValue)) { 383 // return envDefValue; 384 // } 385 // return defaultValue; 386 // }else{ 387 // if (isBlank(originValue)) { 388 // return defaultValue; 389 // } 390 // return originValue.trim(); 391 // } 392 // } 393 394 public Short getShort(String key) { 395 return getShort(key, null); 396 } 397 398 public Short getShort(String key, Short defaultValue) { 399 Object originValue = get(key); 400 401 if(originValue == null) return defaultValue; 402 403 if(originValue instanceof Number) return Short.parseShort(originValue + ""); 404 405 String o = getString(key); 406 if (isBlank(o)) { 407 return defaultValue; 408 } 409 return Short.parseShort(o.trim()); 410 } 411 412 public Integer getInteger(String key) { 413 return getInteger(key, null); 414 } 415 416 public Integer getInteger(String key, Integer defaultValue) { 417 Object originValue = get(key); 418 419 if(originValue == null) return defaultValue; 420 421 if(originValue instanceof Number) return Integer.parseInt(originValue + ""); 422 423 String o = getString(key); 424 if (isBlank(o)) { 425 return defaultValue; 426 } 427 return Integer.parseInt(o.trim()); 428 } 429 430 public Long getLong(String key) { 431 return getLong(key, null); 432 } 433 434 public Long getLong(String key, Long defaultValue) { 435 Object originValue = get(key); 436 437 if(originValue == null) return defaultValue; 438 439 if(originValue instanceof Number) return Long.parseLong(originValue + ""); 440 441 String o = getString(key); 442 if (isBlank(o)) { 443 return defaultValue; 444 } 445 return Long.parseLong(o.trim()); 446 } 447 448 public Float getFloat(String key) { 449 return getFloat(key, null); 450 } 451 452 public Float getFloat(String key, Float defaultValue) { 453 Object originValue = get(key); 454 455 if(originValue == null) return defaultValue; 456 457 if(originValue instanceof Number) return Float.parseFloat(originValue + ""); 458 459 String o = getString(key); 460 if (isBlank(o)) { 461 return defaultValue; 462 } 463 return Float.parseFloat(o.trim()); 464 } 465 466 public Double getDouble(String key) { 467 return getDouble(key, null); 468 } 469 470 public Double getDouble(String key, Double defaultValue) { 471 Object originValue = get(key); 472 473 if(originValue == null) return defaultValue; 474 475 if(originValue instanceof Number) return Double.parseDouble(originValue + ""); 476 477 String o = getString(key); 478 if (isBlank(o)) { 479 return defaultValue; 480 } 481 return Double.parseDouble(o.trim()); 482 } 483 484 public Long getSeconds(String key) { 485 return getSeconds(key, null); 486 } 487 488 public Long getSeconds(String key, Long defaultValue) { 489 return parseTimeToSec(getString(key, null), defaultValue); 490 } 491 492 public Long getMilliSeconds(String key) { 493 return getMilliSeconds(key, null); 494 } 495 496 public Long getMilliSeconds(String key, Long defaultValue) { 497 return parseTimeToMs(getString(key, null), defaultValue); 498 } 499 500 public Long getFileSize(String key, Long defaultValue) { 501 return parseFileSize(getString(key, null), defaultValue); 502 } 503 504 public Date getDateTime(String key, String defaultValue) { 505 return parseDateTime(getString(key, null), defaultValue); 506 } 507 508 public Date getDateTime(String key) { 509 return getDateTime(key,null); 510 } 511 512 public ConfigProperties getConfigProperties(String key) { 513 Map<String, Object> map = getMap(key); 514 if (map != null) { 515 ConfigProperties cp = new ConfigProperties(); 516 cp.putAllAndReturnSlef(map); 517 return cp; 518 } 519 return null; 520 } 521 522 public ConfigProperties getConfigProperties(String key, ConfigProperties defaultValue) { 523 Map<String, Object> map = getMap(key); 524 if (map != null) { 525 ConfigProperties cp = new ConfigProperties(); 526 cp.putAllAndReturnSlef(map); 527 return cp; 528 } 529 return defaultValue; 530 } 531 532 public Map<String, Object> getMap(String key) { 533 return getMap(key, null); 534 } 535 536 public Map<String, Object> getMap(String key, Map<String, Object> defaultValue) { 537 Map<String, Object> cp = null; 538 Set<Object> keys = this.keySet(); 539 for (Object keyObject : keys) { 540 String _key = keyObject.toString(); 541 boolean isMap = _key.startsWith(String.format("%s.", key)); 542 if (isMap) { 543 if (cp == null) 544 cp = new HashMap<String, Object>(); 545 546 String mapKey = _key.replaceFirst(String.format("^%s\\.", key), ""); 547 cp.put(mapKey, this.getString(_key, null)); 548 } 549 } 550 return isBlank(cp) ? defaultValue : cp; 551 } 552 553 public String getContentRaw(){ 554 return this.parseContentRaw; 555 } 556 557 public static boolean isBlank(Object o) { 558 return CommonTools.isBlank(o); 559 } 560 561 public static Long parseTimeToSec(Object objectValue, Long defaultValue) { 562 563 if (objectValue == null) 564 return defaultValue; 565 566 String stringValue = null; 567 if (objectValue instanceof String) { 568 stringValue = objectValue.toString().trim(); 569 } else { 570 return Long.parseLong(objectValue.toString().trim()); 571 } 572 573 Long value = null; 574 try { 575 Pattern pattern = Pattern.compile("^([-\\+]{0,1}[0-9]+)(((?i)ms|s|m|h|d|w){1})$"); 576 Matcher matcher = pattern.matcher(stringValue); 577 if (matcher.find()) { 578 Long originValue = Long.parseLong(matcher.group(1)); 579 String unit = matcher.group(2).toLowerCase(); 580 if (unit.equals("ms")) { 581 value = originValue / 1000; 582 } 583 if (unit.equals("s")) { 584 value = originValue; 585 } 586 if (unit.equals("m")) { 587 value = originValue * 60; 588 } 589 if (unit.equals("h")) { 590 value = originValue * 60 * 60; 591 } 592 if (unit.equals("d")) { 593 value = originValue * 60 * 60 * 24; 594 } 595 if (unit.equals("w")) { 596 value = originValue * 60 * 60 * 24 * 7; 597 } 598 } else { 599 Logger.systemMark(ConfigProperties.class, 600 "Missing unit(ms|s|m|h|d|w) '{}' will using default value '{}', (e.g. {}ms,{}s,...).", 601 stringValue, defaultValue, stringValue, stringValue); 602 } 603 return value == null ? defaultValue : value; 604 } catch (Exception e) { 605 Logger.systemError(ConfigProperties.class, e); 606 return null; 607 } 608 } 609 610 public static Long parseTimeToMs(Object objectValue, Long defaultValue) { 611 612 if (objectValue == null) 613 return defaultValue; 614 615 String stringValue = null; 616 if (objectValue instanceof String) { 617 stringValue = objectValue.toString().trim(); 618 } else { 619 return Long.parseLong(objectValue.toString().trim()); 620 } 621 622 Long value = null; 623 try { 624 Pattern pattern = Pattern.compile("^([-\\+]{0,1}[0-9]+)(((?i)ms|s|m|h|d|w){1})$"); 625 Matcher matcher = pattern.matcher(stringValue); 626 if (matcher.find()) { 627 Long originValue = Long.parseLong(matcher.group(1)); 628 String unit = matcher.group(2).toLowerCase(); 629 if (unit.equals("ms")) { 630 value = originValue; 631 } 632 if (unit.equals("s")) { 633 value = originValue * 1000; 634 } 635 if (unit.equals("m")) { 636 value = originValue * 60 * 1000; 637 } 638 if (unit.equals("h")) { 639 value = originValue * 60 * 60 * 1000; 640 } 641 if (unit.equals("d")) { 642 value = originValue * 60 * 60 * 1000 * 24; 643 } 644 if (unit.equals("w")) { 645 value = originValue * 60 * 60 * 1000 * 24 * 7; 646 } 647 } else { 648 Logger.systemMark(ConfigProperties.class, 649 "Missing unit(ms|s|m|h|d|w) '{}' will using default value '{}', (e.g. {}ms,{}s,...).", 650 stringValue, defaultValue, stringValue, stringValue); 651 } 652 return value == null ? defaultValue : value; 653 } catch (Exception e) { 654 Logger.systemError(ConfigProperties.class, e); 655 return null; 656 } 657 } 658 659 public static Date parseDateTime(String objectValue, String defaultValue) { 660 SimpleDateFormat sf = new SimpleDateFormat(DATETIME_PATTERN); 661 try{ 662 if(CommonTools.isBlank(objectValue)){ 663 if(CommonTools.isBlank(defaultValue)) return null; 664 665 if(!CommonTools.isBlank(defaultValue)) return sf.parse(defaultValue); 666 } 667 668 return sf.parse(objectValue); 669 }catch(Exception e){ 670 Logger.systemError(ConfigProperties.class, e); 671 return null; 672 } 673 } 674 675 public static Long parseFileSize(Object objectValue, Long defaultValue) { 676 677 if (objectValue == null) 678 return defaultValue; 679 680 String stringValue = null; 681 if (objectValue instanceof String) { 682 stringValue = objectValue.toString().trim(); 683 } else { 684 return Long.parseLong(objectValue.toString().trim()); 685 } 686 687 Long value = null; 688 try { 689 Pattern pattern = Pattern.compile("^([-\\+]{0,1}[0-9]+)(((?i)b|kb|mb|gb|tb){1})$"); 690 Matcher matcher = pattern.matcher(stringValue); 691 if (matcher.find()) { 692 Long originValue = Long.parseLong(matcher.group(1)); 693 String unit = matcher.group(2).toLowerCase(); 694 if (unit.equals("b")) { 695 value = originValue; 696 } 697 if (unit.equals("kb")) { 698 value = originValue * 1024; 699 } 700 if (unit.equals("mb")) { 701 value = originValue * 1024 * 1024; 702 } 703 if (unit.equals("gb")) { 704 value = originValue * 1024 * 1024 * 1024; 705 } 706 if (unit.equals("tb")) { 707 value = originValue * 1024 * 1024 * 1024 * 1024; 708 } 709 } else { 710 Logger.systemMark(ConfigProperties.class, 711 "Missing unit(b|kb|mb|gb|tb) '{}' will using default value '{}', (e.g. {}mb,{}kb,...).", 712 stringValue, defaultValue, stringValue, stringValue); 713 } 714 return value == null ? defaultValue : value; 715 } catch (Exception e) { 716 Logger.systemError(ConfigProperties.class, e); 717 return null; 718 } 719 } 720 721 public static String getSysEnv(String envKey) { 722 return System.getenv(envKey) == null ? System.getProperty(envKey) : System.getenv(envKey); 723 } 724 725 public static String getEnv(String originValue) { 726 if (originValue == null) 727 return null; 728 729 Pattern p = Pattern.compile(ENV_VAR_REGEX); 730 Matcher m = p.matcher(originValue.trim()); 731 boolean findEnvFormat = m.find(); 732 if (findEnvFormat) { 733 String envVar = m.group(1); 734 String envDefValue = m.group(3); 735 String envValue = getSysEnv(envVar); 736 if (!isBlank(envValue)) { 737 return envValue; 738 } 739 if (!isBlank(envDefValue)) { 740 return envDefValue; 741 } 742 return null; 743 } 744 return originValue; 745 } 746 747 public static String parseContent(String content) { 748 return parseContent(content, ""); 749 } 750 751 public static String parseContent(String content, String replaceNullChar) { 752 Pattern p = Pattern.compile(ENV_VAR_REGEX, Pattern.DOTALL | Pattern.MULTILINE); 753 StringBuffer sb = new StringBuffer(); 754 Matcher matcher = p.matcher(content); 755 while (matcher.find()) { 756 String envValue = ConfigProperties.getEnv(matcher.group()); 757 envValue = (envValue == null ? replaceNullChar : envValue); 758 matcher.appendReplacement(sb, envValue); 759 } 760 matcher.appendTail(sb); 761 return sb.toString(); 762 } 763 764 public static boolean compareProperties(ConfigProperties props1, ConfigProperties props2) { 765 766 if (props1 == null || props2 == null) 767 return false; 768 769 return props1.parseContentRaw.equals(props2.parseContentRaw); 770 } 771 772}