BUG-4688: Move SourceIdentifier.REVISION_PATTERN to yang.common.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_PATTERN_STR = "\\d\\d\\d\\d\\-\\d\\d-\\d\\d";
45
46     /**
47      * String format pattern, which can be used to match parts of a string into components.
48      */
49     public static final Pattern STRING_FORMAT_PATTERN = Pattern.compile(STRING_FORMAT_PATTERN_STR);
50
51     private final String str;
52
53     private Revision(final String str) {
54         // Since all strings conform to this format, compareTo() can be delegated to String.compareTo()
55         Preconditions.checkArgument(STRING_FORMAT_PATTERN.matcher(str).matches(),
56             "String '%s' does match revision format YYYY-MM-DD", str);
57         this.str = str;
58     }
59
60     /**
61      * Parse a revision string.
62      *
63      * @param str String to be parsed
64      * @return A Revision instance.
65      * @throws IllegalArgumentException if the string format does not conform specification.
66      * @throws NullPointerException if the string is null
67      */
68     public static Revision valueOf(@Nonnull final String str) {
69         return new Revision(str);
70     }
71
72     /**
73      * Compare two {@link Optional}s wrapping Revisions. Arguments and return value are consistent with
74      * {@link java.util.Comparator#compare(Object, Object)} interface contract. Missing revisions compare as lower
75      * than any other revision.
76      *
77      * @param first First optional revision
78      * @param second Second optional revision
79      * @return Positive, zero, or negative integer.
80      */
81     public static int compare(final Optional<Revision> first, final Optional<Revision> second) {
82         if (first.isPresent()) {
83             return second.isPresent() ? first.get().compareTo(second.get()) : 1;
84         }
85         return second.isPresent() ? -1 : 0;
86     }
87
88     /**
89      * Compare two explicitly nullable Revisions. Unlike {@link #compareTo(Revision)}, this handles both arguments
90      * being null such that total ordering is defined.
91      *
92      * @param first First revision
93      * @param second Second revision
94      * @return Positive, zero, or negative integer.
95      */
96     public static int compare(@Nullable final Revision first, @Nullable final Revision second) {
97         if (first != null) {
98             return second != null ? first.compareTo(second) : 1;
99         }
100         return second != null ? -1 : 0;
101     }
102
103     @Override
104     @SuppressWarnings("checkstyle:parameterName")
105     public int compareTo(final Revision o) {
106         return str.compareTo(o.str);
107     }
108
109     @Override
110     public int hashCode() {
111         return str.hashCode();
112     }
113
114     @Override
115     public boolean equals(final Object obj) {
116         return this == obj || obj instanceof Revision && str.equals(((Revision)obj).str);
117     }
118
119     @Override
120     public String toString() {
121         return str;
122     }
123
124     Object writeReplace() {
125         return new Proxy(str);
126     }
127
128     private static final class Proxy implements Externalizable {
129         private static final long serialVersionUID = 1L;
130
131         private String str;
132
133         @SuppressWarnings("checkstyle:redundantModifier")
134         public Proxy() {
135             // For Externalizable
136         }
137
138         Proxy(final String str) {
139             this.str = requireNonNull(str);
140         }
141
142         @Override
143         public void writeExternal(final ObjectOutput out) throws IOException {
144             out.writeObject(str);
145         }
146
147         @Override
148         public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
149             str = (String) in.readObject();
150         }
151
152         private Object readResolve() {
153             return Revision.valueOf(str);
154         }
155     }
156 }