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.NonNull;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.yangtools.concepts.Immutable;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
26 * Abstract base class for objects which are string-equivalent to canonical string representation specified
27 * in a YANG model. Note that each subclass of {@link DerivedString} defines its own {@link #hashCode()} and
28 * {@link #equals(Object)} contracts based on implementation particulars.
31 * Since YANG validation works on top of strings, which in itself is expensive and this class provides storage which
32 * is potentially not based on strings, its design combines 'representation' and 'validated to match constraints'
33 * aspects of a YANG type derived from string. To achieve that it cooperates with {@link DerivedStringValidator} and
34 * {@link DerivedStringSupport}.
37 * Given the following YANG snippet:
41 * pattern "[1-9]?[0-9]";
46 * patter "[1-9][0-9]";
53 * it is obvious we could use a storage class with 'int' as the internal representation of all three types and define
54 * operations on top of it. In this case we would define:
56 * <li>{@code public class FooDerivedString extends DerivedString<FooDerivedString>}, which implements all abstract
57 * methods of {@link DerivedString} as final methods. It will notably not override {@link #validator()} and
58 * must not be final.</li>
59 * <li>{@code public final class FooDerivedStringSupport extends DerivedStringSupport<FooDerivedString>}, which
60 * forms the baseline validator and instantiation for {@code FooDerivedString}. It should be a singleton class
61 * with a getInstance() method.</li>
62 * <li>{@code public class BarDerivedString extends FooDerivedString}, which overrides {@link #validator()} to
63 * indicate its contents have been validated to conform to bar -- it does that by returning the singleton
64 * instance of {@code BarDerivedStringValidator}.
65 * <li>{@code public final class BarDerivedStringValidator extends DerivedStringValidator<FooDerivedString,
66 * BarDerivedString}. This method needs to notably implement
67 * {@link DerivedStringValidator#validateRepresentation(DerivedString)} to hand out BarDerivedString instances.
68 * This class needs to be a singleton with a getInstance() method, too.</li>
70 * Since {@code baz} is not defining any new restrictions, all instances of FooDerivedString are valid for it and we
71 * do not have to define any additional support.
74 * It is important for {@link DerivedString} subclasses not to be final because any YANG type can be further extended
75 * and adding a final class in that hierarchy would prevent a proper class from being defined.
77 * @param <R> derived string representation
78 * @author Robert Varga
83 public abstract class DerivedString<R extends DerivedString<R>> implements Comparable<R>, Immutable, Serializable {
84 private abstract static class AbstractValidator extends ClassValue<Boolean> {
85 private static final Logger LOG = LoggerFactory.getLogger(AbstractValidator.class);
88 protected final Boolean computeValue(final @Nullable Class<?> type) {
89 // Every DerivedString representation class must:
90 checkArgument(DerivedString.class.isAssignableFrom(type), "%s is not a DerivedString", type);
92 // be non-final and public
93 final int modifiers = type.getModifiers();
94 checkArgument(Modifier.isPublic(modifiers), "%s must be public", type);
95 checkArgument(!Modifier.isFinal(modifiers), "%s must not be final", type);
97 // have at least one public or protected constructor (for subclasses)
98 checkArgument(Arrays.stream(type.getDeclaredConstructors()).mapToInt(Constructor::getModifiers)
99 .anyMatch(mod -> Modifier.isProtected(mod) || Modifier.isPublic(mod)),
100 "%s must declare at least one protected or public constructor", type);
103 // have a non-final non-abstract validator() method
106 validator = type.getMethod("validator").getModifiers();
107 } catch (NoSuchMethodException e) {
108 throw new IllegalArgumentException(type + " must have a non-abstract non-final validator() method",
111 checkArgument(!Modifier.isFinal(validator), "%s must not have final validator()", type);
113 // have final toCanonicalString(), support(), hashCode() and equals(Object), compare(T) methods
114 checkFinalMethod(type, "toCanonicalString");
115 checkFinalMethod(type, "support");
116 checkFinalMethod(type, "hashCode");
117 checkFinalMethod(type, "equals", Object.class);
118 } catch (SecurityException e) {
119 LOG.warn("Cannot completely validate {}", type, e);
120 return Boolean.FALSE;
126 abstract void checkCompareTo(Class<?> type);
128 private static void checkFinalMethod(final Class<?> type, final String name) {
130 checkFinalMethod(type.getMethod(name).getModifiers(), type, name, "");
131 } catch (NoSuchMethodException e) {
132 throw new IllegalArgumentException(type + " must have a final " + name + "() method", e);
136 static void checkFinalMethod(final Class<?> type, final String name, final Class<?> arg) {
137 final String argName = arg.getSimpleName();
139 checkFinalMethod(type.getMethod(name, arg).getModifiers(), type, name, argName);
140 } catch (NoSuchMethodException e) {
141 throw new IllegalArgumentException(type + " must have a final " + name + "(" + argName + ") method", e);
145 private static void checkFinalMethod(final int modifiers, final Class<?> type, final String name,
147 checkArgument(Modifier.isFinal(modifiers), "%s must have a final %s(%s) method", type, name, args);
151 private static final ClassValue<Boolean> VALIDATED_REPRESENTATIONS = new AbstractValidator() {
153 void checkCompareTo(@NonNull final Class<?> type) {
154 checkFinalMethod(type, "compareTo", type);
157 private static final ClassValue<Boolean> VALIDATED_VALIDATIONS = new AbstractValidator() {
159 void checkCompareTo(@NonNull final Class<?> type) {
160 // Intentional no-op, as we'd need a type capture of the representation
163 private static final long serialVersionUID = 1L;
166 * Return the canonical string representation of this object's value.
168 * @return Canonical string
170 public abstract String toCanonicalString();
173 * Return the {@link DerivedStringSupport} associated with this type. It can be used to create new instances of this
176 * @return A {@link DerivedStringSupport} instance.
178 public abstract DerivedStringSupport<R> support();
181 * Return a {@link DerivedStringValidator} associated with this value's validated type.
183 * @return A {@link DerivedStringValidator} instance.
185 public DerivedStringValidator<R, ? extends R> validator() {
190 public abstract int hashCode();
193 public abstract boolean equals(@Nullable Object obj);
196 public final String toString() {
197 return toCanonicalString();
200 static <T extends DerivedString<?>> Class<T> validateRepresentationClass(final Class<T> representationClass) {
201 // Validation is reflective, cache its result
202 VALIDATED_REPRESENTATIONS.get(representationClass);
203 return representationClass;
206 static <T extends DerivedString<?>> Class<T> validateValidationClass(final Class<T> representationClass) {
207 // Validation is reflective, cache its result
208 VALIDATED_VALIDATIONS.get(representationClass);
209 return representationClass;