Unfortunately, the ability to detect the class of an object does not allow us to distinguish objects created by user-defined constructors, nor objects created by means of the Module pattern. This is because all such objects report a class-name of 'Object', as shown in Example 36.
In principle, AJS_Validator could attempt to cater for the user-defined constructors problem by supporting a 'Constructors' property for Arg- and Rtn-Defs. This would be an array of references to the various constructor-functions whose progeny were permissible as arguments-to or return-values-from the validated method in question.
However, this would be prone to the problem of different execution-environments that the class-detection section explores; moreover, it would fail to address the problem inherent in the Module pattern, because the constructor-reference for objects that are created using that approach is always 'Object ()'.
// -- Example 36 ------------------------------------------------------------- function MyType () { // Initialisation code here. } function createObj () { return { method_A : function () { } }; } var Object_A = new MyType (); var Object_B = createObj (); console.log (AJS_Validator.getClass (Object_A)); // See the Ancillaries section for information console.log (AJS_Validator.getClass (Object_B)); // on AJS_Validator's getClass method. -- Output -------------------------------------------------------------------- Object Object
For these reasons, AJS_Validator supports the proprietary concept of 'tags'. A tag is simply an arbitrary, user-defined string/string-literal (it cannot be of another class) that you specify using the ApplyTag property within the RtnDef for a given object-creating method (note that they bear no relation to tags in HTML). When that method returns an object, AJS_Validator will attempt to attach a copy of the string to it as an extra property (called 'AJS_Validator_Tag'), which acts thereon as an indentifier by which the object's provenance may be determined.
This allows you to use the complementary Allow-/Disallow-Tags properties (see below) to constrain arguments and return values such that the objects to which they refer must/must-not carry a given AJS_Validator_Tag-string. This resolves the limitation mentioned above that applies when using class-names to police the kinds of objects that are passed/returned to/from a given method.
The AJS_Validator_Tag property of a given object is also of great use during debugging, where you need to track the passage of a object through a chain of calls (or returns), or to track a call-chain through a set of objects. This is akin to tagging molecules with a radioactive isotope, in order to view their progress through a biological system, which is to say that you can see a given object's tag in your watch/locals view in your debugger, and this is invaluable when developing complex systems.
Example 37 shows the use of a ValidationDef that causes the tag 'Progeny_of_MyObjGenerator' to be attached to all objects that createObj returns.
Note that tag values need not be unique, meaning that two or more objects may possess the same AJS_Validator_Tag value. Nor does AJS_Validator prescribe a particular format for tag-strings – the strings you use to tag the objects in your application are up to you entirely.
Finally in this section, note that tags are of further use in debugging because you can place a conditional break-point on a given line of code (in your favourite debugger) that will halt execution when an object with a given tag-value is encountered.
// -- Example 37 ------------------------------------------------------------- var MyObjGenerator = { createObj : function () { return { }; } }; AJS_Validator.applyValidationDef (MyObjGenerator, { RtnDef : { ApplyTag : "Progeny_of_MyObjGenerator" } // This string-property will be added to }); // objects returned from MyObjGenerator. var MyObj = MyObjGenerator.createObj (); console.log (MyObj.AJS_Validator_Tag); -- Output -------------------------------------------------------------------- Progeny_of_MyObjGenerator
Note too that the AJS, AJS_Validator and AJS_ODL objects all possess their own hard-coded AJS_Validator_Tag properties, as do any AJS_Logger objects (where their tags are appended with 0, 1, 2 etc. should you instantiate multiple loggers). This is not of seismic importance, but it does demonstrate that there is nothing to stop you hard-coding an AJS_Validator_Tag property into object-definitions of your creation, which you may wish to do should you not apply a ValidationDef to some kind of factory method that produces such objects.
Obviously, doing this will mean that the property remains in deployed code (unless you implement some sort of conditional commenting scheme using a preprocessor, which, of course, runs contrary to the motivation behind AJS_Validator), but the overhead of that may be negligible, depending on the size and number of the object(s) concerned, and the length of the tag-strings.
You may think that tags are redundant, given that you can supply in your calls to applyValidationDef and pushValidationDef a name for the method-owner to which a given ValidationDef should be applied (as the optional third argument in those functions' signatures).
In fact, in the case of ValidationDef-contravention exceptions, passing the object's identity in a given call to push-/apply-ValidationDef yields in any ensuing exception notification the identity of the object as it was known at the time that the ValidationDef was applied. In contrast, the object in question may also be returned from a function at some point, at which point it may be tagged because the owner of the returning-method is subject to its own ValidationDef.
Here the tag-value represents the object's identity as it is known at the point of return from the method in question, and, clearly, that identity may be different to the identity passed when the object received its ValidationDef originally. In such cases, it follows that AJS_Validator's support for the MethodOwnerName argument in push- and apply-ValidationDef calls complements its support for tags, and thus increases the amount of useful information that it can deliver when your application goes belly-up.
Do remember when using tags that some objects of native type are inextensible – they cannot acquire user-defined properties – moreover, user-defined objects may also be rendered inextensible through the use of the preventExtensions, seal or freeze methods of the Object constructor.
Given this, the presence of an ApplyTag in a RtnDef causes AJS_Validator to test for extensibility, and to add an AJS_Validator_Tag property to the object in question only if it can. If it cannot, AJS_Validator will call its exception handler, as Example 38 shows (which is an extension of Example 37), and the set of object-classes that cannot possess user-defined properties (and thus cannot possess AJS_Validator_Tag properties) is as follows:
See the Specifying Classes section for more information on object-literals.
Note that the more-recent native array-classes, like Int16Array or Float32Array, are not on this list. This is because it is unclear currently whether or not these should be extensible, and the various JS run-times differ in this respect.
Nevertheless, you can cause AJS_Validator to deem a given class to be inextensible by adding additional class-names to a small object that it carries internally. This is a trivial task, and clear instructions are given in a large, prominent comment in the code held within AJS_Validator.js.
// -- Example 38 ------------------------------------------------------------- var MyObjGenerator = { createObj : function () { var Obj = { }; // Create an object. Object.freeze (Obj); // Freeze it. return Obj; // Return it. } }; AJS_Validator.applyValidationDef (MyObjGenerator, { RtnDef : { ApplyTag : "Progeny_Of_My_Obj_Generator" } // Will not work, object }); // returned is frozen. var MyObj = MyObjGenerator.createObj (); -- Output -------------------------------------------------------------------- AJS_Validator_Exception: cannot apply the tag 'Progeny_Of_My_Obj_Generator' to the object returned from createObj because it has been rendered inextensible, and is thus incapable of acquiring new properties (Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag: undefined).
Despite the points made in the previous section, you may be subject to a design-tenet that requires certain objects to be sealed, frozen or rendered immune to extension. Given that this may conflict with your use of the ApplyTag property, AJS_Validator supports the presence of the PreventExt, Seal and Freeze properties within RtnDefs.
These are boolean properties, which, if present in a RtnDef with a value of true (they cannot appear in ArgDefs), will cause AJS_Validator to call the preventExtensions, seal and freeze methods respectively for you, after AJS_Validator has added any AJS_Validator_Tag property to the object in question.
This circumvents any conflict that would arise otherwise, but it does require that you disable temporarily any calls to the preventExtensions, seal and freeze methods that the application-method in question issues. This requires in turn that you re-enable those methods prior to deployment, should your design require it, and Example 39 illustrates these points.
Clearly, this runs contrary to one of the major advantages of using AJS_Validator, which is that the tool obviates (normally) having to perturb the code that implements the application. Frankly, this is a shame, but there is no way around this if we are to avail ourselves of the preventExtensions, seal and freeze methods when using AJS_Validator.
Do note that using PreventExt, Seal or Freeze does not require you to use ApplyTag too, as they are of value on their own. Note also that objects that must be rendered extension-proof, sealed or frozen must not be boolean-/number-/string-literals, as those types do not derive from the basic Object type that ECMAScript defines. (If you do try to freeze one of these types using AJS_Validator, the tool will apprise you of the problem.)
// -- Example 39 ------------------------------------------------------------- var MyObjGenerator = { createObj : function () { var Obj = { }; // Create an object. // Object.freeze (Obj); // Disable the freeze statement. return Obj; // Return it. } }; AJS_Validator.applyValidationDef (MyObjGenerator, { RtnDef : { ApplyTag : "Progeny_Of_My_Obj_Generator", Freeze : true } }); var MyObj = MyObjGenerator.createObj (); // MyObj is now tagged and frozen. console.log (MyObj.AJS_Validator_Tag); console.log (Object.isFrozen (MyObj)); -- Output -------------------------------------------------------------------- Progeny_Of_My_Obj_Generator true
To use tags to police the objects passed-to or returned-from a given method, define an AllowTags property within the relevant ArgDef/RtnDef within a ValidationDef for that method. This is simply an array that constitutes an analogue of the AllowClasses property, and which contains one or more RegExp objects that define the pattern(s) to which the AJS_Validator_Tag-values of objects that are passed or returned should conform.
That is to say: when an object is passed as an argument to a method, where a corresponding ArgDef for that method contains a AllowTags property, AJS_Validator will test the object's AJS_Validator_Tag property against the list of regular expressions in that array. If the object's AJS_Validator_Tag property does not match one of the patterns in the ArgDef's AllowTags-array then AJS_Validator will call its exception handler passing it a suitably-informative message.
Example 40 illustrates this. Here, three object-creation functions are defined as methods of ObjectCreator, as is a ValidationDef for each of those methods. The code also defines a method for the ObjectAcceptor argument, which accepts a single object-reference argument, and which also has a corresponding ValidationDef.
The ValidationDefs for the creator methods direct AJS_Validator to append an AJS_Validator_Tag property to the objects they return, which have values respectively of 'CAT', 'COT' and 'CUT'. The ValidationDef for the object acceptor directs AJS_Validator to accept only objects that carry an AJS_Validator_Tag that has 'C' as it leading character, and 'T' as its trailing character, and where a middle character must be present that is either 'A' or 'O'. This means that the first and second calls to ObjectAcceptor.accept proceed normally, but the third call causes AJS_Validator to invoke the exception handler because the object passed carries an AJS_Validator_Tag string with 'U' as its middle character.
You can also use a AllowTags stricture within a RtnDef, which will cause AJS_Validator to scrutinise the AJS_Validator_Tag property of the objects that a given method returns, and to call the exception handler in the absence of a match. Moreover, you can include an ApplyTag property too, such that, if the object in question passes the AllowTags test, AJS_Validator will then re-tag the little scamp with the tag that you specified. Furthermore, ArgDefs and RtnDefs can contain an AllowClasses and an AllowTags property, meaning that the object in question must satisfy both conditions for execution to continue.
Additionally, and in contrast with the AllowClasses property, empty AllowTags are permitted within ArgDefs and RtnDefs. This means that providing an empty AllowTags constrains matters such that the argument or return-value in question cannot possess an AJS_Validator_Tag property.
// -- Example 40 ------------------------------------------------------------- var ObjectCreator = { createCat : function () { return { }; }, createCot : function () { return { }; }, createCut : function () { return { }; } }; var ObjectAcceptor = { accept : function (Obj) { } }; //-------------------------------------- AJS_Validator.applyValidationDef (ObjectCreator, { MethodNames : ["createCat"], RtnDef : { ApplyTag : "CAT" } }); AJS_Validator.applyValidationDef (ObjectCreator, { MethodNames : ["createCot"], RtnDef : { ApplyTag : "COT" } }); AJS_Validator.applyValidationDef (ObjectCreator, { MethodNames : ["createCut"], RtnDef : { ApplyTag : "CUT" } }); AJS_Validator.applyValidationDef (ObjectAcceptor, { MethodNames : ["accept"], CallDef : [ { AllowTags : [/^C[AO]T$/] } ] }); //-------------------------------------- var Cat = ObjectCreator.createCat (); var Cot = ObjectCreator.createCot (); var Cut = ObjectCreator.createCut (); ObjectAcceptor.accept (Cat); // OK, tag matches ArgDef. ObjectAcceptor.accept (Cot); // Also OK. ObjectAcceptor.accept (Cut); // Not OK, tag does not match. -- Output -------------------------------------------------------------------- AJS_Validator_Exception: argument 0 (Obj) in call to accept carries 'CUT' as the value for its AJS_Validator_Tag property, which does not match any regular expression held within the corresponding AllowTags property within the corresponding ArgDef within the corresponding ValidationDef. The tag-pattern(s) that the relevant ArgDef allows are: /^C[AO]T$/. (Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag: undefined).
In the way that the DisallowClasses property complements the AllowClasses property, the AllowTags property has its complement in the form of DisallowTags.
This property must be an array of zero or more regular expressions, and it directs AJS_Validator in the way that one would expect. When used within an ArgDef, it precludes object-arguments that have an AJS_Validator_Tag-property that can be matched against one of the patterns in the DisallowTags array. Similarly, its presence within a RtnDef disallows the return of objects that possess an AJS_Validator_Tag property with a value that matches one of the patterns.
Note that, in contrast to non-empty AllowTags properties, the presence of a DisallowTags property in an ArgDef or RtnDef does not require arguments and return-values to possess an AJS_Validator_Tag property. Note too that empty DisallowTags properties are also permitted, even though they are redundant (because they allow anything), and this is in order to simplify the dynamic generation of ValidationDefs (which is the same reasoning behind the permissibility of empty ValidationDefs and empty DisallowClasses properties).
Example 41, which is very similar to Example 40 (and is equally-contrived), shows the DisallowTags property in action.
Note that, as with Allow-/Disallow-Classes, a DisallowTags property cannot appear alongside an AllowTags property. See the Property Concurrence and Collisions section for the reasoning behind this.
// -- Example 41 ------------------------------------------------------------- var ObjectCreator = { createCat : function () { return { }; }, createCot : function () { return { }; }, createCut : function () { return { }; } }; var ObjectAcceptor = { accept : function (Obj) { } }; //-------------------------------------- AJS_Validator.applyValidationDef (ObjectCreator, { MethodNames : ["createCat"], RtnDef : { ApplyTag : "CAT" } }); AJS_Validator.applyValidationDef (ObjectCreator, { MethodNames : ["createCot"], RtnDef : { ApplyTag : "COT" } }); AJS_Validator.applyValidationDef (ObjectCreator, { MethodNames : ["createCut"], RtnDef : { ApplyTag : "CUT" } }); AJS_Validator.applyValidationDef (ObjectAcceptor, { MethodNames : ["accept"], CallDef : [ { DisallowTags : [/^C[IEU]T$/] } ] }); //-------------------------------------- var Cat = ObjectCreator.createCat (); var Cot = ObjectCreator.createCot (); var Cut = ObjectCreator.createCut (); ObjectAcceptor.accept (Cat); // OK, tag does not match the disallowed pattern. ObjectAcceptor.accept (Cot); // Also OK. ObjectAcceptor.accept (Cut); // Not OK, tag matches the disallowed pattern. -- Output -------------------------------------------------------------------- AJS_Validator_Exception: argument 0 (Obj) in call to accept carries 'CUT' as the value for its AJS_Validator_Tag property, which the corresponding DisallowTags property within the corresponding ArgDef within the corresponding ValidationDef disallows. The tag-pattern(s) that the relevant ArgDef disallows are: /^C[IEU]T$/. (Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag: undefined).