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 }