transit-ruby 0.8.552-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Transit
16
+ # Transit::Reader converts incoming transit data into appropriate
17
+ # values/objects in Ruby.
18
+ # @see https://github.com/cognitect/transit-format
19
+ class Reader
20
+ extend Forwardable
21
+
22
+ # @!method read
23
+ # Reads transit values from an IO (file, stream, etc), and
24
+ # converts each one to the appropriate Ruby object.
25
+ #
26
+ # With a block, yields each object to the block as it is processed.
27
+ #
28
+ # Without a block, returns a single object.
29
+ #
30
+ # @example
31
+ # reader = Transit::Reader.new(:json, io)
32
+ # reader.read {|obj| do_something_with(obj)}
33
+ #
34
+ # reader = Transit::Reader.new(:json, io)
35
+ # obj = reader.read
36
+ def_delegators :@reader, :read
37
+
38
+ # @param [Symbol] format required any of :msgpack, :json, :json_verbose
39
+ # @param [IO] io required
40
+ # @param [Hash] opts optional
41
+ # Creates a new Reader configured to read from <tt>io</tt>,
42
+ # expecting <tt>format</tt> (<tt>:json</tt>, <tt>:msgpack</tt>).
43
+ #
44
+ # Use opts to register custom read handlers, associating each one
45
+ # with its tag.
46
+ #
47
+ # @example
48
+ #
49
+ # json_reader = Transit::Reader.new(:json, io)
50
+ # # ^^ reads both :json and :json_verbose formats ^^
51
+ # msgpack_writer = Transit::Reader.new(:msgpack, io)
52
+ # writer_with_custom_handlers = Transit::Reader.new(:json, io,
53
+ # :handlers => {"point" => PointReadHandler})
54
+ #
55
+ # @see Transit::ReadHandlers
56
+ def initialize(format, io, opts={})
57
+ @reader = case format
58
+ when :json, :json_verbose
59
+ Unmarshaler::Json.new(io, opts)
60
+ else
61
+ Unmarshaler::MessagePack.new(io, opts)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,70 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Transit
16
+ # @api private
17
+ class RollingCache
18
+ extend Forwardable
19
+
20
+ def_delegators "@key_to_value", :has_key?, :size
21
+
22
+ FIRST_ORD = 48
23
+ LAST_ORD = 91
24
+ CACHE_CODE_DIGITS = 44;
25
+ CACHE_SIZE = CACHE_CODE_DIGITS * CACHE_CODE_DIGITS;
26
+ MIN_SIZE_CACHEABLE = 4
27
+
28
+ def initialize
29
+ clear
30
+ end
31
+
32
+ def read(key)
33
+ @key_to_value[key]
34
+ end
35
+
36
+ def write(val)
37
+ @value_to_key[val] || begin
38
+ clear if @key_to_value.size >= CACHE_SIZE
39
+ key = next_key(@key_to_value.size)
40
+ @value_to_key[val] = key
41
+ @key_to_value[key] = val
42
+ end
43
+ end
44
+
45
+ def cache_key?(str, _=false)
46
+ str[0] == SUB && str != MAP_AS_ARRAY
47
+ end
48
+
49
+ def cacheable?(str, as_map_key=false)
50
+ str.size >= MIN_SIZE_CACHEABLE && (as_map_key || str.start_with?("~#","~$","~:"))
51
+ end
52
+
53
+ private
54
+
55
+ def clear
56
+ @key_to_value = {}
57
+ @value_to_key = {}
58
+ end
59
+
60
+ def next_key(i)
61
+ hi = i / CACHE_CODE_DIGITS;
62
+ lo = i % CACHE_CODE_DIGITS;
63
+ if hi == 0
64
+ "^#{(lo+FIRST_ORD).chr}"
65
+ else
66
+ "^#{(hi+FIRST_ORD).chr}#{(lo+FIRST_ORD).chr}"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,251 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Transit
16
+ class Wrapper
17
+ extend Forwardable
18
+
19
+ def_delegators :@value, :hash, :to_sym, :to_s
20
+
21
+ attr_reader :value
22
+
23
+ def initialize(value)
24
+ @value = value
25
+ end
26
+
27
+ def ==(other)
28
+ other.is_a?(self.class) && @value == other.value
29
+ end
30
+ alias eql? ==
31
+
32
+ def inspect
33
+ "<#{self.class} \"#{to_s}\">"
34
+ end
35
+ end
36
+
37
+ # Represents a transit symbol extension type.
38
+ # @see https://github.com/cognitect/transit-format
39
+ class Symbol < Wrapper
40
+ def initialize(sym)
41
+ super sym.to_sym
42
+ end
43
+
44
+ def namespace
45
+ @namespace ||= parsed[-2]
46
+ end
47
+
48
+ def name
49
+ @name ||= parsed[-1] || "/"
50
+ end
51
+
52
+ private
53
+
54
+ def parsed
55
+ @parsed ||= @value.to_s.split("/")
56
+ end
57
+ end
58
+
59
+ # Represents a transit byte array extension type.
60
+ # @see https://github.com/cognitect/transit-format
61
+ class ByteArray < Wrapper
62
+ def self.from_base64(data)
63
+ new(Base64.decode64(data))
64
+ end
65
+
66
+ def to_base64
67
+ Base64.encode64(@value)
68
+ end
69
+
70
+ def to_s
71
+ @value
72
+ end
73
+ end
74
+
75
+ # Represents a transit UUID extension type.
76
+ # @see https://github.com/cognitect/transit-format
77
+ class UUID
78
+ def self.random
79
+ new
80
+ end
81
+
82
+ def initialize(uuid_or_most_significant_bits=nil,least_significant_bits=nil)
83
+ case uuid_or_most_significant_bits
84
+ when String
85
+ @string_rep = uuid_or_most_significant_bits
86
+ when Array
87
+ @numeric_rep = uuid_or_most_significant_bits.map {|n| twos_complement(n)}
88
+ when Numeric
89
+ @numeric_rep = [twos_complement(uuid_or_most_significant_bits), twos_complement(least_significant_bits)]
90
+ when nil
91
+ @string_rep = SecureRandom.uuid
92
+ else
93
+ raise "Can't build UUID from #{uuid_or_most_significant_bits.inspect}"
94
+ end
95
+ end
96
+
97
+ def to_s
98
+ @string_rep ||= numbers_to_string
99
+ end
100
+
101
+ def most_significant_bits
102
+ @most_significant_bits ||= numeric_rep[0]
103
+ end
104
+
105
+ def least_significant_bits
106
+ @least_significant_bits ||= numeric_rep[1]
107
+ end
108
+
109
+ def inspect
110
+ @inspect ||= "<#{self.class} \"#{to_s}\">"
111
+ end
112
+
113
+ def ==(other)
114
+ return false unless other.is_a?(self.class)
115
+ if @numeric_rep
116
+ other.most_significant_bits == most_significant_bits &&
117
+ other.least_significant_bits == least_significant_bits
118
+ else
119
+ other.to_s == @string_rep
120
+ end
121
+ end
122
+ alias eql? ==
123
+
124
+ def hash
125
+ most_significant_bits.hash + least_significant_bits.hash
126
+ end
127
+
128
+ private
129
+
130
+ def numeric_rep
131
+ @numeric_rep ||= string_to_numbers
132
+ end
133
+
134
+ def numbers_to_string
135
+ most_significant_bits = @numeric_rep[0]
136
+ least_significant_bits = @numeric_rep[1]
137
+ digits(most_significant_bits >> 32, 8) + "-" +
138
+ digits(most_significant_bits >> 16, 4) + "-" +
139
+ digits(most_significant_bits, 4) + "-" +
140
+ digits(least_significant_bits >> 48, 4) + "-" +
141
+ digits(least_significant_bits, 12)
142
+ end
143
+
144
+ def string_to_numbers
145
+ str = @string_rep.delete("-")
146
+ [twos_complement(str[ 0..15].hex), twos_complement(str[16..31].hex)]
147
+ end
148
+
149
+ def digits(val, digits)
150
+ hi = 1 << (digits*4)
151
+ (hi | (val & (hi - 1))).to_s(16)[1..-1]
152
+ end
153
+
154
+ def twos_complement(integer_value, num_of_bits=64)
155
+ max_signed = 2**(num_of_bits-1)
156
+ max_unsigned = 2**num_of_bits
157
+ (integer_value >= max_signed) ? integer_value - max_unsigned : integer_value
158
+ end
159
+ end
160
+
161
+ # Represents a transit hypermedia link extension type.
162
+ # @see https://github.com/cognitect/transit-format
163
+ # @see http://amundsen.com/media-types/collection/format/#arrays-links
164
+ class Link
165
+ KEYS = ["href", "rel", "name", "render", "prompt"]
166
+ RENDER_VALUES = ["link", "image"]
167
+
168
+ # @overload Link.new(hash)
169
+ # @param [Hash] hash
170
+ # Valid keys are:
171
+ # "href" required, String or Addressable::URI
172
+ # "rel" required, String
173
+ # "name" optional, String
174
+ # "render" optional, String (only "link" or "image")
175
+ # "prompt" optional, String
176
+ # @overload Link.new(href, rel, name, render, prompt)
177
+ # @param [String, Addressable::URI] href required
178
+ # @param [String] rel required
179
+ # @param [String] name optional
180
+ # @param [String] render optional (only "link" or "image")
181
+ # @param [String] prompt optional
182
+ def initialize(*args)
183
+ @values = if args[0].is_a?(Hash)
184
+ reconcile_values(args[0])
185
+ elsif args.length >= 2 && (args[0].is_a?(Addressable::URI) || args[0].is_a?(String))
186
+ reconcile_values(Hash[KEYS.zip(args)])
187
+ else
188
+ raise ArgumentError, "The first argument to Link.new can be a URI, String or a Hash. When the first argument is a URI or String, the second argument, rel, must present."
189
+ end
190
+ end
191
+
192
+ def href; @href ||= @values["href"] end
193
+ def rel; @rel ||= @values["rel"] end
194
+ def name; @name ||= @values["name"] end
195
+ def render; @render ||= @values["render"] end
196
+ def prompt; @prompt ||= @values["prompt"] end
197
+
198
+ def to_h
199
+ @values
200
+ end
201
+
202
+ def ==(other)
203
+ other.is_a?(Link) && other.to_h == to_h
204
+ end
205
+ alias eql? ==
206
+
207
+ def hash
208
+ @values.hash
209
+ end
210
+
211
+ private
212
+
213
+ def reconcile_values(map)
214
+ map.dup.tap do |m|
215
+ m["href"] = Addressable::URI.parse(m["href"]) if m["href"].is_a?(String)
216
+ if m["render"]
217
+ render = m["render"].downcase
218
+ if RENDER_VALUES.include?(render)
219
+ m["render"] = render
220
+ else
221
+ raise ArgumentError, "render must be either #{RENDER_VALUES[0]} or #{RENDER_VALUES[1]}"
222
+ end
223
+ end
224
+ end.freeze
225
+ end
226
+ end
227
+
228
+ # Represents a transit tag and value. Returned by default when a
229
+ # reader encounters a tag for which there is no registered
230
+ # handler. Can also be used in a custom write handler to force
231
+ # representation to use a transit ground type using a rep for which
232
+ # there is no registered handler (e.g., an iterable for the
233
+ # representation of an array).
234
+ # @see https://github.com/cognitect/transit-format
235
+ class TaggedValue
236
+ attr_reader :tag, :rep
237
+ def initialize(tag, rep)
238
+ @tag = tag
239
+ @rep = rep
240
+ end
241
+
242
+ def ==(other)
243
+ other.is_a?(self.class) && other.tag == @tag && other.rep == @rep
244
+ end
245
+ alias eql? ==
246
+
247
+ def hash
248
+ @tag.hash + @rep.hash
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,438 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Transit
16
+ # WriteHandlers convert instances of Ruby types to their
17
+ # corresponding Transit semantic types, and ReadHandlers read
18
+ # convert transit values back into instances of Ruby
19
+ # types. transit-ruby ships with default sets of WriteHandlers for
20
+ # each of the Ruby types that map naturally to transit types, and
21
+ # ReadHandlers for each transit type. For the common case, the
22
+ # built-in handlers will suffice, but you can add your own extension
23
+ # types and/or override the built-in handlers.
24
+ #
25
+ # ## Custom handlers
26
+ #
27
+ # For example, Ruby has Date, Time, and DateTime, each with their
28
+ # own semantics. Transit has an instance type, which does not
29
+ # differentiate between Date and Time, so transit-ruby writes Dates,
30
+ # Times, and DateTimes as transit instances, and reads transit
31
+ # instances as DateTimes. If your application cares that Dates are
32
+ # different from DateTimes, you could register custom write and read
33
+ # handlers, overriding the built-in DateHandler and adding a new
34
+ # DateReadHandler.
35
+ #
36
+ # ### Write handlers
37
+ #
38
+ # Write handlers are required to expose <tt>tag</tt>, <tt>rep</tt>, and <tt>string_rep</tt> methods:
39
+ #
40
+ # ```ruby
41
+ # class DateWriteHandler
42
+ # def tag(_) "D" end
43
+ # def rep(o) o.to_s end
44
+ # def string_rep(o) o.to_s end
45
+ # def verbose_handler(_) nil end # optional - see Verbose write handlers, below
46
+ # end
47
+ # ```
48
+ #
49
+ # <tt>tag</tt> returns the tag used to identify the transit type
50
+ # (built-in or extension). It accepts the object being written,
51
+ # which allows the handler to return different tags for different
52
+ # semantics, e.g. the built-in IntHandler, which returns the tag "i"
53
+ # for numbers that fit within a 64-bit signed integer and "n" for
54
+ # anything outside that range.
55
+ #
56
+ # <tt>rep</tt> accepts the object being written and returns its wire
57
+ # representation. This can be a scalar value (identified by a
58
+ # one-character tag) or a map (Ruby Hash) or an array (identified by
59
+ # a multi-character tag).
60
+ #
61
+ # <tt>string_rep</tt> accepts the object being written and returns a
62
+ # string representation. Used when the object is a key in a map.
63
+ #
64
+ # ### Read handlers
65
+ #
66
+ # Read handlers are required to expose a single <tt>from_rep</tt> method:
67
+ #
68
+ # ```ruby
69
+ # class DateReadHandler
70
+ # def from_rep(rep)
71
+ # Date.parse(rep)
72
+ # end
73
+ # end
74
+ # ```
75
+ #
76
+ # <tt>from_rep</tt> accepts the wire representation (without the tag), and
77
+ # uses it to build an appropriate Ruby object.
78
+ #
79
+ # ### Usage
80
+ #
81
+ # ```ruby
82
+ # io = StringIO.new('','w+')
83
+ # writer = Transit::Writer.new(:json, io, :handlers => {Date => DateWriteHandler.new})
84
+ # writer.write(Date.new(2014,7,22))
85
+ # io.string
86
+ # # => "[\"~#'\",\"~D2014-07-22\"]\n"
87
+ #
88
+ # reader = Transit::Reader.new(:json, StringIO.new(io.string), :handlers => {"D" => DateReadHandler.new})
89
+ # reader.read
90
+ # # => #<Date: 2014-07-22 ((2456861j,0s,0n),+0s,2299161j)>
91
+ # ```
92
+ #
93
+ # ## Custom types and representations
94
+ #
95
+ # Transit supports scalar and structured representations. The Date
96
+ # example, above, demonstrates a String representation (scalar) of a
97
+ # Date. This works well because it is a natural representation, but
98
+ # it might not be a good solution for a more complex type, e.g. a
99
+ # Point. While you _could_ represent a Point as a String, e.g.
100
+ # <tt>("x:37,y:42")</tt>, it would be more efficient and arguably
101
+ # more natural to represent it as an array of Integers:
102
+ #
103
+ # ```ruby
104
+ # require 'ostruct'
105
+ # Point = Struct.new(:x,:y) do
106
+ # def to_a; [x,y] end
107
+ # end
108
+ #
109
+ # class PointWriteHandler
110
+ # def tag(_) "point" end
111
+ # def rep(o) o.to_a end
112
+ # def string_rep(_) nil end
113
+ # end
114
+ #
115
+ # class PointReadHandler
116
+ # def from_rep(rep)
117
+ # Point.new(*rep)
118
+ # end
119
+ # end
120
+ #
121
+ # io = StringIO.new('','w+')
122
+ # writer = Transit::Writer.new(:json_verbose, io, :handlers => {Point => PointWriteHandler.new})
123
+ # writer.write(Point.new(37,42))
124
+ # io.string
125
+ # # => "{\"~#point\":[37,42]}\n"
126
+ #
127
+ # reader = Transit::Reader.new(:json, StringIO.new(io.string),
128
+ # :handlers => {"point" => PointReadHandler.new})
129
+ # reader.read
130
+ # # => #<struct Point x=37, y=42>
131
+ # ```
132
+ #
133
+ # Note that Date used a one-character tag, "D", whereas Point uses a
134
+ # multi-character tag, "point". Transit expects one-character tags
135
+ # to have scalar representations (string, integer, float, boolean,
136
+ # etc) and multi-character tags to have structural representations,
137
+ # i.e. maps (Ruby Hashes) or arrays.
138
+ #
139
+ # ## Verbose write handlers
140
+ #
141
+ # Write handlers can, optionally, support the JSON-VERBOSE format by
142
+ # providing a verbose write handler. Transit uses this for instances
143
+ # (Ruby Dates, Times, DateTimes) to differentiate between the more
144
+ # efficient format using an int representing milliseconds since 1970
145
+ # in JSON mode from the more readable format using a String in
146
+ # JSON-VERBOSE mode.
147
+ #
148
+ # ```ruby
149
+ # inst = DateTime.new(1985,04,12,23,20,50,"0")
150
+ #
151
+ # io = StringIO.new('','w+')
152
+ # writer = Transit::Writer.new(:json, io)
153
+ # writer.write(inst)
154
+ # io.string
155
+ # #=> "[\"~#'\",\"~m482196050000\"]\n"
156
+ #
157
+ # io = StringIO.new('','w+')
158
+ # writer = Transit::Writer.new(:json_verbose, io)
159
+ # writer.write(inst)
160
+ # io.string
161
+ # #=> "{\"~#'\":\"~t1985-04-12T23:20:50.000Z\"}\n"
162
+ # ```
163
+ #
164
+ # When you want a more human-readable format for your own custom
165
+ # types in JSON-VERBOSE mode, create a second write handler and add
166
+ # a <tt>verbose_handler</tt> method to the first handler that
167
+ # returns an instance of the verbose handler:
168
+ #
169
+ # ```ruby
170
+ # Element = Struct.new(:id, :name)
171
+ #
172
+ # class ElementWriteHandler
173
+ # def tag(_) "el" end
174
+ # def rep(v) v.id end
175
+ # def string_rep(v) v.name end
176
+ # def verbose_handler() ElementVerboseWriteHandler.new end
177
+ # end
178
+ #
179
+ # class ElementVerboseWriteHandler < ElementWriteHandler
180
+ # def rep(v) v.name end
181
+ # end
182
+ #
183
+ # write_handlers = {Element => ElementWriteHandler.new}
184
+ #
185
+ # e = Element.new(3, "Lithium")
186
+ #
187
+ # io = StringIO.new('','w+')
188
+ # writer = Transit::Writer.new(:json, io, :handlers => write_handlers)
189
+ # writer.write(e)
190
+ # io.string
191
+ # # => "[\"~#el\",3]\n"
192
+ #
193
+ # io = StringIO.new('','w+')
194
+ # writer = Transit::Writer.new(:json_verbose, io, :handlers => write_handlers)
195
+ # writer.write(e)
196
+ # io.string
197
+ # # => "{\"~#el\":\"Lithium\"}\n"
198
+ # ```
199
+ #
200
+ # Note that you register the same handler collection; transit-ruby takes care of
201
+ # asking for the verbose_handler for the :json_verbose format.
202
+ module WriteHandlers
203
+ class NilHandler
204
+ def tag(_) "_" end
205
+ def rep(_) nil end
206
+ def string_rep(n) nil end
207
+ end
208
+
209
+ class KeywordHandler
210
+ def tag(_) ":" end
211
+ def rep(s) s.to_s end
212
+ def string_rep(s) rep(s) end
213
+ end
214
+
215
+ class StringHandler
216
+ def tag(_) "s" end
217
+ def rep(s) s end
218
+ def string_rep(s) s end
219
+ end
220
+
221
+ class TrueHandler
222
+ def tag(_) "?" end
223
+ def rep(_) true end
224
+ def string_rep(_) "t" end
225
+ end
226
+
227
+ class FalseHandler
228
+ def tag(_) "?" end
229
+ def rep(_) false end
230
+ def string_rep(_) "f" end
231
+ end
232
+
233
+ class IntHandler
234
+ def tag(i) i > MAX_INT || i < MIN_INT ? "n" : "i" end
235
+ def rep(i) i > MAX_INT || i < MIN_INT ? i.to_s : i end
236
+ def string_rep(i) i.to_s end
237
+ end
238
+
239
+ class FloatHandler
240
+ def tag(f)
241
+ return "z" if f.nan?
242
+ case f
243
+ when Float::INFINITY, -Float::INFINITY
244
+ "z"
245
+ else
246
+ "d"
247
+ end
248
+ end
249
+
250
+ def rep(f)
251
+ return "NaN" if f.nan?
252
+ case f
253
+ when Float::INFINITY then "INF"
254
+ when -Float::INFINITY then "-INF"
255
+ else f
256
+ end
257
+ end
258
+
259
+ def string_rep(f) rep(f).to_s end
260
+ end
261
+
262
+ class BigDecimalHandler
263
+ def tag(_) "f" end
264
+ def rep(f) f.to_s("f") end
265
+ def string_rep(f) rep(f) end
266
+ end
267
+
268
+ class RationalHandler
269
+ def tag(_) "ratio" end
270
+ def rep(r) [r.numerator, r.denominator] end
271
+ def string_rep(_) nil end
272
+ end
273
+
274
+ # TimeHandler, DateTimeHandler, and DateHandler all have different
275
+ # implementations of string_rep. Here is the rationale:
276
+ #
277
+ # For all three, want to write out the same format
278
+ # e.g. 2014-04-18T18:51:29.478Z, and we want the milliseconds to truncate
279
+ # rather than round, eg 29.4786 seconds should be 29.478, not 29.479.
280
+ # - "sss is the number of complete milliseconds since the start of the
281
+ # second as three decimal digits."
282
+ # - http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
283
+ #
284
+ # Some data points (see benchmarks/encoding_time.rb)
285
+ # - Time and DateTime each offer iso8601 methods, but strftime is faster.
286
+ # - DateTime's strftime (and iso8601) round millis
287
+ # - Time's strftime (and iso8601) truncate millis
288
+ # - we don't care about truncate v round for dates (which have 000 ms)
289
+ # - date.to_datetime.strftime(...) is considerably faster than date.to_time.strftime(...)
290
+ class TimeHandler
291
+ def tag(_) "m" end
292
+ def rep(t) DateTimeUtil.to_millis(t) end
293
+ def string_rep(t) rep(t).to_s end
294
+ def verbose_handler() VerboseTimeHandler.new end
295
+ end
296
+
297
+ class DateTimeHandler < TimeHandler
298
+ def verbose_handler() VerboseDateTimeHandler.new end
299
+ end
300
+
301
+ class DateHandler < TimeHandler
302
+ def verbose_handler() VerboseDateHandler.new end
303
+ end
304
+
305
+ class VerboseTimeHandler
306
+ def tag(_) "t" end
307
+ def rep(t)
308
+ # .getutc because we don't want to modify t
309
+ t.getutc.strftime(Transit::TIME_FORMAT)
310
+ end
311
+ def string_rep(t) rep(t) end
312
+ end
313
+
314
+ class VerboseDateTimeHandler < VerboseTimeHandler
315
+ def rep(t)
316
+ # .utc because to_time already creates a new object
317
+ t.to_time.utc.strftime(Transit::TIME_FORMAT)
318
+ end
319
+ end
320
+
321
+ class VerboseDateHandler < VerboseTimeHandler
322
+ def rep(d)
323
+ # to_datetime because DateTime's strftime is faster
324
+ # thank Time's, and millis are 000 so it doesn't matter
325
+ # if we truncate or round.
326
+ d.to_datetime.strftime(Transit::TIME_FORMAT)
327
+ end
328
+ end
329
+
330
+ class UuidHandler
331
+ def tag(_) "u" end
332
+ def rep(u) [u.most_significant_bits, u.least_significant_bits] end
333
+ def string_rep(u) u.to_s end
334
+ end
335
+
336
+ class LinkHandler
337
+ def tag(_) "link" end
338
+ def rep(l) l.to_h end
339
+ def string_rep(_) nil end
340
+ end
341
+
342
+ class UriHandler
343
+ def tag(_) "r" end
344
+ def rep(u) u.to_s end
345
+ def string_rep(u) rep(u) end
346
+ end
347
+
348
+ class AddressableUriHandler
349
+ def tag(_) "r" end
350
+ def rep(u) u.to_s end
351
+ def string_rep(u) rep(u) end
352
+ end
353
+
354
+ class ByteArrayHandler
355
+ def tag(_) "b" end
356
+ if Transit::jruby?
357
+ def rep(b)
358
+ b.value.to_java_bytes
359
+ end
360
+ else
361
+ def rep(b)
362
+ b.to_base64
363
+ end
364
+ end
365
+ def string_rep(b) rep(b) end
366
+ end
367
+
368
+ class TransitSymbolHandler
369
+ def tag(_) "$" end
370
+ def rep(s) s.to_s end
371
+ def string_rep(s) rep(s) end
372
+ end
373
+
374
+ class ArrayHandler
375
+ def tag(_) "array" end
376
+ def rep(a) a end
377
+ def string_rep(_) nil end
378
+ end
379
+
380
+ class MapHandler
381
+ def handlers=(handlers)
382
+ @handlers = handlers
383
+ end
384
+
385
+ def stringable_keys?(m)
386
+ m.keys.all? {|k| (@handlers[k.class].tag(k).length == 1) }
387
+ end
388
+
389
+ def tag(m)
390
+ stringable_keys?(m) ? "map" : "cmap"
391
+ end
392
+
393
+ def rep(m)
394
+ stringable_keys?(m) ? m : m.reduce([]) {|a, kv| a.concat(kv)}
395
+ end
396
+
397
+ def string_rep(_) nil end
398
+ end
399
+
400
+ class SetHandler
401
+ def tag(_) "set" end
402
+ def rep(s) s.to_a end
403
+ def string_rep(_) nil end
404
+ end
405
+
406
+ class TaggedValueHandler
407
+ def tag(tv) tv.tag end
408
+ def rep(tv) tv.rep end
409
+ def string_rep(_) nil end
410
+ end
411
+
412
+ DEFAULT_WRITE_HANDLERS = {
413
+ NilClass => NilHandler.new,
414
+ ::Symbol => KeywordHandler.new,
415
+ String => StringHandler.new,
416
+ TrueClass => TrueHandler.new,
417
+ FalseClass => FalseHandler.new,
418
+ Fixnum => IntHandler.new,
419
+ Bignum => IntHandler.new,
420
+ Float => FloatHandler.new,
421
+ BigDecimal => BigDecimalHandler.new,
422
+ Rational => RationalHandler.new,
423
+ Time => TimeHandler.new,
424
+ DateTime => DateTimeHandler.new,
425
+ Date => DateHandler.new,
426
+ UUID => UuidHandler.new,
427
+ Link => LinkHandler.new,
428
+ URI => UriHandler.new,
429
+ Addressable::URI => AddressableUriHandler.new,
430
+ ByteArray => ByteArrayHandler.new,
431
+ Transit::Symbol => TransitSymbolHandler.new,
432
+ Array => ArrayHandler.new,
433
+ Hash => MapHandler.new,
434
+ Set => SetHandler.new,
435
+ TaggedValue => TaggedValueHandler.new
436
+ }.freeze
437
+ end
438
+ end