1 /**
  2  * JSV: JSON Schema Validator
  3  * 
  4  * @fileOverview A JavaScript implementation of a extendable, fully compliant JSON Schema validator.
  5  * @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
  6  * @version 3.2
  7  * @see http://github.com/garycourt/JSV
  8  */
  9 
 10 /*
 11  * Copyright 2010 Gary Court. All rights reserved.
 12  * 
 13  * Redistribution and use in source and binary forms, with or without modification, are
 14  * permitted provided that the following conditions are met:
 15  * 
 16  *    1. Redistributions of source code must retain the above copyright notice, this list of
 17  *       conditions and the following disclaimer.
 18  * 
 19  *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 20  *       of conditions and the following disclaimer in the documentation and/or other materials
 21  *       provided with the distribution.
 22  * 
 23  * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED
 24  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 25  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR
 26  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 28  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 29  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 30  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 31  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 32  * 
 33  * The views and conclusions contained in the software and documentation are those of the
 34  * authors and should not be interpreted as representing official policies, either expressed
 35  * or implied, of Gary Court or the JSON Schema specification.
 36  */
 37 
 38 /*jslint white: true, sub: true, onevar: true, undef: true, eqeqeq: true, newcap: true, immed: true, indent: 4 */
 39 
 40 var exports = exports || this,
 41 	require = require || function () {
 42 		return exports;
 43 	};
 44 
 45 (function () {
 46 	
 47 	var URI = require("./uri/uri").URI,
 48 		O = {},
 49 		I2H = "0123456789abcdef".split(""),
 50 		mapArray, filterArray, searchArray,
 51 		
 52 		JSV;
 53 	
 54 	//
 55 	// Utility functions
 56 	//
 57 	
 58 	function typeOf(o) {
 59 		return o === undefined ? "undefined" : (o === null ? "null" : Object.prototype.toString.call(o).split(" ").pop().split("]").shift().toLowerCase());
 60 	}
 61 	
 62 	/** @inner */
 63 	function F() {}
 64 	
 65 	function createObject(proto) {
 66 		F.prototype = proto || {};
 67 		return new F();
 68 	}
 69 	
 70 	function mapObject(obj, func, scope) {
 71 		var newObj = {}, key;
 72 		for (key in obj) {
 73 			if (obj[key] !== O[key]) {
 74 				newObj[key] = func.call(scope, obj[key], key, obj);
 75 			}
 76 		}
 77 		return newObj;
 78 	}
 79 	
 80 	/** @ignore */
 81 	mapArray = function (arr, func, scope) {
 82 		var x = 0, xl = arr.length, newArr = new Array(xl);
 83 		for (; x < xl; ++x) {
 84 			newArr[x] = func.call(scope, arr[x], x, arr);
 85 		}
 86 		return newArr;
 87 	};
 88 		
 89 	if (Array.prototype.map) {
 90 		/** @ignore */
 91 		mapArray = function (arr, func, scope) {
 92 			return Array.prototype.map.call(arr, func, scope);
 93 		};
 94 	}
 95 	
 96 	/** @ignore */
 97 	filterArray = function (arr, func, scope) {
 98 		var x = 0, xl = arr.length, newArr = [];
 99 		for (; x < xl; ++x) {
100 			if (func.call(scope, arr[x], x, arr)) {
101 				newArr[newArr.length] = arr[x];
102 			}
103 		}
104 		return newArr;
105 	};
106 	
107 	if (Array.prototype.filter) {
108 		/** @ignore */
109 		filterArray = function (arr, func, scope) {
110 			return Array.prototype.filter.call(arr, func, scope);
111 		};
112 	}
113 	
114 	/** @ignore */
115 	searchArray = function (arr, o) {
116 		var x = 0, xl = arr.length;
117 		for (; x < xl; ++x) {
118 			if (arr[x] === o) {
119 				return x;
120 			}
121 		}
122 		return -1;
123 	};
124 	
125 	if (Array.prototype.indexOf) {
126 		/** @ignore */
127 		searchArray = function (arr, o) {
128 			return Array.prototype.indexOf.call(arr, o);
129 		};
130 	}
131 	
132 	function toArray(o) {
133 		return o !== undefined && o !== null ? (o instanceof Array && !o.callee ? o : (typeof o.length !== "number" || o.split || o.setInterval || o.call ? [ o ] : Array.prototype.slice.call(o))) : [];
134 	}
135 	
136 	function keys(o) {
137 		var result = [], key;
138 		
139 		switch (typeOf(o)) {
140 		case "object":
141 			for (key in o) {
142 				if (o[key] !== O[key]) {
143 					result[result.length] = key;
144 				}
145 			}
146 			break;
147 		case "array":
148 			for (key = o.length - 1; key >= 0; --key) {
149 				result[key] = key;
150 			}
151 			break;
152 		}
153 		
154 		return result;
155 	}
156 	
157 	function pushUnique(arr, o) {
158 		if (searchArray(arr, o) === -1) {
159 			arr.push(o);
160 		}
161 		return arr;
162 	}
163 	
164 	function randomUUID() {
165 		return [
166 			I2H[Math.floor(Math.random() * 0x10)],
167 			I2H[Math.floor(Math.random() * 0x10)],
168 			I2H[Math.floor(Math.random() * 0x10)],
169 			I2H[Math.floor(Math.random() * 0x10)],
170 			I2H[Math.floor(Math.random() * 0x10)],
171 			I2H[Math.floor(Math.random() * 0x10)],
172 			I2H[Math.floor(Math.random() * 0x10)],
173 			I2H[Math.floor(Math.random() * 0x10)],
174 			"-",
175 			I2H[Math.floor(Math.random() * 0x10)],
176 			I2H[Math.floor(Math.random() * 0x10)],
177 			I2H[Math.floor(Math.random() * 0x10)],
178 			I2H[Math.floor(Math.random() * 0x10)],
179 			"-4",  //set 4 high bits of time_high field to version
180 			I2H[Math.floor(Math.random() * 0x10)],
181 			I2H[Math.floor(Math.random() * 0x10)],
182 			I2H[Math.floor(Math.random() * 0x10)],
183 			"-",
184 			I2H[(Math.floor(Math.random() * 0x10) & 0x3) | 0x8],  //specify 2 high bits of clock sequence
185 			I2H[Math.floor(Math.random() * 0x10)],
186 			I2H[Math.floor(Math.random() * 0x10)],
187 			I2H[Math.floor(Math.random() * 0x10)],
188 			"-",
189 			I2H[Math.floor(Math.random() * 0x10)],
190 			I2H[Math.floor(Math.random() * 0x10)],
191 			I2H[Math.floor(Math.random() * 0x10)],
192 			I2H[Math.floor(Math.random() * 0x10)],
193 			I2H[Math.floor(Math.random() * 0x10)],
194 			I2H[Math.floor(Math.random() * 0x10)],
195 			I2H[Math.floor(Math.random() * 0x10)],
196 			I2H[Math.floor(Math.random() * 0x10)],
197 			I2H[Math.floor(Math.random() * 0x10)],
198 			I2H[Math.floor(Math.random() * 0x10)],
199 			I2H[Math.floor(Math.random() * 0x10)],
200 			I2H[Math.floor(Math.random() * 0x10)]
201 		].join("");
202 	}
203 	
204 	function escapeURIComponent(str) {
205 		return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A');
206 	}
207 	
208 	function formatURI(uri) {
209 		if (typeof uri === "string" && uri.indexOf("#") === -1) {
210 			uri += "#";
211 		}
212 		return uri;
213 	}
214 	
215 	/**
216 	 * Defines an error, found by a schema, with an instance.
217 	 * This class can only be instantiated by {@link Report#addError}. 
218 	 * 
219 	 * @name ValidationError
220 	 * @class
221 	 * @see Report#addError
222 	 */
223 	
224 	/**
225 	 * The URI of the instance that has the error.
226 	 * 
227 	 * @name ValidationError.prototype.uri
228 	 * @type String
229 	 */
230 	
231 	/**
232 	 * The URI of the schema that generated the error.
233 	 * 
234 	 * @name ValidationError.prototype.schemaUri
235 	 * @type String
236 	 */
237 	
238 	/**
239 	 * The name of the schema attribute that generated the error.
240 	 * 
241 	 * @name ValidationError.prototype.attribute
242 	 * @type String
243 	 */
244 	
245 	/**
246 	 * An user-friendly (English) message about what failed to validate.
247 	 * 
248 	 * @name ValidationError.prototype.message
249 	 * @type String
250 	 */
251 	
252 	/**
253 	 * The value of the schema attribute that generated the error.
254 	 * 
255 	 * @name ValidationError.prototype.details
256 	 * @type Any
257 	 */
258 	
259 	/**
260 	 * Reports are returned from validation methods to describe the result of a validation.
261 	 * 
262 	 * @name Report
263 	 * @class
264 	 * @see JSONSchema#validate
265 	 * @see Environment#validate
266 	 */
267 	
268 	function Report() {
269 		/**
270 		 * An array of {@link ValidationError} objects that define all the errors generated by the schema against the instance.
271 		 * 
272 		 * @name Report.prototype.errors
273 		 * @type Array
274 		 * @see Report#addError
275 		 */
276 		this.errors = [];
277 		
278 		/**
279 		 * A hash table of every instance and what schemas were validated against it.
280 		 * <p>
281 		 * The key of each item in the table is the URI of the instance that was validated.
282 		 * The value of this key is an array of strings of URIs of the schema that validated it.
283 		 * </p>
284 		 * 
285 		 * @name Report.prototype.validated
286 		 * @type Object
287 		 * @see Report#registerValidation
288 		 * @see Report#isValidatedBy
289 		 */
290 		this.validated = {};
291 		
292 		/**
293 		 * If the report is generated by {@link Environment#validate}, this field is the generated instance.
294 		 * 
295 		 * @name Report.prototype.instance
296 		 * @type JSONInstance
297 		 * @see Environment#validate
298 		 */
299 		
300 		/**
301 		 * If the report is generated by {@link Environment#validate}, this field is the generated schema.
302 		 * 
303 		 * @name Report.prototype.schema
304 		 * @type JSONSchema
305 		 * @see Environment#validate
306 		 */
307 		 
308 		/**
309 		 * If the report is generated by {@link Environment#validate}, this field is the schema's schema.
310 		 * This value is the same as calling <code>schema.getSchema()</code>.
311 		 * 
312 		 * @name Report.prototype.schemaSchema
313 		 * @type JSONSchema
314 		 * @see Environment#validate
315 		 * @see JSONSchema#getSchema
316 		 */
317 	}
318 	
319 	/**
320 	 * Adds a {@link ValidationError} object to the <a href="#errors"><code>errors</code></a> field.
321 	 * 
322 	 * @param {JSONInstance} instance The instance that is invalid
323 	 * @param {JSONSchema} schema The schema that was validating the instance
324 	 * @param {String} attr The attribute that failed to validated
325 	 * @param {String} message A user-friendly message on why the schema attribute failed to validate the instance
326 	 * @param {Any} details The value of the schema attribute
327 	 */
328 	
329 	Report.prototype.addError = function (instance, schema, attr, message, details) {
330 		this.errors.push({
331 			uri : instance.getURI(),
332 			schemaUri : schema.getURI(),
333 			attribute : attr,
334 			message : message,
335 			details : details
336 		});
337 	};
338 	
339 	/**
340 	 * Registers that the provided instance URI has been validated by the provided schema URI. 
341 	 * This is recorded in the <a href="#validated"><code>validated</code></a> field.
342 	 * 
343 	 * @param {String} uri The URI of the instance that was validated
344 	 * @param {String} schemaUri The URI of the schema that validated the instance
345 	 */
346 	
347 	Report.prototype.registerValidation = function (uri, schemaUri) {
348 		if (!this.validated[uri]) {
349 			this.validated[uri] = [ schemaUri ];
350 		} else {
351 			this.validated[uri].push(schemaUri);
352 		}
353 	};
354 	
355 	/**
356 	 * Returns if an instance with the provided URI has been validated by the schema with the provided URI. 
357 	 * 
358 	 * @param {String} uri The URI of the instance
359 	 * @param {String} schemaUri The URI of a schema
360 	 * @returns {Boolean} If the instance has been validated by the schema.
361 	 */
362 	
363 	Report.prototype.isValidatedBy = function (uri, schemaUri) {
364 		return !!this.validated[uri] && searchArray(this.validated[uri], schemaUri) !== -1;
365 	};
366 	
367 	/**
368 	 * A wrapper class for binding an Environment, URI and helper methods to an instance. 
369 	 * This class is most commonly instantiated with {@link Environment#createInstance}.
370 	 * 
371 	 * @name JSONInstance
372 	 * @class
373 	 * @param {Environment} env The environment this instance belongs to
374 	 * @param {JSONInstance|Any} json The value of the instance
375 	 * @param {String} [uri] The URI of the instance. If undefined, the URI will be a randomly generated UUID. 
376 	 * @param {String} [fd] The fragment delimiter for properties. If undefined, uses the environment default.
377 	 */
378 	
379 	function JSONInstance(env, json, uri, fd) {
380 		if (json instanceof JSONInstance) {
381 			if (typeof fd !== "string") {
382 				fd = json._fd;
383 			}
384 			if (typeof uri !== "string") {
385 				uri = json._uri;
386 			}
387 			json = json._value;
388 		}
389 		
390 		if (typeof uri !== "string") {
391 			uri = "urn:uuid:" + randomUUID() + "#";
392 		} else if (uri.indexOf(":") === -1) {
393 			uri = formatURI(URI.resolve("urn:uuid:" + randomUUID() + "#", uri));
394 		}
395 		
396 		this._env = env;
397 		this._value = json;
398 		this._uri = uri;
399 		this._fd = fd || this._env._options["defaultFragmentDelimiter"];
400 	}
401 	
402 	/**
403 	 * Returns the environment the instance is bound to.
404 	 * 
405 	 * @returns {Environment} The environment of the instance
406 	 */
407 	
408 	JSONInstance.prototype.getEnvironment = function () {
409 		return this._env;
410 	};
411 	
412 	/**
413 	 * Returns the name of the type of the instance.
414 	 * 
415 	 * @returns {String} The name of the type of the instance
416 	 */
417 	
418 	JSONInstance.prototype.getType = function () {
419 		return typeOf(this._value);
420 	};
421 	
422 	/**
423 	 * Returns the JSON value of the instance.
424 	 * 
425 	 * @returns {Any} The actual JavaScript value of the instance
426 	 */
427 	
428 	JSONInstance.prototype.getValue = function () {
429 		return this._value;
430 	};
431 	
432 	/**
433 	 * Returns the URI of the instance.
434 	 * 
435 	 * @returns {String} The URI of the instance
436 	 */
437 	
438 	JSONInstance.prototype.getURI = function () {
439 		return this._uri;
440 	};
441 	
442 	/**
443 	 * Returns a resolved URI of a provided relative URI against the URI of the instance.
444 	 * 
445 	 * @param {String} uri The relative URI to resolve
446 	 * @returns {String} The resolved URI
447 	 */
448 	
449 	JSONInstance.prototype.resolveURI = function (uri) {
450 		return formatURI(URI.resolve(this._uri, uri));
451 	};
452 	
453 	/**
454 	 * Returns an array of the names of all the properties.
455 	 * 
456 	 * @returns {Array} An array of strings which are the names of all the properties
457 	 */
458 	
459 	JSONInstance.prototype.getPropertyNames = function () {
460 		return keys(this._value);
461 	};
462 	
463 	/**
464 	 * Returns a {@link JSONInstance} of the value of the provided property name. 
465 	 * 
466 	 * @param {String} key The name of the property to fetch
467 	 * @returns {JSONInstance} The instance of the property value
468 	 */
469 	
470 	JSONInstance.prototype.getProperty = function (key) {
471 		var value = this._value ? this._value[key] : undefined;
472 		if (value instanceof JSONInstance) {
473 			return value;
474 		}
475 		//else
476 		return new JSONInstance(this._env, value, this._uri + this._fd + escapeURIComponent(key), this._fd);
477 	};
478 	
479 	/**
480 	 * Returns all the property instances of the target instance.
481 	 * <p>
482 	 * If the target instance is an Object, then the method will return a hash table of {@link JSONInstance}s of all the properties. 
483 	 * If the target instance is an Array, then the method will return an array of {@link JSONInstance}s of all the items.
484 	 * </p> 
485 	 * 
486 	 * @returns {Object|Array|undefined} The list of instances for all the properties
487 	 */
488 	
489 	JSONInstance.prototype.getProperties = function () {
490 		var type = typeOf(this._value),
491 			self = this;
492 		
493 		if (type === "object") {
494 			return mapObject(this._value, function (value, key) {
495 				if (value instanceof JSONInstance) {
496 					return value;
497 				}
498 				return new JSONInstance(self._env, value, self._uri + self._fd + escapeURIComponent(key), self._fd);
499 			});
500 		} else if (type === "array") {
501 			return mapArray(this._value, function (value, key) {
502 				if (value instanceof JSONInstance) {
503 					return value;
504 				}
505 				return new JSONInstance(self._env, value, self._uri + self._fd + escapeURIComponent(key), self._fd);
506 			});
507 		}
508 	};
509 	
510 	/**
511 	 * Returns the JSON value of the provided property name. 
512 	 * This method is a faster version of calling <code>instance.getProperty(key).getValue()</code>.
513 	 * 
514 	 * @param {String} key The name of the property
515 	 * @returns {Any} The JavaScript value of the instance
516 	 * @see JSONInstance#getProperty
517 	 * @see JSONInstance#getValue
518 	 */
519 	
520 	JSONInstance.prototype.getValueOfProperty = function (key) {
521 		if (this._value) {
522 			if (this._value[key] instanceof JSONInstance) {
523 				return this._value[key]._value;
524 			}
525 			return this._value[key];
526 		}
527 	};
528 	
529 	/**
530 	 * Return if the provided value is the same as the value of the instance.
531 	 * 
532 	 * @param {JSONInstance|Any} instance The value to compare
533 	 * @returns {Boolean} If both the instance and the value match
534 	 */
535 	
536 	JSONInstance.prototype.equals = function (instance) {
537 		if (instance instanceof JSONInstance) {
538 			return this._value === instance._value;
539 		}
540 		//else
541 		return this._value === instance;
542 	};
543 	
544 	/**
545 	 * Warning: Not a generic clone function
546 	 * Produces a JSV acceptable clone
547 	 */
548 	
549 	function clone(obj, deep) {
550 		var newObj, x;
551 		
552 		if (obj instanceof JSONInstance) {
553 			obj = obj.getValue();
554 		}
555 		
556 		switch (typeOf(obj)) {
557 		case "object":
558 			if (deep) {
559 				newObj = {};
560 				for (x in obj) {
561 					if (obj[x] !== O[x]) {
562 						newObj[x] = clone(obj[x], deep);
563 					}
564 				}
565 				return newObj;
566 			} else {
567 				return createObject(obj);
568 			}
569 			break;
570 		case "array":
571 			if (deep) {
572 				newObj = new Array(obj.length);
573 				x = obj.length;
574 				while (--x >= 0) {
575 					newObj[x] = clone(obj[x], deep);
576 				}
577 				return newObj;
578 			} else {
579 				return Array.pototype.slice.call(obj);
580 			}
581 			break;
582 		default:
583 			return obj;
584 		}
585 	}
586 	
587 	/**
588 	 * This class binds a {@link JSONInstance} with a {@link JSONSchema} to provided context aware methods. 
589 	 * 
590 	 * @name JSONSchema
591 	 * @class
592 	 * @param {Environment} env The environment this schema belongs to
593 	 * @param {JSONInstance|Any} json The value of the schema
594 	 * @param {String} [uri] The URI of the schema. If undefined, the URI will be a randomly generated UUID. 
595 	 * @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If <code>undefined</code>, the environment's default schema will be used. If <code>true</code>, the instance's schema will be itself.
596 	 * @extends JSONInstance
597 	 */
598 	
599 	function JSONSchema(env, json, uri, schema) {
600 		var fr;
601 		JSONInstance.call(this, env, json, uri);
602 		
603 		if (schema === true) {
604 			this._schema = this;
605 		} else if (json instanceof JSONSchema && !(schema instanceof JSONSchema)) {
606 			this._schema = json._schema;  //TODO: Make sure cross environments don't mess everything up
607 		} else {
608 			this._schema = schema instanceof JSONSchema ? schema : this._env.getDefaultSchema() || JSONSchema.createEmptySchema(this._env);
609 		}
610 		
611 		//determine fragment delimiter from schema
612 		fr = this._schema.getValueOfProperty("fragmentResolution");
613 		if (fr === "dot-delimited") {
614 			this._fd = ".";
615 		} else if (fr === "slash-delimited") {
616 			this._fd = "/";
617 		}
618 	}
619 	
620 	JSONSchema.prototype = createObject(JSONInstance.prototype);
621 	
622 	/**
623 	 * Creates an empty schema.
624 	 * 
625 	 * @param {Environment} env The environment of the schema
626 	 * @returns {JSONSchema} The empty schema, who's schema is itself.
627 	 */
628 	
629 	JSONSchema.createEmptySchema = function (env) {
630 		var schema = createObject(JSONSchema.prototype);
631 		JSONInstance.call(schema, env, {}, undefined, undefined);
632 		schema._schema = schema;
633 		return schema;
634 	};
635 	
636 	/**
637 	 * Returns the schema of the schema.
638 	 * 
639 	 * @returns {JSONSchema} The schema of the schema
640 	 */
641 	
642 	JSONSchema.prototype.getSchema = function () {
643 		return this._schema;
644 	};
645 	
646 	/**
647 	 * Returns the value of the provided attribute name.
648 	 * <p>
649 	 * This method is different from {@link JSONInstance#getProperty} as the named property 
650 	 * is converted using a parser defined by the schema's schema before being returned. This
651 	 * makes the return value of this method attribute dependent.
652 	 * </p>
653 	 * 
654 	 * @param {String} key The name of the attribute
655 	 * @param {Any} [arg] Some attribute parsers accept special arguments for returning resolved values. This is attribute dependent.
656 	 * @returns {JSONSchema|Any} The value of the attribute
657 	 */
658 	
659 	JSONSchema.prototype.getAttribute = function (key, arg) {
660 		if (this._attributes && !arg) {
661 			return this._attributes[key];
662 		}
663 		
664 		var schemaProperty = this._schema.getProperty("properties").getProperty(key),
665 			parser = schemaProperty.getValueOfProperty("parser"),
666 			property = this.getProperty(key);
667 		if (typeof parser === "function") {
668 			return parser(property, schemaProperty, arg);
669 		}
670 		//else
671 		return property.getValue();
672 	};
673 	
674 	/**
675 	 * Returns all the attributes of the schema.
676 	 * 
677 	 * @returns {Object} A map of all parsed attribute values
678 	 */
679 	
680 	JSONSchema.prototype.getAttributes = function () {
681 		var properties, schemaProperties, key, schemaProperty, parser;
682 		
683 		if (!this._attributes && this.getType() === "object") {
684 			properties = this.getProperties();
685 			schemaProperties = this._schema.getProperty("properties");
686 			this._attributes = {};
687 			for (key in properties) {
688 				if (properties[key] !== O[key]) {
689 					schemaProperty = schemaProperties && schemaProperties.getProperty(key);
690 					parser = schemaProperty && schemaProperty.getValueOfProperty("parser");
691 					if (typeof parser === "function") {
692 						this._attributes[key] = parser(properties[key], schemaProperty);
693 					} else {
694 						this._attributes[key] = properties[key].getValue();
695 					}
696 				}
697 			}
698 		}
699 		
700 		return clone(this._attributes, false);
701 	};
702 	
703 	/**
704 	 * Convenience method for retrieving a link or link object from a schema. 
705 	 * This method is the same as calling <code>schema.getAttribute("links", [rel, instance])[0];</code>.
706 	 * 
707 	 * @param {String} rel The link relationship
708 	 * @param {JSONInstance} [instance] The instance to resolve any URIs from
709 	 * @returns {String|Object|undefined} If <code>instance</code> is provided, a string containing the resolve URI of the link is returned.
710 	 *   If <code>instance</code> is not provided, a link object is returned with details of the link.
711 	 *   If no link with the provided relationship exists, <code>undefined</code> is returned.
712 	 * @see JSONSchema#getAttribute
713 	 */
714 	
715 	JSONSchema.prototype.getLink = function (rel, instance) {
716 		var schemaLinks = this.getAttribute("links", [rel, instance]);
717 		if (schemaLinks && schemaLinks.length && schemaLinks[schemaLinks.length - 1]) {
718 			return schemaLinks[schemaLinks.length - 1];
719 		}
720 	};
721 	
722 	/**
723 	 * Validates the provided instance against the target schema and returns a {@link Report}.
724 	 * 
725 	 * @param {JSONInstance|Any} instance The instance to validate; may be a {@link JSONInstance} or any JavaScript value
726 	 * @param {Report} [report] A {@link Report} to concatenate the result of the validation to. If <code>undefined</code>, a new {@link Report} is created. 
727 	 * @param {JSONInstance} [parent] The parent/containing instance of the provided instance
728 	 * @param {JSONSchema} [parentSchema] The schema of the parent/containing instance
729 	 * @param {String} [name] The name of the parent object's property that references the instance
730 	 * @returns {Report} The result of the validation
731 	 */
732 	
733 	JSONSchema.prototype.validate = function (instance, report, parent, parentSchema, name) {
734 		var validator = this._schema.getValueOfProperty("validator");
735 		
736 		if (!(instance instanceof JSONInstance)) {
737 			instance = this.getEnvironment().createInstance(instance);
738 		}
739 		
740 		if (!(report instanceof Report)) {
741 			report = new Report();
742 		}
743 		
744 		if (typeof validator === "function" && !report.isValidatedBy(instance.getURI(), this.getURI())) {
745 			report.registerValidation(instance.getURI(), this.getURI());
746 			validator(instance, this, this._schema, report, parent, parentSchema, name);
747 		}
748 		
749 		return report;
750 	};
751 	
752 	/**
753 	 * Merges two schemas/instances together.
754 	 */
755 	
756 	function inherits(base, extra, extension) {
757 		var baseType = base instanceof JSONSchema ? "schema" : typeOf(base),
758 			extraType = extra instanceof JSONSchema ? "schema" : typeOf(extra),
759 			child, x;
760 		
761 		if (extraType === "undefined") {
762 			return clone(base, true);
763 		} else if (baseType === "undefined" || extraType !== baseType) {
764 			return clone(extra, true);
765 		} else if (extraType === "object" || extraType === "schema") {
766 			if (baseType === "schema") {
767 				base = base.getAttributes();
768 				extra = extra.getAttributes();
769 				if (extra["extends"] && extension && extra["extends"] instanceof JSONSchema) {
770 					extra["extends"] = [ extra["extends"] ];
771 				}
772 			}
773 			child = clone(base, true);  //this could be optimized as some properties get overwritten
774 			for (x in extra) {
775 				if (extra[x] !== O[x]) {
776 					child[x] = inherits(base[x], extra[x], extension);
777 				}
778 			}
779 			return child;
780 		} else {
781 			return clone(extra, true);
782 		}
783 	}
784 	
785 	/**
786 	 * An Environment is a sandbox of schemas thats behavior is different from other environments.
787 	 * 
788 	 * @name Environment
789 	 * @class
790 	 */
791 	
792 	function Environment() {
793 		this._id = randomUUID();
794 		this._schemas = {};
795 		this._options = {};
796 	}
797 	
798 	/**
799 	 * Returns a clone of the target environment.
800 	 * 
801 	 * @returns {Environment} A new {@link Environment} that is a exact copy of the target environment 
802 	 */
803 	
804 	Environment.prototype.clone = function () {
805 		var env = new Environment();
806 		env._schemas = createObject(this._schemas);
807 		env._options = createObject(this._options);
808 		
809 		return env;
810 	};
811 	
812 	/**
813 	 * Returns a new {@link JSONInstance} of the provided data.
814 	 * 
815 	 * @param {JSONInstance|Any} data The value of the instance
816 	 * @param {String} [uri] The URI of the instance. If undefined, the URI will be a randomly generated UUID. 
817 	 * @returns {JSONInstance} A new {@link JSONInstance} from the provided data
818 	 */
819 	
820 	Environment.prototype.createInstance = function (data, uri) {
821 		var instance;
822 		uri = formatURI(uri);
823 		
824 		if (data instanceof JSONInstance && (!uri || data.getURI() === uri)) {
825 			return data;
826 		}
827 		//else
828 		instance = new JSONInstance(this, data, uri);
829 		
830 		return instance;
831 	};
832 	
833 	/**
834 	 * Creates a new {@link JSONSchema} from the provided data, and registers it with the environment. 
835 	 * 
836 	 * @param {JSONInstance|Any} data The value of the schema
837 	 * @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If <code>undefined</code>, the environment's default schema will be used. If <code>true</code>, the instance's schema will be itself.
838 	 * @param {String} [uri] The URI of the schema. If undefined, the URI will be a randomly generated UUID. 
839 	 * @returns {JSONSchema} A new {@link JSONSchema} from the provided data
840 	 */
841 	
842 	Environment.prototype.createSchema = function (data, schema, uri) {
843 		var instance, 
844 			initializer;
845 		uri = formatURI(uri);
846 		
847 		if (data instanceof JSONSchema && (!uri || data._uri === uri) && (!schema || data._schema.equals(schema))) {
848 			return data;
849 		}
850 		
851 		instance = new JSONSchema(this, data, uri, schema);
852 		
853 		initializer = instance.getSchema().getValueOfProperty("initializer");
854 		if (typeof initializer === "function") {
855 			instance = initializer(instance);
856 		}
857 		
858 		//register schema
859 		this._schemas[instance._uri] = instance;
860 		
861 		//build & cache the rest of the schema
862 		instance.getAttributes();
863 		
864 		return instance;
865 	};
866 	
867 	/**
868 	 * Creates an empty schema.
869 	 * 
870 	 * @param {Environment} env The environment of the schema
871 	 * @returns {JSONSchema} The empty schema, who's schema is itself.
872 	 */
873 	
874 	Environment.prototype.createEmptySchema = function () {
875 		return JSONSchema.createEmptySchema(this);
876 	};
877 	
878 	/**
879 	 * Returns the schema registered with the provided URI.
880 	 * 
881 	 * @param {String} uri The absolute URI of the required schema
882 	 * @returns {JSONSchema|undefined} The request schema, or <code>undefined</code> if not found
883 	 */
884 	
885 	Environment.prototype.findSchema = function (uri) {
886 		return this._schemas[formatURI(uri)];
887 	};
888 	
889 	/**
890 	 * Sets the specified environment option to the specified value.
891 	 * 
892 	 * @param {String} name The name of the environment option to set
893 	 * @param {Any} value The new value of the environment option
894 	 */
895 	
896 	Environment.prototype.setOption = function (name, value) {
897 		this._options[name] = value;
898 	};
899 	
900 	/**
901 	 * Returns the specified environment option.
902 	 * 
903 	 * @param {String} name The name of the environment option to set
904 	 * @returns {Any} The value of the environment option
905 	 */
906 	
907 	Environment.prototype.getOption = function (name) {
908 		return this._options[name];
909 	};
910 	
911 	/**
912 	 * Sets the default fragment delimiter of the environment.
913 	 * 
914 	 * @deprecated Use {@link Environment#setOption} with option "defaultFragmentDelimiter"
915 	 * @param {String} fd The fragment delimiter character
916 	 */
917 	
918 	Environment.prototype.setDefaultFragmentDelimiter = function (fd) {
919 		if (typeof fd === "string" && fd.length > 0) {
920 			this._options["defaultFragmentDelimiter"] = fd;
921 		}
922 	};
923 	
924 	/**
925 	 * Returns the default fragment delimiter of the environment.
926 	 * 
927 	 * @deprecated Use {@link Environment#getOption} with option "defaultFragmentDelimiter"
928 	 * @returns {String} The fragment delimiter character
929 	 */
930 	
931 	Environment.prototype.getDefaultFragmentDelimiter = function () {
932 		return this._options["defaultFragmentDelimiter"];
933 	};
934 	
935 	/**
936 	 * Sets the URI of the default schema for the environment.
937 	 * 
938 	 * @deprecated Use {@link Environment#setOption} with option "defaultSchemaURI"
939 	 * @param {String} uri The default schema URI
940 	 */
941 	
942 	Environment.prototype.setDefaultSchemaURI = function (uri) {
943 		if (typeof uri === "string") {
944 			this._options["defaultSchemaURI"] = formatURI(uri);
945 		}
946 	};
947 	
948 	/**
949 	 * Returns the default schema of the environment.
950 	 * 
951 	 * @returns {JSONSchema} The default schema
952 	 */
953 	
954 	Environment.prototype.getDefaultSchema = function () {
955 		return this.findSchema(this._options["defaultSchemaURI"]);
956 	};
957 	
958 	/**
959 	 * Validates both the provided schema and the provided instance, and returns a {@link Report}. 
960 	 * If the schema fails to validate, the instance will not be validated.
961 	 * 
962 	 * @param {JSONInstance|Any} instanceJSON The {@link JSONInstance} or JavaScript value to validate.
963 	 * @param {JSONSchema|Any} schemaJSON The {@link JSONSchema} or JavaScript value to use in the validation. This will also be validated againt the schema's schema.
964 	 * @returns {Report} The result of the validation
965 	 */
966 	
967 	Environment.prototype.validate = function (instanceJSON, schemaJSON) {
968 		var instance = this.createInstance(instanceJSON),
969 			schema = this.createSchema(schemaJSON),
970 			schemaSchema = schema.getSchema(),
971 			report = new Report();
972 		
973 		report.instance = instance;
974 		report.schema = schema;
975 		report.schemaSchema = schemaSchema;
976 		
977 		schemaSchema.validate(schema, report);
978 			
979 		if (report.errors.length) {
980 			return report;
981 		}
982 		
983 		return schema.validate(instance, report);
984 	};
985 	
986 	
987 	/**
988 	 * A globaly accessible object that provides the ability to create and manage {@link Environments},
989 	 * as well as providing utility methods.
990 	 * 
991 	 * @namespace
992 	 */
993 	
994 	JSV = {
995 		_environments : {},
996 		_defaultEnvironmentID : "",
997 		
998 		/**
999 		 * Returns if the provide value is an instance of {@link JSONInstance}.
1000 		 * 
1001 		 * @param o The value to test
1002 		 * @returns {Boolean} If the provide value is an instance of {@link JSONInstance}
1003 		 */
1004 		
1005 		isJSONInstance : function (o) {
1006 			return o instanceof JSONInstance;
1007 		},
1008 		
1009 		/**
1010 		 * Returns if the provide value is an instance of {@link JSONSchema}.
1011 		 * 
1012 		 * @param o The value to test
1013 		 * @returns {Boolean} If the provide value is an instance of {@link JSONSchema}
1014 		 */
1015 		
1016 		isJSONSchema : function (o) {
1017 			return o instanceof JSONSchema;
1018 		},
1019 		
1020 		/**
1021 		 * Creates and returns a new {@link Environment} that is a clone of the environment registered with the provided ID.
1022 		 * If no environment ID is provided, the default environment is cloned.
1023 		 * 
1024 		 * @param {String} [id] The ID of the environment to clone. If <code>undefined</code>, the default environment ID is used.
1025 		 * @returns {Environment} A newly cloned {@link Environment}
1026 		 * @throws {Error} If there is no environment registered with the provided ID
1027 		 */
1028 		
1029 		createEnvironment : function (id) {
1030 			id = id || this._defaultEnvironmentID;
1031 			
1032 			if (!this._environments[id]) {
1033 				throw new Error("Unknown Environment ID");
1034 			}
1035 			//else
1036 			return this._environments[id].clone();
1037 		},
1038 		
1039 		Environment : Environment,
1040 		
1041 		/**
1042 		 * Registers the provided {@link Environment} with the provided ID.
1043 		 * 
1044 		 * @param {String} id The ID of the environment
1045 		 * @param {Environment} env The environment to register
1046 		 */
1047 		
1048 		registerEnvironment : function (id, env) {
1049 			id = id || (env || 0)._id;
1050 			if (id && !this._environments[id] && env instanceof Environment) {
1051 				env._id = id;
1052 				this._environments[id] = env;
1053 			}
1054 		},
1055 		
1056 		/**
1057 		 * Sets which registered ID is the default environment.
1058 		 * 
1059 		 * @param {String} id The ID of the registered environment that is default
1060 		 * @throws {Error} If there is no registered environment with the provided ID
1061 		 */
1062 		
1063 		setDefaultEnvironmentID : function (id) {
1064 			if (typeof id === "string") {
1065 				if (!this._environments[id]) {
1066 					throw new Error("Unknown Environment ID");
1067 				}
1068 				
1069 				this._defaultEnvironmentID = id;
1070 			}
1071 		},
1072 		
1073 		/**
1074 		 * Returns the ID of the default environment.
1075 		 * 
1076 		 * @returns {String} The ID of the default environment
1077 		 */
1078 		
1079 		getDefaultEnvironmentID : function () {
1080 			return this._defaultEnvironmentID;
1081 		},
1082 		
1083 		//
1084 		// Utility Functions
1085 		//
1086 		
1087 		/**
1088 		 * Returns the name of the type of the provided value.
1089 		 *
1090 		 * @event //utility
1091 		 * @param {Any} o The value to determine the type of
1092 		 * @returns {String} The name of the type of the value
1093 		 */
1094 		typeOf : typeOf,
1095 		
1096 		/**
1097 		 * Return a new object that inherits all of the properties of the provided object.
1098 		 *
1099 		 * @event //utility
1100 		 * @param {Object} proto The prototype of the new object
1101 		 * @returns {Object} A new object that inherits all of the properties of the provided object
1102 		 */
1103 		createObject : createObject,
1104 		
1105 		/**
1106 		 * Returns a new object with each property transformed by the iterator.
1107 		 *
1108 		 * @event //utility
1109 		 * @param {Object} obj The object to transform
1110 		 * @param {Function} iterator A function that returns the new value of the provided property
1111 		 * @param {Object} [scope] The value of <code>this</code> in the iterator
1112 		 * @returns {Object} A new object with each property transformed
1113 		 */
1114 		mapObject : mapObject,
1115 		
1116 		/**
1117 		 * Returns a new array with each item transformed by the iterator.
1118 		 * 
1119 		 * @event //utility
1120 		 * @param {Array} arr The array to transform
1121 		 * @param {Function} iterator A function that returns the new value of the provided item
1122 		 * @param {Object} scope The value of <code>this</code> in the iterator
1123 		 * @returns {Array} A new array with each item transformed
1124 		 */
1125 		mapArray : mapArray,
1126 		
1127 		/**
1128 		 * Returns a new array that only contains the items allowed by the iterator.
1129 		 *
1130 		 * @event //utility
1131 		 * @param {Array} arr The array to filter
1132 		 * @param {Function} iterator The function that returns true if the provided property should be added to the array
1133 		 * @param {Object} scope The value of <code>this</code> within the iterator
1134 		 * @returns {Array} A new array that contains the items allowed by the iterator
1135 		 */
1136 		filterArray : filterArray,
1137 		
1138 		/**
1139 		 * Returns the first index in the array that the provided item is located at.
1140 		 *
1141 		 * @event //utility
1142 		 * @param {Array} arr The array to search
1143 		 * @param {Any} o The item being searched for
1144 		 * @returns {Number} The index of the item in the array, or <code>-1</code> if not found
1145 		 */
1146 		searchArray : searchArray,
1147 			
1148 		/**
1149 		 * Returns an array representation of a value.
1150 		 * <ul>
1151 		 * <li>For array-like objects, the value will be casted as an Array type.</li>
1152 		 * <li>If an array is provided, the function will simply return the same array.</li>
1153 		 * <li>For a null or undefined value, the result will be an empty Array.</li>
1154 		 * <li>For all other values, the value will be the first element in a new Array. </li>
1155 		 * </ul>
1156 		 *
1157 		 * @event //utility
1158 		 * @param {Any} o The value to convert into an array
1159 		 * @returns {Array} The value as an array
1160 		 */
1161 		toArray : toArray,
1162 		
1163 		/**
1164 		 * Returns an array of the names of all properties of an object.
1165 		 * 
1166 		 * @event //utility
1167 		 * @param {Object|Array} o The object in question
1168 		 * @returns {Array} The names of all properties
1169 		 */
1170 		keys : keys,
1171 		
1172 		/**
1173 		 * Mutates the array by pushing the provided value onto the array only if it is not already there.
1174 		 *
1175 		 * @event //utility
1176 		 * @param {Array} arr The array to modify
1177 		 * @param {Any} o The object to add to the array if it is not already there
1178 		 * @returns {Array} The provided array for chaining
1179 		 */
1180 		pushUnique : pushUnique,
1181 		
1182 		/**
1183 		 * Creates a copy of the target object.
1184 		 * <p>
1185 		 * This method will create a new instance of the target, and then mixin the properties of the target.
1186 		 * If <code>deep</code> is <code>true</code>, then each property will be cloned before mixin.
1187 		 * </p>
1188 		 * <p><b>Warning</b>: This is not a generic clone function, as it will only properly clone objects and arrays.</p>
1189 		 * 
1190 		 * @event //utility
1191 		 * @param {Any} o The value to clone 
1192 		 * @param {Boolean} [deep=false] If each property should be recursively cloned
1193 		 * @returns A cloned copy of the provided value
1194 		 */
1195 		clone : clone,
1196 		
1197 		/**
1198 		 * Generates a pseudo-random UUID.
1199 		 * 
1200 		 * @event //utility
1201 		 * @returns {String} A new universally unique ID
1202 		 */
1203 		randomUUID : randomUUID,
1204 		
1205 		/**
1206 		 * Properly escapes a URI component for embedding into a URI string.
1207 		 * 
1208 		 * @event //utility
1209 		 * @param {String} str The URI component to escape
1210 		 * @returns {String} The escaped URI component
1211 		 */
1212 		escapeURIComponent : escapeURIComponent,
1213 		
1214 		/**
1215 		 * Returns a URI that is formated for JSV. Currently, this only ensures that the URI ends with a hash tag (<code>#</code>).
1216 		 * 
1217 		 * @event //utility
1218 		 * @param {String} uri The URI to format
1219 		 * @returns {String} The URI formatted for JSV
1220 		 */
1221 		formatURI : formatURI,
1222 		
1223 		/**
1224 		 * Merges two schemas/instance together.
1225 		 * 
1226 		 * @event //utility
1227 		 * @param {JSONSchema|Any} base The old value to merge
1228 		 * @param {JSONSchema|Any} extra The new value to merge
1229 		 * @param {Boolean} extension If the merge is a JSON Schema extension
1230 		 * @return {Any} The modified base value
1231 		 */
1232 		 
1233 		inherits : inherits
1234 	};
1235 	
1236 	this.JSV = JSV;  //set global object
1237 	exports.JSV = JSV;  //export to CommonJS
1238 	
1239 	require("./environments");  //load default environments
1240 	
1241 }());