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.Externalizable;
13 import java.io.IOException;
14 import java.io.ObjectInput;
15 import java.io.ObjectOutput;
16 import java.io.Serializable;
17 import java.time.format.DateTimeFormatter;
18 import java.time.format.DateTimeParseException;
19 import java.util.Optional;
20 import java.util.regex.Pattern;
21 import org.checkerframework.checker.regex.qual.Regex;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.yangtools.concepts.Immutable;
27 * Dedicated object identifying a YANG module revision.
29 * <h2>API design note</h2>
30 * This class defines the contents of a revision statement, but modules do not require to have a revision (e.g. they
31 * have not started to keep track of revisions).
34 * APIs which involve this class should always transfer instances via {@code Optional<Revision>}, which is
35 * the primary bridge data type. Implementations can use nullable fields with explicit conversions to/from
36 * {@link Optional}. Both patterns can take advantage of {@link #compare(Optional, Optional)} and
37 * {@link #compare(Revision, Revision)} respectively.
39 * @author Robert Varga
41 public final class Revision implements Comparable<Revision>, Immutable, Serializable {
42 // Note: since we are using writeReplace() this version is not significant.
43 private static final long serialVersionUID = 1L;
45 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
48 // FIXME: we should improve this to filter incorrect dates -- see constructor.
49 private static final String STRING_FORMAT_PATTERN_STR = "\\d\\d\\d\\d\\-\\d\\d-\\d\\d";
52 * String format pattern, which can be used to match parts of a string into components.
54 public static final Pattern STRING_FORMAT_PATTERN = Pattern.compile(STRING_FORMAT_PATTERN_STR);
57 * Revision which compares as greater than any other valid revision.
59 public static final Revision MAX_VALUE = Revision.of("9999-12-31");
61 private final @NonNull String str;
63 private Revision(final @NonNull String str) {
65 * According to RFC7950 (https://tools.ietf.org/html/rfc7950#section-7.1.9):
67 * The "revision" statement specifies the editorial revision history of
68 * the module, including the initial revision. A series of "revision"
69 * statements detail the changes in the module's definition. The
70 * argument is a date string in the format "YYYY-MM-DD", [...]
72 * Hence we use JDK-provided parsing faculties to parse the date.
79 * Parse a revision string.
81 * @param str String to be parsed
82 * @return A Revision instance.
83 * @throws DateTimeParseException if the string format does not conform specification.
84 * @throws NullPointerException if the string is null
86 public static @NonNull Revision of(final @NonNull String str) {
87 return new Revision(str);
91 * Parse a (potentially null) revision string. Null strings result result in {@link Optional#empty()}.
93 * @param str String to be parsed
94 * @return An optional Revision instance.
95 * @throws DateTimeParseException if the string format does not conform specification.
97 public static @NonNull Optional<Revision> ofNullable(final @Nullable String str) {
98 return str == null ? Optional.empty() : Optional.of(new Revision(str));
102 * Compare two {@link Optional}s wrapping Revisions. Arguments and return value are consistent with
103 * {@link java.util.Comparator#compare(Object, Object)} interface contract. Missing revisions compare as lower
104 * than any other revision.
106 * @param first First optional revision
107 * @param second Second optional revision
108 * @return Positive, zero, or negative integer.
110 public static int compare(final @NonNull Optional<Revision> first, final @NonNull Optional<Revision> second) {
111 if (first.isPresent()) {
112 return second.isPresent() ? first.get().compareTo(second.get()) : 1;
114 return second.isPresent() ? -1 : 0;
118 * Compare two explicitly nullable Revisions. Unlike {@link #compareTo(Revision)}, this handles both arguments
119 * being null such that total ordering is defined.
121 * @param first First revision
122 * @param second Second revision
123 * @return Positive, zero, or negative integer.
125 public static int compare(final @Nullable Revision first, final @Nullable Revision second) {
127 return second != null ? first.compareTo(second) : 1;
129 return second != null ? -1 : 0;
133 @SuppressWarnings("checkstyle:parameterName")
134 public int compareTo(final Revision o) {
135 // Since all strings conform to the format, we can use their comparable property to do the correct thing
136 // with respect to temporal ordering.
137 return str.compareTo(o.str);
141 public int hashCode() {
142 return str.hashCode();
146 public boolean equals(final Object obj) {
147 return this == obj || obj instanceof Revision && str.equals(((Revision)obj).str);
151 public String toString() {
155 Object writeReplace() {
156 return new Proxy(str);
159 private static final class Proxy implements Externalizable {
160 private static final long serialVersionUID = 1L;
164 @SuppressWarnings("checkstyle:redundantModifier")
166 // For Externalizable
169 Proxy(final String str) {
170 this.str = requireNonNull(str);
174 public void writeExternal(final ObjectOutput out) throws IOException {
175 out.writeObject(str);
179 public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
180 str = (String) in.readObject();
183 private Object readResolve() {
184 return Revision.of(requireNonNull(str));