1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.fourthline.cling.binding.annotations;
17
18 import org.fourthline.cling.binding.AllowedValueProvider;
19 import org.fourthline.cling.binding.AllowedValueRangeProvider;
20 import org.fourthline.cling.binding.LocalServiceBindingException;
21 import org.fourthline.cling.model.ModelUtil;
22 import org.fourthline.cling.model.meta.StateVariable;
23 import org.fourthline.cling.model.meta.StateVariableAllowedValueRange;
24 import org.fourthline.cling.model.meta.StateVariableEventDetails;
25 import org.fourthline.cling.model.meta.StateVariableTypeDetails;
26 import org.fourthline.cling.model.state.StateVariableAccessor;
27 import org.fourthline.cling.model.types.Datatype;
28
29 import java.util.Set;
30 import java.util.logging.Logger;
31
32
33
34
35 public class AnnotationStateVariableBinder {
36
37 private static Logger log = Logger.getLogger(AnnotationLocalServiceBinder.class.getName());
38
39 protected UpnpStateVariable annotation;
40 protected String name;
41 protected StateVariableAccessor accessor;
42 protected Set<Class> stringConvertibleTypes;
43
44 public AnnotationStateVariableBinder(UpnpStateVariable annotation, String name,
45 StateVariableAccessor accessor, Set<Class> stringConvertibleTypes) {
46 this.annotation = annotation;
47 this.name = name;
48 this.accessor = accessor;
49 this.stringConvertibleTypes = stringConvertibleTypes;
50 }
51
52 public UpnpStateVariable getAnnotation() {
53 return annotation;
54 }
55
56 public String getName() {
57 return name;
58 }
59
60 public StateVariableAccessor getAccessor() {
61 return accessor;
62 }
63
64 public Set<Class> getStringConvertibleTypes() {
65 return stringConvertibleTypes;
66 }
67
68 protected StateVariable createStateVariable() throws LocalServiceBindingException {
69
70 log.fine("Creating state variable '" + getName() + "' with accessor: " + getAccessor());
71
72
73 Datatype datatype = createDatatype();
74
75
76 String defaultValue = createDefaultValue(datatype);
77
78
79 String[] allowedValues = null;
80 if (Datatype.Builtin.STRING.equals(datatype.getBuiltin())) {
81
82 if (getAnnotation().allowedValueProvider() != void.class) {
83 allowedValues = getAllowedValuesFromProvider();
84 } else if (getAnnotation().allowedValues().length > 0) {
85 allowedValues = getAnnotation().allowedValues();
86 } else if (getAnnotation().allowedValuesEnum() != void.class) {
87 allowedValues = getAllowedValues(getAnnotation().allowedValuesEnum());
88 } else if (getAccessor() != null && getAccessor().getReturnType().isEnum()) {
89 allowedValues = getAllowedValues(getAccessor().getReturnType());
90 } else {
91 log.finer("Not restricting allowed values (of string typed state var): " + getName());
92 }
93
94 if (allowedValues != null && defaultValue != null) {
95
96
97 boolean foundValue = false;
98 for (String s : allowedValues) {
99 if (s.equals(defaultValue)) {
100 foundValue = true;
101 break;
102 }
103 }
104 if (!foundValue) {
105 throw new LocalServiceBindingException(
106 "Default value '" + defaultValue + "' is not in allowed values of: " + getName()
107 );
108 }
109 }
110 }
111
112
113 StateVariableAllowedValueRange allowedValueRange = null;
114 if (Datatype.Builtin.isNumeric(datatype.getBuiltin())) {
115
116 if (getAnnotation().allowedValueRangeProvider() != void.class) {
117 allowedValueRange = getAllowedRangeFromProvider();
118 } else if (getAnnotation().allowedValueMinimum() > 0 || getAnnotation().allowedValueMaximum() > 0) {
119 allowedValueRange = getAllowedValueRange(
120 getAnnotation().allowedValueMinimum(),
121 getAnnotation().allowedValueMaximum(),
122 getAnnotation().allowedValueStep()
123 );
124 } else {
125 log.finer("Not restricting allowed value range (of numeric typed state var): " + getName());
126 }
127
128
129 if (defaultValue != null && allowedValueRange != null) {
130
131 long v;
132 try {
133 v = Long.valueOf(defaultValue);
134 } catch (Exception ex) {
135 throw new LocalServiceBindingException(
136 "Default value '" + defaultValue + "' is not numeric (for range checking) of: " + getName()
137 );
138 }
139
140 if (!allowedValueRange.isInRange(v)) {
141 throw new LocalServiceBindingException(
142 "Default value '" + defaultValue + "' is not in allowed range of: " + getName()
143 );
144 }
145 }
146 }
147
148
149 boolean sendEvents = getAnnotation().sendEvents();
150 if (sendEvents && getAccessor() == null) {
151 throw new LocalServiceBindingException(
152 "State variable sends events but has no accessor for field or getter: " + getName()
153 );
154 }
155
156 int eventMaximumRateMillis = 0;
157 int eventMinimumDelta = 0;
158 if (sendEvents) {
159 if (getAnnotation().eventMaximumRateMilliseconds() > 0) {
160 log.finer("Moderating state variable events using maximum rate (milliseconds): " + getAnnotation().eventMaximumRateMilliseconds());
161 eventMaximumRateMillis = getAnnotation().eventMaximumRateMilliseconds();
162 }
163
164 if (getAnnotation().eventMinimumDelta() > 0 && Datatype.Builtin.isNumeric(datatype.getBuiltin())) {
165
166 log.finer("Moderating state variable events using minimum delta: " + getAnnotation().eventMinimumDelta());
167 eventMinimumDelta = getAnnotation().eventMinimumDelta();
168 }
169 }
170
171 StateVariableTypeDetails typeDetails =
172 new StateVariableTypeDetails(datatype, defaultValue, allowedValues, allowedValueRange);
173
174 StateVariableEventDetails eventDetails =
175 new StateVariableEventDetails(sendEvents, eventMaximumRateMillis, eventMinimumDelta);
176
177 return new StateVariable(getName(), typeDetails, eventDetails);
178 }
179
180 protected Datatype createDatatype() throws LocalServiceBindingException {
181
182 String declaredDatatype = getAnnotation().datatype();
183
184 if (declaredDatatype.length() == 0 && getAccessor() != null) {
185 Class returnType = getAccessor().getReturnType();
186 log.finer("Using accessor return type as state variable type: " + returnType);
187
188 if (ModelUtil.isStringConvertibleType(getStringConvertibleTypes(), returnType)) {
189
190 log.finer("Return type is string-convertible, using string datatype");
191 return Datatype.Default.STRING.getBuiltinType().getDatatype();
192 } else {
193 Datatype.Default defaultDatatype = Datatype.Default.getByJavaType(returnType);
194 if (defaultDatatype != null) {
195 log.finer("Return type has default UPnP datatype: " + defaultDatatype);
196 return defaultDatatype.getBuiltinType().getDatatype();
197 }
198 }
199 }
200
201
202 if ((declaredDatatype == null || declaredDatatype.length() == 0) &&
203 (getAnnotation().allowedValues().length > 0 || getAnnotation().allowedValuesEnum() != void.class)) {
204 log.finer("State variable has restricted allowed values, hence using 'string' datatype");
205 declaredDatatype = "string";
206 }
207
208
209 if (declaredDatatype == null || declaredDatatype.length() == 0) {
210 throw new LocalServiceBindingException("Could not detect datatype of state variable: " + getName());
211 }
212
213 log.finer("Trying to find built-in UPnP datatype for detected name: " + declaredDatatype);
214
215
216 Datatype.Builtin builtin = Datatype.Builtin.getByDescriptorName(declaredDatatype);
217 if (builtin != null) {
218 log.finer("Found built-in UPnP datatype: " + builtin);
219 return builtin.getDatatype();
220 } else {
221
222 throw new LocalServiceBindingException("No built-in UPnP datatype found, using CustomDataType (TODO: NOT IMPLEMENTED)");
223 }
224 }
225
226 protected String createDefaultValue(Datatype datatype) throws LocalServiceBindingException {
227
228
229 if (getAnnotation().defaultValue().length() != 0) {
230
231 try {
232 datatype.valueOf(getAnnotation().defaultValue());
233 log.finer("Found state variable default value: " + getAnnotation().defaultValue());
234 return getAnnotation().defaultValue();
235 } catch (Exception ex) {
236 throw new LocalServiceBindingException(
237 "Default value doesn't match datatype of state variable '" + getName() + "': " + ex.getMessage()
238 );
239 }
240 }
241
242 return null;
243 }
244
245 protected String[] getAllowedValues(Class enumType) throws LocalServiceBindingException {
246
247 if (!enumType.isEnum()) {
248 throw new LocalServiceBindingException("Allowed values type is not an Enum: " + enumType);
249 }
250
251 log.finer("Restricting allowed values of state variable to Enum: " + getName());
252 String[] allowedValueStrings = new String[enumType.getEnumConstants().length];
253 for (int i = 0; i < enumType.getEnumConstants().length; i++) {
254 Object o = enumType.getEnumConstants()[i];
255 if (o.toString().length() > 32) {
256 throw new LocalServiceBindingException(
257 "Allowed value string (that is, Enum constant name) is longer than 32 characters: " + o.toString()
258 );
259 }
260 log.finer("Adding allowed value (converted to string): " + o.toString());
261 allowedValueStrings[i] = o.toString();
262 }
263
264 return allowedValueStrings;
265 }
266
267 protected StateVariableAllowedValueRange getAllowedValueRange(long min,
268 long max,
269 long step) throws LocalServiceBindingException {
270 if (max < min) {
271 throw new LocalServiceBindingException(
272 "Allowed value range maximum is smaller than minimum: " + getName()
273 );
274 }
275
276 return new StateVariableAllowedValueRange(min, max, step);
277 }
278
279 protected String[] getAllowedValuesFromProvider() throws LocalServiceBindingException {
280 Class provider = getAnnotation().allowedValueProvider();
281 if (!AllowedValueProvider.class.isAssignableFrom(provider))
282 throw new LocalServiceBindingException(
283 "Allowed value provider is not of type " + AllowedValueProvider.class + ": " + getName()
284 );
285 try {
286 return ((Class<? extends AllowedValueProvider>) provider).newInstance().getValues();
287 } catch (Exception ex) {
288 throw new LocalServiceBindingException(
289 "Allowed value provider can't be instantiated: " + getName(), ex
290 );
291 }
292 }
293
294 protected StateVariableAllowedValueRange getAllowedRangeFromProvider() throws LocalServiceBindingException {
295 Class provider = getAnnotation().allowedValueRangeProvider();
296 if (!AllowedValueRangeProvider.class.isAssignableFrom(provider))
297 throw new LocalServiceBindingException(
298 "Allowed value range provider is not of type " + AllowedValueRangeProvider.class + ": " + getName()
299 );
300 try {
301 AllowedValueRangeProvider providerInstance =
302 ((Class<? extends AllowedValueRangeProvider>) provider).newInstance();
303 return getAllowedValueRange(
304 providerInstance.getMinimum(),
305 providerInstance.getMaximum(),
306 providerInstance.getStep()
307 );
308 } catch (Exception ex) {
309 throw new LocalServiceBindingException(
310 "Allowed value range provider can't be instantiated: " + getName(), ex
311 );
312 }
313 }
314
315
316 }