Issues (204)

src/InterfaceBuilder.js (1 issue)

1
/* Javascript Object Inheritance Implementation                ______  ________
2
 * (c) 2016 <[email protected]>                             __ / / __ \/  _/  _/
3
 * Licensed under MIT.                                    / // / /_/ // /_/ /
4
 * ------------------------------------------------------ \___/\____/___/__*/
5
6
JOII = typeof (JOII) !== 'undefined' ? JOII : {};
7
JOII.InterfaceRegistry = {};
8
9
/**
10
 * Builds an interface for a class to enforce implementation and signature
11
 * of a set of properties and methods.
12
 *
13
 * @return {Object}
14
 */
15
JOII.InterfaceBuilder = function() {
16
17
    var args        = JOII.Compat.ParseArguments(arguments),
18
        name        = args.name,
19
        parameters  = args.parameters,
20
        body        = args.body;
21
22
    // Start by creating a prototype based on the parameters and body.
23
    // The definition will be the resulting function containing all
24
    // required information about this interface.
25
    var prototype  = JOII.PrototypeBuilder(name, parameters, body, true),
26
        definition = function(prototype) {
27
            var reflector = new JOII.Reflection.Class(prototype),
28
                properties = this.reflector.getProperties(),
29
                methods = this.reflector.getMethods(),
30
                i, p1, p2;
31
32
            // If the class is marked as 'abstract', running interface validation
33
            // on it is rather useless since the class can't be instantiated.
34
            if (reflector.isAbstract()) {
35
                return true;
36
            }
37
38
            var verifyMeta = function(t, p1, p2, prefix) {
39
                if (p1.getVisibility() !== p2.getVisibility()) {
40
                    throw prefix + ' ' + p2.getName() + ' cannot be ' + p2.getVisibility() + ' because the interface declared it ' + p1.getVisibility() + '.';
41
                }
42
                if (prefix != 'Method') {
43
                    if (p1.getType() !== p2.getType()) {
44
                        throw prefix + ' ' + p2.getName() + ' cannot be declared as ' + p2.getType() + ' because the interface declared it as ' + p1.getType() + '.';
45
                    }
46
                    if (p1.isNullable() !== p2.isNullable()) {
47
                        throw prefix + ' ' + p2.getName() + ' must be nullable as defined in the interface ' + t.name + '.';
48
                    }
49
                }
50
                return true;
51
            };
52
53
54
            // Verify that all properties exist and have the correct metadata.
55
            for (i in properties) {
56
                if (properties.hasOwnProperty(i) === false) continue;
57
                p1 = properties[i];
58
                
59
                if (p1.isStatic() && !reflector.isStatic()) continue;
60
                if (!p1.isStatic() && reflector.isStatic()) continue;
61
62
63
                if (!reflector.hasProperty(p1.getName())) {
64
                    throw 'Class must implement ' + (p1.toString().split(':')[0]) + ' as defined in the interface ' + this.name + '.';
65
                }
66
                p2 = reflector.getProperty(p1.getName());
67
68
                // Verify meta data
69
                verifyMeta(this, p1, p2, 'Property');
70
            }
71
72
            // Verify methods.
73
            for (i in methods) {
74
                if (methods.hasOwnProperty(i) === false) continue;
75
                p1 = methods[i];
76
77
                if (p1.isStatic() && !reflector.isStatic()) continue;
78
                if (!p1.isStatic() && reflector.isStatic()) continue;
79
80
                if (!reflector.hasMethod(p1.getName())) {
81
                    throw 'Class must implement ' + (p1.toString().split(':')[0]) + ' as defined in the interface ' + this.name + '.';
82
                }
83
                p2 = reflector.getMethod(p1.getName());
84
85
                // Verify meta data
86
                verifyMeta(this, p1, p2, 'Method');
87
88
                // Verify function signature.
89
                var args_interface = p1.getParameters();
90
                var args_class = p2.getParameters();
91
92
                if (args_interface.length == 0 || typeof (args_interface[0]) !== 'object') {
0 ignored issues
show
It is recommended to use === to compare with 0.

Generally, it is recommended to use strict comparison whenever possible and not to rely on the weaker type-juggling comparison operator.

Read more about comparison operations.

Loading history...
93
                    // fallback for backwards compatibility
94
                    if (args_interface.length !== args_class.length) {
95
                        throw 'Method ' + p1.getName() + ' does not match the parameter count as defined in the interface ' + this.name + '.';
96
                    }
97
                } else {
98
                    for (var idx = 0; idx < args_interface.length; idx++) {
99
                        var interface_parameters_meta = args_interface[idx];
100
101
                        var different = true;
102
103
                        for (var x = 0; x < args_class.length; x++) {
104
                            var class_parameters_meta = args_class[x];
105
106
                            if (interface_parameters_meta.length === class_parameters_meta.length) {
107
                                // this signature has the same number of types as the new signature
108
                                // check to see if the types are the same (duplicate signature)
109
                                different = false;
110
111
                                for (var y = 0; y < interface_parameters_meta.length; y++) {
112
                                    if (interface_parameters_meta[y].type != class_parameters_meta[y].type) {
113
                                        different = true;
114
                                    }
115
                                }
116
                                if (!different) {
117
                                    break;
118
                                }
119
                            }
120
                        }
121
                        if (different) {
122
                            throw 'Method ' + p1.getName() + ' does not match the parameter types as defined in the interface ' + this.name + '.';
123
                        }
124
                    }
125
                }
126
            }
127
        };
128
129
    // Set our interface specification
130
    JOII.CreateProperty(definition, '__interface__', {
131
        prototype : prototype,
132
        reflector : new JOII.Reflection.Class(prototype),
133
        name      : name
134
    });
135
136
    // And the standard JOII-metadata.
137
    JOII.CreateProperty(definition, '__joii__', prototype.__joii__);
138
139
    var constructor = JOII.Compat.Bind(definition, definition.__interface__);
140
    constructor.prototype = prototype;
141
142
    // Properties and methods may ever be declared as abstract or final in
143
    // an interface definition, because that wouldn't make any sense in
144
    // this context.
145
    var properties = definition.__interface__.reflector.getProperties(),
146
        methods    = definition.__interface__.reflector.getMethods(),
147
        validate   = function(prop, prefix) {
148
        if (prop.isAbstract()) {
149
            throw 'An interface may not contain abstract definitions. ' + prefix + ' ' + prop.getName() + ' is abstract in interface ' + definition.__interface__.name + '.';
150
        }
151
        if (prop.isFinal()) {
152
            throw 'An interface may not contain final definitions. ' + prefix + ' ' + prop.getName() + ' is final in interface ' + definition.__interface__.name + '.';
153
        }
154
    };
155
156
    // Validate properties and methods.
157
    var i;
158
    for (i in properties) {
159
        if (properties.hasOwnProperty(i) === false) continue;
160
        validate(properties[i], 'Property');
161
    }
162
    for (i in methods) {
163
        if (methods.hasOwnProperty(i) === false) continue;
164
        validate(methods[i], 'Method');
165
    }
166
167
    // Apply the definition to the constructor to have access to metadata
168
    // without running or instantiating the function.
169
    JOII.CreateProperty(constructor, 'definition', definition);
170
171
    // Register the interface, making it available in the PrototypeBuilder
172
    // to use as a type in property definitions.
173
    if (typeof (JOII.InterfaceRegistry[name]) !== 'undefined') {
174
        throw 'Another interface with the name "' + name + '" already exists.';
175
    }
176
    if (JOII.Compat.indexOf(JOII.InternalTypeNames, name) !== -1) {
177
        throw 'An interface may not be named "' + name + '", becase that name is reserved.';
178
    }
179
180
    // Apply constants to the definition
181
    var constants = {};
182
    for (i in prototype.__joii__.constants) {
183
        if (prototype.__joii__.constants.hasOwnProperty(i) === false) continue;
184
        JOII.CreateProperty(constructor, i, prototype.__joii__.constants[i], false);
185
        constants[i] = prototype.__joii__.constants[i];
186
    }
187
188
    // Does the class implement an enumerator?
189
    if (typeof (parameters['enum']) === 'string') {
190
        var e = JOII.EnumBuilder(parameters['enum'], constants);
191
        if (parameters.expose_enum === true) {
192
            var g = typeof window === 'object' ? window : global;
193
            if (typeof (g[parameters['enum']]) !== 'undefined') {
194
                throw 'Cannot expose Enum "' + parameters['enum'] + '" becase it already exists in the global scope.';
195
            }
196
            g[parameters['enum']] = e;
197
        }
198
    }
199
    JOII.InterfaceRegistry[name] = constructor;
200
201
    return constructor;
202
};
203