1 /*
2  * Copyright 2006-2013 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.springframework.classify;
17
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.ConcurrentMap;
22
23 /**
24  * A {@link Classifier} for a parameterised object type based on a map. Classifies objects
25  * according to their inheritance relation with the supplied type map. If the object to be
26  * classified is one of the keys of the provided map, or is a subclass of one of the keys,
27  * then the map entry value for that key is returned. Otherwise returns the default value
28  * which is null by default.
29  *
30  * @author Dave Syer
31  * @author Gary Russell
32  * @param <T> the type of the thing to classify
33  * @param <C> the output of the classifier
34  */

35 @SuppressWarnings("serial")
36 public class SubclassClassifier<T, C> implements Classifier<T, C> {
37
38     private ConcurrentMap<Class<? extends T>, C> classified = new ConcurrentHashMap<Class<? extends T>, C>();
39
40     private C defaultValue = null;
41
42     /**
43      * Create a {@link SubclassClassifier} with null default value.
44      *
45      */

46     public SubclassClassifier() {
47         this(null);
48     }
49
50     /**
51      * Create a {@link SubclassClassifier} with supplied default value.
52      * @param defaultValue the default value
53      */

54     public SubclassClassifier(C defaultValue) {
55         this(new HashMap<Class<? extends T>, C>(), defaultValue);
56     }
57
58     /**
59      * Create a {@link SubclassClassifier} with supplied default value.
60      * @param defaultValue the default value
61      * @param typeMap the map of types
62      */

63     public SubclassClassifier(Map<Class<? extends T>, C> typeMap, C defaultValue) {
64         super();
65         this.classified = new ConcurrentHashMap<Class<? extends T>, C>(typeMap);
66         this.defaultValue = defaultValue;
67     }
68
69     /**
70      * Public setter for the default value for mapping keys that are not found in the map
71      * (or their subclasses). Defaults to false.
72      * @param defaultValue the default value to set
73      */

74     public void setDefaultValue(C defaultValue) {
75         this.defaultValue = defaultValue;
76     }
77
78     /**
79      * Set the classifications up as a map. The keys are types and these will be mapped
80      * along with all their subclasses to the corresponding value. The most specific types
81      * will match first.
82      * @param map a map from type to class
83      */

84     public void setTypeMap(Map<Class<? extends T>, C> map) {
85         this.classified = new ConcurrentHashMap<Class<? extends T>, C>(map);
86     }
87
88     /**
89      * Return the value from the type map whose key is the class of the given Throwable,
90      * or its nearest ancestor if a subclass.
91      * @return C the classified value
92      * @param classifiable the classifiable thing
93      */

94     @Override
95     public C classify(T classifiable) {
96
97         if (classifiable == null) {
98             return this.defaultValue;
99         }
100
101         @SuppressWarnings("unchecked")
102         Class<? extends T> exceptionClass = (Class<? extends T>) classifiable.getClass();
103         if (this.classified.containsKey(exceptionClass)) {
104             return this.classified.get(exceptionClass);
105         }
106
107         // check for subclasses
108         C value = null;
109         for (Class<?> cls = exceptionClass; !cls.equals(Object.class)
110                 && value == null; cls = cls.getSuperclass()) {
111             value = this.classified.get(cls);
112         }
113
114         // check for interfaces subclasses
115         if (value == null) {
116             for (Class<?> cls = exceptionClass; !cls.equals(Object.class)
117                     && value == null; cls = cls.getSuperclass()) {
118                 for (Class<?> ifc : cls.getInterfaces()) {
119                     value = this.classified.get(ifc);
120                     if (value != null) {
121                         break;
122                     }
123                 }
124             }
125         }
126
127         // ConcurrentHashMap doesn't allow nulls
128         if (value != null) {
129             this.classified.put(exceptionClass, value);
130         }
131
132         if (value == null) {
133             value = this.defaultValue;
134         }
135
136         return value;
137     }
138
139     /**
140      * Return the default value supplied in the constructor (default false).
141      * @return C the default value
142      */

143     final public C getDefault() {
144         return this.defaultValue;
145     }
146
147     protected Map<Class<? extends T>, C> getClassified() {
148         return this.classified;
149     }
150
151 }
152