32c6f50e5da64ae6308be6fd8b3dd212a736cf0b
[yangtools.git] / yang / yang-common / src / main / java / org / opendaylight / yangtools / yang / common / Revision.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.common;
9
10 import static java.util.Objects.requireNonNull;
11
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 javax.annotation.RegEx;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24
25 /**
26  * Dedicated object identifying a YANG module revision.
27  *
28  * <p>
29  * <h3>API design note</h3>
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).
32  *
33  * <p>
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.
38  *
39  * @author Robert Varga
40  */
41 public final class Revision implements Comparable<Revision>, Serializable {
42     // Note: since we are using writeReplace() this version is not significant.
43     private static final long serialVersionUID = 1L;
44
45     @RegEx
46     // FIXME: we should improve this to filter incorrect dates -- see constructor.
47     private static final String STRING_FORMAT_PATTERN_STR = "\\d\\d\\d\\d\\-\\d\\d-\\d\\d";
48
49     /**
50      * String format pattern, which can be used to match parts of a string into components.
51      */
52     public static final Pattern STRING_FORMAT_PATTERN = Pattern.compile(STRING_FORMAT_PATTERN_STR);
53
54     private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
55
56     private final @NonNull String str;
57
58     private Revision(final @NonNull String str) {
59         /*
60          * According to RFC7950 (https://tools.ietf.org/html/rfc7950#section-7.1.9):
61          *
62          *   The "revision" statement specifies the editorial revision history of
63          *   the module, including the initial revision.  A series of "revision"
64          *   statements detail the changes in the module's definition.  The
65          *   argument is a date string in the format "YYYY-MM-DD", [...]
66          *
67          * Hence we use JDK-provided parsing faculties to parse the date.
68          */
69         FORMATTER.parse(str);
70         this.str = str;
71     }
72
73     /**
74      * Parse a revision string.
75      *
76      * @param str String to be parsed
77      * @return A Revision instance.
78      * @throws DateTimeParseException if the string format does not conform specification.
79      * @throws NullPointerException if the string is null
80      */
81     public static @NonNull Revision of(final @NonNull String str) {
82         return new Revision(str);
83     }
84
85     /**
86      * Parse a (potentially null) revision string. Null strings result result in {@link Optional#empty()}.
87      *
88      * @param str String to be parsed
89      * @return An optional Revision instance.
90      * @throws DateTimeParseException if the string format does not conform specification.
91      */
92     public static Optional<Revision> ofNullable(final @Nullable String str) {
93         return str == null ? Optional.empty() : Optional.of(new Revision(str));
94     }
95
96     /**
97      * Compare two {@link Optional}s wrapping Revisions. Arguments and return value are consistent with
98      * {@link java.util.Comparator#compare(Object, Object)} interface contract. Missing revisions compare as lower
99      * than any other revision.
100      *
101      * @param first First optional revision
102      * @param second Second optional revision
103      * @return Positive, zero, or negative integer.
104      */
105     public static int compare(final @NonNull Optional<Revision> first, final @NonNull Optional<Revision> second) {
106         if (first.isPresent()) {
107             return second.isPresent() ? first.get().compareTo(second.get()) : 1;
108         }
109         return second.isPresent() ? -1 : 0;
110     }
111
112     /**
113      * Compare two explicitly nullable Revisions. Unlike {@link #compareTo(Revision)}, this handles both arguments
114      * being null such that total ordering is defined.
115      *
116      * @param first First revision
117      * @param second Second revision
118      * @return Positive, zero, or negative integer.
119      */
120     public static int compare(final @Nullable Revision first, final @Nullable Revision second) {
121         if (first != null) {
122             return second != null ? first.compareTo(second) : 1;
123         }
124         return second != null ? -1 : 0;
125     }
126
127     @Override
128     @SuppressWarnings("checkstyle:parameterName")
129     public int compareTo(final Revision o) {
130         // Since all strings conform to the format, we can use their comparable property to do the correct thing
131         // with respect to temporal ordering.
132         return str.compareTo(o.str);
133     }
134
135     @Override
136     public int hashCode() {
137         return str.hashCode();
138     }
139
140     @Override
141     public boolean equals(final Object obj) {
142         return this == obj || obj instanceof Revision && str.equals(((Revision)obj).str);
143     }
144
145     @Override
146     public String toString() {
147         return str;
148     }
149
150     Object writeReplace() {
151         return new Proxy(str);
152     }
153
154     private static final class Proxy implements Externalizable {
155         private static final long serialVersionUID = 1L;
156
157         private String str;
158
159         @SuppressWarnings("checkstyle:redundantModifier")
160         public Proxy() {
161             // For Externalizable
162         }
163
164         Proxy(final String str) {
165             this.str = requireNonNull(str);
166         }
167
168         @Override
169         public void writeExternal(final ObjectOutput out) throws IOException {
170             out.writeObject(str);
171         }
172
173         @Override
174         public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
175             str = (String) in.readObject();
176         }
177
178         private Object readResolve() {
179             return Revision.of(requireNonNull(str));
180         }
181     }
182 }