2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.common;
10 import static java.util.Objects.requireNonNull;
12 import java.io.DataInput;
13 import java.io.Externalizable;
14 import java.io.IOException;
15 import java.io.NotSerializableException;
16 import java.io.ObjectInput;
17 import java.io.ObjectInputStream;
18 import java.io.ObjectOutput;
19 import java.io.ObjectOutputStream;
20 import java.io.ObjectStreamException;
21 import java.time.format.DateTimeFormatter;
22 import java.time.format.DateTimeParseException;
23 import java.util.Optional;
24 import java.util.regex.Pattern;
25 import org.checkerframework.checker.regex.qual.Regex;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.eclipse.jdt.annotation.Nullable;
30 * Dedicated object identifying a YANG module revision.
32 * <h2>API design note</h2>
33 * This class defines the contents of a revision statement, but modules do not require to have a revision (e.g. they
34 * have not started to keep track of revisions).
37 * APIs which involve this class should always transfer instances via {@code Optional<Revision>}, which is
38 * the primary bridge data type. Implementations can use nullable fields with explicit conversions to/from
39 * {@link Optional}. Both patterns can take advantage of {@link #compare(Optional, Optional)} and
40 * {@link #compare(Revision, Revision)} respectively.
42 public final class Revision implements RevisionUnion {
43 // Note: since we are using writeReplace() this version is not significant.
45 private static final long serialVersionUID = 1L;
47 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
50 // FIXME: we should improve this to filter incorrect dates -- see constructor.
51 private static final String STRING_FORMAT_PATTERN_STR = "\\d\\d\\d\\d\\-\\d\\d-\\d\\d";
54 * String format pattern, which can be used to match parts of a string into components.
56 public static final Pattern STRING_FORMAT_PATTERN = Pattern.compile(STRING_FORMAT_PATTERN_STR);
59 * Revision which compares as greater than any other valid revision.
61 public static final Revision MAX_VALUE = Revision.of("9999-12-31");
63 private final @NonNull String str;
65 private Revision(final @NonNull String str) {
67 * According to RFC7950 (https://www.rfc-editor.org/rfc/rfc7950#section-7.1.9):
69 * The "revision" statement specifies the editorial revision history of
70 * the module, including the initial revision. A series of "revision"
71 * statements detail the changes in the module's definition. The
72 * argument is a date string in the format "YYYY-MM-DD", [...]
74 * Hence we use JDK-provided parsing faculties to parse the date.
81 * Parse a revision string.
83 * @param str String to be parsed
84 * @return A Revision instance.
85 * @throws DateTimeParseException if the string format does not conform specification.
86 * @throws NullPointerException if the string is null
88 public static @NonNull Revision of(final @NonNull String str) {
89 return new Revision(str);
93 * Parse a (potentially null) revision string. Null strings result result in {@link Optional#empty()}.
95 * @param str String to be parsed
96 * @return An optional Revision instance.
97 * @throws DateTimeParseException if the string format does not conform specification.
99 public static @NonNull Optional<Revision> ofNullable(final @Nullable String str) {
100 return str == null ? Optional.empty() : Optional.of(new Revision(str));
104 public Revision revision() {
109 public String unionString() {
113 public static @NonNull Revision readFrom(final DataInput in) throws IOException {
114 return ofRead(in.readUTF());
117 static @NonNull Revision ofRead(final @NonNull String str) throws IOException {
120 } catch (DateTimeParseException e) {
121 throw new IOException("Invalid revision-date string", e);
126 * Compare two {@link Optional}s wrapping Revisions. Arguments and return value are consistent with
127 * {@link java.util.Comparator#compare(Object, Object)} interface contract. Missing revisions compare as lower
128 * than any other revision.
130 * @param first First optional revision
131 * @param second Second optional revision
132 * @return Positive, zero, or negative integer.
134 public static int compare(final @NonNull Optional<Revision> first, final @NonNull Optional<Revision> second) {
135 if (first.isPresent()) {
136 return second.isPresent() ? first.orElseThrow().compareTo(second.orElseThrow()) : 1;
138 return second.isPresent() ? -1 : 0;
142 * Compare two explicitly nullable Revisions. Unlike {@link #compareTo(Revision)}, this handles both arguments
143 * being null such that total ordering is defined.
145 * @param first First revision
146 * @param second Second revision
147 * @return Positive, zero, or negative integer.
149 public static int compare(final @Nullable Revision first, final @Nullable Revision second) {
151 return second != null ? first.compareTo(second) : 1;
153 return second != null ? -1 : 0;
157 public int hashCode() {
158 return str.hashCode();
162 public boolean equals(final Object obj) {
163 return this == obj || obj instanceof Revision other && str.equals(other.str);
167 * Return the revision string according to
168 * <a href="https://www.rfc-editor.org/rfc/rfc7950.html#section-7.1.9">RFC7950, section 7.1.9</a>. The string is
169 * guaranteed to be composed on 10 ASCII characters in {@code YYYY-MM-DD} format. The items are guaranteed to be
170 * decimal numbers. There are no further guarantees as to convertibility to an actual calendar date, as this method
171 * can validly return {@code "2019-02-29"} (even if 2019 is not a leap year) or {@code "2022-12-32"} (even if no
172 * month has 32 days).
174 * @return A string in {@code YYYY-MM-DD} format
177 public String toString() {
182 Object writeReplace() {
183 return new RUv1(str);
187 private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException {
192 private void readObjectNoData() throws ObjectStreamException {
197 private void writeObject(final ObjectOutputStream stream) throws IOException {
201 static void throwNSE() throws NotSerializableException {
202 throw new NotSerializableException(Revision.class.getName());
205 @Deprecated(since = "12.0.0", forRemoval = true)
206 private static final class Proxy implements Externalizable {
208 private static final long serialVersionUID = 1L;
210 private Revision revision;
212 @SuppressWarnings("checkstyle:redundantModifier")
214 // For Externalizable
218 public void writeExternal(final ObjectOutput out) throws IOException {
219 throw new NotSerializableException(Proxy.class.getName());
223 public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
224 revision = Revision.of((String) in.readObject());
228 private Object readResolve() {
229 return requireNonNull(revision);