support for circular structures (e.g. with JSON)

This commit is contained in:
DadaMonad
2015-02-24 16:09:42 +00:00
parent fea6de3bf9
commit 3ba89edf7d
13 changed files with 434 additions and 939 deletions

View File

@@ -145,6 +145,21 @@ module.exports = ()->
l @_encode()
@
#
# @private
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: (json = {})->
json.type = @type
json.uid = @getUid()
if @custom_type?
if @custom_type.constructor is String
json.custom_type = @custom_type
else
json.custom_type = @custom_type._name
json
#
# @private
# Operations may depend on other operations (linked lists, etc.).
@@ -313,7 +328,7 @@ module.exports = ()->
applyDelete: (o)->
@deleted_by ?= []
callLater = false
if @parent? and not @isDeleted() and o? # o? : if not o?, then the delimiter deleted this Insertion. Furthermore, it would be wrong to call it. TODO: make this more expressive and save
if @parent? and not @is_deleted and o? # o? : if not o?, then the delimiter deleted this Insertion. Furthermore, it would be wrong to call it. TODO: make this more expressive and save
# call iff wasn't deleted earlyer
callLater = true
if o?
@@ -328,12 +343,6 @@ module.exports = ()->
# garbage collect prev_cl
@prev_cl.applyDelete()
# delete content
if @content instanceof ops.Operation
@content.applyDelete()
delete @content
cleanup: ()->
if @next_cl.isDeleted()
# delete all ops that delete this insertion
@@ -350,6 +359,17 @@ module.exports = ()->
# reconnect left/right
@prev_cl.next_cl = @next_cl
@next_cl.prev_cl = @prev_cl
# delete content
# - we must not do this in applyDelete, because this would lead to inconsistencies
# (e.g. the following operation order must be invertible :
# Insert refers to content, then the content is deleted)
# Therefore, we have to do this in the cleanup
if @content instanceof ops.Operation and not deleted_earlyer
@content.referenced_by--
if @content.referenced_by <= 0 and not @content.is_deleted
@content.applyDelete()
delete @content
super
# else
# Someone inserted something in the meantime.
@@ -378,6 +398,8 @@ module.exports = ()->
else
if @content instanceof ops.Operation
@content.insert_parent = @ # TODO: this is probably not necessary and only nice for debugging
@content.referenced_by ?= 0
@content.referenced_by++
if @parent?
if not @prev_cl?
@prev_cl = @parent.beginning
@@ -481,15 +503,10 @@ module.exports = ()->
# Convert all relevant information of this operation to the json-format.
# This result can be send to other clients.
#
_encode: ()->
json =
{
'type': @type
'uid' : @getUid()
'prev': @prev_cl.getUid()
'next': @next_cl.getUid()
'parent': @parent.getUid()
}
_encode: (json = {})->
json.prev = @prev_cl.getUid()
json.next = @next_cl.getUid()
json.parent = @parent.getUid()
if @origin.type is "Delimiter"
json.origin = "Delimiter"
@@ -500,7 +517,7 @@ module.exports = ()->
json['content'] = @content.getUid()
else
json['content'] = JSON.stringify @content
json
super json
ops.Insert.parse = (json)->
{
@@ -515,47 +532,6 @@ module.exports = ()->
content = JSON.parse(content)
new this null, content, uid, prev, next, origin, parent
#
# @nodoc
# Defines an object that is cannot be changed. You can use this to set an immutable string, or a number.
#
class ops.ImmutableObject extends ops.Operation
#
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Object} content
#
constructor: (custom_type, uid, @content)->
super custom_type, uid
type: "ImmutableObject"
#
# @return [String] The content of this operation.
#
val : ()->
@content
#
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: ()->
json = {
'type': @type
'uid' : @getUid()
'content' : @content
}
json
ops.ImmutableObject.parse = (json)->
{
'uid' : uid
'content' : content
} = json
new this(null, uid, content)
#
# @nodoc
# A delimiter is placed at the end and at the beginning of the associative lists.

View File

@@ -79,20 +79,6 @@ module.exports = ()->
rm.execute()
@_map[property_name]
#
# @private
#
_encode: ()->
json = {
'type' : @type
'uid' : @getUid()
}
if @custom_type.constructor is String
json.custom_type = @custom_type
else
json.custom_type = @custom_type._name
json
ops.MapManager.parse = (json)->
{
'uid' : uid
@@ -279,21 +265,6 @@ module.exports = ()->
delete_ops.push d._encode()
@
#
# @private
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: ()->
json = {
'type': @type
'uid' : @getUid()
}
if @custom_type.constructor is String
json.custom_type = @custom_type
else
json.custom_type = @custom_type._name
json
ops.ListManager.parse = (json)->
{
'uid' : uid
@@ -381,15 +352,10 @@ module.exports = ()->
#
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: ()->
json =
{
'type': @type
'uid' : @getUid()
'beginning' : @beginning.getUid()
'end' : @end.getUid()
}
json
_encode: (json = {})->
json.beginning = @beginning.getUid()
json.end = @end.getUid()
super json
#
# @nodoc
@@ -403,35 +369,12 @@ module.exports = ()->
# @param {ReplaceManager} parent Used to replace this Replaceable with another one.
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
#
constructor: (custom_type, content, parent, uid, prev, next, origin, is_deleted)->
constructor: (custom_type, content, parent, uid, prev, next, origin)->
@saveOperation 'parent', parent
super custom_type, content, uid, prev, next, origin # Parent is already saved by Replaceable
@is_deleted = is_deleted
type: "Replaceable"
#
# Return the content that this operation holds.
#
val: ()->
if @content? and @content.getCustomType?
@content.getCustomType()
else
@content
applyDelete: ()->
res = super
if @content?
if @next_cl.type isnt "Delimiter"
@content.deleteAllObservers?()
@content.applyDelete?()
@content.dontSync?()
@content = null
res
cleanup: ()->
super
#
# This is called, when the Insert-type was successfully executed.
# TODO: consider doing this in a more consistent manner. This could also be
@@ -470,30 +413,8 @@ module.exports = ()->
#
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: ()->
json =
{
'type': @type
'parent' : @parent.getUid()
'prev': @prev_cl.getUid()
'next': @next_cl.getUid()
'uid' : @getUid()
'is_deleted': @is_deleted
}
if @origin.type is "Delimiter"
json.origin = "Delimiter"
else if @origin isnt @prev_cl
json.origin = @origin.getUid()
if @content instanceof ops.Operation
json['content'] = @content.getUid()
else
# This could be a security concern.
# Throw error if the users wants to trick us
if @content? and @content.creator?
throw new Error "You must not set creator here!"
json['content'] = @content
json
_encode: (json = {})->
super json
ops.Replaceable.parse = (json)->
{
@@ -503,10 +424,11 @@ module.exports = ()->
'prev': prev
'next': next
'origin' : origin
'is_deleted': is_deleted
'custom_type' : custom_type
} = json
new this(custom_type, content, parent, uid, prev, next, origin, is_deleted)
if typeof content is "string"
content = JSON.parse(content)
new this(custom_type, content, parent, uid, prev, next, origin)
basic_ops