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; 057 058public class ConfigProperties extends Properties { 059 060 public static final String ENV_VAR_REGEX = "[\\$%#]{1}\\{?([\\.\\w-]+)(:{1}([^\\}]+)){0,1}\\}"; 061 062 public static String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; 063 064 public static Long AUTO_LOAD_TIMER = 5000L; 065 private static final List<Runnable> AUTO_LOAD_CP = new ArrayList<Runnable>(); 066 067 protected static ExecutorService executor = null; 068 069 private String parseContentRaw = null; 070 071 public ConfigProperties(int arg0) { 072 super(arg0); 073 } 074 075 public ConfigProperties(Properties arg0) { 076 super(arg0); 077 } 078 079 public ConfigProperties() { 080 super(); 081 } 082 083 private void addAutoLoad(Runnable cpRun) { 084 AUTO_LOAD_CP.add(cpRun); 085 startAutoLoad(); 086 } 087 088 private static synchronized void startAutoLoad() { 089 if (executor == null) { 090 executor = Executors.newFixedThreadPool(1); 091 executor.execute(new Runnable() { 092 @Override 093 public void run() { 094 while(!Thread.currentThread().isInterrupted()){ 095 for (Runnable cpRun : AUTO_LOAD_CP) { 096 cpRun.run(); 097 } 098 try { 099 Thread.sleep(getAutoLoadTimer()); 100 } catch (InterruptedException e) { 101 Logger.systemError(ConfigProperties.class, e.getMessage(), e); 102 break; 103 } 104 } 105 } 106 }); 107 } 108 } 109 110 private static Long getAutoLoadTimer(){ 111 return AUTO_LOAD_TIMER; 112 } 113 114 public synchronized void load(File file, Runnable updatedRunnable) throws IOException { 115 this.load(new FileInputStream(file)); 116 final ConfigProperties the = this; 117 final Runnable cpRun = new Runnable() { 118 @Override 119 public void run() { 120 Thread.currentThread().setName(String.format("AutoLoad-%s", file.getName())); 121 122 try { 123 ConfigProperties target = new ConfigProperties(); 124 if(Files.exists(Paths.get(file.getAbsolutePath()))){ 125 target.load(new FileInputStream(file)); 126 boolean same = ConfigProperties.compareProperties(the, target); 127 if (!same) { 128 the.load(new FileInputStream(file)); 129 Logger.systemMark(ConfigProperties.class, "Updated"); 130 Logger.systemInfo(ConfigProperties.class, the.parseContentRaw); 131 if (updatedRunnable != null) 132 updatedRunnable.run(); 133 } 134 } 135 } catch (Exception e) { 136 Logger.systemError(ConfigProperties.class, e.getMessage(), e); 137 } 138 } 139 }; 140 addAutoLoad(cpRun); 141 } 142 143 public void load(File file) throws IOException { 144 load(new FileInputStream(file)); 145 } 146 147 @Override 148 public void load(InputStream inputStream) throws IOException { 149 try { 150 Reader reader = new InputStreamReader(inputStream,BaseFile.CHARSET); 151 load(reader); 152 } finally { 153 if (inputStream != null) 154 inputStream.close(); 155 } 156 } 157 158 @Override 159 public void load(Reader reader) throws IOException { 160 try { 161 int readChar; 162 StringBuffer sbf = new StringBuffer(); 163 while ((readChar = reader.read()) != -1) { 164 sbf.append((char) readChar); 165 } 166 this.parseContentRaw = parseContent(sbf.toString()); 167 super.load(new StringReader(this.parseContentRaw)); 168 } finally { 169 if (reader != null) 170 reader.close(); 171 } 172 } 173 174 public ConfigProperties putAndReturnSlef(String key, Object value) { 175 if (isBlank(value)) { 176 put(key, ""); 177 } else if (value instanceof Map) { 178 Map<String, Object> subMap = conver(key, (Map<String, Object>) value); 179 putAllAndReturnSlef(subMap); 180 } else if (value instanceof List) { 181 List<Object> list = (List<Object>) value; 182 int size = list.size(); 183 for (int i = 0; i < size; i++) { 184 Object item = list.get(i); 185 String newKey = String.format("%s[%s]", key, i); 186 putAndReturnSlef(newKey, item); 187 } 188 } else if (value instanceof Object[]) { 189 List<Object> list = Arrays.asList((Object[]) value); 190 int size = list.size(); 191 for (int i = 0; i < size; i++) { 192 Object item = list.get(i); 193 String newKey = String.format("%s[%s]", key, i); 194 putAndReturnSlef(newKey, item); 195 } 196 } else if (value instanceof String) { 197 put(key, value); 198 } else { 199 put(key, value.toString()); 200 } 201 return this; 202 } 203 204 public ConfigProperties removeAndReturnSlef(String key) { 205 this.remove(key); 206 return this; 207 } 208 209 public ConfigProperties putAllAndReturnSlef(Map<String, Object> map) { 210 Set<String> keys = map.keySet(); 211 for (String key : keys) { 212 Object value = map.get(key); 213 putAndReturnSlef(key, value); 214 } 215 return this; 216 } 217 218 public void removeAllNullValues() { 219 Set<Object> keys = this.keySet(); 220 for (Object key : keys) { 221 Object value = get(key); 222 boolean blank = isBlank(value); 223 if (blank) { 224 this.remove(key); 225 continue; 226 } 227 } 228 } 229 230 private Map<String, Object> conver(String parentKey, Map<String, Object> map) { 231 Map<String, Object> subMap = new HashMap<String, Object>(); 232 Set<String> keys = map.keySet(); 233 for (String key : keys) { 234 String newKey = String.format("%s.%s", parentKey, key); 235 Object value = map.get(key); 236 subMap.put(newKey, value); 237 } 238 return subMap; 239 } 240 241 public Boolean getBoolean(String key) { 242 return getBoolean(key, null); 243 } 244 245 public Boolean getBoolean(String key, Boolean defaultValue) { 246 Object originValue = get(key); 247 248 if(originValue == null) return defaultValue; 249 250 if(originValue instanceof Boolean) return (Boolean)originValue; 251 252 String o = getString(key); 253 if (isBlank(o)) { 254 return defaultValue; 255 } 256 return Boolean.parseBoolean(o.trim()); 257 } 258 259 public List<String> getArray(String key) { 260 List<String> defaultValue = null; 261 List<String> v = getArray(key, defaultValue); 262 if (v == null) { 263 String sv = getString(key); 264 if (sv == null) 265 return new ArrayList<String>(); 266 267 return new ArrayList<String>(Arrays.asList(new String[] { sv })); 268 } else { 269 return v; 270 } 271 } 272 273 public List<String> getArray(String key, String[] defaultValue) { 274 return getArray(key, Arrays.asList(defaultValue)); 275 } 276 277 public List<String> getArray(String key, List<String> defaultValue) { 278 String[] list = null; 279 Map<Integer, String> listMap = new HashMap<Integer, String>(); 280 List<Integer> listIndex = new ArrayList<Integer>(); 281 Set<Object> keys = keySet(); 282 for (Object _key : keys) { 283 String format = String.format("^%s\\[(\\w+)\\]$", key); 284 Pattern pattern = Pattern.compile(format); 285 Matcher matcher = pattern.matcher(_key.toString()); 286 if (matcher.find()) { 287 Integer index = Integer.parseInt(matcher.group(1)); 288 String value = getString(_key.toString()); 289 listMap.put(index, value); 290 listIndex.add(index); 291 } 292 } 293 int listIndexSize = listIndex.size(); 294 if (listIndexSize > 0) { 295 Collections.sort(listIndex); 296 list = new String[listIndex.get(listIndexSize - 1) + 1]; 297 for (int i = 0; i < listIndexSize; i++) { 298 int index = listIndex.get(i); 299 String value = listMap.get(index); 300 list[index] = isBlank(value) ? null : value; 301 } 302 return Arrays.asList(list); 303 } else { 304 return defaultValue; 305 } 306 307 } 308 309 public String getString(String key) { 310 return getString(key, null); 311 } 312 313 public String getString(String key, String defaultValue) { 314 String o = getProperty(key); 315 if (isBlank(o)) { 316 return defaultValue; 317 } 318 return o.trim(); 319 } 320 321 // public String getString(String key, String defaultValue) { 322 // String o = getProperty(key); 323 // if(o == null) 324 // return o; 325 326 // String originValue = o.trim(); 327 // Pattern p = Pattern.compile(ENV_VAR_REGEX); 328 // Matcher m = p.matcher(originValue); 329 // boolean findEnvFormat = m.find(); 330 // if(findEnvFormat){ 331 // String envVar = m.group(1); 332 // String envDefValue = m.group(3); 333 // String envValue = getSysEnv(envVar); 334 // if (!isBlank(envValue)) { 335 // return envValue; 336 // } 337 // if (!isBlank(envDefValue)) { 338 // return envDefValue; 339 // } 340 // return defaultValue; 341 // }else{ 342 // if (isBlank(originValue)) { 343 // return defaultValue; 344 // } 345 // return originValue.trim(); 346 // } 347 // } 348 349 public Short getShort(String key) { 350 return getShort(key, null); 351 } 352 353 public Short getShort(String key, Short defaultValue) { 354 Object originValue = get(key); 355 356 if(originValue == null) return defaultValue; 357 358 if(originValue instanceof Number) return Short.parseShort(originValue + ""); 359 360 String o = getString(key); 361 if (isBlank(o)) { 362 return defaultValue; 363 } 364 return Short.parseShort(o.trim()); 365 } 366 367 public Integer getInteger(String key) { 368 return getInteger(key, null); 369 } 370 371 public Integer getInteger(String key, Integer defaultValue) { 372 Object originValue = get(key); 373 374 if(originValue == null) return defaultValue; 375 376 if(originValue instanceof Number) return Integer.parseInt(originValue + ""); 377 378 String o = getString(key); 379 if (isBlank(o)) { 380 return defaultValue; 381 } 382 return Integer.parseInt(o.trim()); 383 } 384 385 public Long getLong(String key) { 386 return getLong(key, null); 387 } 388 389 public Long getLong(String key, Long defaultValue) { 390 Object originValue = get(key); 391 392 if(originValue == null) return defaultValue; 393 394 if(originValue instanceof Number) return Long.parseLong(originValue + ""); 395 396 String o = getString(key); 397 if (isBlank(o)) { 398 return defaultValue; 399 } 400 return Long.parseLong(o.trim()); 401 } 402 403 public Float getFloat(String key) { 404 return getFloat(key, null); 405 } 406 407 public Float getFloat(String key, Float defaultValue) { 408 Object originValue = get(key); 409 410 if(originValue == null) return defaultValue; 411 412 if(originValue instanceof Number) return Float.parseFloat(originValue + ""); 413 414 String o = getString(key); 415 if (isBlank(o)) { 416 return defaultValue; 417 } 418 return Float.parseFloat(o.trim()); 419 } 420 421 public Double getDouble(String key) { 422 return getDouble(key, null); 423 } 424 425 public Double getDouble(String key, Double defaultValue) { 426 Object originValue = get(key); 427 428 if(originValue == null) return defaultValue; 429 430 if(originValue instanceof Number) return Double.parseDouble(originValue + ""); 431 432 String o = getString(key); 433 if (isBlank(o)) { 434 return defaultValue; 435 } 436 return Double.parseDouble(o.trim()); 437 } 438 439 public Long getSeconds(String key) { 440 return getSeconds(key, null); 441 } 442 443 public Long getSeconds(String key, Long defaultValue) { 444 return parseTimeToSec(getString(key, null), defaultValue); 445 } 446 447 public Long getMilliSeconds(String key) { 448 return getMilliSeconds(key, null); 449 } 450 451 public Long getMilliSeconds(String key, Long defaultValue) { 452 return parseTimeToMs(getString(key, null), defaultValue); 453 } 454 455 public Long getFileSize(String key, Long defaultValue) { 456 return parseFileSize(getString(key, null), defaultValue); 457 } 458 459 public Date getDateTime(String key, String defaultValue) { 460 return parseDateTime(getString(key, null), defaultValue); 461 } 462 463 public Date getDateTime(String key) { 464 return getDateTime(key,null); 465 } 466 467 public ConfigProperties getConfigProperties(String key) { 468 Map<String, Object> map = getMap(key); 469 if (map != null) { 470 ConfigProperties cp = new ConfigProperties(); 471 cp.putAllAndReturnSlef(map); 472 return cp; 473 } 474 return null; 475 } 476 477 public ConfigProperties getConfigProperties(String key, ConfigProperties defaultValue) { 478 Map<String, Object> map = getMap(key); 479 if (map != null) { 480 ConfigProperties cp = new ConfigProperties(); 481 cp.putAllAndReturnSlef(map); 482 return cp; 483 } 484 return defaultValue; 485 } 486 487 public Map<String, Object> getMap(String key) { 488 return getMap(key, null); 489 } 490 491 public Map<String, Object> getMap(String key, Map<String, Object> defaultValue) { 492 Map<String, Object> cp = null; 493 Set<Object> keys = this.keySet(); 494 for (Object keyObject : keys) { 495 String _key = keyObject.toString(); 496 boolean isMap = _key.startsWith(String.format("%s.", key)); 497 if (isMap) { 498 if (cp == null) 499 cp = new HashMap<String, Object>(); 500 501 String mapKey = _key.replaceFirst(String.format("^%s\\.", key), ""); 502 cp.put(mapKey, this.getString(_key, null)); 503 } 504 } 505 return isBlank(cp) ? defaultValue : cp; 506 } 507 508 public static boolean isBlank(Object o) { 509 return CommonTools.isBlank(o); 510 } 511 512 public static Long parseTimeToSec(Object objectValue, Long defaultValue) { 513 514 if (objectValue == null) 515 return defaultValue; 516 517 String stringValue = null; 518 if (objectValue instanceof String) { 519 stringValue = objectValue.toString().trim(); 520 } else { 521 return Long.parseLong(objectValue.toString().trim()); 522 } 523 524 Long value = null; 525 try { 526 Pattern pattern = Pattern.compile("^([-\\+]{0,1}[0-9]+)(((?i)ms|s|m|h|d|w){1})$"); 527 Matcher matcher = pattern.matcher(stringValue); 528 if (matcher.find()) { 529 Long originValue = Long.parseLong(matcher.group(1)); 530 String unit = matcher.group(2).toLowerCase(); 531 if (unit.equals("ms")) { 532 value = originValue / 1000; 533 } 534 if (unit.equals("s")) { 535 value = originValue; 536 } 537 if (unit.equals("m")) { 538 value = originValue * 60; 539 } 540 if (unit.equals("h")) { 541 value = originValue * 60 * 60; 542 } 543 if (unit.equals("d")) { 544 value = originValue * 60 * 60 * 24; 545 } 546 if (unit.equals("w")) { 547 value = originValue * 60 * 60 * 24 * 7; 548 } 549 } else { 550 Logger.systemMark(ConfigProperties.class, 551 "Missing unit(ms|s|m|h|d|w) '{}' will using default value '{}', (e.g. {}ms,{}s,...).", 552 stringValue, defaultValue, stringValue, stringValue); 553 } 554 return value == null ? defaultValue : value; 555 } catch (Exception e) { 556 Logger.systemError(ConfigProperties.class, e); 557 return null; 558 } 559 } 560 561 public static Long parseTimeToMs(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; 582 } 583 if (unit.equals("s")) { 584 value = originValue * 1000; 585 } 586 if (unit.equals("m")) { 587 value = originValue * 60 * 1000; 588 } 589 if (unit.equals("h")) { 590 value = originValue * 60 * 60 * 1000; 591 } 592 if (unit.equals("d")) { 593 value = originValue * 60 * 60 * 1000 * 24; 594 } 595 if (unit.equals("w")) { 596 value = originValue * 60 * 60 * 1000 * 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 Date parseDateTime(String objectValue, String defaultValue) { 611 SimpleDateFormat sf = new SimpleDateFormat(DATETIME_PATTERN); 612 try{ 613 if(CommonTools.isBlank(objectValue)){ 614 if(CommonTools.isBlank(defaultValue)) return null; 615 616 if(!CommonTools.isBlank(defaultValue)) return sf.parse(defaultValue); 617 } 618 619 return sf.parse(objectValue); 620 }catch(Exception e){ 621 Logger.systemError(ConfigProperties.class, e); 622 return null; 623 } 624 } 625 626 public static Long parseFileSize(Object objectValue, Long defaultValue) { 627 628 if (objectValue == null) 629 return defaultValue; 630 631 String stringValue = null; 632 if (objectValue instanceof String) { 633 stringValue = objectValue.toString().trim(); 634 } else { 635 return Long.parseLong(objectValue.toString().trim()); 636 } 637 638 Long value = null; 639 try { 640 Pattern pattern = Pattern.compile("^([-\\+]{0,1}[0-9]+)(((?i)b|kb|mb|gb|tb){1})$"); 641 Matcher matcher = pattern.matcher(stringValue); 642 if (matcher.find()) { 643 Long originValue = Long.parseLong(matcher.group(1)); 644 String unit = matcher.group(2).toLowerCase(); 645 if (unit.equals("b")) { 646 value = originValue; 647 } 648 if (unit.equals("kb")) { 649 value = originValue * 1024; 650 } 651 if (unit.equals("mb")) { 652 value = originValue * 1024 * 1024; 653 } 654 if (unit.equals("gb")) { 655 value = originValue * 1024 * 1024 * 1024; 656 } 657 if (unit.equals("tb")) { 658 value = originValue * 1024 * 1024 * 1024 * 1024; 659 } 660 } else { 661 Logger.systemMark(ConfigProperties.class, 662 "Missing unit(b|kb|mb|gb|tb) '{}' will using default value '{}', (e.g. {}mb,{}kb,...).", 663 stringValue, defaultValue, stringValue, stringValue); 664 } 665 return value == null ? defaultValue : value; 666 } catch (Exception e) { 667 Logger.systemError(ConfigProperties.class, e); 668 return null; 669 } 670 } 671 672 public static String getSysEnv(String envKey) { 673 return System.getenv(envKey) == null ? System.getProperty(envKey) : System.getenv(envKey); 674 } 675 676 public static String getEnv(String originValue) { 677 if (originValue == null) 678 return null; 679 680 Pattern p = Pattern.compile(ENV_VAR_REGEX); 681 Matcher m = p.matcher(originValue.trim()); 682 boolean findEnvFormat = m.find(); 683 if (findEnvFormat) { 684 String envVar = m.group(1); 685 String envDefValue = m.group(3); 686 String envValue = getSysEnv(envVar); 687 if (!isBlank(envValue)) { 688 return envValue; 689 } 690 if (!isBlank(envDefValue)) { 691 return envDefValue; 692 } 693 return null; 694 } 695 return originValue; 696 } 697 698 public static String parseContent(String content) { 699 return parseContent(content, ""); 700 } 701 702 public static String parseContent(String content, String replaceNullChar) { 703 Pattern p = Pattern.compile(ENV_VAR_REGEX, Pattern.DOTALL | Pattern.MULTILINE); 704 StringBuffer sb = new StringBuffer(); 705 Matcher matcher = p.matcher(content); 706 while (matcher.find()) { 707 String envValue = ConfigProperties.getEnv(matcher.group()); 708 envValue = (envValue == null ? replaceNullChar : envValue); 709 matcher.appendReplacement(sb, envValue); 710 } 711 matcher.appendTail(sb); 712 return sb.toString(); 713 } 714 715 public static boolean compareProperties(ConfigProperties props1, ConfigProperties props2) { 716 717 if (props1 == null || props2 == null) 718 return false; 719 720 return props1.parseContentRaw.equals(props2.parseContentRaw); 721 } 722 723}