xnlogic-transit-ruby 0.8.572-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,258 @@
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
+ # For human-readable display only. Use value() for programmatic
71
+ # consumption of the decoded value.
72
+ #
73
+ # Forces the platform's default external encoding, which is
74
+ # potentially lossy, but also guarantees that something will be
75
+ # printed instead of raising an error when there is no encoding
76
+ # information provided.
77
+ def to_s
78
+ @value.dup.force_encoding(Encoding.default_external)
79
+ end
80
+ end
81
+
82
+ # Represents a transit UUID extension type.
83
+ # @see https://github.com/cognitect/transit-format
84
+ class UUID
85
+ def self.random
86
+ new
87
+ end
88
+
89
+ def initialize(uuid_or_most_significant_bits=nil,least_significant_bits=nil)
90
+ case uuid_or_most_significant_bits
91
+ when String
92
+ @string_rep = uuid_or_most_significant_bits
93
+ when Array
94
+ @numeric_rep = uuid_or_most_significant_bits.map {|n| twos_complement(n)}
95
+ when Numeric
96
+ @numeric_rep = [twos_complement(uuid_or_most_significant_bits), twos_complement(least_significant_bits)]
97
+ when nil
98
+ @string_rep = SecureRandom.uuid
99
+ else
100
+ raise "Can't build UUID from #{uuid_or_most_significant_bits.inspect}"
101
+ end
102
+ end
103
+
104
+ def to_s
105
+ @string_rep ||= numbers_to_string
106
+ end
107
+
108
+ def most_significant_bits
109
+ @most_significant_bits ||= numeric_rep[0]
110
+ end
111
+
112
+ def least_significant_bits
113
+ @least_significant_bits ||= numeric_rep[1]
114
+ end
115
+
116
+ def inspect
117
+ @inspect ||= "<#{self.class} \"#{to_s}\">"
118
+ end
119
+
120
+ def ==(other)
121
+ return false unless other.is_a?(self.class)
122
+ if @numeric_rep
123
+ other.most_significant_bits == most_significant_bits &&
124
+ other.least_significant_bits == least_significant_bits
125
+ else
126
+ other.to_s == @string_rep
127
+ end
128
+ end
129
+ alias eql? ==
130
+
131
+ def hash
132
+ most_significant_bits.hash + least_significant_bits.hash
133
+ end
134
+
135
+ private
136
+
137
+ def numeric_rep
138
+ @numeric_rep ||= string_to_numbers
139
+ end
140
+
141
+ def numbers_to_string
142
+ most_significant_bits = @numeric_rep[0]
143
+ least_significant_bits = @numeric_rep[1]
144
+ digits(most_significant_bits >> 32, 8) + "-" +
145
+ digits(most_significant_bits >> 16, 4) + "-" +
146
+ digits(most_significant_bits, 4) + "-" +
147
+ digits(least_significant_bits >> 48, 4) + "-" +
148
+ digits(least_significant_bits, 12)
149
+ end
150
+
151
+ def string_to_numbers
152
+ str = @string_rep.delete("-")
153
+ [twos_complement(str[ 0..15].hex), twos_complement(str[16..31].hex)]
154
+ end
155
+
156
+ def digits(val, digits)
157
+ hi = 1 << (digits*4)
158
+ (hi | (val & (hi - 1))).to_s(16)[1..-1]
159
+ end
160
+
161
+ def twos_complement(integer_value, num_of_bits=64)
162
+ max_signed = 2**(num_of_bits-1)
163
+ max_unsigned = 2**num_of_bits
164
+ (integer_value >= max_signed) ? integer_value - max_unsigned : integer_value
165
+ end
166
+ end
167
+
168
+ # Represents a transit hypermedia link extension type.
169
+ # @see https://github.com/cognitect/transit-format
170
+ # @see http://amundsen.com/media-types/collection/format/#arrays-links
171
+ class Link
172
+ KEYS = ["href", "rel", "name", "render", "prompt"]
173
+ RENDER_VALUES = ["link", "image"]
174
+
175
+ # @overload Link.new(hash)
176
+ # @param [Hash] hash
177
+ # Valid keys are:
178
+ # "href" required, String or Addressable::URI
179
+ # "rel" required, String
180
+ # "name" optional, String
181
+ # "render" optional, String (only "link" or "image")
182
+ # "prompt" optional, String
183
+ # @overload Link.new(href, rel, name, render, prompt)
184
+ # @param [String, Addressable::URI] href required
185
+ # @param [String] rel required
186
+ # @param [String] name optional
187
+ # @param [String] render optional (only "link" or "image")
188
+ # @param [String] prompt optional
189
+ def initialize(*args)
190
+ @values = if args[0].is_a?(Hash)
191
+ reconcile_values(args[0])
192
+ elsif args.length >= 2 && (args[0].is_a?(Addressable::URI) || args[0].is_a?(String))
193
+ reconcile_values(Hash[KEYS.zip(args)])
194
+ else
195
+ 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."
196
+ end
197
+ end
198
+
199
+ def href; @href ||= @values["href"] end
200
+ def rel; @rel ||= @values["rel"] end
201
+ def name; @name ||= @values["name"] end
202
+ def render; @render ||= @values["render"] end
203
+ def prompt; @prompt ||= @values["prompt"] end
204
+
205
+ def to_h
206
+ @values
207
+ end
208
+
209
+ def ==(other)
210
+ other.is_a?(Link) && other.to_h == to_h
211
+ end
212
+ alias eql? ==
213
+
214
+ def hash
215
+ @values.hash
216
+ end
217
+
218
+ private
219
+
220
+ def reconcile_values(map)
221
+ map.dup.tap do |m|
222
+ m["href"] = Addressable::URI.parse(m["href"]) if m["href"].is_a?(String)
223
+ if m["render"]
224
+ render = m["render"].downcase
225
+ if RENDER_VALUES.include?(render)
226
+ m["render"] = render
227
+ else
228
+ raise ArgumentError, "render must be either #{RENDER_VALUES[0]} or #{RENDER_VALUES[1]}"
229
+ end
230
+ end
231
+ end.freeze
232
+ end
233
+ end
234
+
235
+ # Represents a transit tag and value. Returned by default when a
236
+ # reader encounters a tag for which there is no registered
237
+ # handler. Can also be used in a custom write handler to force
238
+ # representation to use a transit ground type using a rep for which
239
+ # there is no registered handler (e.g., an iterable for the
240
+ # representation of an array).
241
+ # @see https://github.com/cognitect/transit-format
242
+ class TaggedValue
243
+ attr_reader :tag, :rep
244
+ def initialize(tag, rep)
245
+ @tag = tag
246
+ @rep = rep
247
+ end
248
+
249
+ def ==(other)
250
+ other.is_a?(self.class) && other.tag == @tag && other.rep == @rep
251
+ end
252
+ alias eql? ==
253
+
254
+ def hash
255
+ @tag.hash + @rep.hash
256
+ end
257
+ end
258
+ 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