Permalink
Browse files

Add SC.Freezable and SC.Copyable

Break out copying and freezing functions into
their own mixins.  Makes for a cleaner object
structure.
  • Loading branch information...
Charles Jolley
Charles Jolley committed May 13, 2009
1 parent c24a7b9 commit 3b4725276916a900d181d78cee8118d66777215b
@@ -486,6 +486,7 @@ if (!Array.prototype.lastIndexOf) {
// primitive for array support.
replace: function(idx, amt, objects) {
if (this.isFrozen) throw SC.FROZEN_ERROR ;
if (!objects || objects.length === 0) {
this.splice(idx, amt) ;
} else {
@@ -0,0 +1,64 @@
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
// Portions ©2008-2009 Apple, Inc. All rights reserved.
// License: Licened under MIT license (see license.js)
// ==========================================================================
/**
@namespace
Implements some standard methods for copying an object. Add this mixin to
any object you create that can create a copy of itself. This mixin is
added automatically to the built-in array.
You should generally implement the copy() method to return a copy of the
recevier.
Note that frozenCopy() will only work if you implement SC.Freezable as well.
@since SproutCore 1.0
*/
SC.Copyable = {
/**
walk like a duck. Indicates that the object can be copied.
@type Boolean
*/
isCopyable: YES,
/**
Override to return a copy of the receiver. Default implementation raises
an exception.
@returns {Object} copy of receiver
*/
copy: function() {
throw "%@.copy() is not implemented";
},
/**
If the object implements SC.Freezable, then this will return a new copy
if the object is not frozen and the receiver if the object is frozen.
Raises an exception if you try to call this method on a object that does
not support freezing.
You should use this method whenever you want a copy of a freezable object
since a freezable object can simply return itself without actually
consuming more memory.
@returns {Object} copy of receiver or receiver
*/
frozenCopy: function() {
var isFrozen = this.get ? this.get('isFrozen') : this.isFrozen;
if (isFrozen === YES) return this;
else if (isFrozen === undefined) throw "%@ does not support freezing";
else return this.copy().freeze();
}
};
// Make Array copyable
SC.mixin(Array.prototype, SC.Copyable);
Array.prototype.copy = Array.prototype.slice;
@@ -0,0 +1,104 @@
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
// Portions ©2008-2009 Apple, Inc. All rights reserved.
// License: Licened under MIT license (see license.js)
// ==========================================================================
/**
Standard Error that should be raised when you try to modify a frozen object.
*/
SC.FROZEN_ERROR = new Error("Cannot modify a frozen object");
/**
@namespace
The SC.Freezable mixin implements some basic methods for marking an object
as frozen. Once an object is frozen it should be read only. No changes
may be made the internal state of the object.
h2. Enforcement
To fully support freezing in your subclass, you must include this mixin and
override any method that might alter any property on the object to instead
raise an exception. You can check the state of an object by checking the
isFrozen property.
Although future versions of JavaScript may support language-level freezing
object objects, that is not the case today. Even if an object is freezable,
it is still technically possible to modify the object, even though it could
break other parts of your application that do not expect a frozen object to
change. It is, therefore, very important that you always respect the
isFrozen property on all freezable objects.
h2. Example
The example below shows a simple object that implement the SC.Freezable
protocol.
{{{
Contact = SC.Object.extend(SC.Freezable, {
firstName: null,
lastName: null,
// swaps the names
swapNames: function() {
if (this.get('isFrozen')) throw SC.FROZEN_ERROR;
var tmp = this.get('firstName');
this.set('firstName', this.get('lastName'));
this.set('lastName', tmp);
return this;
}
});
c = Context.create({ firstName: "John", lastName: "Doe" });
c.swapNames(); => returns c
c.freeze();
c.swapNames(); => EXCEPTION
}}}
h2. Copying
Usually the SC.Freezable protocol is implemented in cooperation with the
SC.Copyable protocol, which defines a frozenCopy() method that will return
a frozen object, if the object implements this method as well.
*/
SC.Freezable = {
/**
Walk like a duck.
*/
isFreezable: YES,
/**
Set to YES when the object is frozen. Use this property to detect whether
your object is frozen or not.
*/
isFrozen: NO,
/**
Freezes the object. Once this method has been called the object should
no longer allow any properties to be edited.
@returns {Object} reciever
*/
freeze: function() {
// NOTE: Once someone actually implements Object.freeze() in the browser,
// add a call to that here also.
if (this.set) this.set('isFrozen', YES);
else this.isFrozen = YES;
return this;
}
};
// Add to Array
SC.mixin(Array.prototype, SC.Freezable);
@@ -5,8 +5,10 @@
// License: Licened under MIT license (see license.js)
// ==========================================================================
require('mixins/enumerable') ;
require('mixins/observable') ;
sc_require('mixins/enumerable') ;
sc_require('mixins/observable') ;
sc_require('mixins/freezing');
sc_require('mixins/copying');
/**
@class
@@ -43,9 +45,12 @@ require('mixins/observable') ;
@extends Object
@extends SC.Enumerable
@extends SC.Observable
@extends SC.Copyable
@extends SC.Freezable
@since SproutCore 1.0
*/
SC.IndexSet = SC.mixin({}, SC.Enumerable, SC.Observable,
SC.IndexSet = SC.mixin({},
SC.Enumerable, SC.Observable, SC.Freezable, SC.Copyable,
/** @scope SC.IndexSet.prototype */ {
/** @private
@@ -379,6 +384,8 @@ SC.IndexSet = SC.mixin({}, SC.Enumerable, SC.Observable,
*/
add: function(start, length) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
var content, cur, next;
// normalize IndexSet input
@@ -549,6 +556,8 @@ SC.IndexSet = SC.mixin({}, SC.Enumerable, SC.Observable,
@returns {SC.IndexSet} receiver
*/
remove: function(start, length) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
// normalize input
if (length === undefined) {
@@ -703,6 +712,8 @@ SC.IndexSet = SC.mixin({}, SC.Enumerable, SC.Observable,
Clears the set
*/
clear: function() {
if (this.isFrozen) throw SC.FROZEN_ERROR;
var oldlen = this.length;
this._content.length=1;
this._content[0] = 0;
@@ -714,6 +725,8 @@ SC.IndexSet = SC.mixin({}, SC.Enumerable, SC.Observable,
Add all the ranges in the passed array.
*/
addEach: function(objects) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
var idx = objects.get('length') ;
if (objects.objectAt) {
while(--idx >= 0) this.add(objects.objectAt(idx)) ;
@@ -727,6 +740,8 @@ SC.IndexSet = SC.mixin({}, SC.Enumerable, SC.Observable,
Removes all the ranges in the passed array.
*/
removeEach: function(objects) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
var idx = objects.get('length') ;
if (objects.objectAt) {
while(--idx >= 0) this.remove(objects.objectAt(idx)) ;
@@ -1114,4 +1129,4 @@ SC.IndexSet = SC.mixin({}, SC.Enumerable, SC.Observable,
}) ;
SC.IndexSet.slice = SC.IndexSet.clone ;
SC.IndexSet.slice = SC.IndexSet.copy = SC.IndexSet.clone ;
@@ -7,6 +7,8 @@
sc_require('system/object');
sc_require('mixins/enumerable');
sc_require('mixins/copyable');
sc_require('mixins/freezable');
/** @class
@@ -18,7 +20,7 @@ sc_require('mixins/enumerable');
@extends SC.Enumerable
@since SproutCore 1.0
*/
SC.SelectionSet = SC.Object.extend(SC.Enumerable, {
SC.SelectionSet = SC.Object.extend(SC.Enumerable, SC.Freezable, SC.Copyable, {
isSelectionSet: YES,
@@ -99,6 +101,8 @@ SC.SelectionSet = SC.Object.extend(SC.Enumerable, {
*/
add: function(source, start, length) {
if (this.isFrozen) throw SC.FROZEN_ERROR ;
var sets, len, idx, set, oldlen, newlen, setlen;
// normalize
@@ -152,6 +156,8 @@ SC.SelectionSet = SC.Object.extend(SC.Enumerable, {
*/
remove: function(source, start, length) {
if (this.isFrozen) throw SC.FROZEN_ERROR ;
var sets, len, idx, set, oldlen, newlen, setlen;
// normalize
@@ -268,6 +274,7 @@ SC.SelectionSet = SC.Object.extend(SC.Enumerable, {
Clears the set. Removes all IndexSets from the object
*/
clear: function() {
if (this.isFrozen) throw SC.FROZEN_ERROR;
if (this._sets) this._sets.length = 0 ; // truncate
this.set('length', 0);
return this ;
@@ -437,4 +444,6 @@ SC.SelectionSet = SC.Object.extend(SC.Enumerable, {
}
});
});
SC.SelectionSet.prototype.copy = SC.SelectionSet.prototype.clone;
@@ -5,8 +5,10 @@
// License: Licened under MIT license (see license.js)
// ==========================================================================
require('mixins/enumerable') ;
require('mixins/observable') ;
sc_require('mixins/enumerable') ;
sc_require('mixins/observable') ;
sc_require('mixins/freezable');
sc_require('mixins/copyable');
/**
@class
@@ -104,6 +106,7 @@ SC.Set.prototype = {
Clears the set
*/
clear: function() {
if (this.isFrozen) throw SC.FROZEN_ERROR;
this.length = 0;
return this ;
},
@@ -131,6 +134,7 @@ SC.Set.prototype = {
@returns {Object} this
*/
add: function(obj) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
// cannot add null to a set.
if (obj===null || obj===undefined) return this;
@@ -151,6 +155,8 @@ SC.Set.prototype = {
Add all the items in the passed array.
*/
addEach: function(objects) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
var idx = objects.get('length') ;
if (objects.objectAt) {
while(--idx >= 0) this.add(objects.objectAt(idx)) ;
@@ -169,6 +175,7 @@ SC.Set.prototype = {
@returns {this} this
*/
remove: function(obj) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
if (SC.none(obj)) return this ;
var guid = SC.hashFor(obj);
@@ -199,6 +206,7 @@ SC.Set.prototype = {
@returns {Object} an object from the set or null
*/
pop: function() {
if (this.isFrozen) throw SC.FROZEN_ERROR;
var obj = (this.length > 0) ? this[this.length-1] : null ;
if (obj) this.remove(obj) ;
return obj ;
@@ -208,6 +216,7 @@ SC.Set.prototype = {
Removes all the items in the passed array.
*/
removeEach: function(objects) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
var idx = objects.get('length') ;
if (objects.objectAt) {
while(--idx >= 0) this.remove(objects.objectAt(idx)) ;
@@ -228,6 +237,7 @@ SC.Set.prototype = {
Return a set to the pool for reallocation.
*/
destroy: function() {
this.isFrozen = NO ; // unfreeze to return to pool
SC.Set._pool.push(this.clear());
return this;
},
@@ -246,10 +256,10 @@ SC.Set.prototype = {
} ;
SC.Set.prototype.slice = SC.Set.prototype.clone ;
// Make this enumerable and observable
SC.mixin(SC.Set.prototype, SC.Enumerable, SC.Observable) ;
SC.mixin(SC.Set.prototype, SC.Enumerable, SC.Observable, SC.Freezable, SC.Observable) ;
SC.Set.prototype.slice = SC.Set.prototype.copy = SC.Set.prototype.clone ;
SC.Set.prototype.push = SC.Set.prototype.unshift = SC.Set.prototype.add ;
SC.Set.prototype.shift = SC.Set.prototype.pop ;

0 comments on commit 3b47252

Please sign in to comment.