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