001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.core.model;
007
008import java.util.Map;
009import java.util.Objects;
010import java.util.Set;
011
012import dev.metaschema.core.MetaschemaConstants;
013import dev.metaschema.core.util.CollectionUtil;
014import edu.umd.cs.findbugs.annotations.NonNull;
015
016/**
017 * A marker interface for implementations that supports the mapping of a
018 * namespace valued key to a set of values.
019 */
020@FunctionalInterface
021public interface IAttributable {
022  /**
023   * The default key namespace for a property.
024   */
025  @NonNull
026  String DEFAULT_PROPERY_NAMESPACE = MetaschemaConstants.METASCHEMA_NAMESPACE;
027
028  /**
029   * Get the mapping of property key to values.
030   *
031   * @return the mapping
032   */
033  @NonNull
034  Map<Key, Set<String>> getProperties();
035
036  /**
037   * Determine if a property is defined.
038   *
039   * @param key
040   *          the key of the property
041   * @return {@code true} if the property is defined or {@code false} otherwise
042   */
043  default boolean hasProperty(@NonNull Key key) {
044    return getProperties().containsKey(key);
045  }
046
047  /**
048   * Get the values associated with a given property key.
049   *
050   * @param key
051   *          the key of the property
052   * @return the values or an empty set
053   */
054  @NonNull
055  default Set<String> getPropertyValues(@NonNull Key key) {
056    Set<String> retval = getProperties().get(key);
057    if (retval == null) {
058      retval = CollectionUtil.emptySet();
059    }
060    return retval;
061  }
062
063  /**
064   * Determine if a given property, with a given {@code key}, has the identified
065   * {@code value}.
066   *
067   * @param key
068   *          the key of the property
069   * @param value
070   *          the expected property value
071   * @return {@code true} if the property value is defined or {@code false}
072   *         otherwise
073   */
074  default boolean hasPropertyValue(@NonNull Key key, @NonNull String value) {
075    Set<String> values = getProperties().get(key);
076    return values != null && values.contains(value);
077  }
078
079  /**
080   * Get a property key based on the provided name and namespace.
081   *
082   * @param name
083   *          the name portion of a property key
084   * @param namespace
085   *          the namespace portion of a property key
086   * @return the property key
087   */
088  @NonNull
089  static Key key(@NonNull String name, @NonNull String namespace) {
090    return new Key(name, namespace);
091  }
092
093  /**
094   * Get a property key based on the provided name and the default namespace.
095   *
096   * @param name
097   *          the name portion of a property key
098   * @return the property key
099   * @see #DEFAULT_PROPERY_NAMESPACE
100   */
101  @NonNull
102  static Key key(@NonNull String name) {
103    return new Key(name);
104  }
105
106  /**
107   * Represents a property key based on a name and a namespace.
108   */
109  // FIXME: use IEnhancedQName instead of name and namespace, or replace key
110  // altogether with an integer value
111  final class Key {
112    @NonNull
113    private final String name;
114    @NonNull
115    private final String namespace;
116
117    private Key(@NonNull String name) {
118      this(name, DEFAULT_PROPERY_NAMESPACE);
119    }
120
121    private Key(@NonNull String name, @NonNull String namespace) {
122      this.name = name;
123      this.namespace = namespace;
124    }
125
126    /**
127     * Get the property key's name portion.
128     *
129     * @return the name
130     */
131    @NonNull
132    public String getName() {
133      return name;
134    }
135
136    /**
137     * Get the property key's namespace portion.
138     *
139     * @return the name
140     */
141    @NonNull
142    public String getNamespace() {
143      return namespace;
144    }
145
146    @Override
147    public int hashCode() {
148      return Objects.hash(name, namespace);
149    }
150
151    @Override
152    public boolean equals(Object obj) {
153      if (this == obj) {
154        return true;
155      }
156      if (!(obj instanceof Key)) {
157        return false;
158      }
159      Key other = (Key) obj;
160      return Objects.equals(name, other.name) && Objects.equals(namespace, other.namespace);
161    }
162  }
163}