001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.schematic.document; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.EnumSet; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import org.modeshape.schematic.internal.document.BasicArray; 026import org.modeshape.schematic.internal.document.IndexSequence; 027import org.modeshape.schematic.internal.document.JsonReader; 028 029/** 030 * The constants pertaining to the JSON Schema (draft) specification. 031 * 032 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc. 033 */ 034public class JsonSchema { 035 036 public static class Version { 037 /** 038 * The version-related constants for the 3rd official draft of the specification. 039 * 040 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc. 041 */ 042 public static class Draft3 { 043 /** 044 * The URL of the Core Meta-Schema. 045 */ 046 public static final String CORE_METASCHEMA_URL = "http://json-schema.org/draft-03/schema#"; 047 } 048 049 /** 050 * The version-related constants for the most recent version. 051 * 052 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc. 053 */ 054 public static class Latest { 055 /** 056 * The URL of the Core Meta-Schema. 057 */ 058 public static final String CORE_METASCHEMA_URL = Draft3.CORE_METASCHEMA_URL; 059 } 060 } 061 062 /** 063 * The enumeration representing the standard types used in the JSON Schema (draft) specification. Type. 064 * 065 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc. 066 */ 067 public static enum Type { 068 STRING("string", new StringConverter()), 069 NUMBER("number", new NumberConverter()), 070 INTEGER("integer", new IntegerConverter()), 071 BOOLEAN("boolean", new BooleanConverter()), 072 OBJECT("object", new ObjectConverter()), 073 ARRAY("array", new ArrayConverter()), 074 NULL("null", new NullConverter()), 075 ANY("any", new AnyConverter()), 076 UNION("union", new UnionConverter()), 077 UNKNOWN("unknown", new UnknownConverter()); 078 079 private static final Map<String, Type> TYPE_BY_LOWERCASE; 080 private static final Map<Type, EnumSet<Type>> EQUIVALENT_TYPES; 081 static { 082 Map<String, Type> typeByLowercase = new HashMap<String, JsonSchema.Type>(); 083 typeByLowercase.put(STRING.toString().toLowerCase(), STRING); 084 typeByLowercase.put(NUMBER.toString().toLowerCase(), NUMBER); 085 typeByLowercase.put(INTEGER.toString().toLowerCase(), INTEGER); 086 typeByLowercase.put(BOOLEAN.toString().toLowerCase(), BOOLEAN); 087 typeByLowercase.put(OBJECT.toString().toLowerCase(), OBJECT); 088 typeByLowercase.put(ARRAY.toString().toLowerCase(), ARRAY); 089 typeByLowercase.put(NULL.toString().toLowerCase(), NULL); 090 typeByLowercase.put(ANY.toString().toLowerCase(), ANY); 091 typeByLowercase.put(UNION.toString().toLowerCase(), UNION); 092 typeByLowercase.put(UNKNOWN.toString().toLowerCase(), UNKNOWN); 093 TYPE_BY_LOWERCASE = Collections.unmodifiableMap(typeByLowercase); 094 095 Map<Type, EnumSet<Type>> equiv = new HashMap<Type, EnumSet<Type>>(); 096 equiv.put(Type.STRING, EnumSet.of(Type.STRING)); 097 equiv.put(Type.NUMBER, EnumSet.of(Type.NUMBER, Type.INTEGER)); 098 equiv.put(Type.INTEGER, EnumSet.of(Type.INTEGER)); 099 equiv.put(Type.BOOLEAN, EnumSet.of(Type.BOOLEAN)); 100 equiv.put(Type.OBJECT, EnumSet.of(Type.OBJECT)); 101 equiv.put(Type.ARRAY, EnumSet.of(Type.ARRAY)); 102 equiv.put(Type.NULL, EnumSet.of(Type.NULL)); 103 equiv.put(Type.ANY, EnumSet.of(Type.STRING, 104 Type.NUMBER, 105 Type.INTEGER, 106 Type.BOOLEAN, 107 Type.OBJECT, 108 Type.ARRAY, 109 Type.NULL, 110 Type.ANY, 111 Type.UNION, 112 Type.UNKNOWN)); 113 equiv.put(Type.UNION, EnumSet.of(Type.UNKNOWN)); 114 equiv.put(Type.UNKNOWN, EnumSet.of(Type.UNKNOWN)); 115 EQUIVALENT_TYPES = Collections.unmodifiableMap(equiv); 116 } 117 118 private final String type; 119 private final ValueConverter<?> converter; 120 121 private Type( String type, 122 ValueConverter<?> converter ) { 123 this.type = type; 124 this.converter = converter; 125 } 126 127 @Override 128 public String toString() { 129 return type; 130 } 131 132 /** 133 * Find the type enumeration literal given the (case-insensitive) name. 134 * 135 * @param name the case-independent name of the type 136 * @return the type, or null if there is no such type 137 */ 138 public static Type byName( String name ) { 139 return TYPE_BY_LOWERCASE.get(name.toLowerCase()); 140 } 141 142 /** 143 * Determine whether this type is equivalent to the supplied type. 144 * 145 * @param other the type to be compared with this type 146 * @return true if the types are equivalent, or false otherwise 147 */ 148 public boolean isEquivalent( Type other ) { 149 return other != null ? EQUIVALENT_TYPES.get(this).contains(other) : false; 150 } 151 152 /** 153 * Attempt to convert the supplied value (with the given type) into a value compatible with this type. 154 * 155 * @param actualValue the value to be converted; may be null 156 * @param actualType the type of the value; may be null 157 * @return the converted value, or null if the value could not be converted to this type 158 */ 159 public Object convertValueFrom( Object actualValue, 160 Type actualType ) { 161 if (actualType == null) actualType = Type.typeFor(actualValue); 162 return converter.convert(actualValue, actualType); 163 } 164 165 /** 166 * Determine the type for the given value. 167 * 168 * @param value the field value 169 * @return the corresponding type for the value; never null 170 */ 171 public static Type typeFor( Object value ) { 172 if (value == null) return NULL; 173 if (value instanceof String) // most will be strings 174 return STRING; 175 if (value instanceof Integer) return INTEGER; 176 if (value instanceof Long) return INTEGER; 177 if (value instanceof Float) return NUMBER; 178 if (value instanceof Double) return NUMBER; 179 if (value instanceof Boolean) return BOOLEAN; 180 if (value instanceof List) // needs to be compared **before** Document 181 return ARRAY; 182 if (value instanceof Document) return OBJECT; 183 if (value instanceof Null) return NULL; 184 if (value instanceof Symbol) return STRING; 185 return UNKNOWN; 186 } 187 188 /** 189 * Obtain the set of types given the supplied name or iterable container of names. 190 * 191 * @param typeName the String or Symbol representation of a single type name, or an Iterable&?> list of names 192 * @return the set of types that correspond to the supplied names; never null but possibly an empty set 193 */ 194 public static EnumSet<Type> typesWithNames( Object typeName ) { 195 if (typeName instanceof List) { 196 List<Type> result = new ArrayList<Type>(); 197 Iterable<?> typeNames = (Iterable<?>)typeName; 198 for (Object tname : typeNames) { 199 if (tname == null) continue; 200 String name = tname.toString(); 201 Type type = byName(name); 202 if (type != null) result.add(type); 203 } 204 if (!result.isEmpty()) { 205 return EnumSet.copyOf(result); 206 } 207 } else if (typeName instanceof String || typeName instanceof Symbol) { 208 String name = typeName.toString(); 209 Type type = byName(name); 210 return EnumSet.of(type); 211 } 212 return EnumSet.noneOf(Type.class); 213 } 214 } 215 216 protected static interface ValueConverter<OutputType> { 217 OutputType convert( Object value, 218 Type type ); 219 } 220 221 protected static class StringConverter implements ValueConverter<String> { 222 @Override 223 public String convert( Object value, 224 Type type ) { 225 if (value == null) return null; 226 switch (type) { 227 case STRING: 228 return (String)value; 229 case INTEGER: 230 case NUMBER: 231 case BOOLEAN: 232 return value.toString(); 233 case OBJECT: // this is a document, and we can't convert it to a string ... 234 case UNION: 235 case NULL: 236 case ARRAY: 237 return null; 238 case ANY: // fall through 239 case UNKNOWN: 240 // Figure out the type ... 241 Type inferredType = Type.typeFor(value); 242 if (inferredType == Type.UNKNOWN || inferredType == type) return null; 243 return convert(value, inferredType); 244 } 245 return null; 246 } 247 } 248 249 protected static class NumberConverter implements ValueConverter<Number> { 250 @Override 251 public Number convert( Object value, 252 Type type ) { 253 if (value == null) return null; 254 switch (type) { 255 case STRING: 256 String str = value.toString(); 257 return JsonReader.parseNumber(str); // may return null 258 case INTEGER: 259 case NUMBER: 260 return (Number)value; 261 case BOOLEAN: 262 // Convert a boolean to either '1' or '0' integer ... 263 Boolean bool = (Boolean)value; 264 return Boolean.TRUE.equals(bool) ? new Integer(1) : new Integer(0); 265 case OBJECT: // this is a document, and we can't convert it to a number ... 266 case UNION: 267 case NULL: 268 case ARRAY: 269 return null; 270 case ANY: // fall through 271 case UNKNOWN: 272 // Figure out the type ... 273 Type inferredType = Type.typeFor(value); 274 if (inferredType == Type.UNKNOWN || inferredType == type) return null; 275 return convert(value, inferredType); 276 } 277 return null; 278 } 279 } 280 281 protected static class IntegerConverter implements ValueConverter<Integer> { 282 @Override 283 public Integer convert( Object value, 284 Type type ) { 285 if (value == null) return null; 286 switch (type) { 287 case STRING: 288 String str = value.toString(); 289 Number number = JsonReader.parseNumber(str); // may return null 290 return number instanceof Integer ? (Integer)number : null; 291 case INTEGER: 292 return (Integer)value; 293 case NUMBER: 294 return value instanceof Integer ? (Integer)value : null; 295 case BOOLEAN: 296 // Convert a boolean to either '1' or '0' integer ... 297 Boolean bool = (Boolean)value; 298 return Boolean.TRUE.equals(bool) ? new Integer(1) : new Integer(0); 299 case OBJECT: // this is a document, and we can't convert it to a number ... 300 case UNION: 301 case NULL: 302 case ARRAY: 303 return null; 304 case ANY: // fall through 305 case UNKNOWN: 306 // Figure out the type ... 307 Type inferredType = Type.typeFor(value); 308 if (inferredType == Type.UNKNOWN || inferredType == type) return null; 309 return convert(value, inferredType); 310 } 311 return null; 312 } 313 } 314 315 protected static class BooleanConverter implements ValueConverter<Boolean> { 316 @Override 317 public Boolean convert( Object value, 318 Type type ) { 319 if (value == null) return null; 320 switch (type) { 321 case STRING: 322 String str = value.toString(); 323 if ("true".equalsIgnoreCase(str)) return Boolean.TRUE; 324 if ("false".equalsIgnoreCase(str)) return Boolean.FALSE; 325 return null; 326 case INTEGER: 327 Integer i = (Integer)value; 328 if (i.intValue() == 1) return Boolean.FALSE; 329 if (i.intValue() == 0) return Boolean.FALSE; 330 return null; 331 case NUMBER: 332 Number number = (Number)value; 333 if (number.intValue() == 1) return Boolean.FALSE; 334 if (number.intValue() == 0) return Boolean.FALSE; 335 return null; 336 case BOOLEAN: 337 return (Boolean)value; 338 case OBJECT: // this is a document, and we can't convert it to a number ... 339 case UNION: 340 case NULL: 341 case ARRAY: 342 return null; 343 case ANY: // fall through 344 case UNKNOWN: 345 // Figure out the type ... 346 Type inferredType = Type.typeFor(value); 347 if (inferredType == Type.UNKNOWN || inferredType == type) return null; 348 return convert(value, inferredType); 349 } 350 return null; 351 } 352 } 353 354 protected static class ArrayConverter implements ValueConverter<List<?>> { 355 @Override 356 public List<?> convert( Object value, 357 Type type ) { 358 if (value == null) return null; 359 switch (type) { 360 case ARRAY: 361 return value instanceof List<?> ? (List<?>)value : null; 362 case OBJECT: // this is a document 363 if (value instanceof List<?>) return (List<?>)value; 364 Document doc = (Document)value; 365 BasicArray array = new BasicArray(doc.size()); 366 final Iterator<String> indexIter = IndexSequence.infiniteSequence(); 367 for (Document.Field field : doc.fields()) { 368 String name = field.getName(); 369 String index = indexIter.next(); 370 if (!index.equals(name)) { 371 // The doc's field names don't match integral index values ... 372 return null; 373 } 374 array.addValue(value); 375 } 376 return array; 377 case STRING: 378 case INTEGER: 379 case NUMBER: 380 case BOOLEAN: 381 case UNION: 382 case NULL: 383 return null; 384 case ANY: // fall through 385 case UNKNOWN: 386 // Figure out the type ... 387 Type inferredType = Type.typeFor(value); 388 if (inferredType == Type.UNKNOWN || inferredType == type) return null; 389 return convert(value, inferredType); 390 } 391 return null; 392 } 393 } 394 395 protected static class ObjectConverter implements ValueConverter<Object> { 396 @Override 397 public Object convert( Object value, 398 Type type ) { 399 if (value == null) return null; 400 switch (type) { 401 case OBJECT: // this is a document 402 return value; 403 case STRING: 404 case INTEGER: 405 case NUMBER: 406 case BOOLEAN: 407 case UNION: 408 case NULL: 409 return null; 410 case ARRAY: 411 // The array can already be treated as a document ... 412 return value instanceof Document ? value : null; 413 case ANY: // fall through 414 case UNKNOWN: 415 // Figure out the type ... 416 Type inferredType = Type.typeFor(value); 417 if (inferredType == Type.UNKNOWN || inferredType == type) return null; 418 return convert(value, inferredType); 419 } 420 return null; 421 } 422 } 423 424 protected static class NullConverter implements ValueConverter<Object> { 425 @Override 426 public Object convert( Object value, 427 Type type ) { 428 if (value == null || value instanceof Null) return Null.getInstance(); 429 return null; 430 } 431 } 432 433 protected static class UnionConverter implements ValueConverter<Object> { 434 @Override 435 public Object convert( Object value, 436 Type type ) { 437 return null; 438 } 439 } 440 441 protected static class UnknownConverter implements ValueConverter<Object> { 442 @Override 443 public Object convert( Object value, 444 Type type ) { 445 return value; 446 } 447 } 448 449 protected static class AnyConverter implements ValueConverter<Object> { 450 @Override 451 public Object convert( Object value, 452 Type type ) { 453 return value; 454 } 455 } 456 457}