Promote FeatureSet 27/105227/14
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 4 Apr 2023 17:29:20 +0000 (19:29 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 11 Apr 2023 15:32:40 +0000 (17:32 +0200)
We are using only Set.contains() to evaluate feature expressions and we
need more flexibility that that. FeatureSet already provides this
capability, but unfortunately it also implies all the Set semantics.

This patch refactors FeatureSet to be a simple class, without a tie to
Set and updates IfFeatureExpr to integrate with it.

JIRA: YANGTOOLS-1504
Change-Id: Ie719f7a3d006f2d6ed51c70604236e4105d8f8ed
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
20 files changed:
model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/FeatureSet.java [new file with mode: 0644]
model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/IfFeatureExpr.java
model/yang-model-api/src/test/java/org/opendaylight/yangtools/yang/model/api/stmt/FeatureSetTest.java [new file with mode: 0644]
parser/rfc8040-parser-support/src/test/java/org/opendaylight/yangtools/rfc8040/parser/YangDataExtensionTest.java
parser/yang-parser-api/src/main/java/org/opendaylight/yangtools/yang/parser/api/YangParser.java
parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/DefaultYangLibResolver.java
parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/DefaultYangParser.java
parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/YangTextSchemaContextResolver.java
parser/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaRepositoryWithFeaturesTest.java
parser/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/BuildGlobalContext.java
parser/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/CrossSourceStatementReactor.java
parser/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java
parser/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/stmt/StmtTestUtils.java
parser/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/stmt/TestUtils.java
parser/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/ParserNamespaces.java
parser/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContextUtils.java
parser/yang-test-util/src/main/java/org/opendaylight/yangtools/yang/test/util/YangParserTestUtils.java
tools/yang-model-validator/src/main/java/org/opendaylight/yangtools/yang/validator/SystemTestUtils.java
yang/yang-repo-api/src/main/java/org/opendaylight/yangtools/yang/model/repo/api/FeatureSet.java [deleted file]
yang/yang-repo-api/src/main/java/org/opendaylight/yangtools/yang/model/repo/api/SchemaContextFactoryConfiguration.java

diff --git a/model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/FeatureSet.java b/model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/FeatureSet.java
new file mode 100644 (file)
index 0000000..a966d79
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.model.api.stmt;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+
+/**
+ * A set of features.
+ *
+ * <p>
+ * The semantics of {@link #contains(QName)} is a bit funky, depending on implementation:
+ * <ol>
+ *   <li>explicit implementation, returned from {@link #of()}, {@link #of(Set)} et al., delegates to the underlying
+ *       Set's {@link Set#contains(Object)}, while on the other hand</li>
+ *   <li>sparse implementation, constructed via {@link #builder()} and {@link Builder#build()}, carves the features into
+ *       well-known {@code module} namespaces, expressed as {@link QNameModule} for which we have an explicit
+ *       enumeration of supported features. All other {@code module} namespaces are treated as if there was no
+ *       specification of supported features -- e.g. all features from those namespaces are deemed to be present
+ *       in the instance.</li>
+ * </ol>
+ */
+public abstract sealed class FeatureSet implements Immutable {
+    private static final class Explicit extends FeatureSet {
+        private static final @NonNull Explicit EMPTY = new Explicit(ImmutableSet.of());
+
+        private final ImmutableSet<QName> features;
+
+        Explicit(final ImmutableSet<QName> features) {
+            this.features = requireNonNull(features);
+        }
+
+        @Override
+        public boolean contains(final QName qname) {
+            return features.contains(qname);
+        }
+
+        @Override
+        public int hashCode() {
+            return features.hashCode();
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            return this == obj || obj instanceof Explicit other && features.equals(other.features);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this).add("features", features).toString();
+        }
+    }
+
+    private static final class Sparse extends FeatureSet {
+        // Note: not a ImmutableSetMultimap because we need to distinguish non-presence vs. empty Set
+        private final ImmutableMap<QNameModule, ImmutableSet<String>> featuresByModule;
+
+        Sparse(final Map<QNameModule, ImmutableSet<String>> featuresByModule) {
+            this.featuresByModule = ImmutableMap.copyOf(featuresByModule);
+        }
+
+        @Override
+        public boolean contains(final QName qname) {
+            final var sets = featuresByModule.get(qname.getModule());
+            return sets == null || sets.contains(qname.getLocalName());
+        }
+
+        @Override
+        public int hashCode() {
+            return featuresByModule.hashCode();
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            return this == obj || obj instanceof Sparse other && featuresByModule.equals(other.featuresByModule);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this).add("features", featuresByModule).toString();
+        }
+    }
+
+    /**
+     * A builder for a sparse FeatureSet. The semantics is such that for features which belong to a namespace which
+     * has been explicitly mention, only the specified features are supported. For namespaces not mentioned, all
+     * features are reported as present.
+     */
+    public static final class Builder {
+        // Note: Tree{Map,Set} so we sort keys/entries by their natural ordering -- which also prevents nulls
+        private final TreeMap<QNameModule, TreeSet<String>> moduleFeatures = new TreeMap<>();
+
+        public @NonNull Builder addModuleFeatures(final QNameModule module, final Collection<String> names) {
+            moduleFeatures.computeIfAbsent(module, ignored -> new TreeSet<>()).addAll(names);
+            return this;
+        }
+
+        public @NonNull FeatureSet build() {
+            return new Sparse(Maps.transformValues(moduleFeatures, ImmutableSet::copyOf));
+        }
+    }
+
+    /**
+     * Return an empty {@link FeatureSet}.
+     *
+     * @return An empty {@link FeatureSet}
+     */
+    public static @NonNull FeatureSet of() {
+        return Explicit.EMPTY;
+    }
+
+    /**
+     * Return a {@link FeatureSet} containing specified features.
+     *
+     * @return A {@link FeatureSet}
+     * @throws NullPointerException if {@code features} is or contains {@code null}
+     */
+    public static @NonNull FeatureSet of(final QName... features) {
+        return of(ImmutableSet.copyOf(features));
+    }
+
+    /**
+     * Return a {@link FeatureSet} containing specified features.
+     *
+     * @return A {@link FeatureSet}
+     * @throws NullPointerException if {@code features} is or contains {@code null}
+     */
+    public static @NonNull FeatureSet of(final Set<QName> features) {
+        return of(ImmutableSet.copyOf(features));
+    }
+
+    private static @NonNull FeatureSet of(final ImmutableSet<QName> features) {
+        return features.isEmpty() ? of() : new Explicit(features);
+    }
+
+    public static @NonNull Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Determine whether a particular {@code feature}, as identified by its {@link QName} is part of this set.
+     *
+     * @param qname Feature QName
+     * @return {@code true} if this set contains the feature
+     * @throws NullPointerException if {@code qname} is {@code null}
+     */
+    public abstract boolean contains(@NonNull QName qname);
+
+    @Override
+    public abstract int hashCode();
+
+    @Override
+    public abstract boolean equals(Object obj);
+
+    @Override
+    public abstract String toString();
+}
index ac17a10fea9655e4d27f267ba3141572804ab94d..a932c455986ed26af0971c9436f8d56e87a20412 100644 (file)
@@ -31,7 +31,7 @@ import org.opendaylight.yangtools.yang.common.QName;
  * <p>
  * The set of features referenced in this expression is available through {@link #getReferencedFeatures()}.
  */
-public abstract sealed class IfFeatureExpr implements Immutable, Predicate<Set<QName>> {
+public abstract sealed class IfFeatureExpr implements Immutable, Predicate<FeatureSet> {
     private abstract static sealed class Single extends IfFeatureExpr {
         final @NonNull QName qname;
 
@@ -167,7 +167,7 @@ public abstract sealed class IfFeatureExpr implements Immutable, Predicate<Set<Q
         }
 
         @Override
-        public boolean test(final Set<QName> supportedFeatures) {
+        public boolean test(final FeatureSet supportedFeatures) {
             return !supportedFeatures.contains(qname);
         }
 
@@ -188,7 +188,7 @@ public abstract sealed class IfFeatureExpr implements Immutable, Predicate<Set<Q
         }
 
         @Override
-        public boolean test(final Set<QName> supportedFeatures) {
+        public boolean test(final FeatureSet supportedFeatures) {
             return supportedFeatures.contains(qname);
         }
 
@@ -209,7 +209,7 @@ public abstract sealed class IfFeatureExpr implements Immutable, Predicate<Set<Q
         }
 
         @Override
-        public boolean test(final Set<QName> supportedFeatures) {
+        public boolean test(final FeatureSet supportedFeatures) {
             for (var expr : array) {
                 if (!expr.test(supportedFeatures)) {
                     return false;
@@ -235,7 +235,7 @@ public abstract sealed class IfFeatureExpr implements Immutable, Predicate<Set<Q
         }
 
         @Override
-        public boolean test(final Set<QName> supportedFeatures) {
+        public boolean test(final FeatureSet supportedFeatures) {
             for (var expr : array) {
                 if (expr.test(supportedFeatures)) {
                     return true;
@@ -256,7 +256,7 @@ public abstract sealed class IfFeatureExpr implements Immutable, Predicate<Set<Q
         }
 
         @Override
-        public final boolean test(final Set<QName> supportedFeatures) {
+        public final boolean test(final FeatureSet supportedFeatures) {
             final boolean neg = negated();
             for (var qname : array) {
                 if (supportedFeatures.contains(qname) == neg) {
@@ -310,7 +310,7 @@ public abstract sealed class IfFeatureExpr implements Immutable, Predicate<Set<Q
         }
 
         @Override
-        public final boolean test(final Set<QName> supportedFeatures) {
+        public final boolean test(final FeatureSet supportedFeatures) {
             for (var qname : array) {
                 if (supportedFeatures.contains(qname)) {
                     return !negated();
@@ -358,7 +358,7 @@ public abstract sealed class IfFeatureExpr implements Immutable, Predicate<Set<Q
     }
 
     /**
-     * Construct an assertion that a feature is present in the set passed to {@link #test(Set)}.
+     * Construct an assertion that a feature is present in the set passed to {@link #test(FeatureSet)}.
      *
      * @param qname Feature QName
      * @return An expression
@@ -403,7 +403,7 @@ public abstract sealed class IfFeatureExpr implements Immutable, Predicate<Set<Q
     public abstract @NonNull IfFeatureExpr negate();
 
     @Override
-    public abstract boolean test(Set<QName> supportedFeatures);
+    public abstract boolean test(FeatureSet supportedFeatures);
 
     @Override
     public abstract int hashCode();
diff --git a/model/yang-model-api/src/test/java/org/opendaylight/yangtools/yang/model/api/stmt/FeatureSetTest.java b/model/yang-model-api/src/test/java/org/opendaylight/yangtools/yang/model/api/stmt/FeatureSetTest.java
new file mode 100644 (file)
index 0000000..c9dcd02
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.model.api.stmt;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Set;
+import org.eclipse.jdt.annotation.NonNull;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+
+class FeatureSetTest {
+    private static final @NonNull QName FOO_FOO = QName.create("foo", "foo");
+    private static final @NonNull QName FOO_BAR = QName.create("foo", "bar");
+    private static final @NonNull QName BAR_FOO = QName.create("bar", "foo");
+    private static final @NonNull QName BAR_BAR = QName.create("bar", "bar");
+
+    @Test
+    void emptyIsSingleton() {
+        assertSame(FeatureSet.of(), FeatureSet.of());
+    }
+
+    @Test
+    void explicitContains() {
+        final var set = FeatureSet.of(FOO_FOO, BAR_FOO);
+        assertTrue(set.contains(FOO_FOO));
+        assertFalse(set.contains(FOO_BAR));
+        assertTrue(set.contains(BAR_FOO));
+        assertFalse(set.contains(BAR_BAR));
+    }
+
+    @Test
+    void explicitHashCodeEquals() {
+        final var set = FeatureSet.of(FOO_FOO, BAR_FOO);
+        final var other = FeatureSet.of(Set.of(FOO_FOO, BAR_FOO));
+        assertEquals(set.hashCode(), other.hashCode());
+        assertEquals(set, other);
+    }
+
+    @Test
+    void sparseContains() {
+        final var set = FeatureSet.builder()
+            .addModuleFeatures(FOO_FOO.getModule(), Set.of(FOO_FOO.getLocalName()))
+            .build();
+
+        assertTrue(set.contains(FOO_FOO));
+        assertFalse(set.contains(FOO_BAR));
+        assertTrue(set.contains(BAR_FOO));
+        assertTrue(set.contains(BAR_BAR));
+    }
+
+    @Test
+    void sparseHashCodeEquals() {
+        final var set = FeatureSet.builder()
+            .addModuleFeatures(FOO_FOO.getModule(), Set.of(FOO_FOO.getLocalName()))
+            .build();
+        final var other = FeatureSet.builder()
+            .addModuleFeatures(FOO_FOO.getModule(), Set.of(FOO_FOO.getLocalName()))
+            .build();
+        assertEquals(set.hashCode(), other.hashCode());
+        assertEquals(set, other);
+    }
+}
index d6fa481ce4e4708dc9731c881764810ed5584df1..9ef4f3f2a1d24433dc823642b8b22d16ff87a138 100644 (file)
@@ -14,7 +14,6 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 
-import com.google.common.collect.ImmutableSet;
 import java.util.Optional;
 import org.junit.Test;
 import org.opendaylight.yangtools.rfc8040.model.api.YangDataSchemaNode;
@@ -23,6 +22,7 @@ import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.common.Revision;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.parser.spi.meta.InvalidSubstatementException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.MissingSubstatementException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
@@ -109,7 +109,7 @@ public class YangDataExtensionTest extends AbstractYangDataTest {
     @Test
     public void testIfFeatureStatementBeingIgnoredInYangDataBody() throws Exception {
         final var schemaContext = REACTOR.newBuild()
-            .setSupportedFeatures(ImmutableSet.of())
+            .setSupportedFeatures(FeatureSet.of())
             .addSources(FOOBAR_MODULE, IETF_RESTCONF_MODULE)
             .buildEffective();
         assertNotNull(schemaContext);
index 59aef302f606e494194d6517377fbeb685e615a2..84c44f4df09dbc0801cbeb2db79ac1b2dcc841e9 100644 (file)
@@ -12,12 +12,11 @@ import com.google.common.collect.SetMultimap;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
 
 /**
@@ -106,7 +105,7 @@ public interface YangParser {
      * @param supportedFeatures Set of supported features in the final SchemaContext. If the set is empty, no features
      *                          encountered will be supported.
      */
-    @NonNull YangParser setSupportedFeatures(@NonNull Set<QName> supportedFeatures);
+    @NonNull YangParser setSupportedFeatures(@NonNull FeatureSet supportedFeatures);
 
     /**
      * Set YANG modules which can be deviated by specified modules during the parsing process. Map key (QNameModule)
index e406b4a045b27b70b87f1e3ae9568ac519a8eab5..79b993f57fafbc15bd119ce1e3a8d1ce92427541 100644 (file)
@@ -17,6 +17,7 @@ import org.kohsuke.MetaInfServices;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
 import org.opendaylight.yangtools.yang.parser.api.YangLibModuleSet;
 import org.opendaylight.yangtools.yang.parser.api.YangLibResolver;
@@ -73,7 +74,7 @@ public final class DefaultYangLibResolver implements YangLibResolver {
         }
 
         try {
-            return act.setSupportedFeatures(features.build()).buildEffective();
+            return act.setSupportedFeatures(FeatureSet.of(features.build())).buildEffective();
         } catch (ReactorException e) {
             throw DefaultYangParser.decodeReactorException(e);
         }
index b1cf78db9e1045800744f76136889b986be07b7e..f8425e74e7e23361a4c9435f290a40c61bb8da1c 100644 (file)
@@ -14,13 +14,12 @@ import com.google.common.collect.SetMultimap;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 import javax.xml.transform.TransformerException;
 import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
 import org.opendaylight.yangtools.yang.model.repo.api.YangIRSchemaSource;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
@@ -69,7 +68,7 @@ final class DefaultYangParser implements YangParser {
     }
 
     @Override
-    public @NonNull YangParser setSupportedFeatures(final Set<QName> supportedFeatures) {
+    public @NonNull YangParser setSupportedFeatures(final FeatureSet supportedFeatures) {
         buildAction.setSupportedFeatures(supportedFeatures);
         return this;
     }
index 215297e71985e3bf9f59a2806593f942afff5b1d..bb6e4d40e3485afa24701a99e09bc101eaddfc77 100644 (file)
@@ -13,7 +13,6 @@ import static java.util.Objects.requireNonNull;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Verify;
 import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.FluentFuture;
@@ -38,7 +37,7 @@ import org.opendaylight.yangtools.concepts.Registration;
 import org.opendaylight.yangtools.util.concurrent.FluentFutures;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.repo.api.FeatureSet;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
@@ -225,15 +224,13 @@ public final class YangTextSchemaContextResolver implements AutoCloseable, Schem
     private synchronized @Nullable FeatureSet getSupportedFeatures() {
         var local = supportedFeatures;
         if (local == null && !registeredFeatures.isEmpty()) {
-            final var builder = ImmutableMap.<QNameModule, ImmutableSet<String>>builder();
+            final var builder = FeatureSet.builder();
             for (var entry : registeredFeatures.entrySet()) {
-                builder.put(entry.getKey(), entry.getValue().stream()
-                    .flatMap(Set::stream)
-                    .distinct()
-                    .sorted()
-                    .collect(ImmutableSet.toImmutableSet()));
+                for (var features : entry.getValue()) {
+                    builder.addModuleFeatures(entry.getKey(), features);
+                }
             }
-            supportedFeatures = local = new FeatureSet(builder.build());
+            supportedFeatures = local = builder.build();
         }
         return local;
     }
index 325bf7a9b92c8d581b708bb04401aa91d67cc54e..8280cac5a924f1e8976b141c62095f253f1d99b8 100644 (file)
@@ -12,9 +12,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import com.google.common.collect.ImmutableSet;
 import com.google.common.util.concurrent.ListenableFuture;
-import java.util.Set;
 import org.junit.Test;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
@@ -22,6 +20,7 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration;
 import org.opendaylight.yangtools.yang.model.repo.api.YangIRSchemaSource;
@@ -32,7 +31,7 @@ public class SharedSchemaRepositoryWithFeaturesTest {
 
     @Test
     public void testSharedSchemaRepositoryWithSomeFeaturesSupported() throws Exception {
-        final Set<QName> supportedFeatures = ImmutableSet.of(QName.create("foobar-namespace", "test-feature-1"));
+        final FeatureSet supportedFeatures = FeatureSet.of(QName.create("foobar-namespace", "test-feature-1"));
 
         final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository(
                 "shared-schema-repo-with-features-test");
@@ -116,7 +115,7 @@ public class SharedSchemaRepositoryWithFeaturesTest {
 
         final ListenableFuture<EffectiveModelContext> testSchemaContextFuture =
                 sharedSchemaRepository.createEffectiveModelContextFactory(
-                    SchemaContextFactoryConfiguration.builder().setSupportedFeatures(ImmutableSet.of()).build())
+                    SchemaContextFactoryConfiguration.builder().setSupportedFeatures(FeatureSet.of()).build())
                 .createEffectiveModelContext(foobar.getId());
         assertTrue(testSchemaContextFuture.isDone());
         assertSchemaContext(testSchemaContextFuture.get(), 1);
index e3ac23f204049ed26fd9d56316d37a7b0333cdbe..7e04bd017b647f494916e0ae843bd870056c30e2 100644 (file)
@@ -37,7 +37,7 @@ import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
 import org.opendaylight.yangtools.yang.common.YangVersion;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
-import org.opendaylight.yangtools.yang.model.repo.api.FeatureSet;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.parser.spi.ParserNamespaces;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
@@ -106,12 +106,8 @@ final class BuildGlobalContext extends AbstractNamespaceStorage implements Globa
         libSources.add(new SourceSpecificContext(this, libSource));
     }
 
-    void setSupportedFeatures(final Set<QName> supportedFeatures) {
-        if (supportedFeatures instanceof FeatureSet) {
-            addToNamespace(ParserNamespaces.SUPPORTED_FEATURES, Empty.value(), supportedFeatures);
-        } else {
-            addToNamespace(ParserNamespaces.SUPPORTED_FEATURES, Empty.value(), ImmutableSet.copyOf(supportedFeatures));
-        }
+    void setSupportedFeatures(final FeatureSet supportedFeatures) {
+        addToNamespace(ParserNamespaces.SUPPORTED_FEATURES, Empty.value(), requireNonNull(supportedFeatures));
     }
 
     void setModulesDeviatedByModules(final SetMultimap<QNameModule, QNameModule> modulesDeviatedByModules) {
index 3ab4141327097fab15ab22901aef8ff30f43832b..28e72643dc3cafb41b24778e418a12c599f57556 100644 (file)
@@ -16,11 +16,10 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.EnumMap;
 import java.util.Map;
-import java.util.Set;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.concepts.Mutable;
-import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupportBundle;
@@ -190,7 +189,7 @@ public final class CrossSourceStatementReactor {
          *            If the set is empty, no features encountered will be supported.
          * @return This build action, for fluent use.
          */
-        public @NonNull BuildAction setSupportedFeatures(final @NonNull Set<QName> supportedFeatures) {
+        public @NonNull BuildAction setSupportedFeatures(final @NonNull FeatureSet supportedFeatures) {
             checkState(!supportedFeaturesSet, "Supported features should be set only once.");
             context.setSupportedFeatures(requireNonNull(supportedFeatures));
             supportedFeaturesSet = true;
index 6538848724388c13117ea6b3d1cfb2f2a9c5e3ee..ad8f95c21c2b43ac781c007a0db60ff93b12b5c4 100644 (file)
@@ -16,7 +16,6 @@ import com.google.common.base.VerifyException;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
 import java.util.stream.Stream;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
@@ -490,7 +489,7 @@ abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends Effec
          */
         if (isParentSupportedByFeatures()) {
             // If the set of supported features has not been provided, all features are supported by default.
-            final Set<QName> supportedFeatures = namespaceItem(ParserNamespaces.SUPPORTED_FEATURES, Empty.value());
+            final var supportedFeatures = namespaceItem(ParserNamespaces.SUPPORTED_FEATURES, Empty.value());
             if (supportedFeatures == null || StmtContextUtils.checkFeatureSupport(this, supportedFeatures)) {
                 flags |= SET_SUPPORTED_BY_FEATURES;
                 return true;
index 88d40a527c18c670fbd95cde30fb0a900f974053..9eee31f90d227ae30ace2bd8addd5c79cdfcdab4 100644 (file)
@@ -24,6 +24,7 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
 import org.opendaylight.yangtools.yang.model.repo.api.YinTextSchemaSource;
@@ -100,7 +101,7 @@ public final class StmtTestUtils {
             throws ReactorException {
         final BuildAction build = getReactor(config).newBuild().addSources(sources);
         if (supportedFeatures != null) {
-            build.setSupportedFeatures(supportedFeatures);
+            build.setSupportedFeatures(FeatureSet.of(supportedFeatures));
         }
         return build.buildEffective();
     }
index fce55a6bd5252785f6a6889c7b0f0c16bffbd516..9206be9c89e1a89af0df207bbb37c8338eecc07a 100644 (file)
@@ -22,6 +22,7 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
 import org.opendaylight.yangtools.yang.model.repo.api.YinTextSchemaSource;
 import org.opendaylight.yangtools.yang.parser.rfc7950.reactor.RFC7950Reactors;
@@ -73,7 +74,7 @@ public final class TestUtils {
         final var action = RFC7950Reactors.defaultReactor().newBuild()
             .addSources(loadSources(cls, resourceDirectory));
         if (supportedFeatures != null) {
-            action.setSupportedFeatures(supportedFeatures);
+            action.setSupportedFeatures(FeatureSet.of(supportedFeatures));
         }
         return action.buildEffective();
     }
@@ -90,7 +91,7 @@ public final class TestUtils {
                 TestUtils.class.getResource(resourcePath).toURI()))));
         }
         if (supportedFeatures != null) {
-            reactor.setSupportedFeatures(supportedFeatures);
+            reactor.setSupportedFeatures(FeatureSet.of(supportedFeatures));
         }
         return reactor.buildEffective();
     }
index 5fc9fd532b4681b7c52ac171385f5e936f2992d2..56b2b13511a732f32ee6346a417b3be21c0a17ad 100644 (file)
@@ -10,7 +10,6 @@ package org.opendaylight.yangtools.yang.parser.spi;
 import com.google.common.collect.SetMultimap;
 import java.util.Collection;
 import java.util.Optional;
-import java.util.Set;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.common.Empty;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -21,6 +20,7 @@ import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ExtensionEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ExtensionStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.FeatureEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.api.stmt.FeatureStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingStatement;
@@ -193,7 +193,7 @@ public final class ParserNamespaces {
     public static final @NonNull ParserNamespace<StmtContext<?, ?, ?>, QNameModule> MODULECTX_TO_QNAME =
         new ParserNamespace<>("modulectx-to-qnamemodule");
 
-    public static final @NonNull ParserNamespace<Empty, Set<QName>> SUPPORTED_FEATURES =
+    public static final @NonNull ParserNamespace<Empty, FeatureSet> SUPPORTED_FEATURES =
         new ParserNamespace<>("supportedFeatures");
 
     /**
index 21d19c37b605905168a5455e1175fef56e11b854..250b61f66c586136bb2ef72825e2c36da26bf4d4 100644 (file)
@@ -24,6 +24,7 @@ import org.opendaylight.yangtools.yang.common.YangVersion;
 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.api.stmt.IfFeatureExpr;
 import org.opendaylight.yangtools.yang.model.api.stmt.KeyEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.KeyStatement;
@@ -212,7 +213,7 @@ public final class StmtContextUtils {
     }
 
     public static boolean checkFeatureSupport(final StmtContext<?, ?, ?> stmtContext,
-            final Set<QName> supportedFeatures) {
+            final FeatureSet supportedFeatures) {
         boolean isSupported = false;
         boolean containsIfFeature = false;
         for (var stmt : stmtContext.declaredSubstatements()) {
index c1c9c6e74115da7b342a056d3a797a5827acd9d4..f4e3b9608bbade264061d3416f843253a970cb4b 100644 (file)
@@ -27,6 +27,7 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.YangConstants;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
 import org.opendaylight.yangtools.yang.parser.api.YangParser;
@@ -350,7 +351,7 @@ public final class YangParserTestUtils {
             final Set<QName> supportedFeatures, final Collection<? extends SchemaSourceRepresentation> sources) {
         final YangParser parser = PARSER_FACTORY.createParser(config);
         if (supportedFeatures != null) {
-            parser.setSupportedFeatures(supportedFeatures);
+            parser.setSupportedFeatures(FeatureSet.of(supportedFeatures));
         }
 
         try {
index db42013067959dc8179c25710b5d1696c9b87b58..891f183e0b80000b572d228da06acb475f73382a 100644 (file)
@@ -32,6 +32,7 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.YangConstants;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
 import org.opendaylight.yangtools.yang.parser.api.YangParser;
 import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration;
@@ -97,7 +98,7 @@ final class SystemTestUtils {
                 .warnForUnkeyedLists(warnForUnkeyedLists).build();
         final YangParser parser = PARSER_FACTORY.createParser(configuration);
         if (supportedFeatures != null) {
-            parser.setSupportedFeatures(supportedFeatures);
+            parser.setSupportedFeatures(FeatureSet.of(supportedFeatures));
         }
 
         for (File file : testFiles) {
diff --git a/yang/yang-repo-api/src/main/java/org/opendaylight/yangtools/yang/model/repo/api/FeatureSet.java b/yang/yang-repo-api/src/main/java/org/opendaylight/yangtools/yang/model/repo/api/FeatureSet.java
deleted file mode 100644 (file)
index 3bb5d37..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (c) 2023 PANTHEON.tech s.r.o. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.yangtools.yang.model.repo.api;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import java.util.AbstractSet;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.Set;
-import org.opendaylight.yangtools.concepts.Immutable;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.QNameModule;
-
-/**
- * Set of features. This is nominally a {@link Set} due to API pre-existing API contracts. This class needs to be used
- * <b>very carefully</b> because its {@link #hashCode()} and {@link #equals(Object)} contracts do not conform to
- * the specification laid out by {@link Set} and it cannot enumerate its individual component {@link QName}s -- thus
- * breaking reflexivity requirement of {@link #equals(Object)}.
- *
- * <p>
- * The semantics of {@link #contains(Object)} is a bit funky, but reflects the default of supporting all encountered
- * features without enumerating them. The map supplied to the constructor enumerates all {@code module} namespaces,
- * expressed as {@link QNameModule} for which we have an explicit enumeration of supported features. All other
- * {@code module} namespaces are treated as if there was no specification of supported features -- e.g. all features
- * from those namespaces are deemed to be present in the instance.
- */
-// FIXME: 12.0.0: this should only have 'boolean contains(QName)', with two implementations (Set-based and
-//                module/feature based via a builder). This shouldlive in yang-model-api, where it has a tie-in
-//                with IfFeatureExpr
-@Beta
-public final class FeatureSet extends AbstractSet<QName> implements Immutable {
-    // Note: not a ImmutableSetMultimap because we need to distinguish non-presence vs. empty Set
-    private final ImmutableMap<QNameModule, ImmutableSet<String>> featuresByModule;
-
-    public FeatureSet(final ImmutableMap<QNameModule, ImmutableSet<String>> featuresByModule) {
-        this.featuresByModule = requireNonNull(featuresByModule);
-    }
-
-    @Override
-    @SuppressWarnings("checkstyle:parameterName")
-    public boolean contains(final Object o) {
-        if (o instanceof QName qname) {
-            final var features = featuresByModule.get(qname.getModule());
-            return features == null || features.contains(qname.getLocalName());
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return featuresByModule.hashCode();
-    }
-
-    @Override
-    public boolean equals(final Object obj) {
-        return this == obj || obj instanceof FeatureSet other && featuresByModule.equals(other.featuresByModule);
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(this).add("features", featuresByModule).toString();
-    }
-
-    @Deprecated
-    @Override
-    public Iterator<QName> iterator() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    public boolean isEmpty() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    public int size() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    public Object[] toArray() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    @SuppressWarnings("checkstyle:parameterName")
-    public <T> T[] toArray(final T[] a) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    @SuppressWarnings("checkstyle:parameterName")
-    public boolean add(final QName e) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    @SuppressWarnings("checkstyle:parameterName")
-    public boolean remove(final Object o) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    @SuppressWarnings("checkstyle:parameterName")
-    public boolean addAll(final Collection<? extends QName> c) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    @SuppressWarnings("checkstyle:parameterName")
-    public boolean retainAll(final Collection<?> c) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    @SuppressWarnings("checkstyle:parameterName")
-    public boolean removeAll(final Collection<?> c) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Deprecated
-    @Override
-    public void clear() {
-        throw new UnsupportedOperationException();
-    }
-}
index 8120518cf3f554b864366d854165396518e74fbd..578554f654227ae479733ada3ba37324f6b62d5d 100644 (file)
@@ -11,18 +11,16 @@ import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.SetMultimap;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.concepts.Mutable;
-import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet;
 
 /**
  * SchemaContextFactory configuration class. It currently supports the following options to be set:
@@ -39,12 +37,12 @@ public final class SchemaContextFactoryConfiguration implements Immutable {
 
     private final @NonNull SchemaSourceFilter filter;
     private final @NonNull StatementParserMode statementParserMode;
-    private final @Nullable Set<QName> supportedFeatures;
+    private final @Nullable FeatureSet supportedFeatures;
     private final @Nullable ImmutableSetMultimap<QNameModule, QNameModule> modulesDeviatedByModules;
 
     private SchemaContextFactoryConfiguration(final @NonNull SchemaSourceFilter filter,
             final @NonNull StatementParserMode statementParserMode,
-            final @Nullable Set<QName> supportedFeatures,
+            final @Nullable FeatureSet supportedFeatures,
             final @Nullable ImmutableSetMultimap<QNameModule, QNameModule> modulesDeviatedByModules) {
         this.filter = requireNonNull(filter);
         this.statementParserMode = requireNonNull(statementParserMode);
@@ -60,7 +58,7 @@ public final class SchemaContextFactoryConfiguration implements Immutable {
         return statementParserMode;
     }
 
-    public Optional<Set<QName>> getSupportedFeatures() {
+    public Optional<FeatureSet> getSupportedFeatures() {
         return Optional.ofNullable(supportedFeatures);
     }
 
@@ -85,27 +83,10 @@ public final class SchemaContextFactoryConfiguration implements Immutable {
     public boolean equals(final Object obj) {
         return this == obj || obj instanceof SchemaContextFactoryConfiguration other && filter.equals(other.filter)
             && statementParserMode.equals(other.statementParserMode)
-            && equals(supportedFeatures, other.supportedFeatures)
+            && Objects.equals(supportedFeatures, other.supportedFeatures)
             && Objects.equals(modulesDeviatedByModules, other.modulesDeviatedByModules);
     }
 
-    // This a bit of a dance to deal with FeatureSet not conforming to Set.equals()
-    private static boolean equals(final @Nullable Set<QName> thisFeatures, final @Nullable Set<QName> otherFeatures) {
-        if (thisFeatures == otherFeatures) {
-            return true;
-        }
-        if (thisFeatures == null || otherFeatures == null) {
-            return false;
-        }
-        if (thisFeatures instanceof FeatureSet) {
-            return thisFeatures.equals(otherFeatures);
-        }
-        if (otherFeatures instanceof FeatureSet) {
-            return otherFeatures.equals(thisFeatures);
-        }
-        return thisFeatures.equals(otherFeatures);
-    }
-
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this).omitNullValues().add("schemaSourceFilter", filter)
@@ -117,7 +98,7 @@ public final class SchemaContextFactoryConfiguration implements Immutable {
         private @NonNull SchemaSourceFilter filter = SchemaSourceFilter.ALWAYS_ACCEPT;
         private @NonNull StatementParserMode statementParserMode = StatementParserMode.DEFAULT_MODE;
         private ImmutableSetMultimap<QNameModule, QNameModule> modulesDeviatedByModules;
-        private Set<QName> supportedFeatures;
+        private FeatureSet supportedFeatures;
 
         /**
          * Set schema source filter which will filter available schema sources using the provided filter.
@@ -149,12 +130,8 @@ public final class SchemaContextFactoryConfiguration implements Immutable {
          *                          features encountered will be supported.
          * @return this builder
          */
-        public @NonNull Builder setSupportedFeatures(final Set<QName> supportedFeatures) {
-            if (supportedFeatures == null || supportedFeatures instanceof FeatureSet) {
-                this.supportedFeatures = supportedFeatures;
-            } else {
-                this.supportedFeatures = ImmutableSet.copyOf(supportedFeatures);
-            }
+        public @NonNull Builder setSupportedFeatures(final FeatureSet supportedFeatures) {
+            this.supportedFeatures = supportedFeatures;
             return this;
         }