2 * Copyright (c) 2018 Pantheon Technologies, s.r.o. 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 com.google.common.base.Preconditions.checkArgument;
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;
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.
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}.
36 * Given the following YANG snippet:
40 * pattern "[1-9]?[0-9]";
45 * patter "[1-9][0-9]";
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:
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>
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.
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.
76 * @param <R> derived string representation
77 * @author Robert Varga
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);
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);
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);
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);
102 // have a non-final non-abstract validator() method
105 validator = type.getMethod("validator").getModifiers();
106 } catch (NoSuchMethodException e) {
107 throw new IllegalArgumentException(type + " must have a non-abstract non-final validator() method",
110 checkArgument(!Modifier.isFinal(validator), "%s must not have final validator()", type);
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;
126 private static void checkFinalMethod(final Class<?> type, final String name) {
128 checkFinalMethod(type.getMethod(name).getModifiers(), type, name, "");
129 } catch (NoSuchMethodException e) {
130 throw new IllegalArgumentException(type + " must have a final " + name + "() method", e);
134 private static void checkFinalMethod(final Class<?> type, final String name, final Class<?> arg) {
135 final String argName = arg.getSimpleName();
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);
143 private static void checkFinalMethod(final int modifiers, final Class<?> type, final String name,
145 checkArgument(Modifier.isFinal(modifiers), "%s must have a final %s(%s) method", type, name, args);
149 private static final ClassValue<Boolean> VALIDATED_REPRESENTATIONS = new Validator();
150 private static final long serialVersionUID = 1L;
153 * Return the canonical string representation of this object's value.
155 * @return Canonical string
157 public abstract String toCanonicalString();
160 * Return the {@link DerivedStringSupport} associated with this type. It can be used to create new instances of this
163 * @return A {@link DerivedStringSupport} instance.
165 public abstract DerivedStringSupport<R> support();
168 * Return a {@link DerivedStringValidator} associated with this value's validated type.
170 * @return A {@link DerivedStringValidator} instance.
172 public DerivedStringValidator<R, ? extends R> validator() {
177 public abstract int hashCode();
180 public abstract boolean equals(@Nullable Object obj);
183 public final String toString() {
184 return toCanonicalString();
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;