1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 module hunt.shiro.util.MapContext;
20 
21 import hunt.shiro.util.CollectionUtils;
22 import hunt.shiro.util.Common;
23 
24 import hunt.collection.HashMap;
25 import hunt.collection.Map;
26 import hunt.Exceptions;
27 import hunt.logging.Logger;
28 import hunt.Object;
29 import hunt.util.Common;
30 import hunt.util.ObjectUtils;
31 
32 import std.array;
33 import std.range;
34 
35 
36 /**
37  * A {@code MapContext} provides a common base for context-based data storage in a {@link Map}.  Type-safe attribute
38  * retrieval is provided for subclasses with the {@link #getTypedValue(string, Class)} method.
39  *
40  * @see hunt.shiro.subject.SubjectContext SubjectContext
41  * @see hunt.shiro.session.mgt.SessionContext SessionContext
42  */
43 class MapContext : Map!(string, Object) {
44 
45     private Map!(string, Object) backingMap;
46 
47     this() {
48         this.backingMap = new HashMap!(string, Object)();
49     }
50 
51     this(Map!(string, Object) map) {
52         this();
53         if (!CollectionUtils.isEmpty(map)) {
54             this.backingMap.putAll(map);
55         }
56     }
57 
58     /**
59      * Performs a {@link #get get} operation but additionally ensures that the value returned is of the specified
60      * {@code type}.  If there is no value, {@code null} is returned.
61      *
62      * @param key  the attribute key to look up a value
63      * @param type the expected type of the value
64      * @param <E>  the expected type of the value
65      * @return the typed value or {@code null} if the attribute does not exist.
66      */
67     //@SuppressWarnings({"unchecked"})
68     protected E getTypedValue(E)(string key) {
69         // tracef("object: %s, key %s", cast(void*)this, key);
70 
71         // tracef(toString());
72 
73         E found = null;
74         Object o = backingMap.get(key);
75         if (o is null) {
76             // warningf("No value found for %s", key);
77             // tracef(toString());
78         } else {
79             found = cast(E) o;
80             if (found is null) {
81                 string msg = "Invalid object found in SubjectContext Map under key [" ~ key ~ "].  Expected type " ~
82                         "was [" ~ typeid(E).toString() ~ "], but the object under that key is of type " ~
83                         "[" ~ typeid(o).name ~ "].";
84                 throw new IllegalArgumentException(msg);
85             }
86         }
87         // warningf("object: %s, key %s, null: %s", cast(void*)this, key, found is null);
88         return found;
89     }
90 
91     /**
92      * Places a value in this context map under the given key only if the given {@code value} argument is not null.
93      *
94      * @param key   the attribute key under which the non-null value will be stored
95      * @param value the non-null value to store.  If {@code null}, this method does nothing and returns immediately.
96      */
97     protected void nullSafePut(string key, Object value) {
98         // warningf("object: %s, puting %s, null: %s", cast(void*)this, key, value is null);
99         // warning(toString());
100         if (value !is null) {
101             put(key, value);
102         }
103     }
104 
105     int size() {
106         return backingMap.size();
107     }
108 
109     bool isEmpty() {
110         return backingMap.isEmpty();
111     }
112 
113     bool containsKey(string o) {
114         return backingMap.containsKey(o);
115     }
116 
117      bool containsValue(Object o) {
118         return backingMap.containsValue(o);
119     }
120 
121     Object get(string o) {
122         return backingMap.get(o);
123     }
124 
125     Object put(string s, Object o) {
126         return backingMap.put(s, o);
127     }
128 
129     Object remove(string o) {
130         return backingMap.remove(o);
131     }
132 
133     bool remove(string key, Object value) {
134         Object curValue = get(key);
135         if (curValue != value || !containsKey(key))
136             return false;
137         remove(key);
138         return true;
139     }
140 
141     void putAll(Map!(string, Object) map) {
142         backingMap.putAll(map);
143     }
144 
145     void clear() {
146         backingMap.clear();
147     }
148 
149     bool replace(string key, Object oldValue, Object newValue) {
150         Object curValue = get(key);
151         if (curValue != oldValue || !containsKey(key)) {
152             return false;
153         }
154         put(key, newValue);
155         return true;
156     }
157 
158     Object replace(string key, Object value) {
159         Object curValue = Object.init;
160         if (containsKey(key)) {
161             curValue = put(key, value);
162         }
163         return curValue;
164     }
165 
166     override string toString() {
167         if (isEmpty())
168             return "{}";
169 
170         Appender!string sb;
171         sb.put("{\n");
172         bool isFirst = true;
173         foreach (string key, Object value; this) {
174             if (!isFirst) {
175                 sb.put(";\n");
176             }
177             sb.put(key ~ "=" ~ value.toString());
178             isFirst = false;
179         }
180         sb.put("\n}");
181 
182         return sb.data;
183     }
184 
185     Object putIfAbsent(string key, Object value) {
186         Object v = Object.init;
187 
188         if (!containsKey(key))
189             v = put(key, value);
190 
191         return v;
192     }
193 
194     Object[] values() {
195         return byValue().array();
196     }
197 
198     Object opIndex(string key) {
199         return get(key);
200     }
201 
202     int opApply(scope int delegate(ref string, ref Object) dg) {
203         int result = 0;
204 
205         foreach(string key, Object value; backingMap) {
206             result = dg(key, value);
207         }
208 
209         return result;
210     }
211 
212     int opApply(scope int delegate(MapEntry!(string, Object) entry) dg) {
213         int result = 0;
214 
215         foreach(MapEntry!(string, Object) entry; backingMap) {
216             result = dg(entry);
217         }
218 
219         return result;
220     }
221 
222     InputRange!string byKey() {
223         return backingMap.byKey();
224     }
225 
226     InputRange!Object byValue() {
227         return backingMap.byValue();
228     }
229 
230     override bool opEquals(Object o) {
231         throw new UnsupportedOperationException();
232     }
233 
234     bool opEquals(IObject o) {
235         return opEquals(cast(Object) o);
236     }
237 
238     override size_t toHash() @trusted nothrow {
239         size_t h = 0;
240         try {
241             foreach (MapEntry!(string, Object) i; this) {
242                 h += i.toHash();
243             }
244         } catch (Exception ex) {
245         }
246         return h;
247     }   
248 
249      mixin CloneMemberTemplate!(typeof(this));
250 }