View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.model.types;
17  
18  import org.fourthline.cling.model.Constants;
19  
20  import java.util.logging.Logger;
21  import java.util.regex.Matcher;
22  import java.util.regex.Pattern;
23  
24  /**
25   * Represents a device type, for example <code>urn:my-domain-namespace:device:MyDevice:1</code>.
26   * <p>
27   * Although decimal versions are accepted and parsed, the version used for
28   * comparison is only the integer without the fraction.
29   * </p>
30   *
31   * @author Christian Bauer
32   */
33  public class DeviceType {
34  
35      final private static Logger log = Logger.getLogger(DeviceType.class.getName());
36  
37      public static final String UNKNOWN = "UNKNOWN";
38  
39      public static final Pattern PATTERN =
40              Pattern.compile("urn:(" + Constants.REGEX_NAMESPACE + "):device:(" + Constants.REGEX_TYPE + "):([0-9]+).*");
41  
42      private String namespace;
43      private String type;
44      private int version = 1;
45  
46      public DeviceType(String namespace, String type) {
47          this(namespace, type, 1);
48      }
49  
50      public DeviceType(String namespace, String type, int version) {
51          if (namespace != null && !namespace.matches(Constants.REGEX_NAMESPACE)) {
52              throw new IllegalArgumentException("Device type namespace contains illegal characters");
53          }
54          this.namespace = namespace;
55  
56          if (type != null && !type.matches(Constants.REGEX_TYPE)) {
57              throw new IllegalArgumentException("Device type suffix too long (64) or contains illegal characters");
58          }
59          this.type = type;
60  
61          this.version = version;
62      }
63  
64      public String getNamespace() {
65          return namespace;
66      }
67  
68      public String getType() {
69          return type;
70      }
71  
72      public int getVersion() {
73          return version;
74      }
75  
76      /**
77       * @return Either a {@link UDADeviceType} or a more generic {@link DeviceType}.
78       */
79      public static DeviceType valueOf(String s) throws InvalidValueException {
80  
81          DeviceType deviceType = null;
82  
83          // Sometimes crazy UPnP devices deliver spaces in a URN, don't ask...
84          s = s.replaceAll("\\s", "");
85  
86          // First try UDADeviceType parse
87          try {
88              deviceType = UDADeviceType.valueOf(s);
89          } catch (Exception ex) {
90              // Ignore
91          }
92  
93          if (deviceType != null)
94              return deviceType;
95  
96          try {
97              // Now try a generic DeviceType parse
98              Matcher matcher = PATTERN.matcher(s);
99              if (matcher.matches()) {
100                 return new DeviceType(matcher.group(1), matcher.group(2), Integer.valueOf(matcher.group(3)));
101             }
102 
103             // TODO: UPNP VIOLATION: Escient doesn't provide any device type token
104             // urn:schemas-upnp-org:device::1
105             matcher = Pattern.compile("urn:(" + Constants.REGEX_NAMESPACE + "):device::([0-9]+).*").matcher(s);
106             if (matcher.matches() && matcher.groupCount() >= 2) {
107                 log.warning("UPnP specification violation, no device type token, defaulting to " + UNKNOWN + ": " + s);
108                 return new DeviceType(matcher.group(1), UNKNOWN, Integer.valueOf(matcher.group(2)));
109             }
110 
111             // TODO: UPNP VIOLATION: EyeTV Netstream uses colons in device type token
112             // urn:schemas-microsoft-com:service:pbda:tuner:1
113             matcher = Pattern.compile("urn:(" + Constants.REGEX_NAMESPACE + "):device:(.+?):([0-9]+).*").matcher(s);
114             if (matcher.matches() && matcher.groupCount() >= 3) {
115                 String cleanToken = matcher.group(2).replaceAll("[^a-zA-Z_0-9\\-]", "-");
116                 log.warning(
117                     "UPnP specification violation, replacing invalid device type token '"
118                         + matcher.group(2)
119                         + "' with: "
120                         + cleanToken
121                 );
122                 return new DeviceType(matcher.group(1), cleanToken, Integer.valueOf(matcher.group(3)));
123             }
124         } catch (RuntimeException e) {
125             throw new InvalidValueException(String.format(
126                 "Can't parse device type string (namespace/type/version) '%s': %s", s, e.toString()
127             ));
128         }
129 
130         throw new InvalidValueException("Can't parse device type string (namespace/type/version): " + s);
131     }
132 
133     public boolean implementsVersion(DeviceType that) {
134         if (!namespace.equals(that.namespace)) return false;
135         if (!type.equals(that.type)) return false;
136         if (version < that.version) return false;
137         return true;
138     }
139 
140     public String getDisplayString() {
141         return getType();
142     }
143 
144     @Override
145     public String toString() {
146         return "urn:" + getNamespace() + ":device:" + getType()+ ":" + getVersion();
147     }
148 
149     @Override
150     public boolean equals(Object o) {
151         if (this == o) return true;
152         if (o == null || !(o instanceof DeviceType)) return false;
153 
154         DeviceType that = (DeviceType) o;
155 
156         if (version != that.version) return false;
157         if (!namespace.equals(that.namespace)) return false;
158         if (!type.equals(that.type)) return false;
159 
160         return true;
161     }
162 
163     @Override
164     public int hashCode() {
165         int result = namespace.hashCode();
166         result = 31 * result + type.hashCode();
167         result = 31 * result + version;
168         return result;
169     }
170 }