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