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 super N> 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 super N> 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 super T> 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 super T> 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 super T> 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 super T> 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 super T> 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 super T, R> function, Predicate super R> 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 super LogRecord> 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 super LogRecord> other) {
39 | return logRecord -> isLoggable(logRecord) || other.test(logRecord);
40 | }
41 |
42 | static LogFilter fromPredicate(Predicate super LogRecord> 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 extends Throwable> 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 super String, ? super String> settings) {
193 | loadPropertiesFromUrl(url, settings, LIST);
194 | }
195 |
196 | public static void loadXMLFromUrl(String url, Map super String, ? super String> settings) {
197 | loadPropertiesFromUrl(url, settings, XML);
198 | }
199 |
200 | public static void loadListFromURL(URL url, Map super String, ? super String> settings) {
201 | loadPropertiesFromUrl(url, settings, LIST);
202 | }
203 |
204 | public static void loadXMLFromURL(URL url, Map super String, ? super String> settings) { // TODO name not ideal
205 | loadPropertiesFromUrl(url, settings, XML);
206 | }
207 |
208 | public static void loadPropertiesFromUrl(String url, Map super String, ? super String> 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 super String, ? super String> 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 super String, ? super String> 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 super T, ? extends K> keyMapper) {
36 | return java.util.stream.Collectors.toMap(keyMapper, identity());
37 | }
38 |
39 | public static Collector> toLinkedMap(Function super T, ? extends K> 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 super T> 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