norikra 0.0.1-java
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE +339 -0
- data/README.md +104 -0
- data/Rakefile +10 -0
- data/bin/norikra +8 -0
- data/esper/changelog.txt +1066 -0
- data/esper/esper-4.9.0.jar +0 -0
- data/esper/esper-license.txt +95 -0
- data/esper/esper/lib/antlr-runtime-3.2.jar +0 -0
- data/esper/esper/lib/cglib-nodep-2.2.jar +0 -0
- data/esper/esper/lib/commons-logging-1.1.1.jar +0 -0
- data/esper/esper/lib/esper_3rdparties.license +299 -0
- data/esper/esper/lib/log4j-1.2.16.jar +0 -0
- data/esper/esper/lib/readme.txt +38 -0
- data/esper/esperio-amqp-4.9.0.jar +0 -0
- data/esper/esperio-amqp/lib/commons-cli-1.1.jar +0 -0
- data/esper/esperio-amqp/lib/commons-io-1.2.jar +0 -0
- data/esper/esperio-amqp/lib/esperio_3rdparties.license +1328 -0
- data/esper/esperio-amqp/lib/esperio_amqp_jars.txt +2 -0
- data/esper/esperio-amqp/lib/rabbitmq-client.jar +0 -0
- data/esper/esperio-csv-4.9.0.jar +0 -0
- data/esper/esperio-csv/lib/esperio_3rdparties.license +1328 -0
- data/esper/esperio-db-4.9.0.jar +0 -0
- data/esper/esperio-db/lib/esperio_3rdparties.license +1328 -0
- data/esper/esperio-http-4.9.0.jar +0 -0
- data/esper/esperio-http/lib/esperio_3rdparties.license +1328 -0
- data/esper/esperio-http/lib/httpclient-4.0.1.jar +0 -0
- data/esper/esperio-http/lib/httpcore-4.0.1.jar +0 -0
- data/esper/esperio-http/lib/httpcore-nio-4.0.1.jar +0 -0
- data/esper/esperio-license.txt +95 -0
- data/esper/esperio-socket-4.9.0.jar +0 -0
- data/esper/esperio-socket/lib/esperio_3rdparties.license +1328 -0
- data/esper/esperio-springjms-4.9.0.jar +0 -0
- data/esper/esperio-springjms/lib/activation-1.1.jar +0 -0
- data/esper/esperio-springjms/lib/activemq-core-5.7.0.jar +0 -0
- data/esper/esperio-springjms/lib/activemq-pool-5.7.0.jar +0 -0
- data/esper/esperio-springjms/lib/commons-pool-1.6.jar +0 -0
- data/esper/esperio-springjms/lib/esperio_3rdparties.license +1328 -0
- data/esper/esperio-springjms/lib/geronimo-j2ee-management_1.1_spec-1.0.1.jar +0 -0
- data/esper/esperio-springjms/lib/geronimo-jms_1.1_spec-1.1.1.jar +0 -0
- data/esper/esperio-springjms/lib/junit-4.8.2.jar +0 -0
- data/esper/esperio-springjms/lib/org.springframework.asm-3.1.1.RELEASE.jar +0 -0
- data/esper/esperio-springjms/lib/org.springframework.beans-3.1.1.RELEASE.jar +0 -0
- data/esper/esperio-springjms/lib/org.springframework.context-3.1.1.RELEASE.jar +0 -0
- data/esper/esperio-springjms/lib/org.springframework.core-3.1.1.RELEASE.jar +0 -0
- data/esper/esperio-springjms/lib/org.springframework.expression-3.1.1.RELEASE.jar +0 -0
- data/esper/esperio-springjms/lib/org.springframework.jms-3.1.1.RELEASE.jar +0 -0
- data/esper/esperio-springjms/lib/org.springframework.transaction-3.1.1.RELEASE.jar +0 -0
- data/esper/esperio-springjms/lib/slf4j-api-1.7.2.jar +0 -0
- data/esper/esperio-springjms/lib/slf4j-log4j12-1.7.2.jar +0 -0
- data/esper/esperio-stax-4.9.0.jar +0 -0
- data/esper/esperio-stax/lib/axiom-api-1.2.9.jar +0 -0
- data/esper/esperio-stax/lib/axiom-c14n-1.2.9.jar +0 -0
- data/esper/esperio-stax/lib/axiom-dom-1.2.9.jar +0 -0
- data/esper/esperio-stax/lib/axiom-impl-1.2.9.jar +0 -0
- data/esper/esperio-stax/lib/commons-logging-1.1.1.jar +0 -0
- data/esper/esperio-stax/lib/commons-logging-LICENSE.txt +203 -0
- data/esper/esperio-stax/lib/esperio_3rdparties.license +1328 -0
- data/esper/esperio-stax/lib/geronimo-activation-LICENSE.txt +203 -0
- data/esper/esperio-stax/lib/geronimo-activation_1.1_spec-1.0.2.jar +0 -0
- data/esper/esperio-stax/lib/geronimo-javamail-LICENSE.txt +203 -0
- data/esper/esperio-stax/lib/geronimo-javamail_1.4_spec-1.6.jar +0 -0
- data/esper/esperio-stax/lib/geronimo-stax-api-LICENSE.txt +203 -0
- data/esper/esperio-stax/lib/geronimo-stax-api_1.0_spec-1.0.1.jar +0 -0
- data/esper/esperio-stax/lib/jaxen-1.1.1.jar +0 -0
- data/esper/esperio-stax/lib/jaxen-LICENSE.txt +33 -0
- data/esper/esperio-stax/lib/wstx-LICENSE.txt +203 -0
- data/esper/esperio-stax/lib/wstx-asl-3.2.9.jar +0 -0
- data/junks/esper-test.rb +79 -0
- data/junks/glassfish.rb +17 -0
- data/junks/mizuno.rb +28 -0
- data/junks/simple_test.rb +35 -0
- data/junks/type_confliction.rb +46 -0
- data/junks/type_conversion.rb +49 -0
- data/junks/type_inherit.rb +67 -0
- data/lib/norikra.rb +13 -0
- data/lib/norikra/cli.rb +24 -0
- data/lib/norikra/engine.rb +231 -0
- data/lib/norikra/output_pool.rb +45 -0
- data/lib/norikra/query.rb +173 -0
- data/lib/norikra/rpc.rb +6 -0
- data/lib/norikra/rpc/handler.rb +57 -0
- data/lib/norikra/rpc/http.rb +36 -0
- data/lib/norikra/server.rb +41 -0
- data/lib/norikra/typedef.rb +307 -0
- data/lib/norikra/typedef_manager.rb +91 -0
- data/lib/norikra/version.rb +3 -0
- data/norikra.gemspec +33 -0
- data/script/spec_server_pry +45 -0
- data/spec/field_spec.rb +141 -0
- data/spec/fieldset_spec.rb +172 -0
- data/spec/output_pool_spec.rb +75 -0
- data/spec/query_spec.rb +82 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/typedef_manager_spec.rb +128 -0
- data/spec/typedef_spec.rb +248 -0
- metadata +328 -0
data/lib/norikra/rpc.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
class Norikra::RPC::Handler
|
2
|
+
def initialize(engine)
|
3
|
+
@engine = engine
|
4
|
+
end
|
5
|
+
|
6
|
+
def targets
|
7
|
+
@engine.targets
|
8
|
+
end
|
9
|
+
|
10
|
+
def open(target, fields)
|
11
|
+
r = @engine.open(target, fields)
|
12
|
+
!!r
|
13
|
+
end
|
14
|
+
|
15
|
+
def close(target)
|
16
|
+
r = @engine.close(target)
|
17
|
+
!!r
|
18
|
+
end
|
19
|
+
|
20
|
+
def queries
|
21
|
+
@engine.queries.map(&:to_hash)
|
22
|
+
end
|
23
|
+
|
24
|
+
def register(query_name, expression)
|
25
|
+
r = @engine.register(Norikra::Query.new(:name => query_name, :expression => expression))
|
26
|
+
!!r
|
27
|
+
end
|
28
|
+
|
29
|
+
def deregister(query_name)
|
30
|
+
#TODO: write!
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
def fields(target)
|
35
|
+
@engine.typedef_manager.field_list(target)
|
36
|
+
end
|
37
|
+
|
38
|
+
def reserve(target, fieldname, type)
|
39
|
+
r = @engine.reserve(target, fieldname, type)
|
40
|
+
!!r
|
41
|
+
end
|
42
|
+
|
43
|
+
def send(target, events)
|
44
|
+
r = @engine.send(target, events)
|
45
|
+
!!r
|
46
|
+
end
|
47
|
+
|
48
|
+
def event(query_name)
|
49
|
+
@engine.output_pool.pop(query_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def sweep
|
53
|
+
@engine.output_pool.sweep
|
54
|
+
end
|
55
|
+
|
56
|
+
# post('/listen') # get all events as stream, during connection keepaliving
|
57
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'mizuno/server'
|
2
|
+
require 'rack/builder'
|
3
|
+
require 'msgpack-rpc-over-http-jruby'
|
4
|
+
|
5
|
+
require_relative 'handler'
|
6
|
+
|
7
|
+
module Norikra::RPC
|
8
|
+
class HTTP
|
9
|
+
#TODO Xmx of mizuno/jetty
|
10
|
+
attr_accessor :host, :port, :threads
|
11
|
+
attr_accessor :engine, :mizuno, :thread
|
12
|
+
|
13
|
+
def initialize(opts={})
|
14
|
+
@engine = opts[:engine]
|
15
|
+
@host = opts[:host]
|
16
|
+
@port = opts[:port]
|
17
|
+
handler = Norikra::RPC::Handler.new(@engine)
|
18
|
+
@app = Rack::Builder.new {
|
19
|
+
run MessagePack::RPCOverHTTP::Server.app(handler)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
@thread = Thread.new do
|
25
|
+
@mizuno = Mizuno::Server.new
|
26
|
+
@mizuno.run(@app, :embedded => true, :threads => 5, :port => @port, :host => @host)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@mizuno.stop
|
32
|
+
@thread.kill
|
33
|
+
@thread.join
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'norikra/engine'
|
2
|
+
|
3
|
+
require 'norikra/typedef_manager'
|
4
|
+
require 'norikra/output_pool'
|
5
|
+
require 'norikra/typedef'
|
6
|
+
require 'norikra/query'
|
7
|
+
|
8
|
+
require 'norikra/rpc'
|
9
|
+
|
10
|
+
module Norikra
|
11
|
+
class Server
|
12
|
+
RPC_DEFAULT_HOST = '0.0.0.0'
|
13
|
+
RPC_DEFAULT_PORT = 26571
|
14
|
+
# 26571 = 3026 + 3014 + 2968 + 2950 + 2891 + 2896 + 2975 + 2979 + 2872
|
15
|
+
|
16
|
+
def initialize(host=RPC_DEFAULT_HOST, port=RPC_DEFAULT_PORT, configuration={})
|
17
|
+
#TODO: initial configuration
|
18
|
+
@typedef_manager = Norikra::TypedefManager.new
|
19
|
+
|
20
|
+
@output_pool = Norikra::OutputPool.new
|
21
|
+
|
22
|
+
@engine = Norikra::Engine.new(@output_pool, @typedef_manager)
|
23
|
+
@rpcserver = Norikra::RPC::HTTP.new(:engine => @engine, :port => port)
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
@engine.start
|
28
|
+
@rpcserver.start
|
29
|
+
p "Norikra server started."
|
30
|
+
#TODO: main loop and signal traps
|
31
|
+
#TODO: loggings
|
32
|
+
sleep 50
|
33
|
+
end
|
34
|
+
|
35
|
+
def shutdown
|
36
|
+
#TODO: stop order
|
37
|
+
@rpcserver.stop
|
38
|
+
@engine.stop
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# Norikra::Field, Norikra::FieldSet, Norikra::Typedef
|
5
|
+
|
6
|
+
module Norikra
|
7
|
+
class Field
|
8
|
+
attr_accessor :name, :type, :optional
|
9
|
+
|
10
|
+
def initialize(name, type, optional=nil)
|
11
|
+
@name = name.to_s
|
12
|
+
@type = self.class.valid_type?(type)
|
13
|
+
@optional = optional
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
{'name' => @name, 'type' => @type, 'optional' => @optional}
|
18
|
+
end
|
19
|
+
|
20
|
+
def dup(optional=nil)
|
21
|
+
self.class.new(@name, @type, optional.nil? ? @optional : optional)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
self.name == other.name && self.type == other.type && self.optional == other.optional
|
26
|
+
end
|
27
|
+
|
28
|
+
def optional? # used outside of FieldSet
|
29
|
+
@optional
|
30
|
+
end
|
31
|
+
|
32
|
+
### esper types
|
33
|
+
### http://esper.codehaus.org/esper-4.9.0/doc/reference/en-US/html/epl_clauses.html#epl-syntax-datatype
|
34
|
+
# string A single character to an unlimited number of characters.
|
35
|
+
# boolean A boolean value.
|
36
|
+
# integer An integer value (4 byte).
|
37
|
+
# long A long value (8 byte). Use the "L" or "l" (lowercase L) suffix. # select 1L as field1, 1l as field2
|
38
|
+
# double A double-precision 64-bit IEEE 754 floating point. # select 1.67 as field1, 167e-2 as field2, 1.67d as field3
|
39
|
+
# float A single-precision 32-bit IEEE 754 floating point. Use the "f" suffix. # select 1.2f as field1, 1.2F as field2
|
40
|
+
# byte A 8-bit signed two's complement integer. # select 0x10 as field1
|
41
|
+
#
|
42
|
+
#### 'integer' in epser document IS WRONG.
|
43
|
+
#### If 'integer' specified, esper raises this exception:
|
44
|
+
### Exception: Nestable type configuration encountered an unexpected property type name 'integer' for property 'status',
|
45
|
+
### expected java.lang.Class or java.util.Map or the name of a previously-declared Map or ObjectArray type
|
46
|
+
#### Correct type name is 'int'. see and run 'junks/esper-test.rb'
|
47
|
+
def self.valid_type?(type)
|
48
|
+
case type.to_s.downcase
|
49
|
+
when 'string' then 'string'
|
50
|
+
when 'boolean' then 'boolean'
|
51
|
+
when 'int' then 'int'
|
52
|
+
when 'long' then 'long'
|
53
|
+
when 'float' then 'float'
|
54
|
+
when 'double' then 'double'
|
55
|
+
else
|
56
|
+
raise ArgumentError, "invalid field type #{type}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def format(value)
|
61
|
+
case @type
|
62
|
+
when 'string' then value.to_s
|
63
|
+
when 'boolean' then value =~ /^(true|false)$/i ? ($1.downcase == 'true') : (!!value)
|
64
|
+
when 'long','int' then value.to_i
|
65
|
+
when 'double','float' then value.to_f
|
66
|
+
else
|
67
|
+
raise RuntimeError, "unknown field type (in format), maybe BUG. name:#{@name},type:#{@type}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class FieldSet
|
73
|
+
attr_accessor :summary, :fields
|
74
|
+
attr_accessor :target, :level
|
75
|
+
|
76
|
+
def initialize(fields, default_optional=nil)
|
77
|
+
@fields = {}
|
78
|
+
fields.keys.each do |key|
|
79
|
+
data = fields[key]
|
80
|
+
type,optional = if data.is_a?(Hash)
|
81
|
+
[data[:type], (data.has_key?(:optional) ? data[:optional] : default_optional)]
|
82
|
+
elsif data.is_a?(String)
|
83
|
+
[data, default_optional]
|
84
|
+
else
|
85
|
+
raise ArgumentError, "FieldSet.new argument class unknown: #{fields.class}"
|
86
|
+
end
|
87
|
+
@fields[key.to_s] = Field.new(key, type, optional)
|
88
|
+
end
|
89
|
+
self.update_summary
|
90
|
+
|
91
|
+
@target = nil
|
92
|
+
@level = nil
|
93
|
+
@event_type_name = nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def dup
|
97
|
+
fields = Hash[@fields.map{|key,field| [key, {:type => field.type, :optional => field.optional}]}]
|
98
|
+
self.class.new(fields)
|
99
|
+
end
|
100
|
+
|
101
|
+
def field_names_key
|
102
|
+
@fields.keys.sort.join(',')
|
103
|
+
end
|
104
|
+
|
105
|
+
def update_summary
|
106
|
+
@summary = @fields.keys.sort.map{|k| @fields[k].name + ':' + @fields[k].type}.join(',')
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def update(fields, optional_flag)
|
111
|
+
fields.each do |field|
|
112
|
+
@fields[field.name] = field.dup(optional_flag)
|
113
|
+
end
|
114
|
+
self.update_summary
|
115
|
+
end
|
116
|
+
|
117
|
+
#TODO: have a bug?
|
118
|
+
def ==(other)
|
119
|
+
return false unless self.class != other.class
|
120
|
+
self.summary == other.summary
|
121
|
+
end
|
122
|
+
|
123
|
+
def definition
|
124
|
+
d = {}
|
125
|
+
@fields.each do |key, field|
|
126
|
+
d[field.name] = field.type
|
127
|
+
end
|
128
|
+
d
|
129
|
+
end
|
130
|
+
|
131
|
+
def subset?(other) # self is subset of other (or not)
|
132
|
+
(self.fields.keys - other.fields.keys).size == 0
|
133
|
+
end
|
134
|
+
|
135
|
+
def event_type_name
|
136
|
+
@event_type_name.dup
|
137
|
+
end
|
138
|
+
|
139
|
+
def bind(target, level)
|
140
|
+
@target = target
|
141
|
+
@level = level
|
142
|
+
prefix = case level
|
143
|
+
when :base then 'b_'
|
144
|
+
when :query then 'q_'
|
145
|
+
else 'e_'
|
146
|
+
end
|
147
|
+
@event_type_name = prefix + Digest::MD5.hexdigest(target + "\t" + level.to_s + "\t" + @summary)
|
148
|
+
self
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.simple_guess(data, optional=true)
|
152
|
+
mapping = Hash[
|
153
|
+
data.map{|key,value|
|
154
|
+
type = case value
|
155
|
+
when TrueClass,FalseClass then 'boolean'
|
156
|
+
when Integer then 'long'
|
157
|
+
when Float then 'double'
|
158
|
+
else
|
159
|
+
'string'
|
160
|
+
end
|
161
|
+
[key,type]
|
162
|
+
}
|
163
|
+
]
|
164
|
+
self.new(mapping, optional)
|
165
|
+
end
|
166
|
+
|
167
|
+
# def self.guess(data, optional=true)
|
168
|
+
# mapping = Hash[
|
169
|
+
# data.map{|key,value|
|
170
|
+
# sval = value.to_s
|
171
|
+
# type = case
|
172
|
+
# when val.is_a?(TrueClass) || val.is_a?(FalseClass) || sval =~ /^(?:true|false)$/i
|
173
|
+
# 'boolean'
|
174
|
+
# when val.is_a?(Integer) || sval =~ /^-?\d+[lL]?$/
|
175
|
+
# 'long'
|
176
|
+
# when val.is_a?(Float) || sval =~ /^-?\d+\.\d+(?:[eE]-?\d+|[dDfF])?$/
|
177
|
+
# 'double'
|
178
|
+
# else
|
179
|
+
# 'string'
|
180
|
+
# end
|
181
|
+
# [key,type]
|
182
|
+
# }
|
183
|
+
# ]
|
184
|
+
# self.new(mapping, optional)
|
185
|
+
# end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Typedef is
|
189
|
+
# * known field list of target (and these are optional or not)
|
190
|
+
# * known field-set list of a target
|
191
|
+
# * base set of a target
|
192
|
+
class Typedef
|
193
|
+
attr_accessor :fields, :baseset, :queryfieldsets, :datafieldsets
|
194
|
+
|
195
|
+
def initialize(fields=nil)
|
196
|
+
if fields
|
197
|
+
@baseset = FieldSet.new(fields, false) # all fields are required
|
198
|
+
@fields = @baseset.fields.dup
|
199
|
+
else
|
200
|
+
@baseset = nil
|
201
|
+
@fields = {}
|
202
|
+
end
|
203
|
+
|
204
|
+
@queryfieldsets = []
|
205
|
+
@datafieldsets = []
|
206
|
+
|
207
|
+
@set_map = {} # fieldname.sort.join(',') => data_fieldset
|
208
|
+
|
209
|
+
@mutex = Mutex.new
|
210
|
+
end
|
211
|
+
|
212
|
+
def field_defined?(list)
|
213
|
+
list.reduce(true){|r,f| r && @fields[f]}
|
214
|
+
end
|
215
|
+
|
216
|
+
def lazy?
|
217
|
+
@baseset.nil?
|
218
|
+
end
|
219
|
+
|
220
|
+
def activate(fieldset)
|
221
|
+
@mutex.synchronize do
|
222
|
+
set = fieldset.dup
|
223
|
+
fieldset.fields.dup.each do |fieldname, field|
|
224
|
+
set.fields[fieldname] = field.dup(false)
|
225
|
+
end
|
226
|
+
@baseset = set
|
227
|
+
@fields = @baseset.fields.merge(@fields)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def reserve(fieldname, type, optional=true)
|
232
|
+
fieldname = fieldname.to_s
|
233
|
+
@mutex.synchronize do
|
234
|
+
return false if @fields[fieldname]
|
235
|
+
@fields[fieldname] = Field.new(fieldname, type, optional)
|
236
|
+
end
|
237
|
+
true
|
238
|
+
end
|
239
|
+
|
240
|
+
def consistent?(fieldset)
|
241
|
+
fields = fieldset.fields
|
242
|
+
@baseset.subset?(fieldset) &&
|
243
|
+
@fields.values.select{|f| !f.optional? }.reduce(true){|r,f| r && fields[f.name] && fields[f.name].type == f.type} &&
|
244
|
+
fields.values.reduce(true){|r,f| r && (@fields[f.name].nil? || @fields[f.name].type == f.type)}
|
245
|
+
end
|
246
|
+
|
247
|
+
def push(level, fieldset)
|
248
|
+
unless self.consistent?(fieldset)
|
249
|
+
raise ArgumentError, "inconsistent field set for this typedef"
|
250
|
+
end
|
251
|
+
|
252
|
+
@mutex.synchronize do
|
253
|
+
case level
|
254
|
+
when :base
|
255
|
+
unless @baseset.object_id == fieldset.object_id
|
256
|
+
raise RuntimeError, "baseset mismatch"
|
257
|
+
end
|
258
|
+
when :query
|
259
|
+
unless @queryfieldsets.include?(fieldset)
|
260
|
+
@queryfieldsets.push(fieldset)
|
261
|
+
|
262
|
+
fieldset.fields.each do |fieldname,field|
|
263
|
+
@fields[fieldname] = field.dup(true) unless @fields[fieldname]
|
264
|
+
end
|
265
|
+
end
|
266
|
+
when :data
|
267
|
+
unless @datafieldsets.include?(fieldset)
|
268
|
+
@datafieldsets.push(fieldset)
|
269
|
+
@set_map[fieldset.field_names_key] = fieldset
|
270
|
+
|
271
|
+
fieldset.fields.each do |fieldname,field|
|
272
|
+
@fields[fieldname] = field.dup(true) unless @fields[fieldname]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
else
|
276
|
+
raise ArgumentError, "unknown level #{level}"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
true
|
280
|
+
end
|
281
|
+
|
282
|
+
def refer(data)
|
283
|
+
field_names_key = data.keys.sort.join(',')
|
284
|
+
return @set_map[field_names_key] if @set_map.has_key?(field_names_key)
|
285
|
+
|
286
|
+
guessed = FieldSet.simple_guess(data)
|
287
|
+
guessed_fields = guessed.fields
|
288
|
+
@fields.each do |key,field|
|
289
|
+
if guessed_fields.has_key?(key)
|
290
|
+
guessed_fields[key].type = field.type if guessed_fields[key].type != field.type
|
291
|
+
else
|
292
|
+
guessed_fields[key] = field unless field.optional?
|
293
|
+
end
|
294
|
+
end
|
295
|
+
guessed
|
296
|
+
end
|
297
|
+
|
298
|
+
def format(data)
|
299
|
+
# all keys of data should be already known at #format (before #format, do #refer)
|
300
|
+
ret = {}
|
301
|
+
data.each do |key, value|
|
302
|
+
ret[key] = @fields[key].format(value)
|
303
|
+
end
|
304
|
+
ret
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|