BUG-4688: switch revisions from Date to Revision
[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 com.google.common.base.Preconditions;
13 import java.io.Externalizable;
14 import java.io.IOException;
15 import java.io.ObjectInput;
16 import java.io.ObjectOutput;
17 import java.io.Serializable;
18 import java.util.Optional;
19 import java.util.regex.Pattern;
20 import javax.annotation.Nonnull;
21 import javax.annotation.Nullable;
22 import javax.annotation.RegEx;
23
24 /**
25  * Dedicated object identifying a YANG module revision.
26  *
27  * <p>
28  * <h3>API design note</h3>
29  * This class defines the contents of a revision statement, but modules do not require to have a revision (e.g. they
30  * have not started to keep track of revisions).
31  *
32  * <p>
33  * APIs which involve this class should always transfer instances via {@code Optional<Revision>}, which is
34  * the primary bridge data type. Implementations can use nullable fields with explicit conversions to/from
35  * {@link Optional}. Both patterns can take advantage of {@link #compare(Optional, Optional)} and
36  * {@link #compare(Revision, Revision)} respectively.
37  *
38  * @author Robert Varga
39  */
40 public final class Revision implements Comparable<Revision>, Serializable {
41     private static final long serialVersionUID = 1L;
42
43     @RegEx
44     private static final String STRING_FORMAT_STR = "\\d\\d\\d\\d\\-\\d\\d-\\d\\d";
45     private static final Pattern STRING_FORMAT = Pattern.compile(STRING_FORMAT_STR);
46
47     private final String str;
48
49     private Revision(final String str) {
50         // Since all strings conform to this format, compareTo() can be delegated to String.compareTo()
51         Preconditions.checkArgument(STRING_FORMAT.matcher(str).matches(),
52             "String '%s' does match revision format YYYY-MM-DD", str);
53         this.str = str;
54     }
55
56     /**
57      * Parse a revision string.
58      *
59      * @param str String to be parsed
60      * @return A Revision instance.
61      * @throws IllegalArgumentException if the string format does not conform specification.
62      * @throws NullPointerException if the string is null
63      */
64     public static Revision valueOf(@Nonnull final String str) {
65         return new Revision(str);
66     }
67
68     /**
69      * Compare two {@link Optional}s wrapping Revisions. Arguments and return value are consistent with
70      * {@link java.util.Comparator#compare(Object, Object)} interface contract. Missing revisions compare as lower
71      * than any other revision.
72      *
73      * @param first First optional revision
74      * @param second Second optional revision
75      * @return Positive, zero, or negative integer.
76      */
77     public static int compare(final Optional<Revision> first, final Optional<Revision> second) {
78         if (first.isPresent()) {
79             return second.isPresent() ? first.get().compareTo(second.get()) : 1;
80         }
81         return second.isPresent() ? -1 : 0;
82     }
83
84     /**
85      * Compare two explicitly nullable Revisions. Unlike {@link #compareTo(Revision)}, this handles both arguments
86      * being null such that total ordering is defined.
87      *
88      * @param first First revision
89      * @param second Second revision
90      * @return Positive, zero, or negative integer.
91      */
92     public static int compare(@Nullable final Revision first, @Nullable final Revision second) {
93         if (first != null) {
94             return second != null ? first.compareTo(second) : 1;
95         }
96         return second != null ? -1 : 0;
97     }
98
99     @Override
100     @SuppressWarnings("checkstyle:parameterName")
101     public int compareTo(final Revision o) {
102         return str.compareTo(o.str);
103     }
104
105     @Override
106     public int hashCode() {
107         return str.hashCode();
108     }
109
110     @Override
111     public boolean equals(final Object obj) {
112         return this == obj || obj instanceof Revision && str.equals(((Revision)obj).str);
113     }
114
115     @Override
116     public String toString() {
117         return str;
118     }
119
120     Object writeReplace() {
121         return new Proxy(str);
122     }
123
124     private static final class Proxy implements Externalizable {
125         private static final long serialVersionUID = 1L;
126
127         private String str;
128
129         @SuppressWarnings("checkstyle:redundantModifier")
130         public Proxy() {
131             // For Externalizable
132         }
133
134         Proxy(final String str) {
135             this.str = requireNonNull(str);
136         }
137
138         @Override
139         public void writeExternal(final ObjectOutput out) throws IOException {
140             out.writeObject(str);
141         }
142
143         @Override
144         public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
145             str = (String) in.readObject();
146         }
147
148         private Object readResolve() {
149             return Revision.valueOf(str);
150         }
151     }
152 }