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