3dc15224394ec1068eee246130e3dabe438af0d2
[yangtools.git] / yang / yang-common / src / main / java / org / opendaylight / yangtools / yang / common / DerivedString.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, s.r.o. 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 com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.annotations.Beta;
13 import java.io.Serializable;
14 import java.lang.reflect.Constructor;
15 import java.lang.reflect.Modifier;
16 import java.util.Arrays;
17 import javax.annotation.concurrent.ThreadSafe;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.opendaylight.yangtools.concepts.Immutable;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 /**
25  * Abstract base class for objects which are string-equivalent to canonical string representation specified
26  * in a YANG model. Note that each subclass of {@link DerivedString} defines its own {@link #hashCode()} and
27  * {@link #equals(Object)} contracts based on implementation particulars.
28  *
29  * <p>
30  * Since YANG validation works on top of strings, which in itself is expensive and this class provides storage which
31  * is potentially not based on strings, its design combines 'representation' and 'validated to match constraints'
32  * aspects of a YANG type derived from string. To achieve that it cooperates with {@link DerivedStringValidator} and
33  * {@link DerivedStringSupport}.
34  *
35  * <p>
36  * Given the following YANG snippet:
37  * <pre>
38  *     typedef foo {
39  *         type string;
40  *         pattern "[1-9]?[0-9]";
41  *     }
42  *
43  *     typedef bar {
44  *         type foo;
45  *         patter "[1-9][0-9]";
46  *     }
47  *
48  *     typedef baz {
49  *         type foo;
50  *     }
51  * </pre>
52  * it is obvious we could use a storage class with 'int' as the internal representation of all three types and define
53  * operations on top of it. In this case we would define:
54  * <ul>
55  *     <li>{@code public class FooDerivedString extends DerivedString<FooDerivedString>}, which implements all abstract
56  *         methods of {@link DerivedString} as final methods. It will notably not override {@link #validator()} and
57  *         must not be final.</li>
58  *     <li>{@code public final class FooDerivedStringSupport extends DerivedStringSupport<FooDerivedString>}, which
59  *         forms the baseline validator and instantiation for {@code FooDerivedString}. It should be a singleton class
60  *         with a getInstance() method.</li>
61  *     <li>{@code public class BarDerivedString extends FooDerivedString}, which overrides {@link #validator()} to
62  *         indicate its contents have been validated to conform to bar -- it does that by returning the singleton
63  *         instance of {@code BarDerivedStringValidator}.
64  *     <li>{@code public final class BarDerivedStringValidator extends DerivedStringValidator<FooDerivedString,
65  *         BarDerivedString}. This method needs to notably implement
66  *         {@link DerivedStringValidator#validateRepresentation(DerivedString)} to hand out BarDerivedString instances.
67  *         This class needs to be a singleton with a getInstance() method, too.</li>
68  * </ul>
69  * Since {@code baz} is not defining any new restrictions, all instances of FooDerivedString are valid for it and we
70  * do not have to define any additional support.
71  *
72  * <p>
73  * It is important for {@link DerivedString} subclasses not to be final because any YANG type can be further extended
74  * and adding a final class in that hierarchy would prevent a proper class from being defined.
75  *
76  * @param <R> derived string representation
77  * @author Robert Varga
78  */
79 @Beta
80 @NonNullByDefault
81 @ThreadSafe
82 public abstract class DerivedString<R extends DerivedString<R>> implements Comparable<R>, Immutable, Serializable {
83     private static final class Validator extends ClassValue<Boolean> {
84         private static final Logger LOG = LoggerFactory.getLogger(Validator.class);
85
86         @Override
87         protected Boolean computeValue(final @Nullable Class<?> type) {
88             // Every DerivedString representation class must:
89             checkArgument(DerivedString.class.isAssignableFrom(type), "%s is not a DerivedString", type);
90
91             // be non-final and public
92             final int modifiers = type.getModifiers();
93             checkArgument(Modifier.isPublic(modifiers), "%s must be public", type);
94             checkArgument(!Modifier.isFinal(modifiers), "%s must not be final", type);
95
96             // have at least one public or protected constructor (for subclasses)
97             checkArgument(Arrays.stream(type.getDeclaredConstructors()).mapToInt(Constructor::getModifiers)
98                 .anyMatch(mod -> Modifier.isProtected(mod) || Modifier.isPublic(mod)),
99                 "%s must declare at least one protected or public constructor", type);
100
101             try {
102                 // have a non-final non-abstract validator() method
103                 final int validator;
104                 try {
105                     validator = type.getMethod("validator").getModifiers();
106                 } catch (NoSuchMethodException e) {
107                     throw new IllegalArgumentException(type + " must have a non-abstract non-final validator() method",
108                         e);
109                 }
110                 checkArgument(!Modifier.isFinal(validator), "%s must not have final validator()", type);
111
112                 // have final toCanonicalString(), support(), hashCode() and equals(Object), compare(T) methods
113                 checkFinalMethod(type, "toCanonicalString");
114                 checkFinalMethod(type, "support");
115                 checkFinalMethod(type, "hashCode");
116                 checkFinalMethod(type, "equals", Object.class);
117                 checkFinalMethod(type, "compareTo", type);
118             } catch (SecurityException e) {
119                 LOG.warn("Cannot completely validate {}", type, e);
120                 return Boolean.FALSE;
121             }
122
123             return Boolean.TRUE;
124         }
125
126         private static void checkFinalMethod(final Class<?> type, final String name) {
127             try {
128                 checkFinalMethod(type.getMethod(name).getModifiers(), type, name, "");
129             } catch (NoSuchMethodException e) {
130                 throw new IllegalArgumentException(type + " must have a final " + name + "() method", e);
131             }
132         }
133
134         private static void checkFinalMethod(final Class<?> type, final String name, final Class<?> arg) {
135             final String argName = arg.getSimpleName();
136             try {
137                 checkFinalMethod(type.getMethod(name, arg).getModifiers(), type, name, argName);
138             } catch (NoSuchMethodException e) {
139                 throw new IllegalArgumentException(type + " must have a final " + name + "(" + argName + ") method", e);
140             }
141         }
142
143         private static void checkFinalMethod(final int modifiers, final Class<?> type, final String name,
144                 final String args) {
145             checkArgument(Modifier.isFinal(modifiers), "%s must have a final %s(%s) method", type, name, args);
146         }
147     }
148
149     private static final ClassValue<Boolean> VALIDATED_REPRESENTATIONS = new Validator();
150     private static final long serialVersionUID = 1L;
151
152     /**
153      * Return the canonical string representation of this object's value.
154      *
155      * @return Canonical string
156      */
157     public abstract String toCanonicalString();
158
159     /**
160      * Return the {@link DerivedStringSupport} associated with this type. It can be used to create new instances of this
161      * representation.
162      *
163      * @return A {@link DerivedStringSupport} instance.
164      */
165     public abstract DerivedStringSupport<R> support();
166
167     /**
168      * Return a {@link DerivedStringValidator} associated with this value's validated type.
169      *
170      * @return A {@link DerivedStringValidator} instance.
171      */
172     public DerivedStringValidator<R, ? extends R> validator() {
173         return support();
174     }
175
176     @Override
177     public abstract int hashCode();
178
179     @Override
180     public abstract boolean equals(@Nullable Object obj);
181
182     @Override
183     public final String toString() {
184         return toCanonicalString();
185     }
186
187     static <T extends DerivedString<?>> Class<T> validateRepresentationClass(final Class<T> representationClass) {
188         // Validation is reflective, cache its result
189         VALIDATED_REPRESENTATIONS.get(representationClass);
190         return representationClass;
191     }
192 }