Fix in StringHelper
[appc.git] / appc-common / src / main / java / org / onap / appc / util / StringHelper.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Copyright (C) 2017 Amdocs
8  * =============================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  * 
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  * 
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * 
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  * ============LICENSE_END=========================================================
23  */
24
25
26
27 package org.onap.appc.util;
28
29 import com.att.eelf.configuration.EELFLogger;
30 import com.att.eelf.configuration.EELFManager;
31
32 import java.util.Date;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Properties;
36 import java.util.Set;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39
40 /**
41  * This class contains several static helper methods that can be used to perform string manipulation algorithms.
42  * 
43  */
44
45 public final class StringHelper {
46     private static final EELFLogger logger = EELFManager.getInstance().getLogger(StringHelper.class);
47
48     public static final String DASH = "-";
49     public static final String DOT = ".";
50     public static final String ELLIPSES = "...";
51     public static final String LINE_FEED = "\n";
52     public static final String SLASH = "/";
53     public static final String COMMA = ",";
54
55     /**
56      * Converts the specified string pattern to a regular expression string. If the supplied string is null or empty,
57      * then a regular expression that matches all strings (.*) is returned.
58      * <p>
59      * The expression passed to this method should not already be a regular expression. If it contains problematic
60      * meta-characters for this routine (such as period, asterisk, and plus), they will be escaped and matched literally
61      * in the resulting regular expression returned.
62      * </p>
63      * 
64      * @param value
65      *            The pattern that we need to convert to a regular expression
66      * @return The regular expression that is equivalent to the pattern
67      */
68     public static String convertToRegex(String value) {
69         if (value == null || value.trim().length() == 0) {
70             return ".*";
71         }
72         boolean appendEOL = false;
73         StringBuffer buffer = new StringBuffer(value.trim());
74
75         /*
76          * If there are any period characters, we need to escape them so that they are exactly matched
77          */
78         Pattern pattern = Pattern.compile("\\.");
79         Matcher matcher = pattern.matcher(buffer);
80         int position = 0;
81         while (matcher.find(position)) {
82             buffer.replace(matcher.start(), matcher.end(), "\\.");
83             position = matcher.end() + 1;
84         }
85
86         /*
87          * If there are any asterisks or pluses, which we need to interpret as wildcard characters, we need to convert
88          * them into .* or .
89          */
90         pattern = Pattern.compile("\\*|\\+");
91         matcher = pattern.matcher(buffer);
92         position = 0;
93         while (matcher.find(position)) {
94             String metachar = buffer.substring(matcher.start(), matcher.end());
95             if (metachar.equals("*")) {
96                 buffer.replace(matcher.start(), matcher.end(), ".*");
97                 position = matcher.end() + 1;
98                 if (matcher.end() < buffer.length() - 1) {
99                     appendEOL = true;
100                 }
101             } else if (metachar.equals("+")) {
102                 buffer.replace(matcher.start(), matcher.end(), ".");
103                 position = matcher.end();
104                 if (matcher.end() == buffer.length()) {
105                     appendEOL = true;
106                 }
107             }
108         }
109
110         /*
111          * If the string contains a .* meta-character sequence anywhere in the middle of the string (i.e., there are
112          * other characters following the .* meta-characters), OR the string ends with the .+ sequence, then we need to
113          * append the "end-of-line" boundary condition to the end of the string to get predictable results.
114          */
115         if (appendEOL) {
116             buffer.append("$");
117         }
118         return buffer.toString();
119     }
120
121     /**
122      * Takes a string that may possibly be very long and return a string that is at most maxLength. If the string is
123      * longer than maxLength, the last three characters will be the ellipses (...) to indicate that the string was
124      * shortened.
125      * 
126      * @param possiblyLongString
127      * @param maxLength
128      *            must be at least 4 (one character plus ellipses)
129      * @return possibly shortened string
130      */
131     public static String getShortenedString(String possiblyLongString, int maxLength) {
132         if ((possiblyLongString != null) && (maxLength > ELLIPSES.length())
133             && (possiblyLongString.length() > maxLength)) {
134             return possiblyLongString.substring(0, maxLength - ELLIPSES.length()) + ELLIPSES;
135
136         }
137         return possiblyLongString;
138     }
139
140     /**
141      * Determines that a provided string is not null and not empty (length = 0 after trimming)
142      * 
143      * @param theString
144      *            The string to be tested
145      * @return true if the string IS NOT null and is NOT empty
146      */
147     public static boolean isNotNullNotEmpty(String theString) {
148         return ((theString != null) && (!theString.trim().isEmpty()));
149     }
150
151     /**
152      * Determines that a provided string IS null or an empty string (length = 0 after trimming)
153      * 
154      * @param theString
155      *            The string to be tested
156      * @return true if the string IS null OR is empty
157      */
158     public static boolean isNullOrEmpty(String theString) {
159         return ((theString == null) || (theString.trim().isEmpty()));
160     }
161
162     /**
163      * Returns an indication if the first string is equal to the second string, allowing for either or both strings to
164      * be null.
165      * 
166      * @param a
167      *            The first string to be compared
168      * @param b
169      *            The second string to be compared
170      * @return True if both strings are null, or both strings are non-null AND they are equal. False otherwise.
171      */
172     public static boolean equals(String a, String b) {
173         return equals(a, b, false);
174     }
175
176     /**
177      * Returns an indication if the first string is equal to the second string, allowing for either or both strings to
178      * be null, and ignoring case.
179      * 
180      * @param a
181      *            The first string to be compared
182      * @param b
183      *            The second string to be compared
184      * @return True if both strings are null, or both strings are non-null AND they are equal (without regard to case).
185      *         False otherwise.
186      */
187     public static boolean equalsIgnoreCase(String a, String b) {
188         return equals(a, b, true);
189     }
190
191     /**
192      * Compares two strings (allowing either or both to be null), and allowing for optional case sensitive or
193      * insensitive comparison.
194      * 
195      * @param a
196      *            The first string to be compared
197      * @param b
198      *            The second string to be compared
199      * @param caseInsensitive
200      *            True if the comparison is to be case in-sensitive.
201      * @return True if both strings are null, or both strings are non-null and they are equal
202      */
203     private static boolean equals(String a, String b, boolean caseInsensitive) {
204         if (a == null && b == null) {
205             return true;
206         }
207         if (a != null && b != null) {
208             if (caseInsensitive) {
209                 return a.equalsIgnoreCase(b);
210             } else {
211                 return a.equals(b);
212             }
213         }
214
215         return false;
216     }
217
218     /**
219      * This method is used to mangle a name.
220      * <p>
221      * This method will first remove all unacceptable characters from the name and translate all characters to lower
222      * case. This is done to eliminate any potentially troublesome characters. If the resulting string is empty, then a
223      * random string of characters for the minimum desired length is returned. If the string is too short to meet the
224      * minimum length requirement, it is padded with random characters.
225      * </p>
226      * <p>
227      * Once the string has been scrubbed and possibly padded, it may be truncated (if longer than the maximum value) and
228      * the result is returned. To make the string as unique as possible, the algorithm removes excess letters from the
229      * center of the string, concatenating the first nad last parts of the name together. The assumption is that users
230      * tend to start the names of multiple things in similar ways, and get more descriptive as the name progresses. If
231      * for example, several objects were named "A test Object", "A test Object1", and "A test Object2", shortening the
232      * name only from the left does not generate a unique name.
233      * </p>
234      * 
235      * @param name
236      *            The name to be mangled
237      * @param minLen
238      *            minimum number of characters for the name
239      * @param maxLen
240      *            maximum number of characters for the name
241      * @return The mangled name, or an empty string if the value is null or an empty string.
242      */
243     public static String mangleName(String name, int minLen, int maxLen) {
244         StringBuffer buffer = new StringBuffer(name == null ? "" : name);
245         Pattern pattern = Pattern.compile("[^a-z0-9]+", Pattern.CASE_INSENSITIVE);
246         Matcher matcher = pattern.matcher(buffer);
247         int position = 0;
248         while (matcher.find(position)) {
249             buffer.delete(matcher.start(), matcher.end());
250             position = matcher.start();
251         }
252
253         if (buffer.length() < minLen) {
254             for (int i = buffer.length(); i <= minLen; i++) {
255                 buffer.append("A");
256             }
257         }
258
259         /*
260          * Remove out of the center of the name to preserve start and end and result in a string of max len
261          */
262         if (buffer.length() > maxLen) {
263             int excess = buffer.length() - maxLen;
264             int left = maxLen / 2;
265
266             buffer.delete(left, excess + left);
267         }
268
269         return buffer.toString().toLowerCase();
270     }
271
272     /**
273      * This method is used to normalize a string value.
274      * <p>
275      * This method will ensure that the string value is trimmed of all leading and trailing whitespace if not null. If
276      * it is null or an empty string, then it will return null.
277      * </p>
278      * 
279      * @param value
280      *            The value to be normalized
281      * @return The normalized (no leading or trailing whitespace) value, or null if the string was null or an empty
282      *         string (or all whitespace). This method will never return an empty string.
283      */
284     public static String normalizeString(String value) {
285         if (value != null) {
286             String temp = value.trim();
287             if (temp.length() > 0) {
288                 return temp;
289             }
290         }
291         return null;
292     }
293
294     /**
295      * This method is used to strip all carriage returns and line feed characters from a string
296      * 
297      * @param value
298      * @return The original value less all carriage returns and line feeds
299      */
300     public static String stripCRLF(String value) {
301
302         if (value == null) {
303             return null;
304         }
305         String[] tokens = value.split("\r\n|\n\r|\r|\n");
306         StringBuffer buffer = new StringBuffer();
307         for (String token : tokens) {
308             buffer.append(token.trim());
309         }
310         return buffer.toString();
311     }
312
313     /**
314      * Converts UNIX-style line endings to DOS-style. Replaces LF with CR+LF as long as the LF does not already exist
315      * paired with a CR.
316      * 
317      * @param content
318      *            The content to be converted
319      * @return The converted content.
320      */
321     public static String toDOSLines(String content) {
322         if (content == null) {
323             return null;
324         }
325
326         StringBuffer buffer = new StringBuffer(content);
327         Pattern pattern = Pattern.compile("^(\n)[^\r]|[^\r](\n)[^\r]|[^\r](\n)$");
328         Matcher matcher = pattern.matcher(buffer);
329         int position = 0;
330         while (matcher.find(position)) {
331             int index = matcher.start(1);
332             if (index == -1) {
333                 index = matcher.start(2);
334             }
335             if (index == -1) {
336                 index = matcher.start(3);
337             }
338
339             buffer.replace(index, index + 1, "\r\n");
340             position = index + 1;
341         }
342
343         return buffer.toString();
344     }
345
346     /**
347      * This method will convert a string contents to use the UNIX-style line endings. That is, all occurrences of CR
348      * (Carriage Return) and LF (Line Feed) are reduced to just use LF.
349      * 
350      * @param content
351      *            The buffer to be processed
352      * @return The converted contents
353      */
354     public static String toUnixLines(String content) {
355         if (content == null) {
356             return null;
357         }
358
359         StringBuffer buffer = new StringBuffer(content);
360         Pattern pattern = Pattern.compile("\r\n|\n\r");
361         Matcher matcher = pattern.matcher(buffer);
362         int position = 0;
363         while (matcher.find(position)) {
364             buffer.replace(matcher.start(), matcher.end(), "\n");
365             position = matcher.start();
366         }
367
368         return buffer.toString();
369     }
370
371     /**
372      * This method is used to translate characters in the input sequence that match the characters in the match list to
373      * the corresponding character in the replacement list. If the replacement list is shorter than the match list, then
374      * the character from the replacement list is taken as the modulo of the match character position and the length of
375      * the replacement list.
376      * 
377      * @param sequence
378      *            The input sequence to be processed
379      * @param match
380      *            The list of matching characters to be searched
381      * @param replacement
382      *            The list of replacement characters, positional coincident with the match list. If shorter than the
383      *            match list, then the position "wraps" around on the replacement list.
384      * @return The translated string contents.
385      */
386     public static Object translate(String sequence, String match, String replacement) {
387
388         if (sequence == null) {
389             return sequence;
390         }
391
392         StringBuffer buffer = new StringBuffer(sequence);
393
394         for (int index = 0; index < buffer.length(); index++) {
395             char ch = buffer.charAt(index);
396
397             int position = match.indexOf(ch);
398             if (position == -1) {
399                 continue;
400             }
401
402             if (position >= replacement.length()) {
403                 position %= replacement.length();
404             }
405             buffer.setCharAt(index, replacement.charAt(position));
406         }
407
408         return buffer.toString();
409     }
410
411     /**
412      * Ensures that the name provided is a valid identifier. This means that no spaces are allowed as well as special
413      * characters. This method translates all spaces and illegal characters to underscores (_).
414      * 
415      * @param name
416      *            The name to be checked and converted to an identifier if needed
417      * @return The valid identifier from the name
418      */
419     public static String validIdentifier(String name) {
420         if (name == null || name.length() == 0) {
421             return name;
422         }
423
424         StringBuffer buffer = new StringBuffer(name);
425         for (int index = 0; index < buffer.length(); index++) {
426             char ch = buffer.charAt(index);
427
428             if ((index == 0 && !Character.isJavaIdentifierStart(ch)) || (!Character.isJavaIdentifierPart(ch))) {
429                 buffer.setCharAt(index, '_');
430             }
431         }
432         return buffer.toString();
433     }
434
435     /**
436      * This method verifies that the provided string only contains characters from the legal set, and replaces any
437      * character not in the legal set with the specified replacement character.
438      * 
439      * @param sequence
440      *            The sequence to be verified
441      * @param legal
442      *            The set of all legal characters
443      * @param replacement
444      *            The replacement character if a character is not in the legal set
445      * @return The verified *and possibly updated) string
446      */
447     public static String verify(String sequence, String legal, char replacement) {
448         if (sequence == null) {
449             return sequence;
450         }
451
452         StringBuffer buffer = new StringBuffer(sequence);
453         for (int index = 0; index < buffer.length(); index++) {
454             char ch = buffer.charAt(index);
455             if (legal.indexOf(ch) == -1) {
456                 buffer.setCharAt(index, replacement);
457             }
458         }
459         return buffer.toString();
460     }
461
462     /**
463      * Private constructor to prevent instantiation of this class - All methods are static!
464      */
465     private StringHelper() {
466
467     }
468
469     /**
470      * @param list
471      *            The list of elements
472      * @return The list of elements formatted as a comma-delimited list
473      */
474     public static String asList(List<String> list) {
475         StringBuffer buffer = new StringBuffer();
476         if (list != null) {
477             if (list.size() == 1) {
478                 buffer.append(list.get(0));
479             } else {
480                 for (String element : list) {
481                     buffer.append(element);
482                     buffer.append(", ");
483                 }
484
485                 if (buffer.length() > 2) {
486                     buffer.delete(buffer.length() - 2, buffer.length());
487                 }
488             }
489         }
490         return buffer.toString();
491     }
492
493     /**
494      * @param map
495      *            A map of strings
496      * @return A map expressed as a comma-delimited list of name=value tuples
497      */
498     public static String asList(Map<String, String> map) {
499         StringBuffer buffer = new StringBuffer();
500         if (map != null) {
501             Set<String> keys = map.keySet();
502             for (String key : keys) {
503                 buffer.append(String.format("%s=%s, ", key, map.get(key)));
504             }
505
506             if (buffer.length() > 2) {
507                 buffer.delete(buffer.length() - 2, buffer.length());
508             }
509         }
510         return buffer.toString();
511     }
512
513     /**
514      * @param values
515      *            An array or varargs of Strings to be concatenated into a comma-separated list
516      * @return The comma-seprated list of values
517      */
518     public static String asList(String... values) {
519         StringBuilder builder = new StringBuilder();
520         builder.append('[');
521         if (values != null && values.length > 0) {
522             int count = values.length;
523             for (int index = 0; index < count - 1; index++) {
524                 builder.append(values[index]);
525                 builder.append(',');
526             }
527             builder.append(values[count - 1]);
528         }
529         builder.append(']');
530         return builder.toString();
531     }
532
533     public static Object resolveToType(String input) {
534         String intRegex = "^(\\-)?[0-9]+$";
535         String doubleRegex = "^(\\-)?[0-9\\.]+$";
536         String boolRegex = "(^(?i)((true)|(false))$)";
537
538         // Check for null
539         if (input == null) {
540             return null;
541         }
542
543         // Check int first
544         if (input.matches(intRegex)) {
545             try {
546                 return Integer.parseInt(input);
547             } catch (NumberFormatException nfe) {
548                 // Should not happen
549                 logger.error(nfe.getMessage());
550             }
551         }
552
553         // Check double (int + decimal point)
554         if (input.matches(doubleRegex)) {
555             try {
556                 return Double.parseDouble(input);
557             } catch (NumberFormatException | NullPointerException e) {
558                 // NPE won't happen bc of regex check
559                 logger.error(e.getMessage());
560             }
561         }
562
563         // Check boolean
564         if (input.matches(boolRegex)) {
565             return Boolean.parseBoolean(input);
566         }
567
568         // Try to parse a date
569         Date date = Time.utcParse(input);
570         if (date != null) {
571             return date;
572         }
573
574         // No special type, return string
575         return input;
576     }
577
578     /**
579      * Converts a properties object to a string in the format of <pre>[ key=value, key=value, ... ]</pre>
580      *
581      * @param props
582      *            The properties object to format
583      * @return A string in the format <pre>[ key=value, ... ]</pre> or null if the input was null
584      */
585     public static String propertiesToString(Properties props) {
586         if (props == null) {
587             return null;
588         }
589         StringBuilder out = new StringBuilder();
590         out.append("[");
591         for (Object key : props.keySet()) {
592             out.append(String.format(" %s = %s,", key.toString(), props.getProperty(key.toString())));
593         }
594         if (props.size() > 0) {
595             out.deleteCharAt(out.lastIndexOf(","));
596         }
597         out.append(" ]");
598         return out.toString();
599     }
600 }