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.config.ReflectionBuilder;
20 
21 // import hunt.commons.beanutils.BeanUtilsBean;
22 // import hunt.commons.beanutils.SuppressPropertiesBeanIntrospector;
23 // import hunt.shiro.codec.Base64;
24 // import hunt.shiro.codec.Hex;
25 // import hunt.shiro.config.event.BeanEvent;
26 // import hunt.shiro.config.event.ConfiguredBeanEvent;
27 // import hunt.shiro.config.event.DestroyedBeanEvent;
28 // import hunt.shiro.config.event.InitializedBeanEvent;
29 // import hunt.shiro.config.event.InstantiatedBeanEvent;
30 import hunt.shiro.event.EventBus;
31 import hunt.shiro.event.EventBusAware;
32 import hunt.shiro.event.Subscribe;
33 import hunt.shiro.event.support.DefaultEventBus;
34 import hunt.shiro.Exceptions;
35 // import hunt.shiro.util.Assert;
36 import hunt.shiro.util.ByteSource;
37 // import hunt.shiro.util.ClassUtils;
38 // import hunt.shiro.util.Factory;
39 import hunt.shiro.util.LifecycleUtils;
40 import hunt.shiro.util.Common;
41 // import hunt.shiro.util.StringUtils;
42 // import org.slf4j.Logger;
43 // import org.slf4j.LoggerFactory;
44 
45 // import java.beans.PropertyDescriptor;
46 // import java.util.ArrayList;
47 // import java.util.Arrays;
48 // import java.util.Collection;
49 // import java.util.Collections;
50 // import java.util.LinkedHashMap;
51 // import java.util.LinkedHashSet;
52 // import java.util.List;
53 // import java.util.Map;
54 // import java.util.Set;
55 
56 import hunt.collection;
57 import hunt.Exceptions;
58 import hunt.logging.Logger;
59 import hunt.String;
60 
61 import std.traits;
62 import std.string;
63 
64 
65 /**
66  * Object builder that uses reflection and Apache Commons BeanUtils to build objects given a
67  * map of "property values".  Typically these come from the Shiro INI configuration and are used
68  * to construct or modify the SecurityManager, its dependencies, and web-based security filters.
69  * <p/>
70  * Recognizes {@link Factory} implementations and will call
71  * {@link hunt.shiro.util.Factory#getInstance() getInstance} to satisfy any reference to this bean.
72  *
73  * @since 0.9
74  */
75 class ReflectionBuilder {
76 
77     //TODO - complete JavaDoc
78 
79     private enum string OBJECT_REFERENCE_BEGIN_TOKEN = "$";
80     private enum string ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN = "\\$";
81     private enum string GLOBAL_PROPERTY_PREFIX = "shiro";
82     private enum char MAP_KEY_VALUE_DELIMITER = ':';
83     private enum string HEX_BEGIN_TOKEN = "0x";
84     private enum string NULL_VALUE_TOKEN = "null";
85     private enum string EMPTY_STRING_VALUE_TOKEN = "\"\"";
86     private enum char STRING_VALUE_DELIMETER = '"';
87     private enum char MAP_PROPERTY_BEGIN_TOKEN = '[';
88     private enum char MAP_PROPERTY_END_TOKEN = ']';
89 
90     private enum string EVENT_BUS_NAME = "eventBus";
91 
92     private Map!(string, Object) objects;
93 
94     /**
95      * Interpolation allows for ${key} substitution of values.
96      * @since 1.4
97      */
98     // private Interpolator interpolator;
99 
100     /**
101      * @since 1.3
102      */
103     private EventBus eventBus;
104     /**
105      * Keeps track of event subscribers that were automatically registered by this ReflectionBuilder during
106      * object construction.  This is used in case a new EventBus is discovered during object graph
107      * construction:  upon discovery of the new EventBus, the existing subscribers will be unregistered from the
108      * old EventBus and then re-registered with the new EventBus.
109      *
110      * @since 1.3
111      */
112     private Map!(string, Object) registeredEventSubscribers;
113 
114     /**
115      * @since 1.4
116      */
117     // private final BeanUtilsBean beanUtilsBean;
118 
119     this() {
120         this(null);
121     }
122 
123     this(Map!(string, Object) defaults) {
124 
125         // SHIRO-619
126         // beanUtilsBean = new BeanUtilsBean();
127         // beanUtilsBean.getPropertyUtils().addBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
128 
129         // this.interpolator = createInterpolator();
130 
131         this.objects = createDefaultObjectMap();
132         this.registeredEventSubscribers = new LinkedHashMap!(string, Object)();
133         apply(defaults);
134     }
135 
136     //@since 1.3
137     private Map!(string, Object) createDefaultObjectMap() {
138         Map!(string, Object) map = new LinkedHashMap!(string, Object)();
139         map.put(EVENT_BUS_NAME, new DefaultEventBus());
140         return map;
141     }
142 
143     private void apply(Map!(string, Object) objects) {
144         if(!isEmpty(objects)) {
145             this.objects.putAll(objects);
146         }
147         EventBus found = findEventBus(this.objects);
148         assert(found !is null, "An " ~ fullyQualifiedName!EventBus ~ 
149             " instance must be present in the object defaults");
150         enableEvents(found);
151     }
152 
153     Map!(string, Object) getObjects() {
154         return objects;
155     }
156 
157     /**
158      * @param objects
159      */
160     void setObjects(Map!(string, Object) objects) {
161         this.objects.clear();
162         this.objects.putAll(createDefaultObjectMap());
163         apply(objects);
164     }
165 
166     //@since 1.3
167     private void enableEvents(EventBus eventBus) {
168         assert(eventBus, "EventBus argument cannot be null.");
169         //clean up old auto-registered subscribers:
170         foreach(Object subscriber ; this.registeredEventSubscribers.values()) {
171             this.eventBus.unregister(subscriber);
172         }
173         this.registeredEventSubscribers.clear();
174 
175         this.eventBus = eventBus;
176 
177         foreach(string key, Object value; this.objects) {
178             enableEventsIfNecessary(value, key);
179         }
180     }
181 
182     //@since 1.3
183     private void enableEventsIfNecessary(Object bean, string name) {
184         bool applied = applyEventBusIfNecessary(bean);
185         if (!applied) {
186             //if the event bus is applied, and the bean wishes to be a subscriber as well (not just a publisher),
187             // we assume that the implementation registers itself with the event bus, i.e. eventBus.register(this);
188 
189             //if the event bus isn't applied, only then do we need to check to see if the bean is an event subscriber,
190             // and if so, register it on the event bus automatically since it has no ability to do so itself:
191             if (isEventSubscriber(bean, name)) {
192                 //found an event subscriber, so register them with the EventBus:
193                 this.eventBus.register(bean);
194                 this.registeredEventSubscribers.put(name, bean);
195             }
196         }
197     }
198 
199     //@since 1.3
200     private bool isEventSubscriber(Object bean, string name) {
201         // List annotatedMethods = ClassUtils.getAnnotatedMethods(bean.getClass(), Subscribe.class);
202         // return !isEmpty(annotatedMethods);
203         implementationMissing(false);
204         return false;
205     }
206 
207     //@since 1.3
208     protected EventBus findEventBus(Map!(string, Object) objects) {
209 
210         if (isEmpty(objects)) {
211             return null;
212         }
213 
214         //prefer a named object first:
215         EventBus value = cast(EventBus)objects.get(EVENT_BUS_NAME);
216         if (value !is null) {
217             return value;
218         }
219 
220         //couldn't find a named 'eventBus' EventBus object.  Try to find the first typed value we can:
221         foreach(Object v ; objects.values()) {
222             value = cast(EventBus)v;
223             if (value !is null) {
224                 return value;
225             }
226         }
227 
228         return null;
229     }
230 
231     private bool applyEventBusIfNecessary(Object value) {
232         EventBusAware eba = cast(EventBusAware)value;
233         if (eba !is null) {
234             eba.setEventBus(this.eventBus);
235             return true;
236         }
237         return false;
238     }
239 
240     Object getBean(string id) {
241         return objects.get(id);
242     }
243 
244     T getBean(T)(string id) {
245         // if (requiredType is null) {
246         //     throw new NullPointerException("requiredType argument cannot be null.");
247         // }
248         Object bean = objects.get(id);
249         if (bean is null) {
250             return null;
251         }
252         T v = cast(T) bean;
253         assert(v !is null,
254                 "Bean with id [" ~ id ~ "] is not of the required type [" ~ typeid(T).toString() ~ "].");
255         return cast(T) v;
256     }
257 
258     private string parseBeanId(string lhs) {
259         assert(lhs);
260         if (lhs.indexOf('.') < 0) {
261             return lhs;
262         }
263         string classSuffix = ".class";
264         int index = cast(int)lhs.indexOf(classSuffix);
265         if (index >= 0) {
266             return lhs[0 .. index];
267         }
268         return null;
269     }
270 
271     Map!(string, Object) buildObjects(Map!(string, string) kvPairs) {
272 
273         if (kvPairs !is null && !kvPairs.isEmpty()) {
274 
275             // BeanConfigurationProcessor processor = new BeanConfigurationProcessor();
276 
277             // for (Map.Entry<string, string> entry : kvPairs.entrySet()) {
278             //     string lhs = entry.getKey();
279             //     string rhs = interpolator.interpolate(entry.getValue());
280 
281             //     string beanId = parseBeanId(lhs);
282             //     if (beanId !is null) { //a beanId could be parsed, so the line is a bean instance definition
283             //         processor.add(new InstantiationStatement(beanId, rhs));
284             //     } else { //the line must be a property configuration
285             //         processor.add(new AssignmentStatement(lhs, rhs));
286             //     }
287             // }
288 
289             // processor.execute();
290             implementationMissing(false);
291         }
292 
293         //SHIRO-413: init method must be called for constructed objects that are Initializable
294         LifecycleUtils.init(objects.values());
295 
296         return objects;
297     }
298 
299     void destroy() {
300         Map!(string, Object) immutableObjects = objects;
301 
302         //destroy objects in the opposite order they were initialized:
303         List!(MapEntry!(string, Object)) entries = new ArrayList!(MapEntry!(string, Object))();
304         foreach(MapEntry!(string, Object) entry; objects) {
305             entries.add(entry);
306         }
307         // TODO: Tasks pending completion -@zxp at 5/13/2019, 6:24:35 PM
308         // 
309         // Collections.reverse(entries);
310 
311         foreach(MapEntry!(string, Object) v; entries) {
312             string id = v.getKey();
313             Object bean = v.getValue();
314             Object busObject = cast(Object)this.eventBus;
315             //don't destroy the eventbus until the end - we need it to still be 'alive' while publishing destroy events:
316             if (bean == busObject) { //memory equality check (not .equals) on purpose
317                 LifecycleUtils.destroy(bean);
318                 implementationMissing(false);
319                 // BeanEvent event = new DestroyedBeanEvent(id, bean, immutableObjects);
320                 // eventBus.publish(event);
321                 this.eventBus.unregister(bean); //bean is now destroyed - it should not receive any other events
322             }
323         }
324         //only now destroy the event bus:
325         LifecycleUtils.destroy(cast(Object)this.eventBus);
326     }
327 
328     protected void createNewInstance(Map!(string, Object) objects, string name, string value) {
329 
330         Object currentInstance = objects.get(name);
331         if (currentInstance !is null) {
332             infof("An instance with name '%s' already exists.  " ~
333                     "Redefining this object as a new instance of type %s", name, value);
334         }
335 
336         Object instance;//name with no property, assume right hand side of equals sign is the class name:
337         try {
338             instance = Object.factory(value); // ClassUtils.newInstance(value);
339             Nameable n = cast(Nameable)instance;
340             if (n !is null) {
341                 n.setName(name);
342             }
343         } catch (Exception e) {
344             string msg = "Unable to instantiate class [" ~ value ~ "] for object named '" ~ name ~ "'.  " ~
345                     "Please ensure you've specified the fully qualified class name correctly.";
346             throw new ConfigurationException(msg, e);
347         }
348         objects.put(name, instance);
349     }
350 
351     protected void applyProperty(string key, string value, Map!(string, Object) objects) {
352 
353         int index = cast(int)key.indexOf('.');
354 
355         if (index >= 0) {
356             string name = key[0 .. index];
357             string property = key[index + 1 .. $];
358 
359             if (icmp(GLOBAL_PROPERTY_PREFIX, name) == 0) {
360                 applyGlobalProperty(objects, property, value);
361             } else {
362                 applySingleProperty(objects, name, property, value);
363             }
364 
365         } else {
366             throw new IllegalArgumentException("All property keys must contain a '.' character. " ~
367                     "(e.g. myBean.property = value)  These should already be separated out by buildObjects().");
368         }
369     }
370 
371     protected void applyGlobalProperty(Map!(string, Object) objects, string property, string value) {
372                 implementationMissing(false);
373         foreach (Object instance ; objects.byValue) {
374             try {
375                 // PropertyDescriptor pd = beanUtilsBean.getPropertyUtils().getPropertyDescriptor(instance, property);
376                 // if (pd !is null) {
377                 //     applyProperty(instance, property, value);
378                 // }
379             } catch (Exception e) {
380                 string msg = "Error retrieving property descriptor for instance " ~
381                         "of type [" ~ typeid(instance).name ~ "] " ~
382                         "while setting property [" ~ property ~ "]";
383                 throw new ConfigurationException(msg, e);
384             }
385         }
386     }
387 
388     protected void applySingleProperty(Map!(string, Object) objects, string name, string property, string value) {
389         Object instance = objects.get(name);
390         // if (property.equals("class")) {
391         //     throw new IllegalArgumentException("Property keys should not contain 'class' properties since these " ~
392         //             "should already be separated out by buildObjects().");
393 
394         // } else if (instance is null) {
395         //     string msg = "Configuration error.  Specified object [" ~ name ~ "] with property [" ~
396         //             property ~ "] without first defining that object's class.  Please first " ~
397         //             "specify the class property first, e.g. myObject = fully_qualified_class_name " ~
398         //             "and then define additional properties.";
399         //     throw new IllegalArgumentException(msg);
400 
401         // } else {
402         //     applyProperty(instance, property, value);
403         // }
404         implementationMissing(false);
405     }
406 
407     protected bool isReference(string value) {
408         return value !is null && value.startsWith(OBJECT_REFERENCE_BEGIN_TOKEN);
409     }
410 
411     protected string getId(string referenceToken) {
412         return referenceToken[0 .. OBJECT_REFERENCE_BEGIN_TOKEN.length];
413     }
414 
415     protected Object getReferencedObject(string id) {
416         Object o = objects !is null && !objects.isEmpty() ? objects.get(id) : null;
417         if (o is null) {
418             string msg = "The object with id [" ~ id ~ "] has not yet been defined and therefore cannot be " ~
419                     "referenced.  Please ensure objects are defined in the order in which they should be " ~
420                     "created and made available for future reference.";
421             throw new UnresolveableReferenceException(msg);
422         }
423         return o;
424     }
425 
426     protected string unescapeIfNecessary(string value) {
427         if (value !is null && value.startsWith(ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN)) {
428             return value[0 .. ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN.length - 1];
429         }
430         return value;
431     }
432 
433     protected Object resolveReference(string reference) {
434         string id = getId(reference);
435         tracef("Encountered object reference '%s'.  Looking up object with id '%s'", reference, id);
436         Object referencedObject = getReferencedObject(id);
437         // if (referencedObject instanceof Factory) {
438         //     return ((Factory) referencedObject).getInstance();
439         // }
440         // return referencedObject;
441         implementationMissing(false);
442         return null;
443     }
444 
445     protected bool isTypedProperty(Object object, string propertyName, TypeInfo_Class clazz) {
446         if (clazz is null) {
447             throw new NullPointerException("type (class) argument cannot be null.");
448         }
449 
450         implementationMissing(false);
451         return false;
452         // try {
453         //     PropertyDescriptor descriptor = beanUtilsBean.getPropertyUtils().getPropertyDescriptor(object, propertyName);
454         //     if (descriptor is null) {
455         //         string msg = "Property '" ~ propertyName ~ "' does not exist for object of " ~
456         //                 "type " ~ object.getClass().getName() ~ ".";
457         //         throw new ConfigurationException(msg);
458         //     }
459         //     TypeInfo_Class propertyClazz = descriptor.getPropertyType();
460         //     return clazz.isAssignableFrom(propertyClazz);
461         // } catch (ConfigurationException ce) {
462         //     //let it propagate:
463         //     throw ce;
464         // } catch (Exception e) {
465         //     string msg = "Unable to determine if property [" ~ propertyName ~ "] represents a " ~ clazz.getName();
466         //     throw new ConfigurationException(msg, e);
467         // }
468     }
469 
470     // protected Set<?> toSet(string sValue) {
471     //     string[] tokens = StringUtils.split(sValue);
472     //     if (tokens is null || tokens.length <= 0) {
473     //         return null;
474     //     }
475 
476     //     //SHIRO-423: check to see if the value is a referenced Set already, and if so, return it immediately:
477     //     if (tokens.length == 1 && isReference(tokens[0])) {
478     //         Object reference = resolveReference(tokens[0]);
479     //         if (reference instanceof Set) {
480     //             return (Set)reference;
481     //         }
482     //     }
483 
484     //     Set<string> setTokens = new LinkedHashSet<string>(Arrays.asList(tokens));
485 
486     //     //now convert into correct values and/or references:
487     //     Set<Object> values = new LinkedHashSet<Object>(setTokens.size());
488     //     for (string token : setTokens) {
489     //         Object value = resolveValue(token);
490     //         values.add(value);
491     //     }
492     //     return values;
493     // }
494 
495     // protected Map<?, ?> toMap(string sValue) {
496     //     string[] tokens = StringUtils.split(sValue, StringUtils.DEFAULT_DELIMITER_CHAR,
497     //             StringUtils.DEFAULT_QUOTE_CHAR, StringUtils.DEFAULT_QUOTE_CHAR, true, true);
498     //     if (tokens is null || tokens.length <= 0) {
499     //         return null;
500     //     }
501 
502     //     //SHIRO-423: check to see if the value is a referenced Map already, and if so, return it immediately:
503     //     if (tokens.length == 1 && isReference(tokens[0])) {
504     //         Object reference = resolveReference(tokens[0]);
505     //         if (reference instanceof Map) {
506     //             return (Map)reference;
507     //         }
508     //     }
509 
510     //     Map<string, string> mapTokens = new LinkedHashMap<string, string>(tokens.length);
511     //     for (string token : tokens) {
512     //         string[] kvPair = StringUtils.split(token, MAP_KEY_VALUE_DELIMITER);
513     //         if (kvPair is null || kvPair.length != 2) {
514     //             string msg = "Map property value [" ~ sValue ~ "] contained key-value pair token [" ~
515     //                     token ~ "] that does not properly split to a single key and pair.  This must be the " ~
516     //                     "case for all map entries.";
517     //             throw new ConfigurationException(msg);
518     //         }
519     //         mapTokens.put(kvPair[0], kvPair[1]);
520     //     }
521 
522     //     //now convert into correct values and/or references:
523     //     Map<Object, Object> map = new LinkedHashMap<Object, Object>(mapTokens.size());
524     //     for (Map.Entry<string, string> entry : mapTokens.entrySet()) {
525     //         Object key = resolveValue(entry.getKey());
526     //         Object value = resolveValue(entry.getValue());
527     //         map.put(key, value);
528     //     }
529     //     return map;
530     // }
531 
532     // // @since 1.2.2
533     // protected Collection<?> toCollection(string sValue) {
534 
535     //     string[] tokens = StringUtils.split(sValue);
536     //     if (tokens is null || tokens.length <= 0) {
537     //         return null;
538     //     }
539 
540     //     //SHIRO-423: check to see if the value is a referenced Collection already, and if so, return it immediately:
541     //     if (tokens.length == 1 && isReference(tokens[0])) {
542     //         Object reference = resolveReference(tokens[0]);
543     //         if (reference instanceof Collection) {
544     //             return (Collection)reference;
545     //         }
546     //     }
547 
548     //     //now convert into correct values and/or references:
549     //     List<Object> values = new ArrayList<Object>(tokens.length);
550     //     for (string token : tokens) {
551     //         Object value = resolveValue(token);
552     //         values.add(value);
553     //     }
554     //     return values;
555     // }
556 
557     // protected List<?> toList(string sValue) {
558     //     string[] tokens = StringUtils.split(sValue);
559     //     if (tokens is null || tokens.length <= 0) {
560     //         return null;
561     //     }
562 
563     //     //SHIRO-423: check to see if the value is a referenced List already, and if so, return it immediately:
564     //     if (tokens.length == 1 && isReference(tokens[0])) {
565     //         Object reference = resolveReference(tokens[0]);
566     //         if (reference instanceof List) {
567     //             return (List)reference;
568     //         }
569     //     }
570 
571     //     //now convert into correct values and/or references:
572     //     List<Object> values = new ArrayList<Object>(tokens.length);
573     //     for (string token : tokens) {
574     //         Object value = resolveValue(token);
575     //         values.add(value);
576     //     }
577     //     return values;
578     // }
579 
580     // protected byte[] toBytes(string sValue) {
581     //     if (sValue is null) {
582     //         return null;
583     //     }
584     //     byte[] bytes;
585     //     if (sValue.startsWith(HEX_BEGIN_TOKEN)) {
586     //         string hex = sValue.substring(HEX_BEGIN_TOKEN.length());
587     //         bytes = Hex.decode(hex);
588     //     } else {
589     //         //assume base64 encoded:
590     //         bytes = Base64.decode(sValue);
591     //     }
592     //     return bytes;
593     // }
594 
595     protected Object resolveValue(string stringValue) {
596         Object value;
597         if (isReference(stringValue)) {
598             value = resolveReference(stringValue);
599         } else {
600             value = new String(unescapeIfNecessary(stringValue));
601         }
602         return value;
603     }
604 
605     protected string checkForNullOrEmptyLiteral(string stringValue) {
606         if (stringValue is null) {
607             return null;
608         }
609         //check if the value is the actual literal string 'null' (expected to be wrapped in quotes):
610         if (stringValue == ("\"null\"")) {
611             return NULL_VALUE_TOKEN;
612         }
613         //or the actual literal string of two quotes '""' (expected to be wrapped in quotes):
614         else if (stringValue == ("\"\"\"\"")) {
615             return EMPTY_STRING_VALUE_TOKEN;
616         } else {
617             return stringValue;
618         }
619     }
620     
621     protected void applyProperty(Object object, string propertyPath, Object value) {
622         implementationMissing(false);
623 
624         // int mapBegin = propertyPath.indexOf(MAP_PROPERTY_BEGIN_TOKEN);
625         // int mapEnd = -1;
626         // string mapPropertyPath = null;
627         // string keyString = null;
628 
629         // string remaining = null;
630         
631         // if (mapBegin >= 0) {
632         //     //a map is being referenced in the overall property path.  Find just the map's path:
633         //     mapPropertyPath = propertyPath.substring(0, mapBegin);
634         //     //find the end of the map reference:
635         //     mapEnd = propertyPath.indexOf(MAP_PROPERTY_END_TOKEN, mapBegin);
636         //     //find the token in between the [ and the ] (the map/array key or index):
637         //     keyString = propertyPath.substring(mapBegin+1, mapEnd);
638 
639         //     //find out if there is more path reference to follow.  If not, we're at a terminal of the OGNL expression
640         //     if (propertyPath.length() > (mapEnd+1)) {
641         //         remaining = propertyPath.substring(mapEnd+1);
642         //         if (remaining.startsWith(".")) {
643         //             remaining = StringUtils.clean(remaining.substring(1));
644         //         }
645         //     }
646         // }
647         
648         // if (remaining is null) {
649         //     //we've terminated the OGNL expression.  Check to see if we're assigning a property or a map entry:
650         //     if (keyString is null) {
651         //         //not a map or array value assignment - assign the property directly:
652         //         setProperty(object, propertyPath, value);
653         //     } else {
654         //         //we're assigning a map or array entry.  Check to see which we should call:
655         //         if (isTypedProperty(object, mapPropertyPath, Map.class)) {
656         //             Map map = (Map)getProperty(object, mapPropertyPath);
657         //             Object mapKey = resolveValue(keyString);
658         //             //noinspection unchecked
659         //             map.put(mapKey, value);
660         //         } else {
661         //             //must be an array property.  Convert the key string to an index:
662         //             int index = Integer.valueOf(keyString);
663         //             setIndexedProperty(object, mapPropertyPath, index, value);
664         //         }
665         //     }
666         // } else {
667         //     //property is being referenced as part of a nested path.  Find the referenced map/array entry and
668         //     //recursively call this method with the remaining property path
669         //     Object referencedValue = null;
670         //     if (isTypedProperty(object, mapPropertyPath, Map.class)) {
671         //         Map map = (Map)getProperty(object, mapPropertyPath);
672         //         Object mapKey = resolveValue(keyString);
673         //         referencedValue = map.get(mapKey);
674         //     } else {
675         //         //must be an array property:
676         //         int index = Integer.valueOf(keyString);
677         //         referencedValue = getIndexedProperty(object, mapPropertyPath, index);
678         //     }
679 
680         //     if (referencedValue is null) {
681         //         throw new ConfigurationException("Referenced map/array value '" ~ mapPropertyPath ~ "[" ~
682         //         keyString ~ "]' does not exist.");
683         //     }
684 
685         //     applyProperty(referencedValue, remaining, value);
686         // }
687     }
688     
689     private void setProperty(Object object, string propertyPath, Object value) {
690         // try {
691         //     if (log.isTraceEnabled()) {
692         //         log.trace("Applying property [{}] value [{}] on object of type [{}]",
693         //                 new Object[]{propertyPath, value, object.getClass().getName()});
694         //     }
695         //     beanUtilsBean.setProperty(object, propertyPath, value);
696         // } catch (Exception e) {
697         //     string msg = "Unable to set property '" ~ propertyPath ~ "' with value [" ~ value ~ "] on object " ~
698         //             "of type " ~ (object !is null ? object.getClass().getName() : null) ~ ".  If " ~
699         //             "'" ~ value ~ "' is a reference to another (previously defined) object, prefix it with " ~
700         //             "'" ~ OBJECT_REFERENCE_BEGIN_TOKEN ~ "' to indicate that the referenced " ~
701         //             "object should be used as the actual value.  " ~
702         //             "For example, " ~ OBJECT_REFERENCE_BEGIN_TOKEN + value;
703         //     throw new ConfigurationException(msg, e);
704         // }
705         
706         implementationMissing(false);
707     }
708     
709     private Object getProperty(Object object, string propertyPath) {
710         implementationMissing(false);
711         return null;
712         // try {
713         //     return beanUtilsBean.getPropertyUtils().getProperty(object, propertyPath);
714         // } catch (Exception e) {
715         //     throw new ConfigurationException("Unable to access property '" ~ propertyPath ~ "'", e);
716         // }
717     }
718     
719     private void setIndexedProperty(Object object, string propertyPath, int index, Object value) {
720         implementationMissing(false);
721         // try {
722         //     beanUtilsBean.getPropertyUtils().setIndexedProperty(object, propertyPath, index, value);
723         // } catch (Exception e) {
724         //     throw new ConfigurationException("Unable to set array property '" ~ propertyPath ~ "'", e);
725         // }
726     }
727     
728     private Object getIndexedProperty(Object object, string propertyPath, int index) {
729         
730         implementationMissing(false);
731         return null;
732         // try {
733         //     return beanUtilsBean.getPropertyUtils().getIndexedProperty(object, propertyPath, index);
734         // } catch (Exception e) {
735         //     throw new ConfigurationException("Unable to acquire array property '" ~ propertyPath ~ "'", e);
736         // }
737     }
738     
739     protected bool isIndexedPropertyAssignment(string propertyPath) {
740         return propertyPath.endsWith("" ~ MAP_PROPERTY_END_TOKEN);
741     }
742 
743     protected void applyProperty(Object object, string propertyName, string stringValue) {
744 
745         // Object value;
746 
747         // if (NULL_VALUE_TOKEN.equals(stringValue)) {
748         //     value = null;
749         // } else if (EMPTY_STRING_VALUE_TOKEN.equals(stringValue)) {
750         //     value = StringUtils.EMPTY_STRING;
751         // } else if (isIndexedPropertyAssignment(propertyName)) {
752         //     string checked = checkForNullOrEmptyLiteral(stringValue);
753         //     value = resolveValue(checked);
754         // } else if (isTypedProperty(object, propertyName, Set.class)) {
755         //     value = toSet(stringValue);
756         // } else if (isTypedProperty(object, propertyName, Map.class)) {
757         //     value = toMap(stringValue);
758         // } else if (isTypedProperty(object, propertyName, List.class)) {
759         //     value = toList(stringValue);
760         // } else if (isTypedProperty(object, propertyName, Collection.class)) {
761         //     value = toCollection(stringValue);
762         // } else if (isTypedProperty(object, propertyName, byte[].class)) {
763         //     value = toBytes(stringValue);
764         // } else if (isTypedProperty(object, propertyName, ByteSource.class)) {
765         //     byte[] bytes = toBytes(stringValue);
766         //     value = ByteSource.Util.bytes(bytes);
767         // } else {
768         //     string checked = checkForNullOrEmptyLiteral(stringValue);
769         //     value = resolveValue(checked);
770         // }
771 
772         // applyProperty(object, propertyName, value);
773         
774         implementationMissing(false);
775     }
776 
777     // private Interpolator createInterpolator() {
778 
779     //     if (ClassUtils.isAvailable("hunt.commons.configuration2.interpol.ConfigurationInterpolator")) {
780     //         return new CommonsInterpolator();
781     //     }
782 
783     //     return new DefaultInterpolator();
784     // }
785 
786     /**
787      * Sets the {@link Interpolator} used when evaluating the right side of the expressions.
788      * @since 1.4
789      */
790     // void setInterpolator(Interpolator interpolator) {
791     //     this.interpolator = interpolator;
792     // }
793 
794     // private class BeanConfigurationProcessor {
795 
796     //     private final List<Statement> statements = new ArrayList<Statement>();
797     //     private final List<BeanConfiguration> beanConfigurations = new ArrayList<BeanConfiguration>();
798 
799     //     void add(Statement statement) {
800 
801     //         statements.add(statement); //we execute bean configuration statements in the order they are declared.
802 
803     //         if (statement instanceof InstantiationStatement) {
804     //             InstantiationStatement is = (InstantiationStatement)statement;
805     //             beanConfigurations.add(new BeanConfiguration(is));
806     //         } else {
807     //             AssignmentStatement as = (AssignmentStatement)statement;
808     //             //statements always apply to the most recently defined bean configuration with the same name, so we
809     //             //have to traverse the configuration list starting at the end (most recent elements are appended):
810     //             bool addedToConfig = false;
811     //             string beanName = as.getRootBeanName();
812     //             for( int i = beanConfigurations.size()-1; i >= 0; i--) {
813     //                 BeanConfiguration mostRecent = beanConfigurations.get(i);
814     //                 string mostRecentBeanName = mostRecent.getBeanName();
815     //                 if (beanName.equals(mostRecentBeanName)) {
816     //                     mostRecent.add(as);
817     //                     addedToConfig = true;
818     //                     break;
819     //                 }
820     //             }
821 
822     //             if (!addedToConfig) {
823     //                 // the AssignmentStatement must be for an existing bean that does not yet have a corresponding
824     //                 // configuration object (this would happen if the bean is in the default objects map). Because
825     //                 // BeanConfiguration instances don't exist for default (already instantiated) beans,
826     //                 // we simulate a creation of one to satisfy this processors implementation:
827     //                 beanConfigurations.add(new BeanConfiguration(as));
828     //             }
829     //         }
830     //     }
831 
832     //     void execute() {
833 
834     //         for( Statement statement : statements) {
835 
836     //             statement.execute();
837 
838     //             BeanConfiguration bd = statement.getBeanConfiguration();
839 
840     //             if (bd.isExecuted()) { //bean is fully configured, no more statements to execute for it:
841 
842     //                 //bean configured overrides the 'eventBus' bean - replace the existing eventBus with the one configured:
843     //                 if (bd.getBeanName().equals(EVENT_BUS_NAME)) {
844     //                     EventBus eventBus = (EventBus)bd.getBean();
845     //                     enableEvents(eventBus);
846     //                 }
847 
848     //                 //ignore global 'shiro.' shortcut mechanism:
849     //                 if (!bd.isGlobalConfig()) {
850     //                     BeanEvent event = new ConfiguredBeanEvent(bd.getBeanName(), bd.getBean(),
851     //                             Collections.unmodifiableMap(objects));
852     //                     eventBus.publish(event);
853     //                 }
854 
855     //                 //initialize the bean if necessary:
856     //                 LifecycleUtils.init(bd.getBean());
857 
858     //                 //ignore global 'shiro.' shortcut mechanism:
859     //                 if (!bd.isGlobalConfig()) {
860     //                     BeanEvent event = new InitializedBeanEvent(bd.getBeanName(), bd.getBean(),
861     //                             Collections.unmodifiableMap(objects));
862     //                     eventBus.publish(event);
863     //                 }
864     //             }
865     //         }
866     //     }
867     // }
868 
869     // private class BeanConfiguration {
870 
871     //     private final InstantiationStatement instantiationStatement;
872     //     private final List<AssignmentStatement> assignments = new ArrayList<AssignmentStatement>();
873     //     private final string beanName;
874     //     private Object bean;
875 
876     //     private BeanConfiguration(InstantiationStatement statement) {
877     //         statement.setBeanConfiguration(this);
878     //         this.instantiationStatement = statement;
879     //         this.beanName = statement.lhs;
880     //     }
881 
882     //     private BeanConfiguration(AssignmentStatement as) {
883     //         this.instantiationStatement = null;
884     //         this.beanName = as.getRootBeanName();
885     //         add(as);
886     //     }
887 
888     //     string getBeanName() {
889     //         return this.beanName;
890     //     }
891 
892     //     bool isGlobalConfig() { //BeanConfiguration instance representing the global 'shiro.' properties
893     //         // (we should remove this concept).
894     //         return GLOBAL_PROPERTY_PREFIX.equals(getBeanName());
895     //     }
896 
897     //     void add(AssignmentStatement as) {
898     //         as.setBeanConfiguration(this);
899     //         assignments.add(as);
900     //     }
901 
902     //     /**
903     //      * When this configuration is parsed sufficiently to create (or find) an actual bean instance, that instance
904     //      * will be associated with its configuration by setting it via this method.
905     //      *
906     //      * @param bean the bean instantiated (or found) that corresponds to this BeanConfiguration instance.
907     //      */
908     //     void setBean(Object bean) {
909     //         this.bean = bean;
910     //     }
911 
912     //     Object getBean() {
913     //         return this.bean;
914     //     }
915 
916     //     /**
917     //      * Returns true if all configuration statements have been executed.
918     //      * @return true if all configuration statements have been executed.
919     //      */
920     //     bool isExecuted() {
921     //         if (instantiationStatement !is null && !instantiationStatement.isExecuted()) {
922     //             return false;
923     //         }
924     //         for (AssignmentStatement as : assignments) {
925     //             if (!as.isExecuted()) {
926     //                 return false;
927     //             }
928     //         }
929     //         return true;
930     //     }
931     // }
932 
933     // private abstract class Statement {
934 
935     //     protected final string lhs;
936     //     protected final string rhs;
937     //     protected Object bean;
938     //     private Object result;
939     //     private bool executed;
940     //     private BeanConfiguration beanConfiguration;
941 
942     //     private Statement(string lhs, string rhs) {
943     //         this.lhs = lhs;
944     //         this.rhs = rhs;
945     //         this.executed = false;
946     //     }
947 
948     //     void setBeanConfiguration(BeanConfiguration bd) {
949     //         this.beanConfiguration = bd;
950     //     }
951 
952     //     BeanConfiguration getBeanConfiguration() {
953     //         return this.beanConfiguration;
954     //     }
955 
956     //     Object execute() {
957     //         if (!isExecuted()) {
958     //             this.result = doExecute();
959     //             this.executed = true;
960     //         }
961     //         if (!getBeanConfiguration().isGlobalConfig()) {
962     //             assert(this.bean, "Implementation must set the root bean for which it executed.");
963     //         }
964     //         return this.result;
965     //     }
966 
967     //     Object getBean() {
968     //         return this.bean;
969     //     }
970 
971     //     protected void setBean(Object bean) {
972     //         this.bean = bean;
973     //         if (this.beanConfiguration.getBean() is null) {
974     //             this.beanConfiguration.setBean(bean);
975     //         }
976     //     }
977 
978     //     Object getResult() {
979     //         return result;
980     //     }
981 
982     //     protected abstract Object doExecute();
983 
984     //     bool isExecuted() {
985     //         return executed;
986     //     }
987     // }
988 
989     // private class InstantiationStatement : Statement {
990 
991     //     private InstantiationStatement(string lhs, string rhs) {
992     //         super(lhs, rhs);
993     //     }
994 
995     //     override
996     //     protected Object doExecute() {
997     //         string beanName = this.lhs;
998     //         createNewInstance(objects, beanName, this.rhs);
999     //         Object instantiated = objects.get(beanName);
1000     //         setBean(instantiated);
1001 
1002     //         //also ensure the instantiated bean has access to the event bus or is subscribed to events if necessary:
1003     //         //Note: because events are being enabled on this bean here (before the instantiated event below is
1004     //         //triggered), beans can react to their own instantiation events.
1005     //         enableEventsIfNecessary(instantiated, beanName);
1006 
1007     //         BeanEvent event = new InstantiatedBeanEvent(beanName, instantiated, Collections.unmodifiableMap(objects));
1008     //         eventBus.publish(event);
1009 
1010     //         return instantiated;
1011     //     }
1012     // }
1013 
1014     // private class AssignmentStatement extends Statement {
1015 
1016     //     private final string rootBeanName;
1017 
1018     //     private AssignmentStatement(string lhs, string rhs) {
1019     //         super(lhs, rhs);
1020     //         int index = lhs.indexOf('.');
1021     //         this.rootBeanName = lhs.substring(0, index);
1022     //     }
1023 
1024     //     override
1025     //     protected Object doExecute() {
1026     //         applyProperty(lhs, rhs, objects);
1027     //         Object bean = objects.get(this.rootBeanName);
1028     //         setBean(bean);
1029     //         return null;
1030     //     }
1031 
1032     //     string getRootBeanName() {
1033     //         return this.rootBeanName;
1034     //     }
1035     // }
1036 
1037     //////////////////////////
1038     // From CollectionUtils //
1039     //////////////////////////
1040     // CollectionUtils cannot be removed from shiro-core until 2.0 as it has a dependency on PrincipalCollection
1041 
1042     private static bool isEmpty(K, V)(Map!(K, V) m) {
1043         return m is null || m.isEmpty();
1044     }
1045 
1046     private static bool isEmpty(E)(Collection!E c) {
1047         return c is null || c.isEmpty();
1048     }
1049 
1050 }