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