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 java.util.Calendar;
19  import java.util.HashMap;
20  import java.util.Map;
21  import java.util.Locale;
22  
23  /**
24   * The type of a state variable value, able to convert to/from string representation.
25   *
26   * @param <V> The Java type of the value handled by this datatype.
27   * @author Christian Bauer
28   */
29  public interface Datatype<V> {
30  
31      /**
32       * Mapping from Java type to UPnP built-in type.
33       * <p>
34       * This map is used for service binding, when we have to figure out
35       * the type of a UPnP state variable by reflecting on a method or field of
36       * a service class. From a known Java type we default to a UPnP built-in type.
37       * This is just a list of default mappings between Java and UPnP types. There
38       * might be more than this/more than one UPnP type that can handle a given
39       * Java type.
40       * </p>
41       */
42      public static enum Default {
43  
44          BOOLEAN(Boolean.class, Builtin.BOOLEAN),
45          BOOLEAN_PRIMITIVE(Boolean.TYPE, Builtin.BOOLEAN),
46          SHORT(Short.class, Builtin.I2_SHORT),
47          SHORT_PRIMITIVE(Short.TYPE, Builtin.I2_SHORT),
48          INTEGER(Integer.class, Builtin.I4),
49          INTEGER_PRIMITIVE(Integer.TYPE, Builtin.I4),
50          UNSIGNED_INTEGER_ONE_BYTE(UnsignedIntegerOneByte.class, Builtin.UI1),
51          UNSIGNED_INTEGER_TWO_BYTES(UnsignedIntegerTwoBytes.class, Builtin.UI2),
52          UNSIGNED_INTEGER_FOUR_BYTES(UnsignedIntegerFourBytes.class, Builtin.UI4),
53          FLOAT(Float.class, Builtin.R4),
54          FLOAT_PRIMITIVE(Float.TYPE, Builtin.R4),
55          DOUBLE(Double.class, Builtin.FLOAT),
56          DOUBLE_PRIMTIIVE(Double.TYPE, Builtin.FLOAT),
57          CHAR(Character.class, Builtin.CHAR),
58          CHAR_PRIMITIVE(Character.TYPE, Builtin.CHAR),
59          STRING(String.class, Builtin.STRING),
60          CALENDAR(Calendar.class, Builtin.DATETIME),
61          BYTES(byte[].class, Builtin.BIN_BASE64),
62          URI(java.net.URI.class, Builtin.URI);
63  
64          private Class javaType;
65          private Builtin builtinType;
66  
67          Default(Class javaType, Builtin builtinType) {
68              this.javaType = javaType;
69              this.builtinType = builtinType;
70          }
71  
72          public Class getJavaType() {
73              return javaType;
74          }
75  
76          public Builtin getBuiltinType() {
77              return builtinType;
78          }
79  
80          public static Default getByJavaType(Class javaType) {
81              for (Default d : Default.values()) {
82                  if (d.getJavaType().equals(javaType)) {
83                      return d;
84                  }
85              }
86              return null;
87          }
88  
89          @Override
90          public String toString() {
91              return getJavaType() + " => " + getBuiltinType();
92          }
93      }
94  
95      /**
96       * Mapping from UPnP built-in standardized type to actual subtype of {@link Datatype}.
97       */
98      public static enum Builtin {
99  
100         UI1("ui1", new UnsignedIntegerOneByteDatatype()),
101         UI2("ui2", new UnsignedIntegerTwoBytesDatatype()),
102         UI4("ui4", new UnsignedIntegerFourBytesDatatype()),
103         I1("i1", new IntegerDatatype(1)),
104         I2("i2", new IntegerDatatype(2)),
105         I2_SHORT("i2", new ShortDatatype()),
106         I4("i4", new IntegerDatatype(4)),
107         INT("int", new IntegerDatatype(4)),
108         R4("r4", new FloatDatatype()),
109         R8("r8", new DoubleDatatype()),
110         NUMBER("number", new DoubleDatatype()),
111         FIXED144("fixed.14.4", new DoubleDatatype()),
112         FLOAT("float", new DoubleDatatype()), // TODO: Is that Double or Float?
113         CHAR("char", new CharacterDatatype()),
114         STRING("string", new StringDatatype()),
115         DATE("date", new DateTimeDatatype(
116                 new String[]{"yyyy-MM-dd"},
117                 "yyyy-MM-dd"
118         )),
119         DATETIME("dateTime", new DateTimeDatatype(
120                 new String[]{"yyyy-MM-dd", "yyyy-MM-dd'T'HH:mm:ss"},
121                 "yyyy-MM-dd'T'HH:mm:ss"
122         )),
123         DATETIME_TZ("dateTime.tz", new DateTimeDatatype(
124                 new String[]{"yyyy-MM-dd", "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ssZ"},
125                 "yyyy-MM-dd'T'HH:mm:ssZ"
126         )),
127         TIME("time", new DateTimeDatatype(
128                 new String[]{"HH:mm:ss"},
129                 "HH:mm:ss"
130         )),
131         TIME_TZ("time.tz", new DateTimeDatatype(
132                 new String[]{"HH:mm:ssZ", "HH:mm:ss"},
133                 "HH:mm:ssZ"
134         )),
135         BOOLEAN("boolean", new BooleanDatatype()),
136         BIN_BASE64("bin.base64", new Base64Datatype()),
137         BIN_HEX("bin.hex", new BinHexDatatype()),
138         URI("uri", new URIDatatype()),
139         UUID("uuid", new StringDatatype());
140 
141         private static Map<String, Builtin> byName = new HashMap<String, Builtin>() {{
142             for (Builtin b : Builtin.values()) {
143                 // Lowercase descriptor name!
144                 if (containsKey(b.getDescriptorName().toLowerCase(Locale.ROOT)))
145                     continue; // Ignore double-declarations, take first one only
146                 put(b.getDescriptorName().toLowerCase(Locale.ROOT), b);
147             }
148         }};
149 
150         private String descriptorName;
151         private Datatype datatype;
152 
153         <VT> Builtin(String descriptorName, AbstractDatatype<VT> datatype) {
154             datatype.setBuiltin(this); // Protected, we actually want this to be immutable
155             this.descriptorName = descriptorName;
156             this.datatype = datatype;
157         }
158 
159         public String getDescriptorName() {
160             return descriptorName;
161         }
162 
163         public Datatype getDatatype() {
164             return datatype;
165         }
166 
167         public static Builtin getByDescriptorName(String descriptorName) {
168             // The UPnP spec clearly says "must be one of these values", so I'm assuming
169             // they are case sensitive. But we want to work with broken devices, which of
170             // course produce mixed upper/lowercase values.
171             if (descriptorName == null) return null;
172             return byName.get(descriptorName.toLowerCase(Locale.ROOT));
173         }
174 
175         public static boolean isNumeric(Builtin builtin) {
176             return builtin != null &&
177                     (builtin.equals(UI1) ||
178                             builtin.equals(UI2) ||
179                             builtin.equals(UI4) ||
180                             builtin.equals(I1) ||
181                             builtin.equals(I2) ||
182                             builtin.equals(I4) ||
183                             builtin.equals(INT));
184         }
185     }
186 
187     /**
188      * @return <code>true</code> if this datatype can handle values of the given Java type.
189      */
190     public boolean isHandlingJavaType(Class type);
191 
192     /**
193      * @return The built-in UPnP standardized type this datatype is mapped to or
194      *         <code>null</code> if this is a custom datatype.
195      */
196     public Builtin getBuiltin();
197 
198     /**
199      * @param value The value to validate or <code>null</code>.
200      * @return Returns <code>true</code> if the value was <code>null</code>, validation result otherwise.
201      */
202     public boolean isValid(V value);
203 
204     /**
205      * Transforms a value supported by this datatype into a string representation.
206      * <p>
207      * This method calls {@link #isValid(Object)} before converting the value, it throws
208      * an exception if validation fails.
209      * </p>
210      *
211      * @param value The value to transform.
212      * @return The transformed value as a string, or an empty string when the value is null, never returns <code>null</code>.
213      * @throws InvalidValueException
214      */
215     public String getString(V value) throws InvalidValueException;
216 
217     /**
218      * Transforms a string representation into a value of the supported type.
219      *
220      * @param s The string representation of a value.
221      * @return The converted value or <code>null</code> if the string was <code>null</code> or empty.
222      * @throws InvalidValueException If the string couldn't be parsed.
223      */
224     public V valueOf(String s) throws InvalidValueException;
225 
226     /**
227      * @return Metadata about this datatype, a nice string for display that describes
228      *         this datatype (e.g. concrete class name).
229      */
230     public String getDisplayString();
231 
232 
233 }