// Copyright 2012 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Restricted class definitions for Closure. * * @author johnlenz@google.com (John Lenz) */ goog.provide('goog.labs.classdef'); /** @typedef { {constructor:!Function}| {constructor:!Function, statics:(Object|function(Function):void)}} */ goog.labs.classdef.ClassDescriptor; /** * Creates a restricted form of a Closure "class": * - from the compiler's perspective, the instance returned from the * constructor is sealed (no new properties may be added). This enables * better type checking. * - the compiler will rewrite this definition to a form that is optimal * for type checking and optimization (initially this will be a more * traditional form). * * @param {Function} superClass The superclass or null. * @param {goog.labs.classdef.ClassDescriptor} def * An object literal describing the * the class. It may have the following properties: * "constructor": the constructor function * "statics": an object literal containing methods to add to the constructor * as "static" methods or a function that will recieve the constructor * function as its only parameter to which static properties can * be added. * all other properties are added to the prototype. * @return {!Function} The class constructor. */ goog.labs.classdef.defineClass = function(superClass, def) { // TODO(johnlenz): consider making the superClass an optional parameter. var constructor = def.constructor; var statics = def.statics; // Wrap the constructor prior to setting up the prototype and static methods. if (!constructor || constructor == Object.prototype.constructor) { throw Error('constructor property is required.'); } var cls = goog.labs.classdef.createSealingConstructor_(constructor); if (superClass) { goog.inherits(cls, superClass); } // Remove all the properties that should not be copied to the prototype. delete def.constructor; delete def.statics; goog.labs.classdef.applyProperties_(cls.prototype, def); if (statics != null) { if (statics instanceof Function) { statics(cls); } else { goog.labs.classdef.applyProperties_(cls, statics); } } return cls; }; /** * @define {boolean} Whether the instances returned by * goog.labs.classdef.defineClass should be sealed when possible. */ goog.define('goog.labs.classdef.SEAL_CLASS_INSTANCES', goog.DEBUG); /** * If goog.labs.classdef.SEAL_CLASS_INSTANCES is enabled and Object.seal is * defined, this function will wrap the constructor in a function that seals the * results of the provided constructor function. * * @param {!Function} ctr The constructor whose results maybe be sealed. * @return {!Function} The replacement constructor. * @private */ goog.labs.classdef.createSealingConstructor_ = function(ctr) { if (goog.labs.classdef.SEAL_CLASS_INSTANCES && Object.seal instanceof Function) { /** @this {*} */ var wrappedCtr = function() { // Don't seal an instance of a subclass when it calls the constructor of // its super class as there is most likely still setup to do. var instance = ctr.apply(this, arguments) || this; if (this.constructor === wrappedCtr) { Object.seal(instance); } return instance; }; return wrappedCtr; } return ctr; }; // TODO(johnlenz): share these values with the goog.object /** * The names of the fields that are defined on Object.prototype. * @type {!Array.} * @private * @const */ goog.labs.classdef.OBJECT_PROTOTYPE_FIELDS_ = [ 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf' ]; // TODO(johnlenz): share this function with the goog.object /** * @param {!Object} target The object to add properties to. * @param {!Object} source The object to copy properites from. * @private */ goog.labs.classdef.applyProperties_ = function(target, source) { // TODO(johnlenz): update this to support ES5 getters/setters var key; for (key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } // For IE the for-in-loop does not contain any properties that are not // enumerable on the prototype object (for example isPrototypeOf from // Object.prototype) and it will also not include 'replace' on objects that // extend String and change 'replace' (not that it is common for anyone to // extend anything except Object). for (var i = 0; i < goog.labs.classdef.OBJECT_PROTOTYPE_FIELDS_.length; i++) { key = goog.labs.classdef.OBJECT_PROTOTYPE_FIELDS_[i]; if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } };