/
ImmutableTimePlaceholder.java
212 lines (185 loc) · 8.07 KB
/
ImmutableTimePlaceholder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
/*
* Copyright (c) 2021 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.placeholders;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import org.eclipse.ditto.base.model.common.ConditionChecker;
import org.eclipse.ditto.base.model.common.DittoDuration;
/**
* Placeholder implementation that replaces:
* <ul>
* <li>{@code time:now} ->
* the current system timestamp in ISO-8601 format, e.g.: {@code "2021-11-17T09:44:08Z"}</li>
* <li>{@code time:now_epoch_millis} ->
* the current system timestamp in milliseconds since the epoch of {@code 1970-01-01T00:00:00Z}</li>
* </ul>
* The input value is any Object and is not used.
*/
@Immutable
final class ImmutableTimePlaceholder implements TimePlaceholder {
/**
* Singleton instance of the ImmutableTimePlaceholder.
*/
static final ImmutableTimePlaceholder INSTANCE = new ImmutableTimePlaceholder();
private static final String NOW_PLACEHOLDER = "now";
private static final String NOW_EPOCH_MILLIS_PLACEHOLDER = "now_epoch_millis";
private static final String TRUNCATE_START;
private static final String TRUNCATE_END = "]";
static {
TRUNCATE_START = "[";
}
private ImmutableTimePlaceholder() {
}
@Override
public String getPrefix() {
return PREFIX;
}
@Override
public List<String> getSupportedNames() {
return Collections.emptyList();
}
@Override
public boolean supports(final String name) {
return startsWithNowPrefix(name) && containsEmptyOrValidTimeRangeDefinition(name);
}
@Override
public List<String> resolveValues(final Object someObject, final String placeholder) {
ConditionChecker.argumentNotEmpty(placeholder, "placeholder");
final Instant now = Instant.now();
switch (placeholder) {
case NOW_PLACEHOLDER:
return Collections.singletonList(now.toString());
case NOW_EPOCH_MILLIS_PLACEHOLDER:
return Collections.singletonList(formatAsEpochMilli(now));
default:
return resolveWithPotentialTimeRangeSuffix(now, placeholder);
}
}
private static boolean startsWithNowPrefix(final String name) {
return name.startsWith(NOW_EPOCH_MILLIS_PLACEHOLDER) || name.startsWith(NOW_PLACEHOLDER) ;
}
private boolean containsEmptyOrValidTimeRangeDefinition(final String placeholder) {
final String timeRangeSuffix = extractTimeRangeSuffix(placeholder);
return timeRangeSuffix.isEmpty() || isValidTimeRange(timeRangeSuffix);
}
private static String formatAsEpochMilli(final Instant now) {
return String.valueOf(now.toEpochMilli());
}
private static String extractTimeRangeSuffix(final String placeholder) {
final String timeRangeSuffix;
if (placeholder.startsWith(NOW_EPOCH_MILLIS_PLACEHOLDER)) {
timeRangeSuffix = placeholder.replace(NOW_EPOCH_MILLIS_PLACEHOLDER, "");
} else if (placeholder.startsWith(NOW_PLACEHOLDER)) {
timeRangeSuffix = placeholder.replace(NOW_PLACEHOLDER, "");
} else {
throw new IllegalStateException("Unsupported placeholder prefix for TimePlaceholder: " + placeholder);
}
return timeRangeSuffix;
}
private boolean isValidTimeRange(final String timeRangeSuffix) {
final char sign = timeRangeSuffix.charAt(0);
if (sign == '-' || sign == '+') {
final String durationWithTruncate = timeRangeSuffix.substring(1);
final String durationString;
if (durationWithTruncate.contains(TRUNCATE_START) && durationWithTruncate.contains(TRUNCATE_END)) {
final String[] durationStringAndTruncateString = durationWithTruncate.split(TRUNCATE_START, 2);
durationString = durationStringAndTruncateString[0];
final String truncateString = durationStringAndTruncateString[1];
if (!isValidTruncateStatement(truncateString.substring(0, truncateString.lastIndexOf(TRUNCATE_END)))) {
return false;
}
} else {
durationString = durationWithTruncate;
}
try {
DittoDuration.parseDuration(durationString);
return true;
} catch (final Exception e) {
return false;
}
} else {
return false;
}
}
private boolean isValidTruncateStatement(final String truncateString) {
return DittoDuration.DittoTimeUnit.forSuffix(truncateString).isPresent();
}
private List<String> resolveWithPotentialTimeRangeSuffix(final Instant now, final String placeholder) {
final String timeRangeSuffix = extractTimeRangeSuffix(placeholder);
if (timeRangeSuffix.isEmpty()) {
return Collections.emptyList();
}
@Nullable final ChronoUnit truncateTo = calculateTruncateTo(timeRangeSuffix);
final char sign = timeRangeSuffix.charAt(0);
if (sign == '-') {
final DittoDuration dittoDuration = extractTimeRangeDuration(timeRangeSuffix);
Instant nowMinus = now.minus(dittoDuration.getDuration());
if (truncateTo != null) {
nowMinus = nowMinus.truncatedTo(truncateTo);
}
return buildResult(placeholder, nowMinus);
} else if (sign == '+') {
final DittoDuration dittoDuration = extractTimeRangeDuration(timeRangeSuffix);
Instant nowPlus = now.plus(dittoDuration.getDuration());
if (truncateTo != null) {
nowPlus = nowPlus.truncatedTo(truncateTo);
}
return buildResult(placeholder, nowPlus);
} else if (truncateTo != null) {
final Instant nowTruncated = now.truncatedTo(truncateTo);
return buildResult(placeholder, nowTruncated);
}
return Collections.emptyList();
}
private static List<String> buildResult(final String placeholder, final Instant nowMinus) {
if (placeholder.startsWith(NOW_EPOCH_MILLIS_PLACEHOLDER)) {
return Collections.singletonList(formatAsEpochMilli(nowMinus));
} else if (placeholder.startsWith(NOW_PLACEHOLDER)) {
return Collections.singletonList(nowMinus.toString());
} else {
return Collections.emptyList();
}
}
@Nullable
private static ChronoUnit calculateTruncateTo(final String timeRangeSuffix) {
if (timeRangeSuffix.contains(TRUNCATE_START) && timeRangeSuffix.contains(TRUNCATE_END)) {
final String truncateUnit = timeRangeSuffix.substring(timeRangeSuffix.indexOf(TRUNCATE_START) + 1,
timeRangeSuffix.lastIndexOf(TRUNCATE_END));
final DittoDuration.DittoTimeUnit dittoTimeUnit =
DittoDuration.DittoTimeUnit.forSuffix(truncateUnit).orElseThrow(() ->
new IllegalStateException("Truncating string contained unsupported unit: " + truncateUnit)
);
return dittoTimeUnit.getChronoUnit();
} else {
return null;
}
}
private static DittoDuration extractTimeRangeDuration(final String timeRangeSuffix) {
final int truncateStart = timeRangeSuffix.indexOf(TRUNCATE_START);
final String timeRange;
if (truncateStart > 0) {
timeRange = timeRangeSuffix.substring(0, truncateStart);
} else {
timeRange = timeRangeSuffix;
}
return DittoDuration.parseDuration(timeRange.substring(1));
}
@Override
public String toString() {
return getClass().getSimpleName() + "[]";
}
}