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