1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.model;
7   
8   import java.util.Map;
9   import java.util.Objects;
10  import java.util.Set;
11  
12  import dev.metaschema.core.MetaschemaConstants;
13  import dev.metaschema.core.util.CollectionUtil;
14  import edu.umd.cs.findbugs.annotations.NonNull;
15  
16  /**
17   * A marker interface for implementations that supports the mapping of a
18   * namespace valued key to a set of values.
19   */
20  @FunctionalInterface
21  public interface IAttributable {
22    /**
23     * The default key namespace for a property.
24     */
25    @NonNull
26    String DEFAULT_PROPERY_NAMESPACE = MetaschemaConstants.METASCHEMA_NAMESPACE;
27  
28    /**
29     * Get the mapping of property key to values.
30     *
31     * @return the mapping
32     */
33    @NonNull
34    Map<Key, Set<String>> getProperties();
35  
36    /**
37     * Determine if a property is defined.
38     *
39     * @param key
40     *          the key of the property
41     * @return {@code true} if the property is defined or {@code false} otherwise
42     */
43    default boolean hasProperty(@NonNull Key key) {
44      return getProperties().containsKey(key);
45    }
46  
47    /**
48     * Get the values associated with a given property key.
49     *
50     * @param key
51     *          the key of the property
52     * @return the values or an empty set
53     */
54    @NonNull
55    default Set<String> getPropertyValues(@NonNull Key key) {
56      Set<String> retval = getProperties().get(key);
57      if (retval == null) {
58        retval = CollectionUtil.emptySet();
59      }
60      return retval;
61    }
62  
63    /**
64     * Determine if a given property, with a given {@code key}, has the identified
65     * {@code value}.
66     *
67     * @param key
68     *          the key of the property
69     * @param value
70     *          the expected property value
71     * @return {@code true} if the property value is defined or {@code false}
72     *         otherwise
73     */
74    default boolean hasPropertyValue(@NonNull Key key, @NonNull String value) {
75      Set<String> values = getProperties().get(key);
76      return values != null && values.contains(value);
77    }
78  
79    /**
80     * Get a property key based on the provided name and namespace.
81     *
82     * @param name
83     *          the name portion of a property key
84     * @param namespace
85     *          the namespace portion of a property key
86     * @return the property key
87     */
88    @NonNull
89    static Key key(@NonNull String name, @NonNull String namespace) {
90      return new Key(name, namespace);
91    }
92  
93    /**
94     * Get a property key based on the provided name and the default namespace.
95     *
96     * @param name
97     *          the name portion of a property key
98     * @return the property key
99     * @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 }