├── .github └── workflows │ └── maven.yml ├── .gitignore ├── README.md ├── license.txt ├── pom.xml ├── settings.xml └── src ├── main └── java │ ├── module-info.java │ └── org │ └── omnifaces │ └── utils │ ├── Collections.java │ ├── Comparators.java │ ├── Comparisons.java │ ├── Lang.java │ ├── annotation │ ├── AnnotationInvocationHandler.java │ └── Annotations.java │ ├── collection │ └── PartialResultList.java │ ├── data │ ├── AbstractRange.java │ ├── ImmutableRangeImpl.java │ ├── MutableRange.java │ ├── MutableRangeImpl.java │ └── Range.java │ ├── exceptions │ └── Exceptions.java │ ├── function │ ├── ExceptionlessAutoCloseable.java │ ├── Predicates.java │ └── ThrowingAutoCloseable.java │ ├── image │ └── Images.java │ ├── io │ └── Io.java │ ├── logging │ ├── LogFilter.java │ └── RecursiveStackTraceFormatter.java │ ├── math │ └── BigDecimalMath.java │ ├── properties │ └── PropertiesUtils.java │ ├── reflect │ ├── Getter.java │ └── Reflections.java │ ├── security │ ├── Certificates.java │ ├── DefaultX509TrustManager.java │ ├── InterceptingX509TrustManager.java │ ├── MessageDigests.java │ └── UncheckedNoSuchAlgorithmException.java │ ├── stream │ ├── Collectors.java │ ├── CombinedCollector.java │ ├── FindLastCollector.java │ ├── ForEachBatchCollector.java │ ├── RangeIterator.java │ ├── ReversedStreamCollector.java │ ├── Streams.java │ ├── Summary.java │ └── SummaryCollector.java │ ├── text │ ├── FormatterUtil.java │ ├── NameBasedMessageFormat.java │ ├── SubFormatter.java │ └── SubFormatterFactory.java │ └── time │ └── TemporalAdjusters.java └── test └── java └── org └── omnifaces └── utils ├── AnnotationsTest.java ├── BigDecimalMathTest.java ├── ComparatorsTest.java ├── ComparisonsTest.java ├── LangTest.java ├── data ├── BaseRangeTest.java ├── MutableRangeTest.java └── RangeTest.java ├── function └── PredicatesTest.java ├── stream ├── CollectorsTest.java └── StreamsTest.java ├── text └── NameBasedMessageFormatTest.java └── time └── TemporalAdjustersTest.java /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2021 OmniFaces 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | # the License. You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | # specific language governing permissions and limitations under the License. 12 | # 13 | 14 | name: tests 15 | 16 | on: [push, pull_request] 17 | 18 | jobs: 19 | test: 20 | name: Run tests 21 | runs-on: ubuntu-latest 22 | continue-on-error: true 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: Set up JDK 11 27 | uses: actions/setup-java@v2 28 | with: 29 | java-version: '11' 30 | distribution: 'adopt' 31 | cache: maven 32 | - name: Test with Maven 33 | run: mvn test -Dmaven.javadoc.skip=true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | *.jsfdia 4 | *.asc 5 | /target 6 | /.settings 7 | /.project 8 | /.classpath 9 | .idea/ 10 | *.iml 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maven](https://img.shields.io/maven-metadata/v/https/repo.maven.apache.org/maven2/org/omnifaces/omniutils/maven-metadata.xml.svg)](https://repo.maven.apache.org/maven2/org/omnifaces/omniutils/) 2 | [![Javadoc](https://javadoc.io/badge/org.omnifaces/omniutils.svg)](https://javadoc.io/doc/org.omnifaces/omniutils) 3 | [![Tests](https://github.com/omnifaces/omniutils/actions/workflows/maven.yml/badge.svg)](https://github.com/omnifaces/omniutils/actions) 4 | [![License](https://img.shields.io/:license-apache-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 5 | 6 | OmniFaces Utils 7 | ================= 8 | 9 | A utility library for Java SE. 10 | 11 | OmniUtils is a set of Java SE libraries that includes JDK 8 collectors for working with streams (such as `ForEachBatchCollector` and `ReversedStreamCollector`), functional-like predicates (such as `isAnyEmpty`, `isOneOf`, and `coalesce`), reflection tools (such as `findField`, `listAnnotatedFields`, and `closestMatchingMethod`), security functions (such as `getCertificateChainFromServer` and `generateRandomRSAKeys`), image tools (such as `cropImage`, `toJpg`, `toPng` and `progressiveBilinearDownscale`) and much more! 12 | 13 | ## Install ## 14 | 15 | Maven users can add OmniFaces by adding the following Maven coordinates to the pom.xml of a project: 16 | 17 | ``` 18 | 19 | org.omnifaces 20 | omniutils 21 | 0.12 22 | 23 | ``` 24 | 25 | Note that Java 11 support is introduced in version `0.12`. If you're still on Java 8, then use version `0.11` instead. 26 | 27 | ## Notes ## 28 | 29 | OmniUtils is still in its early stages of development 30 | 31 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2021 OmniFaces 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 4 | the License. You may obtain a copy of the License at 5 | 6 | https://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 9 | an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 10 | specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 4.0.0 8 | 9 | org.omnifaces 10 | omniutils 11 | 0.16 12 | jar 13 | 14 | OmniUtils 15 | Java utility library 16 | https://github.com/omnifaces/omniutils 17 | 18 | OmniFaces 19 | https://omnifaces.org 20 | 21 | 2015 22 | 23 | 24 | 25 | balusc 26 | Bauke Scholtz 27 | balusc@gmail.com 28 | 29 | 30 | arjan.tijms 31 | Arjan Tijms 32 | arjan.tijms@gmail.com 33 | 34 | 35 | jan.beernink 36 | Jan Beernink 37 | jan.beernink@gmail.com 38 | 39 | 40 | 41 | 42 | 43 | The Apache Software License, Version 2.0 44 | https://www.apache.org/licenses/LICENSE-2.0.txt 45 | repo 46 | 47 | 48 | 49 | 50 | https://github.com/omnifaces/omniutils 51 | scm:git:git://github.com/omnifaces/omniutils.git 52 | scm:git:git@github.com:omnifaces/omniutils.git 53 | 54 | 55 | 56 | 57 | ossrh 58 | https://oss.sonatype.org/content/repositories/snapshots 59 | 60 | 61 | 62 | 63 | 64 | 65 | 11 66 | 67 | 68 | UTF-8 69 | UTF-8 70 | ${javase.version} 71 | ${javase.version} 72 | 73 | 74 | 75 | 76 | 77 | 78 | junit 79 | junit 80 | 4.13.2 81 | test 82 | 83 | 84 | org.hamcrest 85 | hamcrest-core 86 | 87 | 88 | 89 | 90 | org.hamcrest 91 | hamcrest-all 92 | 1.3 93 | test 94 | 95 | 96 | 97 | 98 | 99 | 100 | maven-enforcer-plugin 101 | 3.0.0 102 | 103 | 104 | enforce-maven 105 | 106 | enforce 107 | 108 | 109 | 110 | 111 | 3.6.0 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | com.mycila 122 | license-maven-plugin 123 | 4.1 124 | 125 |
license.txt
126 | 127 | *.* 128 | 129 | 130 | SLASHSTAR_STYLE 131 | 132 |
133 | 134 | 135 | process-sources 136 | 137 | format 138 | 139 | 140 | 141 |
142 | 143 | 144 | 145 | org.apache.maven.plugins 146 | maven-jar-plugin 147 | 3.2.2 148 | 149 | 150 | 151 | true 152 | true 153 | 154 | 155 | ${project.url} 156 | ${project.artifactId} 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | org.apache.maven.plugins 165 | maven-source-plugin 166 | 3.2.1 167 | 168 | 169 | attach-sources 170 | 171 | jar-no-fork 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | org.apache.maven.plugins 180 | maven-javadoc-plugin 181 | 3.4.1 182 | 183 | true 184 | -Xdoclint:none 185 | -Xdoclint:none 186 | -Xdoclint:none 187 | true 188 | true 189 | OmniUtils API documentation 190 | 191 | 192 | 193 | attach-javadocs 194 | 195 | jar 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | external.atlassian.jgitflow 204 | jgitflow-maven-plugin 205 | 1.0-m5.1 206 | 207 | true 208 | true 209 | 210 | 211 | 212 | 213 | 214 | org.sonatype.plugins 215 | nexus-staging-maven-plugin 216 | 1.6.13 217 | true 218 | 219 | ossrh 220 | https://oss.sonatype.org/ 221 | true 222 | 223 | 224 |
225 | 226 | 227 | 228 | 229 | 230 | 231 | org.eclipse.m2e 232 | lifecycle-mapping 233 | 1.0.0 234 | 235 | 236 | 237 | 238 | 239 | com.mycila 240 | license-maven-plugin 241 | [4.1,) 242 | 243 | format 244 | 245 | 246 | 247 | 248 | true 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 |
259 | 260 | 261 | 262 | 263 | 264 | release 265 | 266 | 267 | 268 | 269 | 270 | org.apache.maven.plugins 271 | maven-gpg-plugin 272 | 3.0.1 273 | 274 | 275 | sign-artifacts 276 | verify 277 | 278 | sign 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 |
-------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ossrh 5 | ${env.SONATYPE_USERNAME} 6 | ${env.SONATYPE_PASSWORD} 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | /** 14 | * @author Arjan Tijms 15 | */ 16 | module org.omnifaces.utils { 17 | 18 | exports org.omnifaces.utils; 19 | 20 | exports org.omnifaces.utils.annotation; 21 | exports org.omnifaces.utils.collection; 22 | exports org.omnifaces.utils.data; 23 | exports org.omnifaces.utils.exceptions; 24 | exports org.omnifaces.utils.function; 25 | exports org.omnifaces.utils.image; 26 | exports org.omnifaces.utils.io; 27 | exports org.omnifaces.utils.logging; 28 | exports org.omnifaces.utils.math; 29 | exports org.omnifaces.utils.properties; 30 | exports org.omnifaces.utils.reflect; 31 | exports org.omnifaces.utils.security; 32 | exports org.omnifaces.utils.stream; 33 | exports org.omnifaces.utils.text; 34 | exports org.omnifaces.utils.time; 35 | 36 | requires transitive java.desktop; 37 | requires transitive java.logging; 38 | 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/Collections.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Collection; 17 | import java.util.HashMap; 18 | import java.util.HashSet; 19 | import java.util.Iterator; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.Map.Entry; 23 | import java.util.Set; 24 | 25 | public final class Collections { 26 | 27 | private Collections() { 28 | } 29 | 30 | /** 31 | * Creates an unmodifiable set based on the given values. If one of the values is an instance of an array or a 32 | * collection, then each of its values will also be merged into the set. Nested arrays or collections will result 33 | * in a {@link ClassCastException}. 34 | * @param The expected set element type. 35 | * @param values The values to create an unmodifiable set for. 36 | * @return An unmodifiable set based on the given values. 37 | * @throws ClassCastException When one of the values or one of the arrays or collections is of wrong type. 38 | */ 39 | @SuppressWarnings("unchecked") 40 | public static Set unmodifiableSet(Object... values) { 41 | Set set = new HashSet<>(); 42 | 43 | for (Object value : values) { 44 | if (value instanceof Object[]) { 45 | for (Object item : (Object[]) value) { 46 | set.add((E) item); 47 | } 48 | } 49 | else if (value instanceof Collection) { 50 | for (Object item : (Collection) value) { 51 | set.add((E) item); 52 | } 53 | } 54 | else { 55 | set.add((E) value); 56 | } 57 | } 58 | 59 | return java.util.Collections.unmodifiableSet(set); 60 | } 61 | 62 | /** 63 | * Converts an iterable into a list. 64 | *

65 | * This method makes NO guarantee to whether changes to the source iterable are 66 | * reflected in the returned list or not. For instance if the given iterable 67 | * already is a list, it's returned directly. 68 | * 69 | * @param The generic iterable element type. 70 | * @param iterable The iterable to be converted. 71 | * @return The list representation of the given iterable, possibly the same instance as that iterable. 72 | */ 73 | public static List iterableToList(Iterable iterable) { 74 | 75 | List list = null; 76 | 77 | if (iterable instanceof List) { 78 | list = (List) iterable; 79 | } else if (iterable instanceof Collection) { 80 | list = new ArrayList<>((Collection) iterable); 81 | } else { 82 | list = new ArrayList<>(); 83 | Iterator iterator = iterable.iterator(); 84 | while (iterator.hasNext()) { 85 | list.add(iterator.next()); 86 | } 87 | } 88 | 89 | return list; 90 | } 91 | 92 | /** 93 | * Returns a new map that contains the reverse of the given map. 94 | *

95 | * The reverse of a map means that every value X becomes a key X' with as corresponding 96 | * value Y' the key Y that was originally associated with the value X. 97 | * 98 | * @param The generic map key/value type. 99 | * @param source the map that is to be reversed 100 | * @return the reverse of the given map 101 | */ 102 | public static Map reverse(Map source) { 103 | Map target = new HashMap<>(); 104 | for (Entry entry : source.entrySet()) { 105 | target.put(entry.getValue(), entry.getKey()); 106 | } 107 | 108 | return target; 109 | } 110 | 111 | /** 112 | * Returns true if the collection is not null and contains the specified element. 113 | * 114 | * @param collection the collection to test for the specified element 115 | * @param object element to test for in the specified collection 116 | * @return true if the collection is not null and contains the specified element 117 | */ 118 | public static boolean contains(Collection collection, Object object) { 119 | return collection != null && collection.contains(object); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/Comparators.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils; 14 | 15 | import java.util.Comparator; 16 | import java.util.function.Predicate; 17 | 18 | public final class Comparators { 19 | 20 | private Comparators() { 21 | } 22 | 23 | /** 24 | * Create a new {@link Comparator} that places values that match the given {@link Predicate} before values that do not. 25 | * 26 | * If either both values match the predicate or both do not match the predicate, the order is determined by the given comparator. 27 | * 28 | * @param predicate the predicate to use to sort the values 29 | * @param comparator the comparator to use to sort any pairs of values that either both do or do not match the predicate 30 | * @param the type of the values 31 | * 32 | * @return the new comparator 33 | */ 34 | public static Comparator firstWhen(Predicate predicate, Comparator comparator) { 35 | return firstOrLastWhen(true, predicate, comparator); 36 | } 37 | 38 | /** 39 | * Create a new {@link Comparator} that places values that match the given {@link Predicate} after values that do not. 40 | * 41 | * If either both values match the predicate or both do not match the predicate, the order is determined by the given comparator. 42 | * 43 | * @param predicate the predicate to use to sort the values 44 | * @param comparator the comparator to use to sort any pairs of values that either both do or do not match the predicate 45 | * @param the type of the values 46 | * 47 | * @return the new comparator 48 | */ 49 | public static Comparator lastWhen(Predicate predicate, Comparator comparator) { 50 | return firstOrLastWhen(false, predicate, comparator); 51 | } 52 | 53 | private static Comparator firstOrLastWhen(boolean matchesFirst, Predicate predicate, Comparator comparator) { 54 | final int direction = matchesFirst ? -1 : 1; 55 | 56 | return (t1, t2) -> { 57 | boolean t1Matches = predicate.test(t1); 58 | boolean t2Matches = predicate.test(t2); 59 | 60 | if (t1Matches != t2Matches) { 61 | return t1Matches ? direction : -1 * direction; 62 | } 63 | 64 | return comparator.compare(t1, t2); 65 | }; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/Comparisons.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils; 14 | 15 | import static java.util.Comparator.naturalOrder; 16 | 17 | import java.util.Comparator; 18 | 19 | public final class Comparisons { 20 | 21 | private Comparisons() { 22 | } 23 | 24 | public static > T min(T t1, T t2) { 25 | return min(naturalOrder(), t1, t2); 26 | } 27 | 28 | @SafeVarargs 29 | public static > T min(T t1, T t2, T... ts) { 30 | return min(naturalOrder(), t1, t2, ts); 31 | } 32 | 33 | public static T min(Comparator comparator, T t1, T t2) { 34 | if (comparator.compare(t1, t2) <= 0) { 35 | return t1; 36 | } 37 | 38 | return t2; 39 | } 40 | 41 | @SafeVarargs 42 | private static > T min(Comparator tComparator, T t1, T t2, T... ts) { 43 | T min = min(tComparator, t1, t2); 44 | 45 | for (T t : ts) { 46 | min = min(tComparator, min, t); 47 | } 48 | 49 | return min; 50 | } 51 | 52 | public static > T max(T t1, T t2) { 53 | return max(naturalOrder(), t1, t2); 54 | } 55 | 56 | @SafeVarargs 57 | public static > T max(T t1, T t2, T... ts) { 58 | return max(naturalOrder(), t1, t2, ts); 59 | } 60 | 61 | public static T max(Comparator comparator, T t1, T t2) { 62 | if (comparator.compare(t1, t2) >= 0) { 63 | return t1; 64 | } 65 | 66 | return t2; 67 | } 68 | 69 | @SafeVarargs 70 | private static > T max(Comparator comparator, T t1, T t2, T... ts) { 71 | T max = max(comparator, t1, t2); 72 | 73 | for (T t : ts) { 74 | max = max(comparator, max, t); 75 | } 76 | 77 | return max; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/Lang.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils; 14 | 15 | import static java.lang.Character.isSpaceChar; 16 | import static java.lang.Character.toLowerCase; 17 | import static java.lang.Character.toUpperCase; 18 | 19 | import java.io.IOException; 20 | import java.lang.reflect.Array; 21 | import java.text.Normalizer; 22 | import java.text.Normalizer.Form; 23 | import java.util.Collection; 24 | import java.util.Map; 25 | import java.util.Optional; 26 | import java.util.Properties; 27 | import java.util.function.Consumer; 28 | import java.util.function.Supplier; 29 | 30 | public final class Lang { 31 | 32 | private Lang() { 33 | } 34 | 35 | /** 36 | * Returns true if the given string is null or is empty. 37 | * 38 | * @param string The string to be checked on emptiness. 39 | * @return true if the given string is null or is empty. 40 | */ 41 | public static boolean isEmpty(String string) { 42 | return string == null || string.isEmpty(); 43 | } 44 | 45 | /** 46 | * Returns true if the given array is null or is empty. 47 | * 48 | * @param array The array to be checked on emptiness. 49 | * @return true if the given array is null or is empty. 50 | */ 51 | public static boolean isEmpty(Object[] array) { 52 | return array == null || array.length == 0; 53 | } 54 | 55 | /** 56 | * Returns true if the given collection is null or is empty. 57 | * 58 | * @param collection The collection to be checked on emptiness. 59 | * @return true if the given collection is null or is empty. 60 | */ 61 | public static boolean isEmpty(Collection collection) { 62 | return collection == null || collection.isEmpty(); 63 | } 64 | 65 | /** 66 | * Returns true if the given map is null or is empty. 67 | * 68 | * @param map The map to be checked on emptiness. 69 | * @return true if the given map is null or is empty. 70 | */ 71 | public static boolean isEmpty(Map map) { 72 | return map == null || map.isEmpty(); 73 | } 74 | 75 | /** 76 | * Returns true if the given value is null or is empty. Types of String, Collection, Map, Optional and Array are 77 | * recognized. If none is recognized, then examine the emptiness of the toString() representation instead. 78 | * 79 | * @param value The value to be checked on emptiness. 80 | * @return true if the given value is null or is empty. 81 | */ 82 | public static boolean isEmpty(Object value) { 83 | if (value == null) { 84 | return true; 85 | } 86 | else if (value instanceof String) { 87 | return ((String) value).isEmpty(); 88 | } 89 | else if (value instanceof Collection) { 90 | return ((Collection) value).isEmpty(); 91 | } 92 | else if (value instanceof Map) { 93 | return ((Map) value).isEmpty(); 94 | } 95 | else if (value instanceof Optional) { 96 | return !((Optional)value).isPresent(); 97 | } 98 | else if (value.getClass().isArray()) { 99 | return Array.getLength(value) == 0; 100 | } 101 | else { 102 | return value.toString() == null || value.toString().isEmpty(); 103 | } 104 | } 105 | 106 | /** 107 | * Returns true if all values are empty, false if at least one value is not empty. 108 | * @param values the values to be checked on emptiness 109 | * @return True if all values are empty, false otherwise 110 | */ 111 | public static boolean isAllEmpty(Object... values) { 112 | for (Object value : values) { 113 | if (!isEmpty(value)) { 114 | return false; 115 | } 116 | } 117 | 118 | return true; 119 | } 120 | 121 | /** 122 | * Returns true if at least one value is empty. 123 | * 124 | * @param values the values to be checked on emptiness 125 | * @return true if any value is empty and false if no values are empty 126 | */ 127 | public static boolean isAnyEmpty(Object... values) { 128 | for (Object value : values) { 129 | if (isEmpty(value)) { 130 | return true; 131 | } 132 | } 133 | 134 | return false; 135 | } 136 | 137 | public static boolean isNotBlank(String string) { 138 | return string != null && !string.trim().isEmpty(); 139 | } 140 | 141 | public static T requireNotEmpty(T value, Supplier exceptionSupplier) throws E { 142 | if (isEmpty(value)) { 143 | throw exceptionSupplier.get(); 144 | } 145 | 146 | return value; 147 | } 148 | 149 | public static T ifEmptyGet(T value, Supplier defaultSupplier) { 150 | if (isEmpty(value)) { 151 | return defaultSupplier.get(); 152 | } 153 | 154 | return value; 155 | } 156 | 157 | /** 158 | * Call the given setter with the given value if {@link #isEmpty(Object)} returns false for the given value. 159 | * 160 | * @param value the value to set 161 | * @param setter a consumer that calls the setter with the value 162 | * @param the generic type of the value 163 | */ 164 | public static void setIfNotEmpty(T value, Consumer setter) { 165 | if (!isEmpty(value)) { 166 | setter.accept(value); 167 | } 168 | } 169 | 170 | /** 171 | * Returns the first non-null object of the argument list, or null if there is no such element. 172 | * 173 | * @param The generic object type. 174 | * @param objects The argument list of objects to be tested for non-null. 175 | * @return The first non-null object of the argument list, or null if there is no such element. 176 | */ 177 | @SafeVarargs 178 | public static T coalesce(T... objects) { 179 | for (T object : objects) { 180 | if (object != null) { 181 | return object; 182 | } 183 | } 184 | 185 | return null; 186 | } 187 | 188 | /** 189 | * Returns true if the given object equals one of the given objects. 190 | * 191 | * @param The generic object type. 192 | * @param object The object to be checked if it equals one of the given objects. 193 | * @param objects The argument list of objects to be tested for equality. 194 | * @return true if the given object equals one of the given objects. 195 | */ 196 | @SafeVarargs 197 | public static boolean isOneOf(T object, T... objects) { 198 | for (Object other : objects) { 199 | if (object == null ? other == null : object.equals(other)) { 200 | return true; 201 | } 202 | } 203 | 204 | return false; 205 | } 206 | 207 | /** 208 | * Returns true if the given string starts with one of the given prefixes. 209 | * @param string The string to be checked if it starts with one of the given prefixes. 210 | * @param prefixes The argument list of prefixes to be checked. 211 | * @return true if the given string starts with one of the given prefixes. 212 | */ 213 | public static boolean startsWithOneOf(String string, String... prefixes) { 214 | for (String prefix : prefixes) { 215 | if (string.startsWith(prefix)) { 216 | return true; 217 | } 218 | } 219 | 220 | return false; 221 | } 222 | 223 | /** 224 | * Returns true if the given string ends with one of the given suffixes. 225 | * @param string The string to be checked if it ends with one of the given suffixes. 226 | * @param suffixes The argument list of suffixes to be checked. 227 | * @return true if the given string ends with one of the given suffixes. 228 | */ 229 | public static boolean endsWithOneOf(String string, String... suffixes) { 230 | for (String suffix : suffixes) { 231 | if (string.endsWith(suffix)) { 232 | return true; 233 | } 234 | } 235 | 236 | return false; 237 | } 238 | 239 | /** 240 | * Replaces the last substring of given string that matches the given regular expression with the given replacement. 241 | * 242 | * Author: https://stackoverflow.com/a/2282998 243 | * 244 | * @param string The string to be replaced. 245 | * @param regex The regular expression to which given string is to be matched. 246 | * @param replacement The string to be substituted for the last match. 247 | * @return The resulting string. 248 | */ 249 | public static String replaceLast(String string, String regex, String replacement) { 250 | return string.replaceFirst("(?s)(.*)" + regex, "$1" + replacement); 251 | } 252 | 253 | /** 254 | * Returns true if the given string contains any ISO control characters. 255 | * 256 | * @param string the string to check for control characters 257 | * @return true if the string contains any ISO control characters and false otherwise 258 | */ 259 | public static boolean containsIsoControlCharacters(String string) { 260 | return string.codePoints().anyMatch(Character::isISOControl); 261 | } 262 | 263 | /** 264 | * Converts the first character of given string to upper case. 265 | * @param string String to be capitalized. 266 | * @return The given string capitalized. 267 | */ 268 | public static String capitalize(String string) { 269 | if (string == null || string.isEmpty()) { 270 | return string; 271 | } 272 | 273 | return toUpperCase(string.charAt(0)) + string.substring(1); 274 | } 275 | 276 | /** 277 | * Converts given string to title case. 278 | * @param string String to be converted to title case. 279 | * @return The given string converted to title case. 280 | */ 281 | public static String toTitleCase(String string) { 282 | if (string == null) { 283 | return null; 284 | } 285 | 286 | return string.codePoints().collect(StringBuilder::new, (sb, cp) -> { 287 | sb.appendCodePoint(sb.length() == 0 || isSpaceChar(sb.charAt(sb.length() - 1)) ? toUpperCase(cp) : toLowerCase(cp)); 288 | }, (sb1, sb2) -> {}).toString(); 289 | } 290 | 291 | /** 292 | * Converts given string to URL safe format, also called a "slug". 293 | * @param string String to be converted to URL safe format. 294 | * @return The given string converted to URL safe format. 295 | */ 296 | public static String toUrlSafe(String string) { 297 | if (string == null) { 298 | return null; 299 | } 300 | 301 | return Normalizer.normalize(string.trim(), Form.NFD) 302 | .replaceAll("\\p{InCombiningDiacriticalMarks}+", "") 303 | .replaceAll("[^\\p{Alnum}]+", "-") 304 | .replaceAll("(^-|-$)", ""); 305 | } 306 | 307 | /** 308 | * Escape given string as valid {@link Properties} entry value. 309 | * @param string String to be escaped as valid {@link Properties} entry value. 310 | * @return The given string escaped as valid {@link Properties} entry value. 311 | * @throws IOException When appending a character fails. 312 | */ 313 | public static String escapeAsProperty(String string) throws IOException { 314 | Appendable builder = new StringBuilder(string.length()); 315 | 316 | for (char c : string.toCharArray()) { 317 | if ((c > 61) && (c < 127)) { 318 | if (c == '\\') { 319 | builder.append('\\'); 320 | builder.append('\\'); 321 | continue; 322 | } 323 | builder.append(c); 324 | continue; 325 | } 326 | switch(c) { 327 | case '\t':builder.append('\\'); builder.append('t'); 328 | break; 329 | case '\n':builder.append('\\'); builder.append('n'); 330 | break; 331 | case '\r':builder.append('\\'); builder.append('r'); 332 | break; 333 | case '\f':builder.append('\\'); builder.append('f'); 334 | break; 335 | case '=': // Fall through 336 | case ':': 337 | builder.append('\\'); builder.append(c); 338 | break; 339 | default: 340 | if ((c < 0x0020) || (c > 0x007e)) { 341 | builder.append(String.format("\\u%04x", (int) c)); 342 | } else { 343 | builder.append(c); 344 | } 345 | } 346 | } 347 | 348 | return builder.toString(); 349 | } 350 | 351 | } -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/annotation/AnnotationInvocationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.annotation; 14 | 15 | import java.io.Serializable; 16 | import java.lang.annotation.Annotation; 17 | import java.lang.reflect.InvocationHandler; 18 | import java.lang.reflect.Method; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.Objects; 22 | 23 | /** 24 | * {@link InvocationHandler} implementation that implements the base methods required for an annotation. 25 | */ 26 | class AnnotationInvocationHandler implements InvocationHandler, Serializable { 27 | 28 | private static final long serialVersionUID = 1L; 29 | private static final Object[] NO_ARGS = new Object[0]; 30 | 31 | private final Class type; 32 | private final Map attributes; 33 | 34 | /** 35 | * Create a new {@link AnnotationInvocationHandler} instance for the given annotation type and its attributes. 36 | * @param type The annotation type this handler is for. 37 | * @param attributes The attributes of the annotation, may be a partial map or even an empty map. 38 | */ 39 | AnnotationInvocationHandler(Class type, Map attributes) { 40 | this.type = type; 41 | this.attributes = new HashMap<>(attributes); 42 | 43 | for (Method method : type.getDeclaredMethods()) { 44 | this.attributes.putIfAbsent(method.getName(), method.getDefaultValue()); 45 | } 46 | } 47 | 48 | public boolean equals(Object proxy, Object other) { 49 | if (type.isInstance(other)) { 50 | try { 51 | Method[] methods = type.getDeclaredMethods(); 52 | 53 | if (methods.length == attributes.size()) { 54 | for (Method method : methods) { 55 | if (!Objects.deepEquals(invoke(proxy, method, NO_ARGS), method.invoke(other))) { 56 | return false; 57 | } 58 | } 59 | 60 | return true; 61 | } 62 | } 63 | catch (Throwable ignore) { 64 | // 65 | } 66 | } 67 | 68 | return false; 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | int hashCode = 0; 74 | 75 | for (Method method : type.getDeclaredMethods()) { 76 | try { 77 | hashCode += Objects.hashCode(invoke(null, method, NO_ARGS)) ^ 127 * method.getName().hashCode(); 78 | } 79 | catch (Throwable ignore) { 80 | // 81 | } 82 | } 83 | 84 | return hashCode; 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return "@" + type.getName() + "(" + attributes + ")"; 90 | } 91 | 92 | @Override 93 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 94 | switch (method.getName()) { 95 | case "annotationType": 96 | return type; 97 | case "equals": 98 | return args.length > 0 && equals(proxy, args[0]); 99 | case "hashCode": 100 | return hashCode(); 101 | case "toString": 102 | return toString(); 103 | default: 104 | return attributes.get(method.getName()); 105 | } 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/annotation/Annotations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.annotation; 14 | 15 | import java.lang.annotation.Annotation; 16 | import java.lang.reflect.Array; 17 | import java.lang.reflect.InvocationHandler; 18 | import java.lang.reflect.Proxy; 19 | import java.util.Collections; 20 | import java.util.Map; 21 | 22 | public final class Annotations { 23 | 24 | private Annotations() { 25 | // 26 | } 27 | 28 | /** 29 | * Create an instance of the specified annotation type with default attributes. 30 | * @param The generic annotation type. 31 | * @param type The annotation type. 32 | * @return An annotation instance of the specified annotation type with default attributes. 33 | */ 34 | public static A createAnnotationInstance(Class type) { 35 | return createAnnotationInstances(type)[0]; 36 | } 37 | 38 | /** 39 | * Create an array of instances of the specified annotation types with default attributes. Useful for varargs calls 40 | * such as CDI.current().select(type, createAnnotationInstances(Qualifier1.class, Qualifier2.class)). 41 | * @param The generic annotation type. 42 | * @param types The annotation types. 43 | * @return An array of instances of the specified annotation types with default attributes. 44 | */ 45 | @SafeVarargs 46 | @SuppressWarnings("unchecked") 47 | public static A[] createAnnotationInstances(Class... types) { 48 | if (types == null) { 49 | return null; 50 | } 51 | 52 | A[] instances = (A[]) Array.newInstance(Annotation.class, types.length); 53 | 54 | for (int i = 0; i < types.length; i++) { 55 | instances[i] = createAnnotationInstance(types[i], Collections.emptyMap()); 56 | } 57 | 58 | return instances; 59 | } 60 | 61 | /** 62 | * Create an instance of the specified annotation type with given attributes. 63 | * @param The generic annotation type. 64 | * @param type The annotation type. 65 | * @param attributes The annotation attributes. May be a partial attribute map or even an empty map. 66 | * @return An annotation instance of the specified annotation type with given attributes. 67 | */ 68 | public static A createAnnotationInstance(Class type, Map attributes) { 69 | InvocationHandler handler = new AnnotationInvocationHandler(type, attributes); 70 | return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, handler)); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/collection/PartialResultList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.collection; 14 | 15 | import static java.util.Collections.unmodifiableList; 16 | 17 | import java.io.Serializable; 18 | import java.util.Collection; 19 | import java.util.Iterator; 20 | import java.util.List; 21 | import java.util.ListIterator; 22 | 23 | /** 24 | * An immutable list that represents a subset of a larger sequence of results. This list maintains the position of this list within the larger 25 | * sequence and an estimation of the total number of results in this larger sequence. 26 | * 27 | * The position of a single element of this list in the full sequence can be determined by taking the index of the element within this list and adding 28 | * the offset of this list in the sequence to it. So if an item is at index i, this item is at position i + list.getOffset() 29 | * in the full sequence. 30 | * 31 | * @param 32 | * the type of element the list should contain 33 | */ 34 | public class PartialResultList implements List, Serializable { 35 | 36 | private static final long serialVersionUID = 1L; 37 | 38 | public static final int UNKNOWN_NUMBER_OF_RESULTS = -1; 39 | 40 | private List wrappedList; 41 | 42 | private int offset; 43 | private int estimatedTotalNumberOfResults; 44 | 45 | public PartialResultList(List wrappedList, int offset, int estimatedTotalNumberOfResults) { 46 | // Delegate to unmodifiableList to avoid having to implement unmodifiable iterators, etc. 47 | this.wrappedList = unmodifiableList(wrappedList); 48 | this.offset = offset; 49 | this.estimatedTotalNumberOfResults = estimatedTotalNumberOfResults; 50 | } 51 | 52 | @Override 53 | public int size() { 54 | return wrappedList.size(); 55 | } 56 | 57 | @Override 58 | public boolean isEmpty() { 59 | return wrappedList.isEmpty(); 60 | } 61 | 62 | @Override 63 | public boolean contains(Object o) { 64 | return wrappedList.contains(o); 65 | } 66 | 67 | @Override 68 | public Iterator iterator() { 69 | return wrappedList.iterator(); 70 | } 71 | 72 | @Override 73 | public Object[] toArray() { 74 | return wrappedList.toArray(); 75 | } 76 | 77 | @Override 78 | public T[] toArray(T[] a) { 79 | return wrappedList.toArray(a); 80 | } 81 | 82 | @Override 83 | public boolean add(E e) { 84 | return wrappedList.add(e); 85 | } 86 | 87 | @Override 88 | public boolean remove(Object o) { 89 | return wrappedList.remove(o); 90 | } 91 | 92 | @Override 93 | public boolean containsAll(Collection c) { 94 | return wrappedList.containsAll(c); 95 | } 96 | 97 | @Override 98 | public boolean addAll(Collection c) { 99 | return wrappedList.addAll(c); 100 | } 101 | 102 | @Override 103 | public boolean addAll(int index, Collection c) { 104 | return wrappedList.addAll(index, c); 105 | } 106 | 107 | @Override 108 | public boolean removeAll(Collection c) { 109 | return wrappedList.removeAll(c); 110 | } 111 | 112 | @Override 113 | public boolean retainAll(Collection c) { 114 | return wrappedList.retainAll(c); 115 | } 116 | 117 | @Override 118 | public void clear() { 119 | wrappedList.clear(); 120 | } 121 | 122 | @Override 123 | public E get(int index) { 124 | return wrappedList.get(index); 125 | } 126 | 127 | @Override 128 | public E set(int index, E element) { 129 | return wrappedList.set(index, element); 130 | } 131 | 132 | @Override 133 | public void add(int index, E element) { 134 | wrappedList.add(index, element); 135 | } 136 | 137 | @Override 138 | public E remove(int index) { 139 | return wrappedList.remove(index); 140 | } 141 | 142 | @Override 143 | public int indexOf(Object o) { 144 | return wrappedList.indexOf(o); 145 | } 146 | 147 | @Override 148 | public int lastIndexOf(Object o) { 149 | return wrappedList.lastIndexOf(o); 150 | } 151 | 152 | @Override 153 | public ListIterator listIterator() { 154 | return wrappedList.listIterator(); 155 | } 156 | 157 | @Override 158 | public ListIterator listIterator(int index) { 159 | return wrappedList.listIterator(index); 160 | } 161 | 162 | @Override 163 | public List subList(int fromIndex, int toIndex) { 164 | return wrappedList.subList(fromIndex, toIndex); 165 | } 166 | 167 | /** 168 | * Returns the offset of this list in the full sequence. 169 | * 170 | * @return the offset of this list in the full sequence 171 | */ 172 | public int getOffset() { 173 | return offset; 174 | } 175 | 176 | /** 177 | * Returns the estimated total number of results in the full sequence. 178 | * 179 | * @return the total number of results in the full sequence or -1 if no estimate can be given 180 | */ 181 | public int getEstimatedTotalNumberOfResults() { 182 | return estimatedTotalNumberOfResults; 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/data/AbstractRange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.data; 14 | 15 | import static java.util.Objects.requireNonNull; 16 | 17 | import java.io.Serializable; 18 | import java.util.Objects; 19 | 20 | public abstract class AbstractRange implements Serializable, Range { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | private void checkNonNull(Range range) { 25 | requireNonNull(range, "other"); 26 | requireNonNull(range.getMin(), (range != this ? "other " : "") + "min"); 27 | requireNonNull(range.getMax(), (range != this ? "other " : "") + "max"); 28 | } 29 | 30 | @Override 31 | public boolean intersects(Range other) { 32 | checkNonNull(this); 33 | checkNonNull(other); 34 | 35 | boolean intersects; 36 | if (!isMinInclusive() || !other.isMaxInclusive()) { 37 | intersects = compare(getMin(), other.getMax()) < 0; 38 | } 39 | else { 40 | intersects = compare(getMin(), other.getMax()) <= 0; 41 | } 42 | 43 | if (!isMaxInclusive() || !other.isMinInclusive()) { 44 | intersects &= compare(getMax(), other.getMin()) > 0; 45 | } 46 | else { 47 | intersects &= compare(getMax(), other.getMin()) >= 0; 48 | } 49 | 50 | return intersects; 51 | } 52 | 53 | protected abstract int compare(N left, N right); 54 | 55 | @Override 56 | public boolean contains(N value) { 57 | int minComparison = getMin() == null ? -1 : compare(getMin(), value); 58 | int maxComparison = getMax() == null ? 1 : compare(getMax(), value); 59 | 60 | return (isMinInclusive() ? minComparison <= 0 : minComparison < 0) && (isMaxInclusive() ? maxComparison >= 0 : maxComparison > 0); 61 | } 62 | 63 | @Override 64 | public boolean equals(Object object) { 65 | if (object == this) { 66 | return true; 67 | } 68 | 69 | if (object instanceof Range) { 70 | Range other = (Range) object; 71 | 72 | return Objects.equals(getMin(), other.getMin()) && 73 | Objects.equals(getMax(), other.getMax())&& 74 | Objects.equals(isMinInclusive(), other.isMinInclusive()) && 75 | Objects.equals(isMaxInclusive(), other.isMaxInclusive()); 76 | } 77 | 78 | return false; 79 | } 80 | 81 | @Override 82 | public int hashCode() { 83 | return Objects.hash(getClass(), getMin(), getMax(), isMinInclusive(), isMaxInclusive()); 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return (isMinInclusive() ? "(" : "[") + getMin() + ".." + getMax() + (isMaxInclusive() ? ")" :"]"); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/data/ImmutableRangeImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.data; 14 | 15 | import static java.util.Objects.requireNonNull; 16 | 17 | import java.util.Comparator; 18 | import java.util.Objects; 19 | 20 | class ImmutableRangeImpl extends AbstractRange { 21 | 22 | private static final long serialVersionUID = -1899797137173600162L; 23 | 24 | private final N min; 25 | private final N max; 26 | private final boolean minInclusive; 27 | private final boolean maxInclusive; 28 | private final Comparator comparator; 29 | 30 | ImmutableRangeImpl(N min, N max, boolean minInclusive, boolean maxInclusive, Comparator comparator) { 31 | this.min = requireNonNull(min); 32 | this.max = requireNonNull(max); 33 | this.minInclusive = minInclusive; 34 | this.maxInclusive = maxInclusive; 35 | this.comparator = requireNonNull(comparator); 36 | } 37 | 38 | @Override 39 | public N getMin() { 40 | return min; 41 | } 42 | 43 | @Override 44 | public N getMax() { 45 | return max; 46 | } 47 | 48 | @Override 49 | public boolean isMinInclusive() { 50 | return minInclusive; 51 | } 52 | 53 | @Override 54 | public boolean isMaxInclusive() { 55 | return maxInclusive; 56 | } 57 | 58 | @Override 59 | protected int compare(N left, N right) { 60 | return Objects.compare(left, right, comparator); 61 | } 62 | 63 | @Override 64 | public Range withMin(N newMin) { 65 | return new ImmutableRangeImpl<>(requireNonNull(newMin), max, minInclusive, maxInclusive, comparator); 66 | } 67 | 68 | @Override 69 | public Range withMax(N newMax) { 70 | return new ImmutableRangeImpl<>(min, requireNonNull(newMax), minInclusive, maxInclusive, comparator); 71 | } 72 | 73 | @Override 74 | public Range withMinInclusive(boolean newMinInclusive) { 75 | return new ImmutableRangeImpl<>(min, max, newMinInclusive, maxInclusive, comparator); 76 | } 77 | 78 | @Override 79 | public Range withMaxInclusive(boolean newMaxInclusive) { 80 | return new ImmutableRangeImpl<>(min, max, minInclusive, newMaxInclusive, comparator); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/data/MutableRange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.data; 14 | 15 | import static java.util.Comparator.naturalOrder; 16 | 17 | import java.util.Comparator; 18 | 19 | public interface MutableRange extends Range { 20 | void setMin(N min); 21 | 22 | void setMax(N max); 23 | 24 | static > MutableRange of(N min, N max) { 25 | return new MutableRangeImpl<>(min, max, true, false, naturalOrder()); 26 | } 27 | 28 | static MutableRange of(N min, N max, Comparator comparator) { 29 | return new MutableRangeImpl<>(min, max, true, false, comparator); 30 | } 31 | 32 | static MutableRange ofDouble(double min, double max) { 33 | return new MutableRangeImpl<>(min, max, true, false, naturalOrder()); 34 | } 35 | 36 | static MutableRange ofInteger(int min, int max) { 37 | return new MutableRangeImpl<>(min, max, true, false, naturalOrder()); 38 | } 39 | 40 | static MutableRange ofLong(long min, long max) { 41 | return new MutableRangeImpl<>(min, max, true, false, naturalOrder()); 42 | } 43 | 44 | static > MutableRange ofClosed(N min, N max) { 45 | return new MutableRangeImpl<>(min, max, true, true, naturalOrder()); 46 | } 47 | 48 | static MutableRange ofClosed(N min, N max, Comparator comparator) { 49 | return new MutableRangeImpl<>(min, max, true, true, comparator); 50 | } 51 | 52 | static MutableRange ofDoubleClosed(double min, double max) { 53 | return new MutableRangeImpl<>(min, max, true, true, naturalOrder()); 54 | } 55 | 56 | static MutableRange ofIntegerClosed(int min, int max) { 57 | return new MutableRangeImpl<>(min, max, true, true, naturalOrder()); 58 | } 59 | 60 | static MutableRange ofLongClosed(long min, long max) { 61 | return new MutableRangeImpl<>(min, max, true, true, naturalOrder()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/data/MutableRangeImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.data; 14 | 15 | import static java.util.Objects.requireNonNull; 16 | 17 | import java.util.Comparator; 18 | import java.util.Objects; 19 | 20 | class MutableRangeImpl extends AbstractRange implements MutableRange { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | private N min; 25 | private N max; 26 | 27 | private boolean minInclusive; 28 | private boolean maxInclusive; 29 | 30 | private final Comparator comparator; 31 | 32 | MutableRangeImpl(N min, N max, boolean minInclusive, boolean maxInclusive, Comparator comparator) { 33 | this.min = min; 34 | this.max = max; 35 | this.minInclusive = minInclusive; 36 | this.maxInclusive = maxInclusive; 37 | this.comparator = comparator; 38 | } 39 | 40 | @Override 41 | public N getMin() { 42 | return min; 43 | } 44 | 45 | @Override 46 | public void setMin(N min) { 47 | this.min = min; 48 | } 49 | 50 | @Override 51 | public N getMax() { 52 | return max; 53 | } 54 | 55 | @Override 56 | public void setMax(N max) { 57 | this.max = max; 58 | } 59 | 60 | @Override 61 | public boolean isMinInclusive() { 62 | return minInclusive; 63 | } 64 | 65 | @Override 66 | public boolean isMaxInclusive() { 67 | return maxInclusive; 68 | } 69 | 70 | @Override 71 | public Range withMin(N newMin) { 72 | return new MutableRangeImpl<>(requireNonNull(newMin), max, minInclusive, maxInclusive, comparator); 73 | } 74 | 75 | @Override 76 | public Range withMax(N newMax) { 77 | return new MutableRangeImpl<>(min, requireNonNull(newMax), minInclusive, maxInclusive, comparator); 78 | } 79 | 80 | @Override 81 | public Range withMinInclusive(boolean newMinInclusive) { 82 | return new MutableRangeImpl<>(min, max, newMinInclusive, maxInclusive, comparator); 83 | } 84 | 85 | @Override 86 | public Range withMaxInclusive(boolean newMaxInclusive) { 87 | return new MutableRangeImpl<>(min, max, minInclusive, newMaxInclusive, comparator); 88 | } 89 | 90 | @Override 91 | protected int compare(N left, N right) { 92 | return Objects.compare(left, right, comparator); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/data/Range.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.data; 14 | 15 | import static java.util.Comparator.naturalOrder; 16 | import static java.util.Objects.requireNonNull; 17 | import static java.util.Spliterator.DISTINCT; 18 | import static java.util.Spliterator.IMMUTABLE; 19 | import static java.util.Spliterator.NONNULL; 20 | import static java.util.Spliterator.ORDERED; 21 | import static java.util.Spliterator.SORTED; 22 | 23 | import java.util.Comparator; 24 | import java.util.Iterator; 25 | import java.util.Spliterator; 26 | import java.util.Spliterators; 27 | import java.util.function.UnaryOperator; 28 | import java.util.stream.Stream; 29 | import java.util.stream.StreamSupport; 30 | 31 | public interface Range { 32 | 33 | N getMin(); 34 | 35 | N getMax(); 36 | 37 | boolean isMinInclusive(); 38 | 39 | boolean isMaxInclusive(); 40 | 41 | /** 42 | * Check if the given range intersects the current range. 43 | * 44 | * @param other 45 | * the range to check against the current range 46 | * 47 | * @return true if both ranges intersect and false otherwise 48 | */ 49 | boolean intersects(Range other); 50 | 51 | /** 52 | * Check if a given value is contained within this range 53 | * 54 | * @param value 55 | * the value to check 56 | * 57 | * @return true if the value is contained by this range and false otherwise 58 | */ 59 | boolean contains(N value); 60 | 61 | /** 62 | * Create a copy of the current range with the given min value. 63 | * 64 | * @param min 65 | * the new min value for the copy 66 | * 67 | * @return a copy of the current range 68 | */ 69 | Range withMin(N min); 70 | 71 | /** 72 | * Create a copy of the current range with the given max value. 73 | * 74 | * @param max 75 | * the new max value for the copy 76 | * 77 | * @return a copy of the current range 78 | */ 79 | Range withMax(N max); 80 | 81 | /** 82 | * Create a copy of the current range where the min value will be either inclusive or exclusive. 83 | * 84 | * @param minInclusive 85 | * boolean indicating if the min value should be inclusive or exclusive 86 | * 87 | * @return a copy of the current range 88 | */ 89 | Range withMinInclusive(boolean minInclusive); 90 | 91 | /** 92 | * Create a copy of the current range where the max value will be either inclusive or exclusive. 93 | * 94 | * @param maxInclusive 95 | * boolean indicating if the max value should be inclusive or exclusive 96 | * 97 | * @return a copy of the current range 98 | */ 99 | Range withMaxInclusive(boolean maxInclusive); 100 | 101 | /** 102 | * Return a {@link Stream} containing all values contained by the range, with a given incrementer. 103 | *

104 | * This method should not return the min or max value if they are exclusive. 105 | *

106 | * The default implementation will apply the incrementer repeatedly on the current value, starting with the min value and will continue 107 | * as long as {@link Range#contains(Object)} returns true. 108 | * 109 | * @param incrementer 110 | * the incrementer to use to determine the next value 111 | * 112 | * @return a stream containing all values within the range 113 | */ 114 | default Stream stream(UnaryOperator incrementer) { 115 | N min = requireNonNull(getMin()); 116 | 117 | N start = contains(min) ? min : incrementer.apply(min); 118 | 119 | Iterator iterator = new Iterator() { 120 | 121 | private N next = start; 122 | 123 | @Override 124 | public boolean hasNext() { 125 | return contains(next); 126 | } 127 | 128 | @Override 129 | public N next() { 130 | N n = next; 131 | 132 | next = incrementer.apply(next); 133 | 134 | return n; 135 | } 136 | }; 137 | 138 | Spliterator spliterator = Spliterators.spliteratorUnknownSize(iterator, DISTINCT | IMMUTABLE | NONNULL | ORDERED | SORTED); 139 | 140 | return StreamSupport.stream(spliterator, false); 141 | } 142 | 143 | static > Range of(N min, N max) { 144 | return new ImmutableRangeImpl<>(min, max, true, false, naturalOrder()); 145 | } 146 | 147 | static Range of(N min, N max, Comparator comparator) { 148 | return new ImmutableRangeImpl<>(min, max, true, false, comparator); 149 | } 150 | 151 | static Range ofDouble(double min, double max) { 152 | return new ImmutableRangeImpl<>(min, max, true, false, naturalOrder()); 153 | } 154 | 155 | static Range ofInteger(int min, int max) { 156 | return new ImmutableRangeImpl<>(min, max, true, false, naturalOrder()); 157 | } 158 | 159 | static Range ofLong(long min, long max) { 160 | return new ImmutableRangeImpl<>(min, max, true, false, naturalOrder()); 161 | } 162 | 163 | static > Range ofClosed(N min, N max) { 164 | return new ImmutableRangeImpl<>(min, max, true, true, naturalOrder()); 165 | } 166 | 167 | static Range ofClosed(N min, N max, Comparator comparator) { 168 | return new ImmutableRangeImpl<>(min, max, true, true, comparator); 169 | } 170 | 171 | static Range ofDoubleClosed(double min, double max) { 172 | return new ImmutableRangeImpl<>(min, max, true, true, naturalOrder()); 173 | } 174 | 175 | static Range ofIntegerClosed(int min, int max) { 176 | return new ImmutableRangeImpl<>(min, max, true, true, naturalOrder()); 177 | } 178 | 179 | static Range ofLongClosed(long min, long max) { 180 | return new ImmutableRangeImpl<>(min, max, true, true, naturalOrder()); 181 | } 182 | 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/exceptions/Exceptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.exceptions; 14 | 15 | import static java.util.Arrays.asList; 16 | import static java.util.Collections.unmodifiableList; 17 | import static java.util.stream.Collectors.joining; 18 | 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import java.util.Objects; 22 | import java.util.function.Predicate; 23 | import java.util.stream.Stream; 24 | 25 | public final class Exceptions { 26 | 27 | private static final List JAVA_SE_STACK_TRACE_EXCLUSIONS = unmodifiableList(asList( 28 | "java.lang.reflect", 29 | "java.lang.Thread.run", 30 | "sun.reflect" 31 | )); 32 | 33 | // TODO Give clearer name and expand with packages from other containers 34 | private static final List JAVA_EE_STACK_TRACE_EXCLUSIONS = unmodifiableList(asList( 35 | "org.omnifaces.filter.HttpFilter", 36 | "com.sun.faces.el.DemuxCompositeELResolver._getValue", 37 | "com.sun.faces.lifecycle.Phase.doPhase", 38 | "javax.faces.component.UIComponentBase.processValidators", 39 | "org.apache.catalina.core", 40 | "org.apache.catalina.valves.ErrorReportValve", 41 | "org.apache.coyote.http11.Http11Protocol", 42 | "org.apache.el.parser.AstValue", 43 | // JBoss/WildFly specific exclusions 44 | "org.jboss.aop", 45 | "org.jboss.aspects", 46 | "org.jboss.as.ee.component", 47 | "org.jboss.as.ee.component.interceptors.UserInterceptorFactory", 48 | "org.jboss.as.ejb3.component.interceptors", 49 | "org.jboss.as.ejb3.tx.CMTTxInterceptor", 50 | "org.jboss.as.web.deployment.component.WebComponentInstantiator$2.", 51 | "org.jboss.as.weld.ejb.Jsr299BindingsInterceptor", 52 | "org.jboss.ejb3", 53 | "org.jboss.invocation.InterceptorContext.proceed", 54 | "org.jboss.invocation.WeavedInterceptor.processInvocation", 55 | "org.jboss.weld.bean.proxy.EnterpriseBeanProxyMethodHandler.invoke", 56 | "org.jboss.weld.bean.proxy.EnterpriseTargetBeanInstance", 57 | "org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke", 58 | "org.jboss.weld.util.reflection.SecureReflection", 59 | "org.jboss.invocation.ChainedInterceptor.processInvocation", 60 | "org.jboss.invocation.InterceptorContext$Invocation.proceed" 61 | )); 62 | 63 | public static final int SHORT_STACKTRACE_DEPTH = 2; 64 | 65 | private Exceptions() { 66 | } 67 | 68 | public static String getRecursiveStackTrace(Throwable throwable) { 69 | return getRecursiveStackTrace(throwable, stackTraceElement -> true); 70 | } 71 | 72 | public static String getRecursiveStackTrace(Throwable throwable, Predicate filter) { 73 | StringBuilder headerBuilder = new StringBuilder("Exception summary:\n\n"); 74 | StringBuilder builder = new StringBuilder("\n\nException details:"); 75 | 76 | int exceptionLevel = 0; 77 | 78 | Throwable rootCause = null; 79 | Throwable currentThrowable = throwable; 80 | 81 | while (currentThrowable != null) { 82 | String currentMessage = "Exception level " + exceptionLevel + ": " + getNameAndMessage(currentThrowable) + "\n"; 83 | builder.append("\n\n").append(currentMessage); 84 | headerBuilder.append(currentMessage); 85 | 86 | appendStackTrace(builder, currentThrowable, filter, exceptionLevel, 1); 87 | 88 | rootCause = currentThrowable; 89 | 90 | currentThrowable = currentThrowable.getCause(); 91 | exceptionLevel++; 92 | } 93 | 94 | StringBuilder messageBuilder = new StringBuilder().append(headerBuilder); 95 | 96 | if (exceptionLevel > 1) { 97 | 98 | messageBuilder.append("\n\nRoot cause at level ") 99 | .append(exceptionLevel - 1) 100 | .append(": ") 101 | .append(getNameAndMessage(rootCause)) 102 | .append("\n"); 103 | 104 | appendShortStackTrace(messageBuilder, rootCause, filter, 1); 105 | } 106 | 107 | return messageBuilder.append(builder) 108 | .toString(); 109 | } 110 | 111 | private static void appendShortStackTrace(StringBuilder builder, Throwable throwable, Predicate filter, int indentLevel) { 112 | String indentString = getIndentString(indentLevel); 113 | 114 | Arrays.stream(throwable.getStackTrace()) 115 | .filter(filter) 116 | .limit(SHORT_STACKTRACE_DEPTH) 117 | .forEach(stackTraceElement -> builder.append(indentString).append("at ").append(stackTraceElement).append("\n")); 118 | } 119 | 120 | private static void appendStackTrace(StringBuilder bodyBuilder, Throwable throwable, Predicate filter, int exceptionLevel, 121 | int indentLevel) { 122 | String indentString = getIndentString(indentLevel); 123 | 124 | appendStackTrace(bodyBuilder, throwable, exceptionLevel, indentString, filter); 125 | 126 | } 127 | 128 | private static void appendStackTrace(StringBuilder builder, Throwable throwable, int exceptionLevel, String indentString, 129 | Predicate filter) { 130 | Arrays.stream(throwable.getStackTrace()) 131 | .filter(filter) 132 | .forEach(stackTraceElement -> builder.append(indentString).append("at ").append(stackTraceElement).append("\n")); 133 | 134 | 135 | for (Throwable suppressed : throwable.getSuppressed()) { 136 | builder.append(indentString) 137 | .append("Suppressed at level ") 138 | .append(exceptionLevel) 139 | .append(": ") 140 | .append(getNameAndMessage(suppressed)) 141 | .append("\n"); 142 | 143 | appendStackTrace(builder, suppressed, exceptionLevel, indentString + "\t", filter); 144 | } 145 | } 146 | 147 | private static String getNameAndMessage(Throwable throwable) { 148 | String message = throwable.getMessage(); 149 | 150 | if (message == null) { 151 | return throwable.getClass().getName(); 152 | } else if (message.contains(":")) { 153 | String[] messageParts = message.split(":"); 154 | StringBuilder messageBuilder = new StringBuilder(); 155 | int count = 0; 156 | 157 | for (String messagePart : messageParts) { 158 | boolean lastMessagePart = count == messageParts.length - 1; 159 | 160 | if (lastMessagePart || !messagePart.endsWith("Exception")) { 161 | messageBuilder.append(messagePart); 162 | 163 | if (lastMessagePart) { 164 | messageBuilder.append(":"); 165 | } 166 | } 167 | } 168 | } 169 | 170 | return throwable.getClass().getName() + ": " + message; 171 | } 172 | 173 | private static String getIndentString(int indentLevel) { 174 | return Stream.generate(() -> "\t") 175 | .limit(indentLevel) 176 | .collect(joining()); 177 | } 178 | 179 | public static Predicate excludeJavaSE() { 180 | // TODO better name for this 181 | return excludeFromStackTrace(JAVA_SE_STACK_TRACE_EXCLUSIONS); 182 | } 183 | 184 | public static Predicate excludeJavaEE() { 185 | return excludeFromStackTrace(JAVA_EE_STACK_TRACE_EXCLUSIONS); 186 | } 187 | 188 | public static Predicate excludeAll() { 189 | // TODO better name for method and include the other exclusions as well 190 | return excludeJavaSE().and(excludeJavaEE()); 191 | } 192 | 193 | public static Predicate excludeFromStackTrace(List packageOrClassNames) { 194 | Objects.requireNonNull(packageOrClassNames); 195 | return stackTraceElement -> packageOrClassNames.stream() 196 | .noneMatch(exclusion -> stackTraceElement.toString().startsWith(exclusion) || 197 | stackTraceElement.getClassName().startsWith(exclusion)); 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/function/ExceptionlessAutoCloseable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.function; 14 | 15 | /** 16 | * Functional helper-interface to allow for use of closeable resources that don't implement AutoCloseable in a try-with-resources statement. 17 | * 18 | *

19 | * This interface can be used with any object instance that doesn't implement AutoCloseable, but does have a close or similar method that needs to be 20 | * called to free up resources. This functional interface is useable for any close method that doesn't throw any checked exceptions, for methods that 21 | * throw a more specific exception than Exception, please see @link{ThrowingAutoCloseable}. 22 | *

23 | * 24 | * 25 | *

Example:

26 | * 27 | * CloseableResource resource = ... 28 | * try (ExceptionlessAutoCloseable eac = resource::close) { 29 | * // Use resource 30 | * } 31 | * 32 | * 33 | */ 34 | @FunctionalInterface 35 | public interface ExceptionlessAutoCloseable extends AutoCloseable { 36 | 37 | @Override 38 | void close(); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/function/Predicates.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.function; 14 | 15 | import static java.util.Comparator.naturalOrder; 16 | 17 | import java.util.Comparator; 18 | import java.util.Objects; 19 | import java.util.function.Function; 20 | import java.util.function.Predicate; 21 | 22 | import org.omnifaces.utils.Lang; 23 | 24 | public final class Predicates { 25 | 26 | private Predicates() { 27 | } 28 | 29 | public static Predicate always() { 30 | return t -> true; 31 | } 32 | 33 | public static Predicate never() { 34 | return t -> false; 35 | } 36 | 37 | public static Predicate isEmpty() { 38 | return Lang::isEmpty; 39 | } 40 | 41 | public static Predicate isNotEmpty() { 42 | return Predicates.isEmpty().negate(); 43 | } 44 | 45 | public static Predicate isNull() { 46 | return t -> t == null; 47 | } 48 | 49 | public static Predicate isNotNull() { 50 | return t -> t != null; 51 | } 52 | 53 | public static > Predicate isLessThan(T value) { 54 | return isLessThan(value, naturalOrder()); 55 | } 56 | 57 | public static Predicate isLessThan(T value, Comparator comparator) { 58 | Objects.requireNonNull(value); 59 | Objects.requireNonNull(comparator); 60 | 61 | return t -> comparator.compare(t, value) < 0; 62 | } 63 | 64 | public static > Predicate isLessThanOrEqual(T value) { 65 | return isLessThanOrEqual(value, naturalOrder()); 66 | } 67 | 68 | public static Predicate isLessThanOrEqual(T value, Comparator comparator) { 69 | Objects.requireNonNull(value); 70 | Objects.requireNonNull(comparator); 71 | 72 | return t -> comparator.compare(t, value) <= 0; 73 | } 74 | 75 | public static > Predicate isComparativelyEqualTo(T value) { 76 | return isComparativelyEqualTo(value, naturalOrder()); 77 | } 78 | 79 | public static > Predicate isComparativelyEqualTo(T value, Comparator comparator) { 80 | Objects.requireNonNull(value); 81 | Objects.requireNonNull(comparator); 82 | 83 | return t -> comparator.compare(t, value) == 0; 84 | } 85 | 86 | public static > Predicate isGreaterThan(T value) { 87 | return isGreaterThan(value, naturalOrder()); 88 | } 89 | 90 | public static Predicate isGreaterThan(T value, Comparator comparator) { 91 | Objects.requireNonNull(value); 92 | Objects.requireNonNull(comparator); 93 | 94 | return t -> comparator.compare(t, value) > 0; 95 | } 96 | 97 | public static > Predicate isGreaterThanOrEqual(T value) { 98 | return isGreaterThanOrEqual(value, naturalOrder()); 99 | } 100 | 101 | public static Predicate isGreaterThanOrEqual(T value, Comparator comparator) { 102 | Objects.requireNonNull(value); 103 | Objects.requireNonNull(comparator); 104 | 105 | return t -> comparator.compare(t, value) >= 0; 106 | } 107 | 108 | public static Predicate mapped(Function function, Predicate predicate) { 109 | return t -> predicate.test(function.apply(t)); 110 | } 111 | 112 | public static Predicate not(Predicate predicate) { 113 | return t -> !predicate.test(t); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/function/ThrowingAutoCloseable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.function; 14 | 15 | /** 16 | * Functional helper-interface to allow for use of closeable resources that don't implement AutoCloseable in a try-with-resources statement. 17 | * 18 | *

19 | * This interface can be used with any object instance that doesn't implement AutoCloseable, but does have a close or similar method that needs to be 20 | * called to free up resources. This functional interface is usable for any close method that throws a more specific exception than AutoCloseable 21 | * defines. For methods that don't throw any exceptions, see @link{ExceptionLessAutoCloseable}. 22 | *

23 | * 24 | * 25 | *

Example:

26 | * 27 | * CloseableResource resource = ... 28 | * try (ThrowingAutoCloseable<IOException> eac = resource::close) { 29 | * // Use resource 30 | * } 31 | * catch(IOException e) { 32 | * // Handle exception 33 | * } 34 | * 35 | * 36 | */ 37 | @FunctionalInterface 38 | public interface ThrowingAutoCloseable extends AutoCloseable { 39 | 40 | @Override 41 | void close() throws E; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/image/Images.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.image; 14 | 15 | import static java.awt.RenderingHints.KEY_INTERPOLATION; 16 | import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR; 17 | import static java.awt.Transparency.OPAQUE; 18 | import static java.awt.image.BufferedImage.TYPE_INT_ARGB; 19 | import static java.awt.image.BufferedImage.TYPE_INT_RGB; 20 | import static java.lang.Math.max; 21 | import static java.util.stream.IntStream.range; 22 | import static javax.imageio.ImageIO.read; 23 | 24 | import java.awt.Color; 25 | import java.awt.image.BufferedImage; 26 | import java.io.ByteArrayInputStream; 27 | import java.io.ByteArrayOutputStream; 28 | import java.io.IOException; 29 | import java.math.BigInteger; 30 | import java.util.Base64; 31 | import java.util.concurrent.atomic.AtomicInteger; 32 | import java.util.function.Consumer; 33 | 34 | import javax.imageio.IIOImage; 35 | import javax.imageio.ImageIO; 36 | import javax.imageio.ImageWriteParam; 37 | import javax.imageio.stream.MemoryCacheImageOutputStream; 38 | 39 | public final class Images { 40 | 41 | private Images() { 42 | // 43 | } 44 | 45 | public static BufferedImage toBufferedImage(byte[] content) throws IOException { 46 | return read(new ByteArrayInputStream(content)); 47 | } 48 | 49 | public static byte[] toPng(BufferedImage image) throws IOException { 50 | var output = new ByteArrayOutputStream(); 51 | ImageIO.write(image, "png", output); 52 | return output.toByteArray(); 53 | } 54 | 55 | public static byte[] toJpg(BufferedImage image) throws IOException { 56 | // Start with a white layer to have images with an alpha layer handled correctly. 57 | var newBufferedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); 58 | newBufferedImage.createGraphics().drawImage(image, 0, 0, Color.WHITE, null); 59 | 60 | // Manually get the ImageWriter to be able to adjust quality 61 | var writer = ImageIO.getImageWritersBySuffix("jpg").next(); 62 | var imageWriterParam = writer.getDefaultWriteParam(); 63 | imageWriterParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); 64 | imageWriterParam.setCompressionQuality(1f); 65 | 66 | var output = new ByteArrayOutputStream(); 67 | writer.setOutput(new MemoryCacheImageOutputStream(output)); 68 | writer.write(null, new IIOImage(newBufferedImage, null, null), imageWriterParam); 69 | writer.dispose(); 70 | 71 | return output.toByteArray(); 72 | } 73 | 74 | public static BufferedImage cropImage(BufferedImage image, int desiredWidth, int desiredHeight) { 75 | var cropHorizontally = image.getWidth() > desiredWidth; 76 | 77 | var x = cropHorizontally ? (image.getWidth() - desiredWidth) / 2 : 0; 78 | var y = cropHorizontally ? 0 : (image.getHeight() - desiredHeight) / 2; 79 | 80 | return image.getSubimage(x, y, desiredWidth, desiredHeight); 81 | } 82 | 83 | /* 84 | * Examples of aspect ratios: 85 | * 1:1 = 1.0 (will delegate to cropToSquareImage()) 86 | * 4:3 = 1.33333 87 | * 3:2 = 1.5 88 | * 16:9 = 1.77778 89 | */ 90 | public static BufferedImage cropImage(BufferedImage image, double desiredAspectRatio) { 91 | if (desiredAspectRatio == 1.0) { 92 | return cropToSquareImage(image); 93 | } 94 | 95 | var currentAspectRatio = image.getWidth() * 1.0 / image.getHeight(); 96 | 97 | if (currentAspectRatio == desiredAspectRatio) { 98 | return image; 99 | } 100 | 101 | var cropHorizontally = currentAspectRatio > desiredAspectRatio; 102 | var desiredWidth = cropHorizontally ? (int) (image.getHeight() * desiredAspectRatio) : image.getWidth(); 103 | var desiredHeight = cropHorizontally ? image.getHeight() : (int) (image.getWidth() / desiredAspectRatio); 104 | return cropImage(image, desiredWidth, desiredHeight); 105 | } 106 | 107 | public static BufferedImage cropToSquareImage(BufferedImage image) { 108 | var cropHorizontally = image.getWidth() > image.getHeight(); 109 | var desiredSize = cropHorizontally ? image.getHeight() : image.getWidth(); 110 | return cropImage(image, desiredSize, desiredSize); 111 | } 112 | 113 | public static BufferedImage progressiveBilinearDownscale(BufferedImage image, int desiredWidth, int desiredHeight) { 114 | var rescaledImage = image; 115 | 116 | while (rescaledImage.getWidth() > desiredWidth || rescaledImage.getHeight() > desiredHeight) { 117 | var nextWidth = max(rescaledImage.getWidth() / 2, desiredWidth); 118 | var nextHeight = max(rescaledImage.getHeight() / 2, desiredHeight); 119 | var nextScaledImage = new BufferedImage(nextWidth, nextHeight, image.getTransparency() == OPAQUE ? TYPE_INT_RGB : TYPE_INT_ARGB); 120 | var graphics = nextScaledImage.createGraphics(); 121 | graphics.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR); 122 | graphics.drawImage(rescaledImage, 0, 0, nextWidth, nextHeight, null); 123 | graphics.dispose(); 124 | rescaledImage = nextScaledImage; 125 | } 126 | 127 | return rescaledImage; 128 | } 129 | 130 | public static BufferedImage grayscale(BufferedImage image) { 131 | var grayscale = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY); 132 | var g = grayscale.getGraphics(); 133 | g.drawImage(image, 0, 0, null); 134 | g.dispose(); 135 | return grayscale; 136 | } 137 | 138 | /** 139 | * https://apiumhub.com/tech-blog-barcelona/introduction-perceptual-hashes-measuring-similarity/ 140 | */ 141 | public static String computePerceptualHash(BufferedImage image, int size, int base) { 142 | if (size < 8 || size > 32) { 143 | throw new IllegalArgumentException("size " + size + " must be between 8 and 32"); 144 | } 145 | if (base != 2 && base != 10 && base != 16 && base != 32 && base != 36 && base != 64) { 146 | throw new IllegalArgumentException("base " + base + " must be 2, 10, 16, 32, 36 or 64"); 147 | } 148 | 149 | var grayscale = grayscale(progressiveBilinearDownscale(image, size, size)); 150 | Consumer> forEachPixel = perPixel -> range(0, size).forEach(x -> range(0, size).forEach(y -> perPixel.accept(grayscale.getRGB(x, y) & 0xFF))); 151 | var totalPixelValue = new AtomicInteger(); 152 | forEachPixel.accept(pixel -> totalPixelValue.addAndGet(pixel)); 153 | var averagePixelValue = totalPixelValue.get() / (size * size); 154 | var perceptualHash = new StringBuilder(); 155 | forEachPixel.accept(pixel -> perceptualHash.append(pixel > averagePixelValue ? "1" : "0")); 156 | var hash = perceptualHash.toString(); 157 | 158 | if (base == 2) { 159 | return hash; 160 | } else { 161 | var integer = new BigInteger(hash, 2); 162 | 163 | if (base == 64) { 164 | return Base64.getEncoder().encodeToString(integer.toByteArray()); 165 | } else { 166 | return integer.toString(base); 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/io/Io.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.io; 14 | 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.OutputStream; 18 | 19 | public final class Io { 20 | 21 | private Io() { 22 | } 23 | 24 | public static void transferData(InputStream in, OutputStream out) throws IOException { 25 | transferData(in, out, 1024); 26 | } 27 | 28 | public static void transferData(InputStream in, OutputStream out, int transferBlockSize) throws IOException { 29 | byte[] buffer = new byte[transferBlockSize]; 30 | int read; 31 | 32 | while ((read = in.read(buffer)) >= 0) { 33 | out.write(buffer, 0, read); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/logging/LogFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.logging; 14 | 15 | import java.util.function.Predicate; 16 | import java.util.logging.Filter; 17 | import java.util.logging.LogRecord; 18 | 19 | @FunctionalInterface 20 | public interface LogFilter extends Filter, Predicate { 21 | 22 | @Override 23 | default boolean test(LogRecord logRecord) { 24 | return isLoggable(logRecord); 25 | } 26 | 27 | @Override 28 | default LogFilter and(Predicate other) { 29 | return (LogRecord logRecord) -> isLoggable(logRecord) && other.test(logRecord); 30 | } 31 | 32 | @Override 33 | default LogFilter negate() { 34 | return logRecord -> !isLoggable(logRecord); 35 | } 36 | 37 | @Override 38 | default LogFilter or(Predicate other) { 39 | return logRecord -> isLoggable(logRecord) || other.test(logRecord); 40 | } 41 | 42 | static LogFilter fromPredicate(Predicate predicate ) { 43 | return predicate::test; 44 | } 45 | 46 | static LogFilter fromFilter(Filter filter) { 47 | return filter::isLoggable; 48 | } 49 | 50 | static LogFilter hasThrowable() { 51 | return logRecord -> logRecord.getThrown() != null; 52 | } 53 | 54 | static LogFilter hasThrowableOfType(Class clazz) { 55 | return logRecord -> clazz.isInstance(logRecord.getThrown()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/logging/RecursiveStackTraceFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.logging; 14 | 15 | import static org.omnifaces.utils.exceptions.Exceptions.getRecursiveStackTrace; 16 | 17 | import java.util.function.Predicate; 18 | import java.util.logging.Formatter; 19 | import java.util.logging.LogRecord; 20 | 21 | public class RecursiveStackTraceFormatter extends Formatter { 22 | 23 | private final Formatter wrappedFormatter; 24 | private final Predicate filter; 25 | 26 | public RecursiveStackTraceFormatter(Formatter wrappedFormatter) { 27 | this(wrappedFormatter, stackTraceElement -> true); 28 | } 29 | 30 | public RecursiveStackTraceFormatter(Formatter wrappedFormatter, Predicate filter) { 31 | this.wrappedFormatter = wrappedFormatter; 32 | this.filter = filter; 33 | } 34 | 35 | @Override 36 | public String format(LogRecord record) { 37 | if (record.getThrown() != null) { 38 | Throwable throwable = record.getThrown(); 39 | try { 40 | record.setThrown(null); 41 | 42 | String message = wrappedFormatter.format(record); 43 | 44 | String recursiveStackTrace = getRecursiveStackTrace(throwable, filter); 45 | 46 | return String.format("%s%n%s", message, recursiveStackTrace); 47 | } 48 | finally { 49 | record.setThrown(throwable); 50 | } 51 | 52 | } 53 | 54 | return wrappedFormatter.format(record); 55 | } 56 | 57 | public Formatter getWrappedFormatter() { 58 | return wrappedFormatter; 59 | } 60 | 61 | public Predicate getFilter() { 62 | return filter; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/math/BigDecimalMath.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.math; 14 | 15 | import java.math.BigDecimal; 16 | import java.math.MathContext; 17 | 18 | public final class BigDecimalMath { 19 | 20 | private BigDecimalMath() {} 21 | 22 | public static BigDecimal nRoot(BigDecimal number, int n, MathContext context) { 23 | // TODO input validation 24 | BigDecimal power = BigDecimal.valueOf(n); 25 | 26 | BigDecimal previous = number; 27 | 28 | BigDecimal current = number.divide(power, context); 29 | 30 | while (previous.compareTo(current) != 0) { 31 | BigDecimal f = current.pow(n).subtract(number); 32 | BigDecimal fDerivative = power.multiply(current.pow(n - 1)); 33 | 34 | BigDecimal next = current.subtract( 35 | f.divide(fDerivative, context), 36 | context 37 | ); 38 | 39 | previous = current; 40 | current = next; 41 | } 42 | 43 | return current; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/properties/PropertiesUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.properties; 14 | 15 | import static java.lang.System.getProperty; 16 | import static java.util.Arrays.asList; 17 | import static java.util.Collections.unmodifiableMap; 18 | import static java.util.logging.Level.SEVERE; 19 | import static org.omnifaces.utils.properties.PropertiesUtils.PropertiesFormat.LIST; 20 | import static org.omnifaces.utils.properties.PropertiesUtils.PropertiesFormat.XML; 21 | 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.net.MalformedURLException; 25 | import java.net.URL; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | import java.util.Map.Entry; 29 | import java.util.Optional; 30 | import java.util.Properties; 31 | import java.util.function.BiConsumer; 32 | import java.util.logging.Logger; 33 | 34 | public final class PropertiesUtils { 35 | 36 | private static final Logger logger = Logger.getLogger(PropertiesUtils.class.getName()); 37 | private static final String CONFIGURATION_BASE_DIR = "/conf/"; 38 | private static final String META_INF_CONFIGURATION_BASE_DIR = "META-INF/conf/"; 39 | 40 | public static enum PropertiesFormat {XML, LIST} 41 | 42 | private PropertiesUtils() { 43 | } 44 | 45 | public static Map loadPropertiesFromClasspath(String baseName) { 46 | 47 | Map properties = new HashMap<>(); 48 | 49 | getResource(baseName + ".xml").ifPresent(url -> loadPropertiesFromUrl(url, properties, XML)); 50 | getResource(baseName + ".properties").ifPresent(url -> loadPropertiesFromUrl(url, properties, LIST)); 51 | 52 | return unmodifiableMap(properties); 53 | } 54 | 55 | /** 56 | * Loads the properties file in properties list format with the given name from the configuration directory of META-INF with support for staging. 57 | * 58 | * The properties will loaded from the default properties file and the properties file from the given stage. If both files contain properties with 59 | * the same key, the returned Map object will only contain the stage specific ones. 60 | * 61 | * @param fileName the file name of the properties file 62 | * @param stageSystemPropertyName the name of the system property from which the stage is read 63 | * @param defaultStage the default stage 64 | * @return an immutable map instance containing the key/value pairs from the given file 65 | */ 66 | public static Map loadPropertiesListStagedFromClassPath(String fileName, String stageSystemPropertyName, String defaultStage) { 67 | return loadStagedFromClassPath(PropertiesUtils::loadListFromURL, fileName, stageSystemPropertyName, defaultStage); 68 | } 69 | 70 | /** 71 | * Loads the properties file in XML format with the given name from the configuration directory of META-INF with support for staging. 72 | * 73 | * The properties will loaded from the default properties file and the properties file from the given stage. If both files contain properties 74 | * with the same key, the returned Map object will only contain the stage specific ones. 75 | * 76 | * @param fileName 77 | * the file name of the properties file 78 | * @param stageSystemPropertyName 79 | * the name of the system property from which the stage is read 80 | * @param defaultStage 81 | * the default stage 82 | * @return an immutable map instance containing the key/value pairs from the given file 83 | */ 84 | public static Map loadXMLPropertiesStagedFromClassPath(String fileName, String stageSystemPropertyName, String defaultStage) { 85 | return loadStagedFromClassPath(PropertiesUtils::loadXMLFromURL, fileName, stageSystemPropertyName, defaultStage); 86 | } 87 | 88 | public static Map loadStagedFromClassPath(BiConsumer> loadMethod, String fileName, String stageSystemPropertyName, String defaultStage) { 89 | 90 | String stage = getStage(stageSystemPropertyName, defaultStage); 91 | 92 | Map settings = new HashMap<>(); 93 | 94 | asList(META_INF_CONFIGURATION_BASE_DIR + fileName, META_INF_CONFIGURATION_BASE_DIR + stage + "/" + fileName) 95 | .forEach( 96 | path -> getResource(path) 97 | .ifPresent( 98 | url -> loadMethod.accept(url, settings))); 99 | 100 | return unmodifiableMap(settings); 101 | } 102 | 103 | 104 | /** 105 | * Loads the properties file in properties list format with the given name from the configuration directory of an EAR with support for staging. 106 | * 107 | * The properties will loaded from the default properties file and the properties file from the given stage. If both files contain properties with 108 | * the same key, the returned Map object will only contain the stage specific ones. 109 | * 110 | * @param fileName 111 | * the file name of the properties file 112 | * @param stageSystemPropertyName 113 | * the name of the system property from which the stage is read 114 | * @return an immutable map instance containing the key/value pairs from the given file 115 | */ 116 | public static Map loadPropertiesListStagedFromEar(String fileName, String stageSystemPropertyName) { 117 | return loadStagedFromEar(PropertiesUtils::loadListFromUrl, fileName, stageSystemPropertyName); 118 | } 119 | 120 | /** 121 | * Loads the properties file in XML format with the given name from the configuration directory of an EAR with support for staging. 122 | * 123 | * The properties will loaded from the default properties file and the properties file from the given stage. If both files contain properties 124 | * with the same key, the returned Map object will only contain the stage specific ones. 125 | * 126 | * @param fileName 127 | * the file name of the properties file 128 | * @param stageSystemPropertyName 129 | * the name of the system property from which the stage is read 130 | * @return an immutable map instance containing the key/value pairs from the 131 | * given file 132 | */ 133 | public static Map loadXMLPropertiesStagedFromEar(String fileName, String stageSystemPropertyName) { 134 | return loadStagedFromEar(PropertiesUtils::loadXMLFromUrl, fileName, stageSystemPropertyName); 135 | } 136 | 137 | public static Map loadStagedFromEar(BiConsumer> loadMethod, String fileName, String stageSystemPropertyName) { 138 | 139 | String earBaseUrl = getEarBaseUrl(); 140 | String stage = getProperty(stageSystemPropertyName); 141 | if (stage == null) { 142 | throw new IllegalStateException(stageSystemPropertyName + " property not found. Please add it to VM arguments, e.g. -D" + stageSystemPropertyName + "=some_stage"); 143 | } 144 | 145 | Map settings = new HashMap<>(); 146 | 147 | loadMethod.accept(earBaseUrl + CONFIGURATION_BASE_DIR + fileName, settings); 148 | loadMethod.accept(earBaseUrl + CONFIGURATION_BASE_DIR + stage + "/" + fileName, settings); 149 | 150 | return unmodifiableMap(settings); 151 | } 152 | 153 | public static String getEarBaseUrl() { 154 | Optional dummyUrl = getResource("META-INF/dummy.txt"); 155 | 156 | if (dummyUrl.isPresent()) { 157 | String dummyExternalForm = dummyUrl.get().toExternalForm(); 158 | 159 | // Exploded deployment JBoss example 160 | // vfs:/opt/jboss/standalone/deployments/someapp.ear/someapp.jar/META-INF/dummy.txt 161 | 162 | // Packaged deployment JBoss example 163 | // vfs:/content/someapp.ear/someapp.jar/META-INF/dummy.txt 164 | 165 | int jarPos = dummyExternalForm.lastIndexOf(".jar"); 166 | if (jarPos != -1) { 167 | 168 | String withoutJar = dummyExternalForm.substring(0, jarPos); 169 | int lastSlash = withoutJar.lastIndexOf('/'); 170 | 171 | withoutJar = withoutJar.substring(0, lastSlash); 172 | 173 | if (withoutJar.endsWith("/lib")) { 174 | withoutJar = withoutJar.substring(0, withoutJar.length() - 4); 175 | } 176 | 177 | if (withoutJar.endsWith("/WEB-INF")) { 178 | withoutJar += "/classes"; 179 | } 180 | 181 | return withoutJar; 182 | } 183 | 184 | // TODO add support for other servers and JRebel 185 | 186 | throw new IllegalStateException("Can't derive EAR root from: " + dummyExternalForm); 187 | } 188 | 189 | throw new IllegalStateException("Can't find META-INF/dummy.txt on the classpath. This file should be present in a jar in the ear/lib folder"); 190 | } 191 | 192 | public static void loadListFromUrl(String url, Map settings) { 193 | loadPropertiesFromUrl(url, settings, LIST); 194 | } 195 | 196 | public static void loadXMLFromUrl(String url, Map settings) { 197 | loadPropertiesFromUrl(url, settings, XML); 198 | } 199 | 200 | public static void loadListFromURL(URL url, Map settings) { 201 | loadPropertiesFromUrl(url, settings, LIST); 202 | } 203 | 204 | public static void loadXMLFromURL(URL url, Map settings) { // TODO name not ideal 205 | loadPropertiesFromUrl(url, settings, XML); 206 | } 207 | 208 | public static void loadPropertiesFromUrl(String url, Map settings, PropertiesFormat propertiesFormat) { 209 | try { 210 | loadPropertiesFromUrl(new URL(url), settings, propertiesFormat); 211 | } catch (MalformedURLException e) { 212 | logger.log(SEVERE, "Error while loading settings.", e); 213 | } 214 | } 215 | 216 | public static void loadPropertiesFromUrl(URL url, Map settings, PropertiesFormat propertiesFormat) { 217 | try { 218 | loadPropertiesFromStream(url.openStream(), url.toString(), settings, propertiesFormat); 219 | } catch (IOException e) { 220 | logger.log(SEVERE, "Error while loading settings.", e); 221 | } 222 | } 223 | 224 | public static void loadPropertiesFromStream(InputStream in, String locationDescription, Map settings, PropertiesFormat propertiesFormat) { 225 | Properties properties = new Properties(); 226 | try { 227 | 228 | if (propertiesFormat == XML) { 229 | properties.loadFromXML(in); 230 | } else { 231 | properties.load(in); 232 | } 233 | 234 | logger.info(String.format("Loaded %d settings from %s.", properties.size(), locationDescription)); 235 | 236 | for (Entry entry : properties.entrySet()) { 237 | settings.put((String) entry.getKey(), (String) entry.getValue()); 238 | } 239 | 240 | } catch (IOException e) { 241 | logger.log(SEVERE, "Error while loading settings.", e); 242 | } 243 | } 244 | 245 | public static String getStage(String stageSystemPropertyName, String defaultStage) { 246 | String stage = getProperty(stageSystemPropertyName); 247 | if (stage == null) { 248 | if (defaultStage == null) { 249 | throw new IllegalStateException(stageSystemPropertyName + " property not found. Please add it to VM arguments, e.g. -D" + stageSystemPropertyName + "=some_stage"); 250 | } 251 | 252 | stage = defaultStage; 253 | } 254 | 255 | return stage; 256 | } 257 | 258 | private static Optional getResource(String name) { 259 | URL url = Thread.currentThread().getContextClassLoader().getResource(name); 260 | if (url == null) { 261 | url = PropertiesUtils.class.getClassLoader().getResource(name); 262 | } 263 | return Optional.ofNullable(url); 264 | } 265 | 266 | 267 | 268 | } 269 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/reflect/Getter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.reflect; 14 | 15 | import static java.util.Arrays.stream; 16 | 17 | import java.beans.BeanInfo; 18 | import java.beans.IntrospectionException; 19 | import java.beans.Introspector; 20 | import java.io.Serializable; 21 | import java.lang.invoke.SerializedLambda; 22 | import java.lang.reflect.Method; 23 | import java.util.Objects; 24 | import java.util.function.Function; 25 | 26 | /** 27 | *

28 | * So we can extract method info from a method reference. 29 | * Usage example: 30 | *

 31 |  * Map<Getter<YourEntity>, Object> criteria = new HashMap<>();
 32 |  * criteria.put(YourEntity::getName, Like.startsWith(searchNameStartsWith));
 33 |  * criteria.put(YourEntity::getCreated, Order.greaterThanOrEqualTo(searchStartDate));
 34 |  * criteria.put(YourEntity::getType, searchTypes);
 35 |  * criteria.put(YourEntity::isDeleted, false);
 36 |  * 
37 | *

38 | * And then later on in "the backend": 39 | *

 40 |  * criteria.forEach((getter, value) -> requiredCriteria.put(getter.getPropertyName(), value));
 41 |  * 
42 | *

43 | * This allows a type safe way of defining property names. 44 | *

45 | * Inspired by Lambda parameter names with reflection. 46 | * NOTE: works only in Java 8u60 and newer. 47 | * 48 | * @param The generic base type. 49 | * @author Bauke Scholtz 50 | */ 51 | public interface Getter extends Function, Serializable { 52 | 53 | default SerializedLambda getSerializedLambda() { 54 | try { 55 | Method writeReplace = getClass().getDeclaredMethod("writeReplace"); 56 | writeReplace.setAccessible(true); 57 | return (SerializedLambda) writeReplace.invoke(this); 58 | } 59 | catch (Exception e) { 60 | throw new UnsupportedOperationException(e); 61 | } 62 | } 63 | 64 | @SuppressWarnings("unchecked") 65 | default Class getBaseType() { 66 | String className = getSerializedLambda().getImplClass().replace("/", "."); 67 | 68 | try { 69 | return (Class) Class.forName(className, true, Thread.currentThread().getContextClassLoader()); 70 | } 71 | catch (Exception e) { 72 | throw new IllegalStateException(e); 73 | } 74 | } 75 | 76 | default Method getMethod() { 77 | String methodName = getSerializedLambda().getImplMethodName(); 78 | 79 | return stream(getBaseType().getDeclaredMethods()) 80 | .filter(method -> Objects.equals(method.getName(), methodName)) 81 | .findFirst().orElseThrow(IllegalStateException::new); 82 | } 83 | 84 | default String getPropertyName() { 85 | Method method = getMethod(); 86 | BeanInfo beanInfo; 87 | 88 | try { 89 | beanInfo = Introspector.getBeanInfo(getBaseType()); 90 | } 91 | catch (IntrospectionException e) { 92 | throw new IllegalStateException(e); 93 | } 94 | 95 | return stream(beanInfo.getPropertyDescriptors()) 96 | .filter(property -> property.getReadMethod() != null && Objects.equals(property.getReadMethod().getName(), method.getName())) 97 | .findFirst().orElseThrow(IllegalStateException::new) 98 | .getName(); 99 | } 100 | 101 | default Class getReturnType() { 102 | return getMethod().getReturnType(); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/security/Certificates.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.security; 14 | 15 | import static org.omnifaces.utils.Lang.isEmpty; 16 | 17 | import java.io.FileOutputStream; 18 | import java.io.IOException; 19 | import java.io.UncheckedIOException; 20 | import java.nio.file.Files; 21 | import java.nio.file.Path; 22 | import java.security.KeyManagementException; 23 | import java.security.KeyPair; 24 | import java.security.KeyPairGenerator; 25 | import java.security.KeyStore; 26 | import java.security.KeyStore.PasswordProtection; 27 | import java.security.KeyStore.PrivateKeyEntry; 28 | import java.security.NoSuchAlgorithmException; 29 | import java.security.NoSuchProviderException; 30 | import java.security.PrivateKey; 31 | import java.security.cert.Certificate; 32 | import java.security.cert.X509Certificate; 33 | 34 | import javax.net.ssl.SSLContext; 35 | import javax.net.ssl.SSLSocket; 36 | import javax.net.ssl.TrustManager; 37 | 38 | /** 39 | * Collection of utility methods for working with Certificates and SSL. 40 | * 41 | * @author Arjan Tijms 42 | * 43 | */ 44 | public final class Certificates { 45 | 46 | private Certificates() { 47 | } 48 | 49 | /** 50 | * Attempts to query a server for the X509 certificate chain it will 51 | * use in the SSL handshake. 52 | * 53 | *

54 | * This method uses a default timeout of 15 seconds. 55 | * 56 | * @param host the server's host 57 | * @param port the server's port 58 | * @return The certificate chain, or null if it could not be obtained. 59 | */ 60 | public static X509Certificate[] getCertificateChainFromServer(String host, int port) { 61 | return getCertificateChainFromServer(host, port, 15000); 62 | } 63 | 64 | /** 65 | * Attempts to query a server for the X509 certificate chain it will 66 | * use in the SSL handshake. 67 | * 68 | * @param host the server's host 69 | * @param port the server's port 70 | * @param timeout the socket timeout, in milliseconds. 71 | * @return The certificate chain, or null if it could not be obtained. 72 | */ 73 | public static X509Certificate[] getCertificateChainFromServer(String host, int port, int timeout) { 74 | 75 | InterceptingX509TrustManager interceptingTrustManager = new InterceptingX509TrustManager(); 76 | 77 | try { 78 | SSLContext context = SSLContext.getInstance("TLS"); 79 | context.init(null, new TrustManager[] { interceptingTrustManager }, null); 80 | 81 | try (SSLSocket socket = (SSLSocket) context.getSocketFactory().createSocket(host, port)) { 82 | socket.setSoTimeout(timeout); 83 | socket.startHandshake(); 84 | } 85 | 86 | } catch (NoSuchAlgorithmException | KeyManagementException | IOException e) { 87 | e.printStackTrace(); 88 | } 89 | 90 | if (interceptingTrustManager.getX509ServerCertificates().isEmpty()) { 91 | return null; 92 | } 93 | 94 | return interceptingTrustManager.getX509ServerCertificates().get(0); 95 | } 96 | 97 | /** 98 | * Extracts the host name from the first X509 certificate in a chain. 99 | * 100 | *

101 | * This method assumes RFC 2253 format of the distinguished named, and will take the CN name 102 | * to be representative of the host name. 103 | * 104 | * @param serverCertificateChain the chain from which to extract the host name 105 | * @return the CN from the first certificate corresponding to the host name 106 | */ 107 | public static String getHostFromCertificate(X509Certificate[] serverCertificateChain) { 108 | String[] names = serverCertificateChain[0] 109 | .getIssuerX500Principal() 110 | .getName() 111 | .split(","); 112 | 113 | if (isEmpty(names)) { 114 | throw new IllegalStateException("No CN name found"); 115 | } 116 | 117 | // In the X.500 distinguished name using the format defined in RFC 2253, CN is the first 118 | // element and represents the host 119 | String cn = names[0]; 120 | 121 | return cn.substring(cn.indexOf('=') + 1).trim(); 122 | } 123 | 124 | /** 125 | * Generates a random RSA keypair with a keysize of 2048 bits. 126 | * 127 | * @return a random RSA keypair 128 | */ 129 | public static KeyPair generateRandomRSAKeys() { 130 | try { 131 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); 132 | keyPairGenerator.initialize(2048); 133 | 134 | return keyPairGenerator.generateKeyPair(); 135 | } catch (NoSuchAlgorithmException | NoSuchProviderException e) { 136 | throw new IllegalStateException(e); 137 | } 138 | } 139 | 140 | /** 141 | * Creates a temporary JKS key store on disk initialized with the given private key and 142 | * certificate and the well known default password "changeit" (without quotes). 143 | * 144 | * @param privateKey the key used to initialize the key store 145 | * @param certificate the certificate used to initialize the key store 146 | * @return the path on disk to the temporary key store 147 | */ 148 | public static String createTempJKSKeyStore(PrivateKey privateKey, X509Certificate certificate) { 149 | try { 150 | Path tmpKeyStorePath = Files.createTempFile("trustStore", ".jks"); 151 | 152 | createJKSKeyStore(tmpKeyStorePath, "changeit".toCharArray(), privateKey, certificate); 153 | 154 | return tmpKeyStorePath.toString(); 155 | 156 | } catch (IOException cause) { 157 | throw new UncheckedIOException(cause); 158 | } 159 | } 160 | 161 | /** 162 | * Creates a JKS key store on disk initialized with the given private key and 163 | * certificate, at the given location and with the given password. 164 | * 165 | * @param path the full path (directory and file name) where the key store is created 166 | * @param password the password used to protect the key store 167 | * @param privateKey the key used to initialize the key store 168 | * @param certificate the certificate used to initialize the key store 169 | */ 170 | public static void createJKSKeyStore(Path path, char[] password, PrivateKey privateKey, X509Certificate certificate) { 171 | try { 172 | KeyStore keyStore = KeyStore.getInstance("jks"); 173 | keyStore.load(null, null); 174 | 175 | keyStore.setEntry( 176 | "omniKey", 177 | new PrivateKeyEntry(privateKey, new Certificate[] { certificate }), 178 | new PasswordProtection(password)); 179 | 180 | keyStore.store(new FileOutputStream(path.toFile()), password); 181 | } catch (Exception ex) { 182 | ex.printStackTrace(); 183 | } 184 | } 185 | 186 | /** 187 | * Creates a temporary JKS trust store on disk initialized with the given 188 | * certificates and the well known default password "changeit" (without quotes). 189 | * 190 | * @param certificates the certificates used to initialize the trust store 191 | * @return the path on disk to the temporary trust store 192 | */ 193 | public static String createTempJKSTrustStore(X509Certificate[] certificates) { 194 | try { 195 | Path tmpTrustStorePath = Files.createTempFile("trustStore", ".jks"); 196 | 197 | createJKSTrustStore(tmpTrustStorePath, "changeit".toCharArray(), certificates); 198 | 199 | return tmpTrustStorePath.toString(); 200 | } catch (IOException cause) { 201 | throw new UncheckedIOException(cause); 202 | } 203 | } 204 | 205 | /** 206 | * Creates a JKS key trust on disk initialized with the given 207 | * certificates, at the given location and with the given password. 208 | * 209 | * @param path the full path (directory and file name) where the trust store is created 210 | * @param password the password used to protect the trust store 211 | * @param certificates the certificates used to initialize the trust store 212 | */ 213 | public static void createJKSTrustStore(Path path, char[] password, X509Certificate[] certificates) { 214 | try { 215 | KeyStore trustStore = KeyStore.getInstance("jks"); 216 | trustStore.load(null, null); 217 | 218 | for (int i = 0; i < certificates.length; i++) { 219 | trustStore.setCertificateEntry("omniCert" + i, certificates[i]); 220 | } 221 | 222 | trustStore.store(new FileOutputStream(path.toFile()), password); 223 | 224 | } catch (Exception ex) { 225 | throw new RuntimeException(ex); 226 | } 227 | } 228 | 229 | /** 230 | * Sets the system-wide (JVM) trust store to the one referenced by the 231 | * given path. 232 | * 233 | *

234 | * The default password "changeit" is used. 235 | * 236 | * @param path the path on disk where the trust store is located 237 | */ 238 | public static void setSystemTrustStore(String path) { 239 | setSystemTrustStore(path, "changeit"); 240 | } 241 | 242 | /** 243 | * Sets the system-wide (JVM) trust store to the one referenced by the 244 | * given path. 245 | * 246 | *

247 | * The default password "changeit" is used. 248 | * 249 | * @param path the path on disk where the trust store is located 250 | * @param password the password to access the trust store 251 | */ 252 | public static void setSystemTrustStore(String path, String password) { 253 | System.setProperty("javax.net.ssl.trustStore", path); 254 | System.setProperty("javax.net.ssl.trustStorePassword", password); 255 | } 256 | 257 | 258 | } 259 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/security/DefaultX509TrustManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.security; 14 | 15 | import java.security.cert.CertificateException; 16 | import java.security.cert.X509Certificate; 17 | 18 | import javax.net.ssl.X509TrustManager; 19 | 20 | /** 21 | * An X509TrustManager with provided default methods, so these don't need to be needlessly 22 | * defined by implementations. 23 | * 24 | * @author Arjan Tijms 25 | * 26 | */ 27 | public interface DefaultX509TrustManager extends X509TrustManager { 28 | 29 | @Override 30 | default X509Certificate[] getAcceptedIssuers() { 31 | return new X509Certificate[] {}; 32 | } 33 | 34 | @Override 35 | default void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 36 | // Do nothing 37 | } 38 | 39 | @Override 40 | default void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 41 | // Do nothing 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/security/InterceptingX509TrustManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.security; 14 | 15 | import java.security.cert.CertificateException; 16 | import java.security.cert.X509Certificate; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | /** 21 | * A trust manager implementation that doesn't do anything other than 22 | * 23 | * @author Arjan Tijms 24 | * 25 | */ 26 | public class InterceptingX509TrustManager implements DefaultX509TrustManager { 27 | 28 | private List x509ClientCertificates = new ArrayList<>(); 29 | private List x509ServerCertificates = new ArrayList<>(); 30 | 31 | @Override 32 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 33 | x509ClientCertificates.add(chain); 34 | } 35 | 36 | @Override 37 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 38 | x509ServerCertificates.add(chain); 39 | } 40 | 41 | /** 42 | * Returns the client certificates that have been collected 43 | * 44 | * @return the client certificates 45 | */ 46 | public List getX509ClientCertificates() { 47 | return x509ClientCertificates; 48 | } 49 | 50 | /** 51 | * Returns the server certificates that have been collected 52 | * 53 | * @return the server certificates 54 | */ 55 | public List getX509ServerCertificates() { 56 | return x509ServerCertificates; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/security/MessageDigests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.security; 14 | 15 | import static java.nio.charset.StandardCharsets.UTF_8; 16 | 17 | import java.nio.charset.Charset; 18 | import java.security.MessageDigest; 19 | import java.security.NoSuchAlgorithmException; 20 | 21 | public final class MessageDigests { 22 | 23 | private MessageDigests() { 24 | } 25 | 26 | /** 27 | * Returns a {@link MessageDigest} instance that implements the specified digest algorithm. 28 | * 29 | *

30 | * This method calls {@link MessageDigest#getInstance(String)}, but wraps any potential {@link NoSuchAlgorithmException}s in a 31 | * {@link UncheckedNoSuchAlgorithmException} as this is an unrecoverable problem in most cases. 32 | *

33 | * 34 | * @param algorithm 35 | * the name of the algorithm to use 36 | * @return a {@link MessageDigest} instance that implements the specified algorithm 37 | * @throws UncheckedNoSuchAlgorithmException 38 | * if no implementation of the given algorithm is found 39 | */ 40 | public static MessageDigest getMessageDigestInstance(String algorithm) throws UncheckedNoSuchAlgorithmException { 41 | try { 42 | return MessageDigest.getInstance(algorithm); 43 | } 44 | catch (NoSuchAlgorithmException e) { 45 | throw new UncheckedNoSuchAlgorithmException(e); 46 | } 47 | } 48 | 49 | /** 50 | * Calculate a message digest over a given string using the specified algorithm. 51 | * 52 | * This method will use {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} encoding. 53 | * 54 | * @param message 55 | * the message to calculate the digest over 56 | * @param algorithm 57 | * the name of the algorithm 58 | * @return a byte array containing the message digest 59 | * @throws UncheckedNoSuchAlgorithmException 60 | * if no implementation of the given algorithm could be found 61 | */ 62 | public static byte[] digest(String message, String algorithm) throws UncheckedNoSuchAlgorithmException { 63 | return digest(message, UTF_8, algorithm); 64 | } 65 | 66 | public static byte[] digest(String message, Charset charset, String algorithm) throws UncheckedNoSuchAlgorithmException { 67 | return digest(message.getBytes(charset), algorithm); 68 | } 69 | 70 | public static byte[] digest(String message, byte[] salt, String algorithm) throws UncheckedNoSuchAlgorithmException { 71 | return digest(message, UTF_8, salt, algorithm); 72 | } 73 | 74 | public static byte[] digest(String message, Charset charset, byte[] salt, String algorithm) throws UncheckedNoSuchAlgorithmException { 75 | return digest(message.getBytes(charset), salt, algorithm); 76 | } 77 | 78 | public static byte[] digest(byte[] message, String algorithm) throws UncheckedNoSuchAlgorithmException { 79 | return getMessageDigestInstance(algorithm).digest(message); 80 | } 81 | 82 | public static byte[] digest(byte[] message, byte[] salt, String algorithm) throws UncheckedNoSuchAlgorithmException { 83 | MessageDigest messageDigest = getMessageDigestInstance(algorithm); 84 | 85 | messageDigest.update(salt); 86 | 87 | return messageDigest.digest(message); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/security/UncheckedNoSuchAlgorithmException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.security; 14 | 15 | import java.security.NoSuchAlgorithmException; 16 | 17 | public class UncheckedNoSuchAlgorithmException extends RuntimeException { 18 | 19 | private static final long serialVersionUID = 6300183450203082745L; 20 | 21 | public UncheckedNoSuchAlgorithmException(NoSuchAlgorithmException e) { 22 | super(e); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/stream/Collectors.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import static java.util.Comparator.naturalOrder; 16 | import static java.util.function.Function.identity; 17 | 18 | import java.util.Comparator; 19 | import java.util.LinkedHashMap; 20 | import java.util.LinkedHashSet; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Optional; 24 | import java.util.Set; 25 | import java.util.function.Consumer; 26 | import java.util.function.Function; 27 | import java.util.stream.Collector; 28 | import java.util.stream.Stream; 29 | 30 | public final class Collectors { 31 | 32 | private Collectors() { 33 | } 34 | 35 | public static Collector> toMap(Function keyMapper) { 36 | return java.util.stream.Collectors.toMap(keyMapper, identity()); 37 | } 38 | 39 | public static Collector> toLinkedMap(Function keyMapper) { 40 | return java.util.stream.Collectors.toMap(keyMapper, identity(), (l, r) -> l, LinkedHashMap::new); 41 | } 42 | 43 | public static Collector> toLinkedSet() { 44 | return java.util.stream.Collectors.toCollection(LinkedHashSet::new); 45 | } 46 | 47 | public static Collector forEachBatch(Consumer> batchConsumer, int batchSize) { 48 | return new ForEachBatchCollector<>(batchConsumer, batchSize); 49 | } 50 | 51 | public static Collector> combine(Collector collector1, Collector collector2) { 52 | return new CombinedCollector<>(collector1, collector2); 53 | } 54 | 55 | /** 56 | * Returns a collector which takes the elements of the current stream and returns a new stream with the same elements in reverse order. 57 | * 58 | *

This collector will collect all elements from a stream into memory in order to return the reversed stream. As a result this collector may not 59 | * be suitable for extremely large or infinite streams. 60 | *

61 | * 62 | * @param The type of the elements 63 | * @return A Collector that returns the elements of a stream in reverse order. 64 | */ 65 | public static Collector> reversedStream() { 66 | return new ReversedStreamCollector<>(); 67 | } 68 | 69 | /** 70 | * Returns a collector which will return the last element of a stream, if present. 71 | * 72 | * @param The type of the elements 73 | * 74 | * @return An {@link Optional} containing the last element of the stream or {@link Optional#empty()} if the stream is empty. 75 | */ 76 | public static Collector> findLast() { 77 | return new FindLastCollector<>(); 78 | } 79 | 80 | public static > Collector> summary() { 81 | return summaryBy(naturalOrder()); 82 | } 83 | 84 | public static Collector> summaryBy(Comparator comparator) { 85 | return new SummaryCollector<>(comparator); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/stream/CombinedCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import java.util.AbstractMap; 16 | import java.util.Collections; 17 | import java.util.Map; 18 | import java.util.Set; 19 | import java.util.function.BiConsumer; 20 | import java.util.function.BinaryOperator; 21 | import java.util.function.Function; 22 | import java.util.function.Supplier; 23 | import java.util.stream.Collector; 24 | 25 | // TODO find proper pair option 26 | class CombinedCollector implements Collector, Map.Entry> { 27 | 28 | private final Collector collector1; 29 | private final Collector collector2; 30 | 31 | CombinedCollector(Collector collector1, Collector collector2) { 32 | this.collector1 = collector1; 33 | this.collector2 = collector2; 34 | } 35 | 36 | @Override 37 | public Supplier> supplier() { 38 | return () -> new AbstractMap.SimpleEntry<>(collector1.supplier().get(), collector2.supplier().get()); 39 | } 40 | 41 | @Override 42 | public BiConsumer, T> accumulator() { 43 | BiConsumer accumulator1 = collector1.accumulator(); 44 | BiConsumer accumulator2 = collector2.accumulator(); 45 | 46 | return (pair, element) -> { 47 | accumulator1.accept(pair.getKey(), element); 48 | accumulator2.accept(pair.getValue(), element); 49 | }; 50 | } 51 | 52 | @Override 53 | public BinaryOperator> combiner() { 54 | BinaryOperator combiner1 = collector1.combiner(); 55 | BinaryOperator combiner2 = collector2.combiner(); 56 | 57 | return (pair1, pair2) -> { 58 | I1 i1 = combiner1.apply(pair1.getKey(), pair2.getKey()); 59 | I2 i2 = combiner2.apply(pair1.getValue(), pair2.getValue()); 60 | 61 | return new AbstractMap.SimpleEntry<>(i1, i2); 62 | }; 63 | } 64 | 65 | @Override 66 | public Function, Map.Entry> finisher() { 67 | Function finisher1 = collector1.finisher(); 68 | Function finisher2 = collector2.finisher(); 69 | 70 | return (pair) -> { 71 | R1 r1 = finisher1.apply(pair.getKey()); 72 | R2 r2 = finisher2.apply(pair.getValue()); 73 | 74 | return new AbstractMap.SimpleEntry<>(r1, r2); 75 | }; 76 | } 77 | 78 | @Override 79 | public Set characteristics() { 80 | // TODO correctly determine elements 81 | return Collections.emptySet(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/stream/FindLastCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import static java.util.Collections.emptySet; 16 | 17 | import java.util.Optional; 18 | import java.util.Set; 19 | import java.util.function.BiConsumer; 20 | import java.util.function.BinaryOperator; 21 | import java.util.function.Function; 22 | import java.util.function.Supplier; 23 | import java.util.stream.Collector; 24 | 25 | class FindLastCollector implements Collector, Optional> { 26 | 27 | static class LastEncounteredElemement { 28 | private boolean set; 29 | private T element; 30 | 31 | void nextElement(T nextElement) { 32 | set = true; 33 | element = nextElement; 34 | } 35 | 36 | LastEncounteredElemement combine(LastEncounteredElemement other) { 37 | if (other.set) { 38 | return other; 39 | } 40 | 41 | return this; 42 | } 43 | 44 | Optional toOptional() { 45 | if (set) { 46 | return Optional.of(element); 47 | } 48 | 49 | return Optional.empty(); 50 | } 51 | } 52 | 53 | @Override 54 | public Supplier> supplier() { 55 | return LastEncounteredElemement::new; 56 | } 57 | 58 | @Override 59 | public BiConsumer, T> accumulator() { 60 | return LastEncounteredElemement::nextElement; 61 | } 62 | 63 | @Override 64 | public BinaryOperator> combiner() { 65 | return LastEncounteredElemement::combine; 66 | } 67 | 68 | @Override 69 | public Function, Optional> finisher() { 70 | return LastEncounteredElemement::toOptional; 71 | } 72 | 73 | @Override 74 | public Set characteristics() { 75 | return emptySet(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/stream/ForEachBatchCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Collections; 17 | import java.util.List; 18 | import java.util.Set; 19 | import java.util.function.BiConsumer; 20 | import java.util.function.BinaryOperator; 21 | import java.util.function.Consumer; 22 | import java.util.function.Function; 23 | import java.util.function.Supplier; 24 | import java.util.stream.Collector; 25 | 26 | class ForEachBatchCollector implements Collector, Void> { 27 | 28 | private final Consumer> batchConsumer; 29 | private final int batchSize; 30 | 31 | ForEachBatchCollector(Consumer> batchConsumer, int batchSize) { 32 | if (batchSize <= 0) { 33 | throw new IllegalArgumentException("Batch size must be greater than 0"); 34 | } 35 | 36 | this.batchConsumer = batchConsumer; 37 | this.batchSize = batchSize; 38 | } 39 | 40 | 41 | @Override 42 | public Supplier> supplier() { 43 | return ArrayList::new; 44 | } 45 | 46 | @Override 47 | public BiConsumer, T> accumulator() { 48 | return (list, element) -> { 49 | list.add(element); 50 | 51 | if (list.size() == batchSize) { 52 | batchConsumer.accept(list); 53 | list.clear(); 54 | } 55 | }; 56 | } 57 | 58 | @Override 59 | public BinaryOperator> combiner() { 60 | return (list1, list2) -> { 61 | if (list1.size() + list2.size() < batchSize) { 62 | list1.addAll(list2); 63 | return list1; 64 | } 65 | 66 | while (list1.size() < batchSize) { 67 | list1.add(list2.remove(0)); 68 | } 69 | 70 | batchConsumer.accept(list1); 71 | 72 | return list2; 73 | }; 74 | } 75 | 76 | @Override 77 | public Function, Void> finisher() { 78 | return (list) -> { 79 | if (!list.isEmpty()) { 80 | batchConsumer.accept(list); 81 | } 82 | 83 | return null; 84 | }; 85 | } 86 | 87 | @Override 88 | public Set characteristics() { 89 | return Collections.emptySet(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/stream/RangeIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import static org.omnifaces.utils.function.Predicates.isLessThan; 16 | import static org.omnifaces.utils.function.Predicates.isLessThanOrEqual; 17 | 18 | import java.util.Comparator; 19 | import java.util.Iterator; 20 | import java.util.NoSuchElementException; 21 | import java.util.function.Function; 22 | import java.util.function.Predicate; 23 | 24 | class RangeIterator implements Iterator { 25 | 26 | private T next; 27 | private final Predicate hasNext; 28 | private final Function incrementer; 29 | 30 | RangeIterator(T start, T end, boolean rangeClosed, Comparator comparator, Function incrementer) { 31 | this.next = start; 32 | this.hasNext = rangeClosed ? isLessThanOrEqual(end, comparator) : isLessThan(end, comparator); 33 | this.incrementer = incrementer; 34 | } 35 | 36 | @Override 37 | public boolean hasNext() { 38 | return hasNext.test(next); 39 | } 40 | 41 | @Override 42 | public T next() { 43 | if (!hasNext()) { 44 | throw new NoSuchElementException(); 45 | } 46 | 47 | T current = next; 48 | 49 | next = incrementer.apply(next); 50 | 51 | return current; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/stream/ReversedStreamCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import static java.util.Collections.emptySet; 16 | 17 | import java.util.LinkedList; 18 | import java.util.Set; 19 | import java.util.function.BiConsumer; 20 | import java.util.function.BinaryOperator; 21 | import java.util.function.Function; 22 | import java.util.function.Supplier; 23 | import java.util.stream.Collector; 24 | import java.util.stream.Stream; 25 | 26 | class ReversedStreamCollector implements Collector, Stream> { 27 | 28 | @Override 29 | public Supplier> supplier() { 30 | return LinkedList::new; 31 | } 32 | 33 | @Override 34 | public BiConsumer, T> accumulator() { 35 | return LinkedList::addFirst; 36 | } 37 | 38 | @Override 39 | public BinaryOperator> combiner() { 40 | return (t1, t2) -> { 41 | t2.addAll(t1); 42 | 43 | return t2; 44 | }; 45 | } 46 | 47 | @Override 48 | public Function, Stream> finisher() { 49 | return LinkedList::stream; 50 | } 51 | 52 | @Override 53 | public Set characteristics() { 54 | return emptySet(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/stream/Streams.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import static java.util.Comparator.naturalOrder; 16 | import static java.util.Spliterator.DISTINCT; 17 | import static java.util.Spliterator.IMMUTABLE; 18 | import static java.util.Spliterator.NONNULL; 19 | import static java.util.Spliterator.ORDERED; 20 | import static java.util.Spliterator.SORTED; 21 | import static java.util.Spliterators.spliteratorUnknownSize; 22 | 23 | import java.util.Arrays; 24 | import java.util.Comparator; 25 | import java.util.Iterator; 26 | import java.util.Map; 27 | import java.util.Map.Entry; 28 | import java.util.Spliterator; 29 | import java.util.Spliterators; 30 | import java.util.function.BiFunction; 31 | import java.util.function.Function; 32 | import java.util.stream.Stream; 33 | import java.util.stream.StreamSupport; 34 | 35 | public class Streams { 36 | 37 | private static class ZippedIterator implements Iterator { 38 | 39 | private final Iterator iterator1; 40 | 41 | private final Iterator iterator2; 42 | private final BiFunction zipFunction; 43 | public ZippedIterator(Iterator iterator1, Iterator iterator2, 44 | BiFunction zipFunction) { 45 | this.iterator1 = iterator1; 46 | this.iterator2 = iterator2; 47 | this.zipFunction = zipFunction; 48 | } 49 | 50 | @Override 51 | public boolean hasNext() { 52 | return iterator1.hasNext() || iterator2.hasNext(); 53 | } 54 | 55 | @Override 56 | public R next() { 57 | return zipFunction.apply(iterator1.next(), iterator2.next()); 58 | } 59 | 60 | } 61 | public static Stream zip(Stream stream1, Stream stream2, BiFunction zipFunction) { 62 | Spliterator spliterator1 = stream1.spliterator(); 63 | Spliterator spliterator2 = stream2.spliterator(); 64 | 65 | 66 | ZippedIterator zippedIterator = new ZippedIterator<>(Spliterators.iterator(spliterator1), Spliterators.iterator(spliterator2), zipFunction); 67 | 68 | // TODO determine and set flags 69 | return StreamSupport.stream(spliteratorUnknownSize(zippedIterator, 0), false); 70 | } 71 | 72 | 73 | public static > Stream rangeClosed(T start, T endInclusive, Function incrementer) { 74 | return rangeClosed(start, endInclusive, incrementer, Comparator.naturalOrder()); 75 | } 76 | 77 | public static Stream rangeClosed(T start, T endInclusive, Function incrementer, Comparator comparator) { 78 | return rangeStream(start, endInclusive, true, incrementer, comparator); 79 | } 80 | 81 | public static > Stream range(T start, T endExclusive, Function incrementer) { 82 | return range(start, endExclusive, incrementer, naturalOrder()); 83 | } 84 | 85 | public static Stream range(T start, T endExclusive, Function incrementer, Comparator comparator) { 86 | return rangeStream(start, endExclusive, false, incrementer, comparator); 87 | } 88 | 89 | private static Stream rangeStream(T start, T endExclusive, boolean rangeClosed, Function incrementer, 90 | Comparator comparator) { 91 | Iterator iterator = new RangeIterator<>(start, endExclusive, rangeClosed, comparator, incrementer); 92 | 93 | Spliterator spliterator = spliteratorUnknownSize(iterator, ORDERED | SORTED | DISTINCT | NONNULL | IMMUTABLE); 94 | 95 | return StreamSupport.stream(spliterator, false); 96 | } 97 | 98 | /** 99 | * Returns a {@link java.util.stream.Stream#flatMap(Function) flatMap} {@link java.util.function.Function} that only retains a instances of a given type and casts them to this type. 100 | * 101 | *

102 | * Unlike other flatMap functions, this function will only return 0 or 1 result. If an instance passed to it 103 | * is of the specified type, then the function will return a {@link java.util.stream.Stream} with only this item, cast to this type. 104 | * If the instance is not of this type, the function will return {@link java.util.stream.Stream#empty()}. 105 | * 106 | * Example use 107 | * Say we have a Stream<X> from which we want retain only all instances of Y, then could do the following to 108 | * obtain a Stream<Y>: 109 | * 110 | * Stream<X> streamOfX = ...; 111 | * Stream<Y> streamOfY = streamOfX.flatMap(mapToType(Y.class)); 112 | * 113 | * 114 | * Which is the equivalent of this: 115 | * 116 | * streamOfX.filter(x -> x instanceof Y) 117 | * .map(x -> (Y)x) 118 | * 119 | *

120 | * 121 | * @param clazz the type of the instances to retain 122 | * @param the type of the elements in the stream 123 | * @param the type of the instances to retain 124 | * @return a flatMap function that only retains instances of a given type. 125 | */ 126 | public static Function> mapToType(Class clazz) { 127 | return t -> { 128 | if (clazz.isInstance(t)) { 129 | return Stream.of(clazz.cast(t)); 130 | } 131 | 132 | return Stream.empty(); 133 | }; 134 | } 135 | 136 | /** 137 | * Returns a stream of given object. Supported types are: 138 | *
    139 | *
  • {@link Iterable} 140 | *
  • {@link Map} (returns a stream of entryset) 141 | *
  • int[] 142 | *
  • long[] 143 | *
  • double[] 144 | *
  • Object[] 145 | *
  • {@link Stream} 146 | *
147 | * Anything else is returned as a single-element stream. Null is returned as an empty stream. 148 | * 149 | * @param The expected stream type. 150 | * @param object Any object to get a stream for. 151 | * @return A stream of given object. 152 | * @throws ClassCastException When T is of wrong type. 153 | */ 154 | @SuppressWarnings("unchecked") 155 | public static Stream stream(Object object) { 156 | if (object instanceof Iterable) { 157 | return (Stream) StreamSupport.stream(((Iterable) object).spliterator(), false); 158 | } 159 | else if (object instanceof Map) { 160 | return (Stream) ((Map) object).entrySet().stream(); 161 | } 162 | else if (object instanceof int[]) { 163 | return (Stream) Arrays.stream((int[]) object).boxed(); 164 | } 165 | else if (object instanceof long[]) { 166 | return (Stream) Arrays.stream((long[]) object).boxed(); 167 | } 168 | else if (object instanceof double[]) { 169 | return (Stream) Arrays.stream((double[]) object).boxed(); 170 | } 171 | else if (object instanceof Object[]) { 172 | return (Stream) Arrays.stream((Object[]) object); 173 | } 174 | else if (object instanceof Stream) { 175 | return (Stream) object; 176 | } 177 | else if (object != null) { 178 | return (Stream) Stream.of(object); 179 | } 180 | else { 181 | return Stream.empty(); 182 | } 183 | } 184 | 185 | public static Stream stream(Iterable iterable) { 186 | return iterable == null ? Stream.empty() : StreamSupport.stream(iterable.spliterator(), false); 187 | } 188 | 189 | public static Stream> stream(Map map) { 190 | return map == null ? Stream.empty() : map.entrySet().stream(); 191 | } 192 | 193 | public static Stream stream(T[] array) { 194 | return array == null ? Stream.empty() : Arrays.stream(array); 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/stream/Summary.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import java.util.function.Consumer; 16 | 17 | public interface Summary extends Consumer { 18 | 19 | long getCount(); 20 | 21 | T getMin(); 22 | 23 | T getMax(); 24 | 25 | void combine(Summary summary); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/stream/SummaryCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; 16 | import static java.util.stream.Collector.Characteristics.UNORDERED; 17 | 18 | import java.util.Comparator; 19 | import java.util.EnumSet; 20 | import java.util.Objects; 21 | import java.util.Set; 22 | import java.util.function.BiConsumer; 23 | import java.util.function.BinaryOperator; 24 | import java.util.function.Function; 25 | import java.util.function.Supplier; 26 | import java.util.stream.Collector; 27 | 28 | class SummaryCollector implements Collector, Summary> { 29 | 30 | private final Comparator comparator; 31 | 32 | SummaryCollector(Comparator comparator) { 33 | this.comparator = comparator; 34 | } 35 | 36 | private static class ComparableSummary implements Summary { 37 | private final Comparator comparator; 38 | 39 | private T min; 40 | private T max; 41 | 42 | private long count; 43 | 44 | private ComparableSummary(Comparator comparator) { 45 | this.comparator = comparator; 46 | } 47 | 48 | @Override 49 | public long getCount() { 50 | return count; 51 | } 52 | 53 | @Override 54 | public T getMin() { 55 | return min; 56 | } 57 | 58 | @Override 59 | public T getMax() { 60 | return max; 61 | } 62 | 63 | @Override 64 | public void accept(T t) { 65 | if (count == 0) { 66 | min = t; 67 | max = t; 68 | } else { 69 | if (Objects.compare(min, t, comparator) > 0) { 70 | min = t; 71 | } 72 | if (Objects.compare(t, max, comparator) > 0) { 73 | max = t; 74 | } 75 | } 76 | 77 | count++; 78 | } 79 | 80 | public void combine(Summary summary) { 81 | if (count == 0) { 82 | min = summary.getMin(); 83 | max = summary.getMax(); 84 | } 85 | else { 86 | if (Objects.compare(min, summary.getMin(), comparator) > 0) { 87 | min = summary.getMin(); 88 | } 89 | if (Objects.compare(summary.getMax(), max, comparator) > 0) { 90 | max = summary.getMax(); 91 | } 92 | } 93 | 94 | count += summary.getCount(); 95 | 96 | } 97 | } 98 | 99 | @Override 100 | public Supplier> supplier() { 101 | return () -> new ComparableSummary<>(comparator); 102 | } 103 | 104 | @Override 105 | public BiConsumer, T> accumulator() { 106 | return Summary::accept; 107 | } 108 | 109 | @Override 110 | public BinaryOperator> combiner() { 111 | return (a, b) -> { 112 | a.combine(b); 113 | 114 | return a; 115 | }; 116 | } 117 | 118 | @Override 119 | public Function, Summary> finisher() { 120 | return Function.identity(); 121 | } 122 | 123 | @Override 124 | public Set characteristics() { 125 | return EnumSet.of(IDENTITY_FINISH, UNORDERED); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/text/FormatterUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.text; 14 | 15 | import static java.time.Instant.ofEpochMilli; 16 | import static java.util.Collections.unmodifiableMap; 17 | 18 | import java.math.BigDecimal; 19 | import java.time.LocalDate; 20 | import java.time.LocalDateTime; 21 | import java.time.LocalTime; 22 | import java.time.ZonedDateTime; 23 | import java.time.format.DateTimeFormatter; 24 | import java.time.temporal.TemporalAccessor; 25 | import java.util.Date; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | 29 | final class FormatterUtil { 30 | 31 | private static final DateTimeFormatter PARSING_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("[HH:mm:ss][yyyy-MM-dd['T'HH:mm:ss[x]]"); 32 | 33 | static final Map DEFAULT_FORMATTER_FACTORIES; 34 | 35 | static { 36 | Map map = new HashMap<>(); 37 | 38 | map.put("string", SubFormatter::stringFormatter); 39 | map.put("number", SubFormatter::numberFormatter); 40 | map.put("date", SubFormatter::dateFormatter); 41 | map.put("time", SubFormatter::timeFormatter); 42 | map.put("dateTime", SubFormatter::dateTimeFormatter); 43 | map.put("choice", SubFormatter::choiceFormatter); 44 | map.put("optional", SubFormatter::optionalFormatter); 45 | map.put("boolean", SubFormatter::booleanFormatter); 46 | 47 | DEFAULT_FORMATTER_FACTORIES = unmodifiableMap(map); 48 | } 49 | 50 | private FormatterUtil() { 51 | } 52 | 53 | static Number getNumber(Object number) { 54 | if (number instanceof String) { 55 | return new BigDecimal((String) number); 56 | } 57 | 58 | if (number instanceof Number) { 59 | return (Number) number; 60 | } 61 | 62 | throw new IllegalArgumentException(number + " is not a number"); 63 | } 64 | 65 | static TemporalAccessor getTemporalAccessor(Object object) { 66 | if (object instanceof TemporalAccessor) { 67 | return (TemporalAccessor) object; 68 | } 69 | 70 | if (object instanceof Date) { 71 | return ((Date) object).toInstant(); 72 | } 73 | 74 | if (object instanceof Long) { 75 | return ofEpochMilli((Long) object); 76 | } 77 | 78 | if (object instanceof String) { 79 | return PARSING_DATE_TIME_FORMATTER.parseBest((String)object, ZonedDateTime::from, LocalDateTime::from, LocalDate::from, LocalTime::from); 80 | } 81 | 82 | throw new IllegalArgumentException(object + " is not of a valid temporal type"); 83 | } 84 | 85 | static int firstIndexOfNonQuoted(String string, char c) { 86 | boolean quoted = false; 87 | for (int i = 0; i < string.length(); i++) { 88 | if (string.charAt(i) == '\'') { 89 | quoted = !quoted; 90 | } 91 | else if (!quoted && string.charAt(i) == c) { 92 | return i; 93 | } 94 | } 95 | 96 | return -1; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/text/NameBasedMessageFormat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.text; 14 | 15 | import static java.util.Collections.unmodifiableList; 16 | import static java.util.Collections.unmodifiableMap; 17 | import static org.omnifaces.utils.text.FormatterUtil.DEFAULT_FORMATTER_FACTORIES; 18 | 19 | import java.io.IOException; 20 | import java.io.StringReader; 21 | import java.text.FieldPosition; 22 | import java.text.Format; 23 | import java.text.ParsePosition; 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Locale; 28 | import java.util.Map; 29 | import java.util.function.Consumer; 30 | import java.util.function.Function; 31 | 32 | public class NameBasedMessageFormat extends Format { 33 | 34 | private static final long serialVersionUID = -4520307378273079056L; 35 | 36 | private final String pattern; 37 | private final List, String>> segmentFunctions; 38 | private final Map subFormatterFactories; 39 | private final Locale locale; 40 | 41 | public NameBasedMessageFormat(String messagePattern) { 42 | this(messagePattern, Locale.getDefault()); 43 | } 44 | 45 | public NameBasedMessageFormat(String pattern, Locale locale) { 46 | this(pattern, locale, DEFAULT_FORMATTER_FACTORIES); 47 | } 48 | 49 | public NameBasedMessageFormat(String pattern, Locale locale, Map subFormatterFactories) { 50 | this.pattern = pattern; 51 | this.segmentFunctions = parsePattern(pattern, locale, subFormatterFactories); 52 | this.subFormatterFactories = copyToUnmodifiableMap(subFormatterFactories); 53 | this.locale = locale; 54 | } 55 | 56 | @Override 57 | @SuppressWarnings("unchecked") 58 | public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { 59 | if (obj instanceof Map) { 60 | format((Map)obj, toAppendTo::append); 61 | 62 | return toAppendTo; 63 | } 64 | 65 | throw new IllegalArgumentException("Can only format map-based arguments"); 66 | } 67 | 68 | public String format(Map parameters) { 69 | StringBuilder builder = new StringBuilder(); 70 | 71 | format(parameters, builder::append); 72 | 73 | return builder.toString(); 74 | } 75 | 76 | public NameBasedMessageFormat withLocale(Locale locale) { 77 | if (this.locale.equals(locale)) { 78 | return this; 79 | } 80 | 81 | return new NameBasedMessageFormat(pattern, locale, subFormatterFactories); 82 | } 83 | 84 | public NameBasedMessageFormat withPattern(String pattern) { 85 | if (this.pattern.equals(pattern)) { 86 | return this; 87 | } 88 | 89 | return new NameBasedMessageFormat(pattern, locale, subFormatterFactories); 90 | } 91 | 92 | @Override 93 | public Object parseObject(String source, ParsePosition pos) { 94 | throw new UnsupportedOperationException(); 95 | } 96 | 97 | public String getPattern() { 98 | return pattern; 99 | } 100 | 101 | public Map getSubFormatterFactories() { 102 | return subFormatterFactories; 103 | } 104 | 105 | public Locale getLocale() { 106 | return locale; 107 | } 108 | 109 | public static String format(String pattern, Map parameters) { 110 | return format(pattern, parameters, Locale.getDefault()); 111 | } 112 | 113 | public static String format(String pattern, Map parameters, Locale locale) { 114 | return new NameBasedMessageFormat(pattern, locale).format(parameters); 115 | } 116 | 117 | public static String format(String pattern, Map parameters, Locale locale, Map formatterFactories) { 118 | return new NameBasedMessageFormat(pattern, locale, formatterFactories).format(parameters); 119 | } 120 | 121 | private void format(Map parameters, Consumer toAppendTo) { 122 | segmentFunctions.stream() 123 | .map(function -> function.apply(parameters)) 124 | .forEachOrdered(toAppendTo); 125 | } 126 | 127 | private static List, String>> parsePattern(String pattern, Locale locale, Map formatterFactories) { 128 | List, String>> segmentFunctions = new ArrayList<>(); 129 | 130 | try (StringReader reader = new StringReader(pattern)) { 131 | int peek; 132 | while ((peek = peek(reader)) >= 0) { 133 | char nextChar = (char) peek; 134 | 135 | if (nextChar == '{') { 136 | segmentFunctions.add(parseFormat(reader, locale, formatterFactories)); 137 | } 138 | else { 139 | segmentFunctions.add(readText(reader)); 140 | } 141 | 142 | } 143 | } 144 | catch (IOException e) { 145 | throw new IllegalArgumentException("Illegal pattern format", e); 146 | } 147 | 148 | return unmodifiableList(segmentFunctions); 149 | } 150 | 151 | private static Function, String> readText(StringReader reader) throws IOException { 152 | int peek; 153 | 154 | StringBuilder builder = new StringBuilder(); 155 | 156 | while ((peek = peek(reader)) >= 0 && peek != '{') { 157 | char c = (char) reader.read(); 158 | 159 | if (c == '\'') { 160 | builder.append(readQuotedString(reader)); 161 | } 162 | else { 163 | builder.append(c); 164 | } 165 | 166 | } 167 | 168 | String s = builder.toString(); 169 | 170 | return parameters -> s; 171 | } 172 | 173 | private static String readQuotedString(StringReader reader) throws IOException { 174 | StringBuilder builder = new StringBuilder(); 175 | int read; 176 | boolean quoted = true; 177 | 178 | while ((read = reader.read()) >= 0 && quoted) { 179 | 180 | if (read == '\'') { 181 | int peek = peek(reader); 182 | 183 | if (builder.length() == 0 && peek != '\'') { 184 | // Just an escaped single quote 185 | builder.append('\''); 186 | quoted = false; 187 | 188 | return builder.toString(); 189 | } 190 | else if (peek == '\'') { 191 | reader.skip(1); 192 | builder.append('\''); 193 | } 194 | else { 195 | quoted = false; 196 | } 197 | } 198 | else { 199 | builder.append((char) read); 200 | } 201 | 202 | } 203 | 204 | return builder.toString(); 205 | } 206 | 207 | private static Function, String> parseFormat(StringReader reader, Locale locale, Map subFormatterFactories) 208 | throws IOException { 209 | if (reader.read() != '{') { 210 | throw new IllegalStateException("Not at the start of a format specification"); 211 | } 212 | 213 | StringBuilder parameterNameBuilder = new StringBuilder(); 214 | int read; 215 | while ((read = reader.read()) >= 0 && read != '}' && read != ',') { 216 | if (read == '\'') { 217 | parameterNameBuilder.append(readQuotedString(reader)); 218 | } 219 | else { 220 | parameterNameBuilder.append((char) read); 221 | } 222 | } 223 | 224 | String parameterName = parameterNameBuilder.toString(); 225 | 226 | String format; 227 | if (read == ',') { 228 | StringBuilder formatBuilder = new StringBuilder(); 229 | 230 | while ((read = reader.read()) >= 0 && read != '}' && read != ',') { 231 | formatBuilder.append((char) read); 232 | } 233 | 234 | format = formatBuilder.toString(); 235 | } 236 | else { 237 | format = "string"; 238 | } 239 | 240 | String modifier = null; 241 | if (read == ',') { 242 | StringBuilder modifierBuilder = new StringBuilder(); 243 | int bracketDepth = 0; 244 | boolean inQuotes = false; 245 | while ((read = reader.read()) >= 0 && (read != '}' || bracketDepth > 0 || inQuotes)) { 246 | modifierBuilder.append((char) read); 247 | 248 | if (read == '\'') { 249 | inQuotes = !inQuotes; 250 | } 251 | else if (!inQuotes) { 252 | if (read == '{') { 253 | bracketDepth++; 254 | } 255 | else if (read == '}') { 256 | bracketDepth--; 257 | } 258 | } 259 | } 260 | 261 | modifier = modifierBuilder.toString(); 262 | } 263 | 264 | if (read != '}') { 265 | throw new IllegalArgumentException("Invalid message format, formats must end with a }"); 266 | } 267 | 268 | SubFormatterFactory subFormatterFactory = subFormatterFactories.get(format); 269 | 270 | if (subFormatterFactory == null) { 271 | throw new IllegalArgumentException("Unknown format name: " + format); 272 | } 273 | 274 | SubFormatter subFormatter = subFormatterFactory.apply(modifier, locale); 275 | 276 | return parameters -> subFormatter.format(parameterName, parameters, pattern -> new NameBasedMessageFormat(pattern, locale, subFormatterFactories)); 277 | } 278 | 279 | private static int peek(StringReader reader) throws IOException { 280 | reader.mark(1); 281 | 282 | int result = reader.read(); 283 | 284 | reader.reset(); 285 | 286 | return result; 287 | } 288 | 289 | private static Map copyToUnmodifiableMap(Map subFormatterFactories) { 290 | Map subFormatterFactoriesCopy = new HashMap<>(subFormatterFactories); 291 | 292 | return unmodifiableMap(subFormatterFactoriesCopy); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/text/SubFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.text; 14 | 15 | import static java.text.NumberFormat.getCurrencyInstance; 16 | import static java.text.NumberFormat.getIntegerInstance; 17 | import static java.text.NumberFormat.getPercentInstance; 18 | import static java.time.format.DateTimeFormatter.ofLocalizedDate; 19 | import static java.time.format.DateTimeFormatter.ofLocalizedDateTime; 20 | import static java.time.format.DateTimeFormatter.ofLocalizedTime; 21 | import static java.time.format.DateTimeFormatter.ofPattern; 22 | import static java.time.format.FormatStyle.FULL; 23 | import static java.time.format.FormatStyle.LONG; 24 | import static java.time.format.FormatStyle.MEDIUM; 25 | import static java.time.format.FormatStyle.SHORT; 26 | import static org.omnifaces.utils.Lang.isEmpty; 27 | import static org.omnifaces.utils.text.FormatterUtil.firstIndexOfNonQuoted; 28 | import static org.omnifaces.utils.text.FormatterUtil.getNumber; 29 | import static org.omnifaces.utils.text.FormatterUtil.getTemporalAccessor; 30 | 31 | import java.io.Serializable; 32 | import java.text.ChoiceFormat; 33 | import java.text.DecimalFormat; 34 | import java.text.DecimalFormatSymbols; 35 | import java.text.NumberFormat; 36 | import java.time.format.DateTimeFormatter; 37 | import java.util.Locale; 38 | import java.util.Map; 39 | import java.util.function.Function; 40 | 41 | @FunctionalInterface 42 | public interface SubFormatter extends Serializable { 43 | 44 | /** 45 | * Formats the the parameter with the given name from the given set of parameters. 46 | * 47 | * {@link SubFormatter}s can recursively format messages using the supplied parameters and the given {@link NameBasedMessageFormat} factory. The 48 | * {@link NameBasedMessageFormat} factory should create an instance that uses the same settings as the format that is calling the current 49 | * sub-formatter, but with a different pattern. 50 | * 51 | * @param name 52 | * the name of the parameter to format 53 | * @param parameters 54 | * the map that contains all the parameters used in the current formatter operation 55 | * @param nameBasedMessageFormatFactory 56 | * a factory to create a new {@link NameBasedMessageFormat} for any recursively defined message patterns. 57 | * @return the formatted parameter 58 | */ 59 | String format(String name, Map parameters, Function nameBasedMessageFormatFactory); 60 | 61 | static SubFormatter stringFormatter(String modifier, Locale locale) { 62 | return (name, parameters, patternFormatFactory) -> "" + parameters.get(name); 63 | } 64 | 65 | static SubFormatter numberFormatter(String modifier, Locale locale) { 66 | if (isEmpty(modifier)) { 67 | return (name, parameters, patternFormatFactory) -> NumberFormat.getInstance(locale).format(getNumber(parameters.get(name))); 68 | } 69 | 70 | switch (modifier) { 71 | case "integer": 72 | return (name, parameters, patternFormatFactory) -> getIntegerInstance(locale).format(getNumber(parameters.get(name))); 73 | case "currency": 74 | return (name, parameters, patternFormatFactory) -> getCurrencyInstance(locale).format(getNumber(parameters.get(name))); 75 | case "percent": 76 | return (name, parameters, patternFormatFactory) -> getPercentInstance(locale).format(getNumber(parameters.get(name))); 77 | default: 78 | return (name, parameters, patternFormatFactory) -> new DecimalFormat(modifier, DecimalFormatSymbols.getInstance(locale)).format(getNumber(parameters 79 | .get(name))); 80 | } 81 | } 82 | 83 | static SubFormatter dateFormatter(String modifier, Locale locale) { 84 | DateTimeFormatter dateTimeFormatter; 85 | if (isEmpty(modifier)) { 86 | dateTimeFormatter = ofLocalizedDate(MEDIUM).withLocale(locale); 87 | } 88 | else { 89 | switch (modifier) { 90 | case "short": 91 | dateTimeFormatter = ofLocalizedDate(SHORT).withLocale(locale); 92 | break; 93 | case "medium": 94 | dateTimeFormatter = ofLocalizedDate(MEDIUM).withLocale(locale); 95 | break; 96 | case "long": 97 | dateTimeFormatter = ofLocalizedDate(LONG).withLocale(locale); 98 | break; 99 | case "full": 100 | dateTimeFormatter = ofLocalizedDate(FULL).withLocale(locale); 101 | break; 102 | default: 103 | dateTimeFormatter = ofPattern(modifier).withLocale(locale); 104 | } 105 | } 106 | 107 | return (name, parameters, patternFormatFactory) -> dateTimeFormatter.format(getTemporalAccessor(parameters.get(name))); 108 | } 109 | 110 | static SubFormatter dateTimeFormatter(String modifier, Locale locale) { 111 | DateTimeFormatter dateTimeFormatter; 112 | if (isEmpty(modifier)) { 113 | dateTimeFormatter = ofLocalizedDateTime(MEDIUM).withLocale(locale); 114 | } 115 | else { 116 | switch (modifier) { 117 | case "short": 118 | dateTimeFormatter = ofLocalizedDateTime(SHORT).withLocale(locale); 119 | break; 120 | case "medium": 121 | dateTimeFormatter = ofLocalizedDateTime(MEDIUM).withLocale(locale); 122 | break; 123 | case "long": 124 | dateTimeFormatter = ofLocalizedDateTime(LONG).withLocale(locale); 125 | break; 126 | case "full": 127 | dateTimeFormatter = ofLocalizedDateTime(FULL).withLocale(locale); 128 | break; 129 | default: 130 | dateTimeFormatter = ofPattern(modifier).withLocale(locale); 131 | } 132 | } 133 | 134 | return (name, parameters, patternFormatFactory) -> dateTimeFormatter.format(getTemporalAccessor(parameters.get(name))); 135 | } 136 | 137 | static SubFormatter timeFormatter(String modifier, Locale locale) { 138 | DateTimeFormatter dateTimeFormatter; 139 | if (isEmpty(modifier)) { 140 | dateTimeFormatter = ofLocalizedTime(MEDIUM).withLocale(locale); 141 | } 142 | else { 143 | switch (modifier) { 144 | case "short": 145 | dateTimeFormatter = ofLocalizedTime(SHORT).withLocale(locale); 146 | break; 147 | case "medium": 148 | dateTimeFormatter = ofLocalizedTime(MEDIUM).withLocale(locale); 149 | break; 150 | case "long": 151 | dateTimeFormatter = ofLocalizedTime(LONG).withLocale(locale); 152 | break; 153 | case "full": 154 | dateTimeFormatter = ofLocalizedTime(FULL).withLocale(locale); 155 | break; 156 | default: 157 | dateTimeFormatter = ofPattern(modifier).withLocale(locale); 158 | } 159 | } 160 | 161 | return (name, parameters, patternFormatFactory) -> dateTimeFormatter.format(getTemporalAccessor(parameters.get(name))); 162 | } 163 | 164 | static SubFormatter choiceFormatter(String modifier, Locale locale) { 165 | return (name, parameters, patternFormatFactory) -> { 166 | Number number = getNumber(parameters.get(name)); 167 | 168 | ChoiceFormat choiceFormat = new ChoiceFormat(modifier); 169 | String format = choiceFormat.format(number.doubleValue()); 170 | 171 | if (format.indexOf('{') >= 0) { 172 | return patternFormatFactory.apply(format).format(parameters); 173 | } 174 | 175 | return format; 176 | }; 177 | } 178 | 179 | static SubFormatter optionalFormatter(String modifier, Locale locale) { 180 | String replacementPattern; 181 | String formatPattern; 182 | 183 | int indexOfSeparator = firstIndexOfNonQuoted(modifier, '|'); 184 | if (indexOfSeparator >= 0) { 185 | replacementPattern = modifier.substring(0, indexOfSeparator); 186 | formatPattern = modifier.substring(indexOfSeparator + 1); 187 | } else { 188 | replacementPattern = modifier; 189 | formatPattern = null; 190 | } 191 | 192 | return (name, parameters, patternFormatFactory) -> { 193 | Object parameter = parameters.get(name); 194 | 195 | String outputPattern; 196 | 197 | if (parameter == null) { 198 | outputPattern = replacementPattern; 199 | } else if (formatPattern == null) { 200 | return "" + parameter; 201 | } else { 202 | outputPattern = formatPattern; 203 | } 204 | 205 | if (outputPattern.contains("{") || outputPattern.contains("'")) { 206 | // Pattern contains special characters, format using a new NameBasedMessageFormat instance 207 | return patternFormatFactory.apply(outputPattern) 208 | .format(parameters); 209 | } 210 | 211 | return outputPattern; 212 | }; 213 | } 214 | 215 | static SubFormatter booleanFormatter(String modifier, Locale locale) { 216 | String truePattern; 217 | String falsePattern; 218 | 219 | int indexOfSeparator = firstIndexOfNonQuoted(modifier, '|'); 220 | 221 | if (indexOfSeparator >= 0) { 222 | truePattern = modifier.substring(0, indexOfSeparator); 223 | falsePattern = modifier.substring(indexOfSeparator + 1); 224 | } else { 225 | truePattern = modifier; 226 | falsePattern = ""; 227 | } 228 | 229 | return (name, parameters, patternFormatFactory) -> { 230 | if (Boolean.parseBoolean("" + parameters.get(name))) { 231 | return patternFormatFactory.apply(truePattern) 232 | .format(parameters); 233 | } 234 | else { 235 | return patternFormatFactory.apply(falsePattern) 236 | .format(parameters); 237 | } 238 | }; 239 | } 240 | 241 | /** 242 | * Formatter that always formats the given modifier as a pattern instead of using the given parameter. This formatter may be useful for testing or 243 | * for more complex dynamic formatting. 244 | * 245 | * @param modifier the {@link String} to use as alternative 246 | * @param locale the locale to use when formatting 247 | * @return a formatter that outputs the given modifier, formatted as pattern 248 | */ 249 | static SubFormatter alternativeFormatter(String modifier, Locale locale) { 250 | return (name, parameters, patternFormatFactory) -> patternFormatFactory.apply(modifier).format(parameters); 251 | } 252 | 253 | } 254 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/text/SubFormatterFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.text; 14 | 15 | import java.util.Locale; 16 | import java.util.Map; 17 | import java.util.function.BiFunction; 18 | 19 | @FunctionalInterface 20 | public interface SubFormatterFactory extends BiFunction { 21 | 22 | SubFormatter createFormatter(String modifier, Locale locale); 23 | 24 | @Override 25 | default SubFormatter apply(String modifier, Locale locale) { 26 | return createFormatter(modifier, locale); 27 | } 28 | 29 | static Map defaultFormatterFactories() { 30 | return FormatterUtil.DEFAULT_FORMATTER_FACTORIES; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/utils/time/TemporalAdjusters.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.time; 14 | 15 | import static java.time.temporal.ChronoField.DAY_OF_MONTH; 16 | import static java.time.temporal.TemporalAdjusters.firstDayOfNextMonth; 17 | 18 | import java.time.temporal.TemporalAdjuster; 19 | import java.time.temporal.ValueRange; 20 | 21 | public final class TemporalAdjusters { 22 | 23 | private TemporalAdjusters() { 24 | } 25 | 26 | public static TemporalAdjuster nextDayOfMonth(int dayOfMonth) { 27 | validateDayOfMonth(dayOfMonth); 28 | 29 | return temporal -> { 30 | int currentDayOfMonth = temporal.get(DAY_OF_MONTH); 31 | 32 | if(currentDayOfMonth >= dayOfMonth || temporal.range(DAY_OF_MONTH).getMaximum() == currentDayOfMonth) { 33 | temporal = temporal.with(firstDayOfNextMonth()); 34 | } 35 | 36 | ValueRange dayRange = temporal.range(DAY_OF_MONTH); 37 | 38 | int newDayOfMonth = dayOfMonth; 39 | 40 | if (dayRange.getMaximum() < dayOfMonth) { 41 | newDayOfMonth = (int) dayRange.getMaximum(); 42 | } 43 | 44 | return temporal.with(DAY_OF_MONTH, newDayOfMonth); 45 | }; 46 | } 47 | 48 | public static TemporalAdjuster nextOrSameDayOfMonth(int dayOfMonth) { 49 | validateDayOfMonth(dayOfMonth); 50 | 51 | TemporalAdjuster nextDayOfMonth = nextDayOfMonth(dayOfMonth); 52 | 53 | return temporal -> { 54 | int currentDayOfMonth = temporal.get(DAY_OF_MONTH); 55 | 56 | if (currentDayOfMonth == dayOfMonth || (currentDayOfMonth < dayOfMonth && currentDayOfMonth == temporal.range(DAY_OF_MONTH).getMaximum())) { 57 | return temporal; 58 | } 59 | 60 | return temporal.with(nextDayOfMonth); 61 | }; 62 | } 63 | 64 | private static void validateDayOfMonth(int dayOfMonth) { 65 | DAY_OF_MONTH.checkValidValue(dayOfMonth); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/AnnotationsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils; 14 | 15 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 16 | import static org.junit.Assert.assertEquals; 17 | import static org.junit.Assert.assertFalse; 18 | import static org.junit.Assert.assertNotEquals; 19 | import static org.junit.Assert.assertTrue; 20 | 21 | import java.lang.annotation.Annotation; 22 | import java.lang.annotation.Retention; 23 | import java.util.Collections; 24 | 25 | import org.junit.Test; 26 | import org.omnifaces.utils.annotation.Annotations; 27 | 28 | public class AnnotationsTest { 29 | 30 | @Test 31 | public void createAnnotationInstanceTest() throws Exception { 32 | Annotation actualFoo = AnnotationsTest.class.getMethod("foo").getAnnotation(Foo.class); 33 | Annotation actualFooWithAttribute = AnnotationsTest.class.getMethod("fooWithAttribute").getAnnotation(Foo.class); 34 | 35 | Annotation proxiedFoo = Annotations.createAnnotationInstance(Foo.class); 36 | Annotation proxiedFooWithAttribute = Annotations.createAnnotationInstance(Foo.class, Collections.singletonMap("foo", (Object) "foo")); 37 | 38 | assertTrue(actualFoo.equals(proxiedFoo)); 39 | assertTrue(actualFooWithAttribute.equals(proxiedFooWithAttribute)); 40 | assertEquals(actualFoo.hashCode(), proxiedFoo.hashCode()); 41 | assertEquals(actualFooWithAttribute.hashCode(), proxiedFooWithAttribute.hashCode()); 42 | 43 | assertFalse(actualFoo.equals(proxiedFooWithAttribute)); 44 | assertFalse(actualFooWithAttribute.equals(proxiedFoo)); 45 | assertNotEquals(actualFoo.hashCode(), proxiedFooWithAttribute.hashCode()); 46 | assertNotEquals(actualFooWithAttribute.hashCode(), proxiedFoo.hashCode()); 47 | } 48 | 49 | public static @Retention(RUNTIME) @interface Foo { 50 | String foo() default ""; 51 | } 52 | 53 | @Foo 54 | public void foo() { 55 | // 56 | } 57 | 58 | @Foo(foo="foo") 59 | public void fooWithAttribute() { 60 | // 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/BigDecimalMathTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils; 14 | 15 | import static java.math.BigDecimal.TEN; 16 | import static java.math.BigDecimal.ZERO; 17 | import static org.hamcrest.MatcherAssert.assertThat; 18 | import static org.hamcrest.number.BigDecimalCloseTo.closeTo; 19 | import static org.omnifaces.utils.math.BigDecimalMath.nRoot; 20 | 21 | import java.math.BigDecimal; 22 | import java.math.MathContext; 23 | 24 | import org.junit.Test; 25 | 26 | public class BigDecimalMathTest { 27 | 28 | @Test 29 | public void testNRoot() { 30 | MathContext context = new MathContext(10); 31 | 32 | assertThat(nRoot(BigDecimal.valueOf(100), 2, context), closeTo(TEN, ZERO)); 33 | assertThat(nRoot(BigDecimal.valueOf(256), 8, context), closeTo(BigDecimal.valueOf(2), ZERO)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/ComparatorsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils; 14 | 15 | import static java.util.Comparator.naturalOrder; 16 | import static java.util.stream.Collectors.toList; 17 | import static org.junit.Assert.assertEquals; 18 | import static org.omnifaces.utils.Comparators.firstWhen; 19 | import static org.omnifaces.utils.Comparators.lastWhen; 20 | 21 | import java.util.Arrays; 22 | import java.util.List; 23 | import java.util.stream.Stream; 24 | 25 | import org.junit.Test; 26 | 27 | public class ComparatorsTest { 28 | 29 | @Test 30 | public void testFirstWhen() { 31 | List result = Stream.of("D", "A", "BB", "C", "BA") 32 | .sorted(firstWhen(s -> s.startsWith("B"), naturalOrder())) 33 | .collect(toList()); 34 | 35 | assertEquals(Arrays.asList("BA", "BB", "A", "C", "D"), result); 36 | } 37 | 38 | @Test 39 | public void testLastWhen() { 40 | List result = Stream.of("D", "A", "BB", "C", "BA") 41 | .sorted(lastWhen(s -> s.startsWith("B"), naturalOrder())) 42 | .collect(toList()); 43 | 44 | 45 | assertEquals(Arrays.asList("A", "C", "D", "BA", "BB"), result); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/ComparisonsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils; 14 | 15 | import static java.util.Comparator.reverseOrder; 16 | import static org.junit.Assert.assertEquals; 17 | import static org.omnifaces.utils.Comparisons.max; 18 | import static org.omnifaces.utils.Comparisons.min; 19 | 20 | import org.junit.Test; 21 | 22 | public class ComparisonsTest { 23 | 24 | @Test 25 | public void testMin() { 26 | assertEquals(1L, (long) min(1, 2)); 27 | assertEquals(1L, (long) min(2, 1)); 28 | assertEquals(2L, (long) min(2, 2)); 29 | 30 | assertEquals(2L, (long) min(4, 3, 2)); 31 | } 32 | 33 | @Test 34 | public void testMinWithComparator() { 35 | assertEquals(2L, (long) min(reverseOrder(), 1, 2)); 36 | assertEquals(2L, (long) min(reverseOrder(), 2, 1)); 37 | assertEquals(2L, (long) min(reverseOrder(), 2, 2)); 38 | } 39 | 40 | @Test 41 | public void testMax() { 42 | assertEquals(2L, (long) max(1, 2)); 43 | assertEquals(2L, (long) max(2, 1)); 44 | assertEquals(2L, (long) max(2, 2)); 45 | 46 | assertEquals(2L, (long) max(0, 1, 2)); 47 | } 48 | 49 | @Test 50 | public void testMaxWithComparator() { 51 | assertEquals(1L, (long) max(reverseOrder(), 1, 2)); 52 | assertEquals(1L, (long) max(reverseOrder(), 2, 1)); 53 | assertEquals(2L, (long) max(reverseOrder(), 2, 2)); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/LangTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils; 14 | 15 | import static java.util.Collections.emptyList; 16 | import static java.util.Collections.singletonList; 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.assertNotNull; 19 | import static org.junit.Assert.assertNull; 20 | import static org.junit.Assert.fail; 21 | import static org.omnifaces.utils.Lang.setIfNotEmpty; 22 | import static org.omnifaces.utils.Lang.toTitleCase; 23 | import static org.omnifaces.utils.Lang.toUrlSafe; 24 | 25 | import java.util.List; 26 | import java.util.concurrent.atomic.AtomicReference; 27 | 28 | import org.junit.Test; 29 | 30 | public class LangTest { 31 | 32 | @Test 33 | public void setIfNotEmptyTest() { 34 | setIfNotEmpty(emptyList(), collection -> fail()); 35 | 36 | AtomicReference> reference = new AtomicReference<>(); 37 | 38 | setIfNotEmpty(singletonList(""), reference::set); 39 | 40 | assertNotNull(reference.get()); 41 | } 42 | 43 | @Test 44 | public void titleCaseTest() { 45 | assertNull(toTitleCase(null)); 46 | assertEquals("Lorem Ipsum Dolor Sit Amet", toTitleCase("lorem ipsum dolor sit amet")); 47 | assertEquals("Lorem Ipsum Dolor Sit Amet", toTitleCase("LOREM IPSUM DOLOR SIT AMET")); 48 | assertEquals("Lorem Ipsum Dolor Sit Amet", toTitleCase("lOREM iPSUM dOLOR sIT aMET")); 49 | assertEquals("Lorem Ipsum Dolor Sit Amet", toTitleCase("LOREm IPSUm DOLOr SIt AMEt")); 50 | assertEquals("Lorem Ipsum Dolor Sit Amet", toTitleCase("LoReM IpSuM DoLoR SiT AmEt")); 51 | assertEquals("Lorem Ipsum Dolor Sit Amet", toTitleCase("Lorem Ipsum Dolor Sit Amet")); 52 | } 53 | 54 | @Test 55 | public void urlSafeTest() { 56 | assertNull(toUrlSafe(null)); 57 | assertEquals("lorem-ipsum-dolor-sit-amet", toUrlSafe("lorem ipsum dolor sit amet")); 58 | assertEquals("LOREM-IPSUM-DOLOR-SIT-AMET", toUrlSafe("LOREM IPSUM DOLOR SIT AMET")); 59 | assertEquals("lorem-ipsum-dOlor-sIt-amEt", toUrlSafe("lórém ípsúm dÓlor sÍt ámÉt")); 60 | assertEquals("lorem-ipsum-dolor-sit-amet", toUrlSafe("lorem--ipsum--dolor--sit--amet")); 61 | assertEquals("lorem-ipsum-dolor-sit-amet", toUrlSafe("?lorem&ipsum%dolor_sit amet-")); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/data/BaseRangeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.data; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.junit.Assert.assertFalse; 17 | import static org.junit.Assert.assertNotEquals; 18 | import static org.junit.Assert.assertTrue; 19 | 20 | import org.junit.Test; 21 | 22 | public abstract class BaseRangeTest { 23 | 24 | protected abstract > Range newRange(N min, N max); 25 | 26 | @Test 27 | public void testWithMin() { 28 | Range range = newRange(1, 10); 29 | 30 | Range newRange = range.withMin(0); 31 | 32 | assertEquals((Integer)1, range.getMin()); 33 | assertNotEquals(range, newRange); 34 | 35 | assertEquals(range.getMax(), newRange.getMax()); 36 | assertEquals(range.isMinInclusive(), newRange.isMinInclusive()); 37 | assertEquals(range.isMaxInclusive(), newRange.isMaxInclusive()); 38 | 39 | assertEquals((Integer)0, newRange.getMin()); 40 | } 41 | 42 | @Test 43 | public void testWithMax() { 44 | Range range = newRange(1, 10); 45 | 46 | Range newRange = range.withMax(100); 47 | 48 | assertEquals((Integer)10, range.getMax()); 49 | assertNotEquals(range, newRange); 50 | 51 | assertEquals(range.getMin(), newRange.getMin()); 52 | assertEquals(range.isMinInclusive(), newRange.isMinInclusive()); 53 | assertEquals(range.isMaxInclusive(), newRange.isMaxInclusive()); 54 | 55 | assertEquals((Integer)100, newRange.getMax()); 56 | } 57 | 58 | @Test 59 | public void testWithMinInclusive() { 60 | Range range = newRange(1, 10).withMinInclusive(true); 61 | 62 | assertTrue(range.isMinInclusive()); 63 | 64 | Range newRange = range.withMinInclusive(false); 65 | 66 | assertTrue(range.isMinInclusive()); 67 | 68 | assertFalse(newRange.isMinInclusive()); 69 | 70 | assertNotEquals(range, newRange); 71 | assertEquals(range.getMin(), newRange.getMin()); 72 | assertEquals(range.getMax(), newRange.getMax()); 73 | assertEquals(range.isMaxInclusive(), newRange.isMaxInclusive()); 74 | } 75 | 76 | @Test 77 | public void testWithMaxInclusive() { 78 | Range range = newRange(1, 10).withMaxInclusive(true); 79 | 80 | assertTrue(range.isMaxInclusive()); 81 | 82 | Range newRange = range.withMaxInclusive(false); 83 | 84 | assertTrue(range.isMaxInclusive()); 85 | 86 | assertFalse(newRange.isMaxInclusive()); 87 | 88 | assertNotEquals(range, newRange); 89 | assertEquals(range.getMin(), newRange.getMin()); 90 | assertEquals(range.getMax(), newRange.getMax()); 91 | assertEquals(range.isMinInclusive(), newRange.isMinInclusive()); 92 | } 93 | 94 | @Test 95 | public void testContains() { 96 | Range range = newRange(1, 10).withMinInclusive(true).withMaxInclusive(true); 97 | 98 | assertFalse(range.contains(0)); 99 | assertFalse(range.contains(11)); 100 | assertTrue(range.contains(1)); 101 | assertTrue(range.contains(10)); 102 | assertTrue(range.contains(5)); 103 | 104 | range = range.withMinInclusive(false); 105 | 106 | assertFalse(range.contains(1)); 107 | 108 | range= range.withMaxInclusive(false); 109 | 110 | assertFalse(range.contains(10)); 111 | } 112 | 113 | @Test 114 | public void testIntersects() { 115 | Range range1 = newRange(1, 10).withMinInclusive(true).withMaxInclusive(false); 116 | Range range2 = newRange(10, 20).withMinInclusive(true).withMaxInclusive(false); 117 | 118 | assertTrue(range1.intersects(range1)); 119 | assertTrue(range2.intersects(range2)); 120 | 121 | assertFalse(range1.intersects(range2)); 122 | assertFalse(range2.intersects(range1)); 123 | 124 | range1 = range1.withMaxInclusive(true); 125 | 126 | assertTrue(range1.intersects(range2)); 127 | assertTrue(range2.intersects(range1)); 128 | 129 | range2 = range2.withMinInclusive(false); 130 | 131 | assertFalse(range1.intersects(range2)); 132 | assertFalse(range2.intersects(range1)); 133 | 134 | range1 = range1.withMaxInclusive(false); 135 | 136 | assertFalse(range1.intersects(range2)); 137 | assertFalse(range2.intersects(range1)); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/data/MutableRangeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.data; 14 | 15 | public class MutableRangeTest extends BaseRangeTest { 16 | 17 | @Override 18 | protected > Range newRange(N min, N max) { 19 | return MutableRange.of(min, max); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/data/RangeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.data; 14 | 15 | public class RangeTest extends BaseRangeTest { 16 | 17 | @Override 18 | protected > Range newRange(N min, N max) { 19 | return Range.of(min, max); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/function/PredicatesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.function; 14 | 15 | import static org.junit.Assert.*; 16 | 17 | import org.junit.Test; 18 | 19 | public class PredicatesTest { 20 | 21 | @Test 22 | public void testIsLessThan() { 23 | assertTrue(Predicates.isLessThan("b").test("a")); 24 | assertFalse(Predicates.isLessThan("b").test("b")); 25 | assertFalse(Predicates.isLessThan("a").test("b")); 26 | } 27 | 28 | @Test 29 | public void testIsLessThanOrEqual() { 30 | assertTrue(Predicates.isLessThanOrEqual("b").test("a")); 31 | assertTrue(Predicates.isLessThanOrEqual("b").test("b")); 32 | assertFalse(Predicates.isLessThanOrEqual("a").test("b")); 33 | } 34 | 35 | @Test 36 | public void testIsGreaterThan() { 37 | assertFalse(Predicates.isGreaterThan("b").test("a")); 38 | assertFalse(Predicates.isGreaterThan("b").test("b")); 39 | assertTrue(Predicates.isGreaterThan("a").test("b")); 40 | } 41 | 42 | @Test 43 | public void testIsGreaterThanOrEqual() { 44 | assertFalse(Predicates.isGreaterThanOrEqual("b").test("a")); 45 | assertTrue(Predicates.isGreaterThanOrEqual("b").test("b")); 46 | assertTrue(Predicates.isGreaterThanOrEqual("a").test("b")); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/stream/CollectorsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.junit.Assert.assertTrue; 17 | import static org.omnifaces.utils.stream.Collectors.findLast; 18 | 19 | import java.util.Iterator; 20 | import java.util.Optional; 21 | import java.util.stream.IntStream; 22 | import java.util.stream.Stream; 23 | 24 | import org.junit.Test; 25 | 26 | public class CollectorsTest { 27 | 28 | @Test 29 | public void testReversedStream() { 30 | Iterator iterator = IntStream.rangeClosed(0, 10000000) 31 | .boxed() 32 | .parallel() 33 | .collect(Collectors.reversedStream()).iterator(); 34 | 35 | for (int i = 10000000; i >= 0; i--) { 36 | assertTrue(iterator.hasNext()); 37 | assertEquals(i, iterator.next().intValue()); 38 | } 39 | } 40 | 41 | @Test 42 | public void testFindLast() { 43 | assertEquals(Optional.of("a"), Stream.of("a").collect(findLast())); 44 | assertEquals(Optional.of("b"), Stream.of("a", "b").collect(findLast())); 45 | assertEquals(Optional.empty(), Stream.empty().collect(findLast())); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/stream/StreamsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.stream; 14 | 15 | import static java.math.BigDecimal.ONE; 16 | import static java.util.Arrays.asList; 17 | import static java.util.stream.Collectors.toList; 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertFalse; 20 | import static org.omnifaces.utils.stream.Streams.mapToType; 21 | import static org.omnifaces.utils.stream.Streams.range; 22 | import static org.omnifaces.utils.stream.Streams.rangeClosed; 23 | 24 | import java.math.BigDecimal; 25 | import java.util.Comparator; 26 | import java.util.Objects; 27 | import java.util.Optional; 28 | import java.util.stream.Stream; 29 | 30 | import org.junit.Test; 31 | 32 | public class StreamsTest { 33 | 34 | private static final BigDecimal TWO = BigDecimal.valueOf(2L); 35 | private static final BigDecimal THREE = BigDecimal.valueOf(3L); 36 | 37 | private static class NonComparable { 38 | 39 | private final int value; 40 | 41 | private NonComparable(int value) { 42 | this.value = value; 43 | } 44 | 45 | public int getValue() { 46 | return value; 47 | } 48 | 49 | public NonComparable decrement() { 50 | return new NonComparable(value - 1); 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) { 56 | return true; 57 | } 58 | if (o == null || getClass() != o.getClass()) { 59 | return false; 60 | } 61 | NonComparable that = (NonComparable) o; 62 | return value == that.value; 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Objects.hash(value); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return "NonComparable{" + 73 | "value=" + value + 74 | '}'; 75 | } 76 | } 77 | 78 | @Test 79 | public void testMapToType() { 80 | Optional result = Stream.of((CharSequence) "") 81 | .flatMap(mapToType(String.class)) 82 | .findAny(); 83 | 84 | 85 | assertEquals("", result.get()); 86 | 87 | Optional stringBuilderOptional = Stream.of((CharSequence) "") 88 | .flatMap(mapToType(StringBuilder.class)) 89 | .findAny(); 90 | assertFalse(stringBuilderOptional.isPresent()); 91 | } 92 | 93 | @Test 94 | public void testRange() { 95 | assertEquals(asList(ONE, TWO), range(ONE, THREE, i -> i.add(ONE)).collect(toList())); 96 | 97 | assertEquals( 98 | asList(new NonComparable(2), new NonComparable(1), new NonComparable(0)), 99 | range(new NonComparable(2), new NonComparable(-1), NonComparable::decrement, 100 | Comparator.comparingInt(NonComparable::getValue).reversed()).collect(toList()) 101 | ); 102 | } 103 | 104 | @Test 105 | public void testRangeClosed() { 106 | assertEquals(asList(ONE, TWO, THREE), rangeClosed(ONE, THREE, i -> i.add(ONE)).collect(toList())); 107 | 108 | assertEquals( 109 | asList(new NonComparable(2), new NonComparable(1), new NonComparable(0)), 110 | range(new NonComparable(2), new NonComparable(-1), NonComparable::decrement, 111 | Comparator.comparingInt(NonComparable::getValue).reversed()).collect(toList()) 112 | ); 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/text/NameBasedMessageFormatTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.text; 14 | 15 | import static java.util.Collections.emptyMap; 16 | import static java.util.Locale.ENGLISH; 17 | import static org.junit.Assert.assertEquals; 18 | 19 | import java.time.LocalDate; 20 | import java.time.LocalDateTime; 21 | import java.time.LocalTime; 22 | import java.time.ZoneId; 23 | import java.time.ZonedDateTime; 24 | import java.util.HashMap; 25 | import java.util.Locale; 26 | import java.util.Map; 27 | 28 | import org.junit.Ignore; 29 | import org.junit.Test; 30 | 31 | public class NameBasedMessageFormatTest { 32 | 33 | @Test 34 | public void testString() { 35 | assertEquals("Test", format("Test", emptyMap())); 36 | assertEquals("Test'", format("Test''", emptyMap())); 37 | assertEquals("Test{0,number,currency}", format("Test'{0,number,currency}'", emptyMap())); 38 | assertEquals("{'}", format("'{''}'", emptyMap())); 39 | } 40 | 41 | @Test 42 | public void testWithToStringParameter() { 43 | Map parameters = buildParameters(); 44 | assertEquals("Test15", new NameBasedMessageFormat("Test{integer}").format(parameters)); 45 | } 46 | 47 | private Map buildParameters() { 48 | Map parameters = new HashMap<>(); 49 | 50 | parameters.put("integer", 15); 51 | parameters.put("double", 16.5); 52 | parameters.put("long", Long.MAX_VALUE); 53 | parameters.put("integerAsString", "" + 54321); 54 | parameters.put("doubleAsString", "" + 15.6); 55 | parameters.put("firstOfJanuary2015", LocalDate.of(2015, 1, 1)); 56 | parameters.put("404Time", LocalTime.of(16, 4, 42)); 57 | parameters.put("localDateTime", LocalDateTime.of(2020, 12, 1, 15, 35, 12, 4)); 58 | parameters.put("zonedDateTime", ZonedDateTime.of(2015, 2, 24, 15, 47, 58, 0, ZoneId.of("Europe/Amsterdam"))); 59 | parameters.put("isoLocalDate", "2011-12-03"); 60 | parameters.put("isoLocalTime", "10:15:30"); 61 | parameters.put("isoLocalDateTime", "2012-01-15T11:12:13"); 62 | parameters.put("isoOffsetDateTime", "2009-05-14T15:16:17-0900"); 63 | parameters.put("isoInstant", "2014-11-11T05:06:07Z"); 64 | parameters.put("zero", "0"); 65 | parameters.put("one", "1"); 66 | parameters.put("ten", "10"); 67 | 68 | return parameters; 69 | } 70 | 71 | @Test 72 | public void testWithNumberParameter() { 73 | Map parameters = buildParameters(); 74 | 75 | assertEquals("Test 15", format("Test {integer,number}", parameters)); 76 | assertEquals("Test 16", format("Test {double,number,integer}", parameters)); 77 | Locale locale = new Locale("nl", "NL"); 78 | 79 | assertEquals("Test 54.321", format("Test {integerAsString,number,integer}", locale, parameters)); 80 | assertEquals("Test € 16,50", format("Test {double,number,currency}", locale, parameters)); 81 | assertEquals("Test 1.650%", format("Test {double,number,percent}", locale, parameters)); 82 | 83 | assertEquals("Test 1.6,5", format("Test {double,number,#,#.#}", locale, parameters)); 84 | } 85 | 86 | 87 | @Test 88 | @Ignore 89 | public void testWithDates() { 90 | Map parameters = buildParameters(); 91 | Locale locale = new Locale("nl", "NL"); 92 | 93 | assertEquals("Test 1 jan. 2015", format("Test {firstOfJanuary2015,date}", locale, parameters)); 94 | assertEquals("Test 01-01-15", format("Test {firstOfJanuary2015,date,short}", locale, parameters)); 95 | assertEquals("Test 1 jan. 2015", format("Test {firstOfJanuary2015,date,medium}", locale, parameters)); 96 | assertEquals("Test 1 januari 2015", format("Test {firstOfJanuary2015,date,long}", locale, parameters)); 97 | assertEquals("Test donderdag 1 januari 2015", format("Test {firstOfJanuary2015,date,full}", locale, parameters)); 98 | 99 | assertEquals("Test 2015-01-01", format("Test {firstOfJanuary2015,date,yyyy-MM-dd}", locale, parameters)); 100 | 101 | assertEquals("Test zaterdag 3 december 2011", format("Test {isoLocalDate,date,full}", locale, parameters)); 102 | } 103 | 104 | @Test 105 | public void testWithTimes() { 106 | Map parameters = buildParameters(); 107 | Locale locale = new Locale("nl", "NL"); 108 | 109 | assertEquals("Test 16:04:42", format("Test {404Time,time}", locale, parameters)); 110 | assertEquals("Test 16:04", format("Test {404Time,time,short}", locale, parameters)); 111 | assertEquals("Test 16:04:42", format("Test {404Time,time,medium}", locale, parameters)); 112 | assertEquals("Test 15:47:58 CET", format("Test {zonedDateTime,time,long}", locale, parameters)); 113 | assertEquals("Test 15:47:58 Midden-Europese standaardtijd", format("Test {zonedDateTime,time,full}", locale, parameters)); 114 | 115 | assertEquals("Test 404", format("Test {404Time,time,hmm}", locale, parameters)); 116 | 117 | assertEquals("Test 10:15:30", format("Test {isoLocalTime,time,medium}", locale, parameters)); 118 | assertEquals("Test 15:16:17 -09:00", format("Test {isoOffsetDateTime,time,full}", locale, parameters)); 119 | } 120 | 121 | @Test 122 | @Ignore 123 | public void testWithDateTimes() { 124 | Map parameters = buildParameters(); 125 | Locale locale = new Locale("nl", "NL"); 126 | 127 | assertEquals("Test 24 feb. 2015 15:47:58", format("Test {zonedDateTime,dateTime}", locale, parameters)); 128 | assertEquals("Test 24-02-15 15:47", format("Test {zonedDateTime,dateTime,short}", locale, parameters)); 129 | assertEquals("Test 24 feb. 2015 15:47:58", format("Test {zonedDateTime,dateTime,medium}", locale, parameters)); 130 | assertEquals("Test 24 februari 2015 om 15:47:58 CET", format("Test {zonedDateTime,dateTime,long}", locale, parameters)); 131 | assertEquals("Test dinsdag 24 februari 2015 om 15:47:58 Midden-Europese standaardtijd", format("Test {zonedDateTime,dateTime,full}", locale, parameters)); 132 | 133 | assertEquals("Test 1 dec. 2020 15:35:12", format("Test {localDateTime,dateTime,medium}", locale, parameters)); 134 | assertEquals("Test donderdag 14 mei 2009 om 15:16:17 -09:00", format("Test {isoOffsetDateTime,dateTime,full}", locale, parameters)); 135 | assertEquals("Test 15 jan. 2012 11:12:13", format("Test {isoLocalDateTime,dateTime,medium}", locale, parameters)); 136 | } 137 | 138 | @Test 139 | public void testChoiceFormat() { 140 | Map parameters = buildParameters(); 141 | Locale locale = new Locale("nl", "NL"); 142 | 143 | assertEquals("Test ZERO", format("Test {zero,choice,0#ZERO|1#ONE|1 parameters = buildParameters(); 156 | 157 | assertEquals("Is missing", format("{zero1,optional,Is missing}", parameters)); 158 | assertEquals("0", format("{zero,optional,Is missing}", parameters)); 159 | 160 | assertEquals("Number 0%", format("{zero,optional,Is missing|Number {zero,number,percent}}", ENGLISH, parameters)); 161 | assertEquals("", format("{zero,optional,Is missing|}", ENGLISH, parameters)); 162 | 163 | assertEquals("Is missing |", format("{zero1,optional,Is missing '|'|Number {zero1,number,percent}}", ENGLISH, parameters)); 164 | } 165 | 166 | @Test 167 | public void testEscapeSequences() { 168 | Map parameters = buildParameters(); 169 | 170 | assertEquals("{zero1,optional,Is missing}", format("'{zero1,optional,Is missing}'", parameters)); 171 | assertEquals("'Is missing", format("''{zero1,optional,Is missing}", parameters)); 172 | assertEquals("{zero1,optional,'Is missing}", format("'{zero1,optional,''Is missing}'", parameters)); 173 | } 174 | 175 | private String format(String formatPattern, Locale locale, Map parameters) { 176 | return new NameBasedMessageFormat(formatPattern, locale).format(parameters); 177 | } 178 | 179 | private String format(String formatPattern, Map parameters) { 180 | return new NameBasedMessageFormat(formatPattern).format(parameters); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/utils/time/TemporalAdjustersTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.utils.time; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.omnifaces.utils.time.TemporalAdjusters.nextDayOfMonth; 17 | import static org.omnifaces.utils.time.TemporalAdjusters.nextOrSameDayOfMonth; 18 | 19 | import java.time.LocalDate; 20 | 21 | import org.junit.Test; 22 | 23 | public class TemporalAdjustersTest { 24 | 25 | @Test 26 | public void testNextDayOfMonth() { 27 | assertEquals(LocalDate.of(2016, 1, 31), LocalDate.of(2016, 1, 1).with(nextDayOfMonth(31))); 28 | 29 | assertEquals(LocalDate.of(2016, 3, 31), LocalDate.of(2016, 2, 29).with(nextDayOfMonth(31))); 30 | 31 | assertEquals(LocalDate.of(2015, 2, 28), LocalDate.of(2015, 2, 21).with(nextDayOfMonth(31))); 32 | 33 | assertEquals(LocalDate.of(2016, 2, 29), LocalDate.of(2016, 2, 21).with(nextDayOfMonth(31))); 34 | 35 | assertEquals(LocalDate.of(2016, 4, 30), LocalDate.of(2016, 3, 31).with(nextDayOfMonth(31))); 36 | 37 | assertEquals(LocalDate.of(2016, 1, 1), LocalDate.of(2015, 12, 1).with(nextDayOfMonth(1))); 38 | } 39 | 40 | @Test 41 | public void testNextOrSameDayOfMonth() { 42 | assertEquals(LocalDate.of(2016, 1, 31), LocalDate.of(2016, 1, 1).with(nextOrSameDayOfMonth(31))); 43 | 44 | assertEquals(LocalDate.of(2016, 2, 29), LocalDate.of(2016, 2, 29).with(nextOrSameDayOfMonth(31))); 45 | 46 | assertEquals(LocalDate.of(2015, 2, 28), LocalDate.of(2015, 2, 21).with(nextOrSameDayOfMonth(31))); 47 | 48 | assertEquals(LocalDate.of(2016, 2, 29), LocalDate.of(2016, 2, 21).with(nextOrSameDayOfMonth(31))); 49 | 50 | assertEquals(LocalDate.of(2016, 4, 30), LocalDate.of(2016, 3, 31).with(nextOrSameDayOfMonth(30))); 51 | 52 | assertEquals(LocalDate.of(2016, 3, 31), LocalDate.of(2016, 3, 31).with(nextOrSameDayOfMonth(31))); 53 | 54 | assertEquals(LocalDate.of(2015, 12, 1), LocalDate.of(2015, 12, 1).with(nextOrSameDayOfMonth(1))); 55 | } 56 | } 57 | --------------------------------------------------------------------------------