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