View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.statistics.distribution;
19  
20  import java.util.function.Supplier;
21  
22  /**
23   * Utilities for creating String messages.
24   */
25  final class StringUtils {
26      /** The space ' ' character. */
27      private static final char SPACE_CHAR = ' ';
28      /** The AND string. */
29      private static final String AND = " && ";
30      /** The OR string. */
31      private static final String OR = " || ";
32      /** The wrapped unknown string: "(NA)". */
33      private static final String NA = "(NA)";
34  
35      /**
36       * Do not allow public construction.
37       */
38      private StringUtils() {}
39  
40      /**
41       * Checks if the string is not null or empty whitespace.
42       *
43       * @param string the string
44       * @return true, if is not empty
45       */
46      static boolean isNotEmpty(String string) {
47          return string != null && hasNonWhiteSpace(string);
48      }
49  
50      /**
51       * Checks if the string is null or empty whitespace.
52       *
53       * @param string the string
54       * @return true, if null or empty
55       */
56      static boolean isNullOrEmpty(String string) {
57          return string == null || !hasNonWhiteSpace(string);
58      }
59  
60      /**
61       * Checks for non white space.
62       *
63       * @param string the string
64       * @return true, if successful
65       */
66      private static boolean hasNonWhiteSpace(String string) {
67          for (int i = 0; i < string.length(); i++) {
68              if (string.charAt(i) > SPACE_CHAR) {
69                  return true;
70              }
71          }
72          return false;
73      }
74  
75      /**
76       * Gets the string representation. The object may be a {@link String} or {@link Supplier}.
77       *
78       * <ul>
79       *  <li>If the object is a {@link String} then the string is returned.
80       *  <li>If the object is a {@link Supplier} then the supplied object is obtained and,
81       *      if not null, {@link Object#toString()} is returned.
82       *  <li>Otherwise returns {@code null}.
83       * </ul>
84       *
85       * @param object the object
86       * @return the string representation
87       */
88      static String toString(Object object) {
89          if (object instanceof String) {
90              return (String) object;
91          }
92          if (object instanceof Supplier) {
93              final Object supplied = ((Supplier<?>) object).get();
94              if (supplied != null) {
95                  return supplied.toString();
96              }
97          }
98          return null;
99      }
100 
101     /**
102      * Gets the negation of the string representation, obtained from {@link #toString(Object)}.
103      *
104      * <ul>
105      *  <li>If the object has a string representation the result is {@code !(string)}.
106      *  <li>Otherwise returns {@code null}.
107      * </ul>
108      *
109      * @param object the object
110      * @return the string representation
111      */
112     static String negateToString(Object object) {
113         final String s = toString(object);
114         return (isNotEmpty(s)) ? "!(" + s + ")" : null;
115     }
116 
117     /**
118      * Gets the logical OR combination of the string representation of two objects, each obtained from
119      * {@link #toString(Object)}.
120      *
121      * <ul>
122      *  <li>If both strings are valid the result is {@code (string1) || (string2)}.
123      *  <li>If either string is {@code null} or empty it will be replaced with {@code NA}.
124      *  <li>Otherwise returns {@code null}.
125      * </ul>
126      *
127      * @param object1 the first object
128      * @param object2 the second object
129      * @return the OR string representation
130      * @see #toString(Object)
131      */
132     static String orToString(Object object1, Object object2) {
133         final String s1 = toString(object1);
134         final String s2 = toString(object2);
135         if (isNotEmpty(s1) || isNotEmpty(s2)) {
136             return concatenate(s1, OR, s2);
137         }
138         return null;
139     }
140 
141     /**
142      * Gets the logical AND combination of the string representation of two objects, each obtained
143      * from {@link #toString(Object)}.
144      *
145      * <ul>
146      *  <li>If both strings are valid the result is {@code (string1) && (string2)}.
147      *  <li>If either string is {@code null} or empty it will be replaced with {@code NA}.
148      *  <li>Otherwise returns {@code null}.
149      * </ul> {@link String} or {@link Supplier} of {@link String}.
150      *
151      * @param object1 the first object
152      * @param object2 the second object
153      * @return the AND string representation
154      * @see #toString(Object)
155      */
156     static String andToString(Object object1, Object object2) {
157         final String s1 = toString(object1);
158         final String s2 = toString(object2);
159         if (isNotEmpty(s1) || isNotEmpty(s2)) {
160             return concatenate(s1, AND, s2);
161         }
162         return null;
163     }
164 
165     /**
166      * Concatenate the two strings. Adds parentheses if required.
167      * This is made simple by only supporting {@code &&} or {@code ||}.
168      *
169      * @param s1 the first string
170      * @param operator the operator to join the strings
171      * @param s2 the second string
172      * @return the string
173      */
174     private static String concatenate(String s1, String operator, String s2) {
175         // Detect the last and first operators in the two strings.
176         // If the same as the joining operator then no parentheses are required.
177         final StringBuilder sb = new StringBuilder(64);
178         if (isNotEmpty(s1)) {
179             final String op = lastOperator(s1);
180             if (op == null || op.equals(operator)) {
181                 sb.append(s1);
182             } else {
183                 sb.append('(').append(s1).append(')');
184             }
185         } else {
186             sb.append(NA);
187         }
188         sb.append(operator);
189         if (isNotEmpty(s2)) {
190             final String op = firstOperator(s2);
191             if (op == null || op.equals(operator)) {
192                 sb.append(s2);
193             } else {
194                 sb.append('(').append(s2).append(')');
195             }
196         } else {
197             sb.append(NA);
198         }
199         return sb.toString();
200     }
201 
202     /**
203      * Find the last operator in the string.
204      *
205      * @param s the string
206      * @return the operator (or null)
207      */
208     private static String lastOperator(String s) {
209         final int a = s.lastIndexOf(AND);
210         final int o = s.lastIndexOf(OR);
211         if (a > o) {
212             return AND;
213         }
214         return o != -1 ? OR : null;
215     }
216 
217     /**
218      * Find the last operator in the string.
219      *
220      * @param s the string
221      * @return the operator (or null)
222      */
223     private static String firstOperator(String s) {
224         int a = s.indexOf(AND);
225         int o = s.indexOf(OR);
226         final int len = s.length();
227         a = a < 0 ? len : a;
228         o = o < 0 ? len : o;
229         if (a < o) {
230             return AND;
231         }
232         return o < len ? OR : null;
233     }
234 }