1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.fourthline.cling.model.meta;
17
18 import org.fourthline.cling.model.Namespace;
19 import org.fourthline.cling.model.profile.RemoteClientInfo;
20 import org.fourthline.cling.model.resource.Resource;
21 import org.fourthline.cling.model.Validatable;
22 import org.fourthline.cling.model.ValidationError;
23 import org.fourthline.cling.model.ValidationException;
24 import org.fourthline.cling.model.types.DeviceType;
25 import org.fourthline.cling.model.types.ServiceId;
26 import org.fourthline.cling.model.types.ServiceType;
27 import org.fourthline.cling.model.types.UDN;
28
29 import java.net.URI;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.logging.Level;
36 import java.util.logging.Logger;
37
38
39
40
41
42
43 public abstract class Device<DI extends DeviceIdentity, D extends Device, S extends Service> implements Validatable {
44
45 final private static Logger log = Logger.getLogger(Device.class.getName());
46
47 final private DI identity;
48
49 final private UDAVersion version;
50 final private DeviceType type;
51 final private DeviceDetails details;
52 final private Icon[] icons;
53 final protected S[] services;
54 final protected D[] embeddedDevices;
55
56
57 private D parentDevice;
58
59 public Device(DI identity) throws ValidationException {
60 this(identity, null, null, null, null, null);
61 }
62
63 public Device(DI identity, DeviceType type, DeviceDetails details,
64 Icon[] icons, S[] services) throws ValidationException {
65 this(identity, null, type, details, icons, services, null);
66 }
67
68 public Device(DI identity, DeviceType type, DeviceDetails details,
69 Icon[] icons, S[] services, D[] embeddedDevices) throws ValidationException {
70 this(identity, null, type, details, icons, services, embeddedDevices);
71 }
72
73 public Device(DI identity, UDAVersion version, DeviceType type, DeviceDetails details,
74 Icon[] icons, S[] services, D[] embeddedDevices) throws ValidationException {
75
76 this.identity = identity;
77 this.version = version == null ? new UDAVersion() : version;
78 this.type = type;
79 this.details = details;
80
81
82
83
84 List<Icon> validIcons = new ArrayList<>();
85 if (icons != null) {
86 for (Icon icon : icons) {
87 if (icon != null) {
88 icon.setDevice(this);
89 List<ValidationError> iconErrors = icon.validate();
90 if(iconErrors.isEmpty()) {
91 validIcons.add(icon);
92 } else {
93 log.warning("Discarding invalid '" + icon + "': " + iconErrors);
94 }
95 }
96 }
97 }
98 this.icons = validIcons.toArray(new Icon[validIcons.size()]);
99
100 boolean allNullServices = true;
101 if (services != null) {
102 for (S service : services) {
103 if (service != null) {
104 allNullServices = false;
105 service.setDevice(this);
106 }
107 }
108 }
109 this.services = services == null || allNullServices ? null : services;
110
111 boolean allNullEmbedded = true;
112 if (embeddedDevices != null) {
113 for (D embeddedDevice : embeddedDevices) {
114 if (embeddedDevice != null) {
115 allNullEmbedded = false;
116 embeddedDevice.setParentDevice(this);
117 }
118 }
119 }
120 this.embeddedDevices = embeddedDevices == null || allNullEmbedded ? null : embeddedDevices;
121
122 List<ValidationError> errors = validate();
123 if (errors.size() > 0) {
124 if (log.isLoggable(Level.FINEST)) {
125 for (ValidationError error : errors) {
126 log.finest(error.toString());
127 }
128 }
129 throw new ValidationException("Validation of device graph failed, call getErrors() on exception", errors);
130 }
131 }
132
133 public DI getIdentity() {
134 return identity;
135 }
136
137 public UDAVersion getVersion() {
138 return version;
139 }
140
141 public DeviceType getType() {
142 return type;
143 }
144
145 public DeviceDetails getDetails() {
146 return details;
147 }
148
149 public DeviceDetails getDetails(RemoteClientInfo info) {
150 return this.getDetails();
151 }
152
153 public Icon[] getIcons() {
154 return icons;
155 }
156
157 public boolean hasIcons() {
158 return getIcons() != null && getIcons().length > 0;
159 }
160
161 public boolean hasServices() {
162 return getServices() != null && getServices().length > 0;
163 }
164
165
166 public boolean hasEmbeddedDevices() {
167 return getEmbeddedDevices() != null && getEmbeddedDevices().length > 0;
168 }
169
170 public D getParentDevice() {
171 return parentDevice;
172 }
173
174 void setParentDevice(D parentDevice) {
175 if (this.parentDevice != null)
176 throw new IllegalStateException("Final value has been set already, model is immutable");
177 this.parentDevice = parentDevice;
178 }
179
180 public boolean isRoot() {
181 return getParentDevice() == null;
182 }
183
184 public abstract S[] getServices();
185
186 public abstract D[] getEmbeddedDevices();
187
188 public abstract D getRoot();
189
190 public abstract D findDevice(UDN udn);
191
192 public D[] findEmbeddedDevices() {
193 return toDeviceArray(findEmbeddedDevices((D) this));
194 }
195
196 public D[] findDevices(DeviceType deviceType) {
197 return toDeviceArray(find(deviceType, (D) this));
198 }
199
200 public D[] findDevices(ServiceType serviceType) {
201 return toDeviceArray(find(serviceType, (D) this));
202 }
203
204 public Icon[] findIcons() {
205 List<Icon> icons = new ArrayList<>();
206 if (hasIcons()) {
207 icons.addAll(Arrays.asList(getIcons()));
208 }
209 D[] embeddedDevices = findEmbeddedDevices();
210 for (D embeddedDevice : embeddedDevices) {
211 if (embeddedDevice.hasIcons()) {
212 icons.addAll(Arrays.asList(embeddedDevice.getIcons()));
213 }
214 }
215 return icons.toArray(new Icon[icons.size()]);
216 }
217
218 public S[] findServices() {
219 return toServiceArray(findServices(null, null, (D) this));
220 }
221
222 public S[] findServices(ServiceType serviceType) {
223 return toServiceArray(findServices(serviceType, null, (D) this));
224 }
225
226 protected D find(UDN udn, D current) {
227 if (current.getIdentity() != null && current.getIdentity().getUdn() != null) {
228 if (current.getIdentity().getUdn().equals(udn)) return current;
229 }
230 if (current.hasEmbeddedDevices()) {
231 for (D embeddedDevice : (D[]) current.getEmbeddedDevices()) {
232 D match;
233 if ((match = find(udn, embeddedDevice)) != null) return match;
234 }
235 }
236 return null;
237 }
238
239 protected Collection<D> findEmbeddedDevices(D current) {
240 Collection<D> devices = new HashSet<>();
241 if (!current.isRoot() && current.getIdentity().getUdn() != null)
242 devices.add(current);
243
244 if (current.hasEmbeddedDevices()) {
245 for (D embeddedDevice : (D[]) current.getEmbeddedDevices()) {
246 devices.addAll(findEmbeddedDevices(embeddedDevice));
247 }
248 }
249 return devices;
250 }
251
252 protected Collection<D> find(DeviceType deviceType, D current) {
253 Collection<D> devices = new HashSet<>();
254
255 if (current.getType() != null && current.getType().implementsVersion(deviceType)) {
256 devices.add(current);
257 }
258 if (current.hasEmbeddedDevices()) {
259 for (D embeddedDevice : (D[]) current.getEmbeddedDevices()) {
260 devices.addAll(find(deviceType, embeddedDevice));
261 }
262 }
263 return devices;
264 }
265
266 protected Collection<D> find(ServiceType serviceType, D current) {
267 Collection<S> services = findServices(serviceType, null, current);
268 Collection<D> devices = new HashSet<>();
269 for (Service service : services) {
270 devices.add((D) service.getDevice());
271 }
272 return devices;
273 }
274
275 protected Collection<S> findServices(ServiceType serviceType, ServiceId serviceId, D current) {
276 Collection services = new HashSet<>();
277 if (current.hasServices()) {
278 for (Service service : current.getServices()) {
279 if (isMatch(service, serviceType, serviceId))
280 services.add(service);
281 }
282 }
283 Collection<D> embeddedDevices = findEmbeddedDevices(current);
284 if (embeddedDevices != null) {
285 for (D embeddedDevice : embeddedDevices) {
286 if (embeddedDevice.hasServices()) {
287 for (Service service : embeddedDevice.getServices()) {
288 if (isMatch(service, serviceType, serviceId))
289 services.add(service);
290 }
291 }
292 }
293 }
294 return services;
295 }
296
297 public S findService(ServiceId serviceId) {
298 Collection<S> services = findServices(null, serviceId, (D) this);
299 return services.size() == 1 ? services.iterator().next() : null;
300 }
301
302 public S findService(ServiceType serviceType) {
303 Collection<S> services = findServices(serviceType, null, (D) this);
304 return services.size() > 0 ? services.iterator().next() : null;
305 }
306
307 public ServiceType[] findServiceTypes() {
308 Collection<S> services = findServices(null, null, (D) this);
309 Collection<ServiceType> col = new HashSet<>();
310 for (S service : services) {
311 col.add(service.getServiceType());
312 }
313 return col.toArray(new ServiceType[col.size()]);
314 }
315
316 private boolean isMatch(Service s, ServiceType serviceType, ServiceId serviceId) {
317 boolean matchesType = serviceType == null || s.getServiceType().implementsVersion(serviceType);
318 boolean matchesId = serviceId == null || s.getServiceId().equals(serviceId);
319 return matchesType && matchesId;
320 }
321
322 public boolean isFullyHydrated() {
323 S[] services = findServices();
324 for (S service : services) {
325 if (service.hasStateVariables()) return true;
326 }
327 return false;
328 }
329
330 public String getDisplayString() {
331
332
333
334 String cleanModelName = null;
335 String cleanModelNumber = null;
336
337 if (getDetails() != null && getDetails().getModelDetails() != null) {
338
339
340 ModelDetails modelDetails = getDetails().getModelDetails();
341 if (modelDetails.getModelName() != null) {
342 cleanModelName = modelDetails.getModelNumber() != null && modelDetails.getModelName().endsWith(modelDetails.getModelNumber())
343 ? modelDetails.getModelName().substring(0, modelDetails.getModelName().length() - modelDetails.getModelNumber().length())
344 : modelDetails.getModelName();
345 }
346
347
348 if (cleanModelName != null) {
349 cleanModelNumber = modelDetails.getModelNumber() != null && !cleanModelName.startsWith(modelDetails.getModelNumber())
350 ? modelDetails.getModelNumber()
351 : "";
352 } else {
353 cleanModelNumber = modelDetails.getModelNumber();
354 }
355 }
356
357 StringBuilder sb = new StringBuilder();
358
359 if (getDetails() != null && getDetails().getManufacturerDetails() != null) {
360
361
362 if (cleanModelName != null && getDetails().getManufacturerDetails().getManufacturer() != null) {
363 cleanModelName = cleanModelName.startsWith(getDetails().getManufacturerDetails().getManufacturer())
364 ? cleanModelName.substring(getDetails().getManufacturerDetails().getManufacturer().length()).trim()
365 : cleanModelName.trim();
366 }
367
368 if (getDetails().getManufacturerDetails().getManufacturer() != null) {
369 sb.append(getDetails().getManufacturerDetails().getManufacturer());
370 }
371 }
372
373 sb.append((cleanModelName != null && cleanModelName.length() > 0 ? " " + cleanModelName : ""));
374 sb.append((cleanModelNumber != null && cleanModelNumber.length() > 0 ? " " + cleanModelNumber.trim() : ""));
375 return sb.toString();
376 }
377
378 public List<ValidationError> validate() {
379 List<ValidationError> errors = new ArrayList<>();
380
381 if (getType() != null) {
382
383
384
385
386
387
388 errors.addAll(getVersion().validate());
389
390 if(getIdentity() != null) {
391 errors.addAll(getIdentity().validate());
392 }
393
394 if (getDetails() != null) {
395 errors.addAll(getDetails().validate());
396 }
397
398 if (hasServices()) {
399 for (Service service : getServices()) {
400 if (service != null)
401 errors.addAll(service.validate());
402 }
403 }
404
405 if (hasEmbeddedDevices()) {
406 for (Device embeddedDevice : getEmbeddedDevices()) {
407 if (embeddedDevice != null)
408 errors.addAll(embeddedDevice.validate());
409 }
410 }
411 }
412
413 return errors;
414 }
415
416 @Override
417 public boolean equals(Object o) {
418 if (this == o) return true;
419 if (o == null || getClass() != o.getClass()) return false;
420
421 Device device = (Device) o;
422
423 if (!identity.equals(device.identity)) return false;
424
425 return true;
426 }
427
428 @Override
429 public int hashCode() {
430 return identity.hashCode();
431 }
432
433 public abstract D newInstance(UDN udn, UDAVersion version, DeviceType type, DeviceDetails details,
434 Icon[] icons, S[] services, List<D> embeddedDevices) throws ValidationException;
435
436 public abstract S newInstance(ServiceType serviceType, ServiceId serviceId,
437 URI descriptorURI, URI controlURI, URI eventSubscriptionURI,
438 Action<S>[] actions, StateVariable<S>[] stateVariables) throws ValidationException;
439
440 public abstract D[] toDeviceArray(Collection<D> col);
441
442 public abstract S[] newServiceArray(int size);
443
444 public abstract S[] toServiceArray(Collection<S> col);
445
446 public abstract Resource[] discoverResources(Namespace namespace);
447
448 @Override
449 public String toString() {
450 return "(" + getClass().getSimpleName() + ") Identity: " + getIdentity().toString() + ", Root: " + isRoot();
451 }
452 }