jinx 2.1.3 → 2.1.4
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.
- data/Gemfile.lock +27 -0
- data/History.md +4 -0
- data/lib/jinx/helpers/class.rb +16 -12
- data/lib/jinx/helpers/collection.rb +277 -29
- data/lib/jinx/helpers/collections.rb +35 -2
- data/lib/jinx/helpers/conditional_enumerator.rb +2 -2
- data/lib/jinx/helpers/filter.rb +8 -2
- data/lib/jinx/helpers/flattener.rb +2 -2
- data/lib/jinx/helpers/hash.rb +3 -2
- data/lib/jinx/helpers/{hashable.rb → hasher.rb} +125 -77
- data/lib/jinx/helpers/module.rb +1 -1
- data/lib/jinx/helpers/multi_enumerator.rb +25 -9
- data/lib/jinx/helpers/options.rb +4 -3
- data/lib/jinx/helpers/partial_order.rb +16 -8
- data/lib/jinx/helpers/pretty_print.rb +14 -4
- data/lib/jinx/helpers/transformer.rb +3 -1
- data/lib/jinx/helpers/transitive_closure.rb +3 -3
- data/lib/jinx/helpers/visitor.rb +33 -42
- data/lib/jinx/import/java.rb +40 -27
- data/lib/jinx/importer.rb +86 -33
- data/lib/jinx/metadata/attribute_enumerator.rb +5 -11
- data/lib/jinx/metadata/dependency.rb +65 -30
- data/lib/jinx/metadata/id_alias.rb +1 -0
- data/lib/jinx/metadata/introspector.rb +21 -9
- data/lib/jinx/metadata/inverse.rb +14 -11
- data/lib/jinx/metadata/java_property.rb +15 -26
- data/lib/jinx/metadata/propertied.rb +80 -19
- data/lib/jinx/metadata/property.rb +13 -8
- data/lib/jinx/metadata/property_characteristics.rb +2 -2
- data/lib/jinx/resource.rb +62 -32
- data/lib/jinx/resource/inversible.rb +4 -0
- data/lib/jinx/resource/match_visitor.rb +0 -1
- data/lib/jinx/resource/mergeable.rb +16 -6
- data/lib/jinx/resource/reference_enumerator.rb +1 -2
- data/lib/jinx/version.rb +1 -1
- data/test/lib/jinx/helpers/collections_test.rb +29 -14
- data/test/lib/jinx/helpers/visitor_test.rb +7 -20
- data/test/lib/jinx/import/mixed_case_test.rb +17 -3
- metadata +4 -4
- data/lib/jinx/helpers/enumerable.rb +0 -245
data/lib/jinx/helpers/module.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
require 'jinx/helpers/collection'
|
2
|
+
|
1
3
|
module Jinx
|
2
|
-
# A MultiEnumerator iterates over several Enumerators in sequence. Unlike Array#+,
|
3
|
-
# underlying enumerators.
|
4
|
+
# A MultiEnumerator iterates over several Enumerators in sequence. Unlike Array#+,
|
5
|
+
# MultiEnumerator reflects changes to the underlying enumerators.
|
4
6
|
#
|
5
7
|
# @example
|
6
8
|
# a = [1, 2]
|
@@ -8,24 +10,38 @@ module Jinx
|
|
8
10
|
# ab = MultiEnumerator.new(a, b)
|
9
11
|
# ab.to_a #=> [1, 2, 4, 5]
|
10
12
|
# a << 3; b << 6; ab.to_a #=> [1, 2, 3, 4, 5, 6]
|
13
|
+
# MultiEnumerator.new(ab, [7]).to_a #=> [1, 2, 3, 4, 5, 6, 7]
|
11
14
|
class MultiEnumerator
|
12
|
-
include Collection
|
13
|
-
|
14
|
-
# @return [<Enumerable>] the enumerated collections
|
15
|
-
attr_reader :components
|
15
|
+
include Enumerable, Collection
|
16
16
|
|
17
17
|
# Initializes a new {MultiEnumerator} on the given components.
|
18
18
|
#
|
19
19
|
# @param [<Enumerable>] the component enumerators to compose
|
20
|
-
|
20
|
+
# @yield [item] the optional appender block
|
21
|
+
# @yieldparam item the item to append
|
22
|
+
def initialize(*enums, &appender)
|
21
23
|
super()
|
22
24
|
@components = enums
|
23
25
|
@components.compact!
|
26
|
+
@appender = appender
|
24
27
|
end
|
25
28
|
|
26
|
-
# Iterates over each of this MultiEnumerator's
|
29
|
+
# Iterates over each of this MultiEnumerator's enumerators in sequence.
|
27
30
|
def each
|
28
|
-
@components.each
|
31
|
+
@components.each do |enum|
|
32
|
+
enum.each { |item| yield item }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param item the item to append
|
37
|
+
# @raise [NoSuchMethodError] if this {MultiEnumerator} does not have an appender
|
38
|
+
def <<(item)
|
39
|
+
@appender ? @appender << item : super
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the union of the results of calling the given method symbol on each component.
|
43
|
+
def method_missing(symbol, *args)
|
44
|
+
self.class.new(@components.map { |enum|enum.send(symbol, *args) })
|
29
45
|
end
|
30
46
|
end
|
31
47
|
end
|
data/lib/jinx/helpers/options.rb
CHANGED
@@ -3,7 +3,8 @@ require 'jinx/helpers/collections'
|
|
3
3
|
require 'jinx/helpers/validation'
|
4
4
|
require 'jinx/helpers/merge'
|
5
5
|
|
6
|
-
# Options is a utility class to support method
|
6
|
+
# Options is a utility class to support a method option parameter.
|
7
|
+
# Option argument parsing is described in {Options.get}.
|
7
8
|
class Options
|
8
9
|
# Returns the value of option in options as follows:
|
9
10
|
# * If options is a hash which contains the option key, then this method returns
|
@@ -80,8 +81,8 @@ class Options
|
|
80
81
|
|
81
82
|
private
|
82
83
|
|
83
|
-
def self.detect_in_enumerable(key,
|
84
|
-
|
84
|
+
def self.detect_in_enumerable(key, opts)
|
85
|
+
opts.detect_value do |opt|
|
85
86
|
Hash === opt ? opt[key] : opt == key
|
86
87
|
end
|
87
88
|
end
|
@@ -1,12 +1,15 @@
|
|
1
1
|
module Jinx
|
2
|
-
# A PartialOrder is a Comparable
|
3
|
-
# are required to implement the <=> operator with the following
|
4
|
-
#
|
5
|
-
#
|
2
|
+
# A PartialOrder is a Comparable with restricted scope. Classes which include
|
3
|
+
# PartialOrder are required to implement the <=> operator with the following
|
4
|
+
# semantics:
|
5
|
+
# * If if a and b are comparable, then return the value of the comparison.
|
6
|
+
# * Otherwise, return nil.
|
7
|
+
# A PartialOrder thus relaxes comparison symmetry, e.g.:
|
6
8
|
# a < b
|
7
|
-
# does not imply
|
9
|
+
# does not imply:
|
8
10
|
# b >= a.
|
9
|
-
#
|
11
|
+
#
|
12
|
+
# @example
|
10
13
|
# module Queued
|
11
14
|
# attr_reader :queue
|
12
15
|
# def <=>(other)
|
@@ -14,18 +17,23 @@ module Jinx
|
|
14
17
|
# end
|
15
18
|
# end
|
16
19
|
# q1 = [a, b] # a, b are Queued
|
17
|
-
# q2 = [c] # c is
|
20
|
+
# q2 = [c] # c is Queued
|
18
21
|
# a < b #=> true
|
19
22
|
# b < c #=> nil
|
20
23
|
module PartialOrder
|
21
24
|
include Comparable
|
22
25
|
|
26
|
+
# Override the Comparable instance methods to accomodate comparisons which return nil.
|
27
|
+
# Each method returns the following result:
|
28
|
+
# * If the method argument is comparable to this object, then delegate to the standard
|
29
|
+
# Comparable method.
|
30
|
+
# * Otherwise, return nil.
|
23
31
|
Comparable.instance_methods(false).each do |m|
|
24
32
|
define_method(m.to_sym) do |other|
|
25
33
|
self <=> other ? super : nil
|
26
34
|
end
|
27
35
|
end
|
28
|
-
|
36
|
+
|
29
37
|
# @return [Boolean] true if other is an instance of this object's class and other == self,
|
30
38
|
# false otherwise
|
31
39
|
def eql?(other)
|
@@ -154,8 +154,8 @@ module Enumerable
|
|
154
154
|
end
|
155
155
|
|
156
156
|
module Jinx
|
157
|
-
module
|
158
|
-
# qp, short for quick-print, prints this
|
157
|
+
module Hasher
|
158
|
+
# qp, short for quick-print, prints this Hasher with a filter that calls qp on each key and value.
|
159
159
|
#
|
160
160
|
# @return [String] the quick-print result
|
161
161
|
def qp
|
@@ -182,9 +182,19 @@ class String
|
|
182
182
|
end
|
183
183
|
|
184
184
|
class DateTime
|
185
|
-
#
|
185
|
+
# Pretty-prints the DateTime +strftime+ to the given queue in the format
|
186
|
+
# _date_+T+_hr_+h+_mm_+m+_ss_+s+, e.g. +2010-10-10T04h10m30+.
|
187
|
+
#
|
188
|
+
# @quirk JRuby pp standard libary bug - Printing the formatted date:time string sporadically
|
189
|
+
# results in a corrupted line, e.g.:
|
190
|
+
# at top level in 2010-10-10T00:00:00-07:00 at line 1
|
191
|
+
# The Jinx work-around is to substitute the colons with +h+, +m+ and +s+ and omit the GMT offset.
|
192
|
+
# The bug cause is unknown. Perhaps it is a flaw in the line break logic.
|
193
|
+
#
|
194
|
+
# @param q the print queue
|
195
|
+
# @return [String] the formatted date and time
|
186
196
|
def pretty_print(q)
|
187
|
-
q.text(strftime)
|
197
|
+
q.text(strftime.sub(/T(\d{2}):(\d{2}):(\d{2})-\d{2}:\d{2}/, 'T\1h\2m\3s'))
|
188
198
|
end
|
189
199
|
|
190
200
|
# qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
|
@@ -1,7 +1,9 @@
|
|
1
|
+
require 'jinx/helpers/collection'
|
2
|
+
|
1
3
|
module Jinx
|
2
4
|
# This Transformer helper class applies a transformer block to a base enumeration.
|
3
5
|
class Transformer
|
4
|
-
include Collection
|
6
|
+
include Enumerable, Collection
|
5
7
|
|
6
8
|
def initialize(enum=[], &transformer)
|
7
9
|
@base = enum
|
@@ -9,9 +9,6 @@ class Object
|
|
9
9
|
# In either case, the call is expected to return an object or Enumerable of objects which also respond
|
10
10
|
# to the method or block.
|
11
11
|
#
|
12
|
-
# @param [Symbol, nil] method the child reference, or nil if a block is given
|
13
|
-
# @yield [node] the parent node's children
|
14
|
-
# @yieldparam node the parent node
|
15
12
|
# @example
|
16
13
|
# class Node
|
17
14
|
# attr_reader :parent, :children
|
@@ -26,6 +23,9 @@ class Object
|
|
26
23
|
# a = Node.new('a'); b = Node.new('b', a), c = Node.new('c', a); d = Node.new('d', c)
|
27
24
|
# a.transitive_closure { |node| node.children }.to_a.join(", ") #=> a, b, c, d
|
28
25
|
# a.transitive_closure(:children).to_a.join(", ") #=> a, b, c, d
|
26
|
+
# @param [Symbol, nil] method the child reference, or nil if a block is given
|
27
|
+
# @yield [node] the parent node's children
|
28
|
+
# @yieldparam node the parent node
|
29
29
|
def transitive_closure(method=nil)
|
30
30
|
raise ArgumentError.new("Missing both a method argument and a block") if method.nil? and not block_given?
|
31
31
|
# If there is a method argument, then the transitive closure is based on that method.
|
data/lib/jinx/helpers/visitor.rb
CHANGED
@@ -56,26 +56,15 @@ module Jinx
|
|
56
56
|
# Jinx::Visitor.new { |node| node.children }.to_enum(parent).detect { |node| node.value == 2 }
|
57
57
|
class Visitor
|
58
58
|
|
59
|
-
attr_reader :options, :visited, :lineage
|
59
|
+
attr_reader :options, :visited, :lineage
|
60
60
|
|
61
61
|
# Creates a new Visitor which traverses the child objects returned by the navigator block.
|
62
62
|
# The navigator block takes a parent node argument and returns an enumerator on the children
|
63
|
-
# to visit.
|
63
|
+
# to visit. The options argument is described in {Options.get}.
|
64
64
|
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
# value determines whether a child is visited depth-first. See the {#visit} method for more
|
69
|
-
# information.
|
70
|
-
#
|
71
|
-
# If the :operator option is set, then the visit operator block is called when a node is visited.
|
72
|
-
# The operator block argument is the visited node.
|
73
|
-
#
|
74
|
-
# @param [Symbol, {Symbol => Object}] opts the visit options. A symbol argument is the same
|
75
|
-
# as symbol => true
|
76
|
-
# @option opts [String] :depth_first depth-first traversal
|
77
|
-
# @option opts [Proc] :operator the operator applied to each visited node
|
78
|
-
# @option opts [String] :prune_cycle flag indicating whether to exclude cycles to the root in a visit
|
65
|
+
# @param [Symbol, {Symbol => Object}] opts the visit options
|
66
|
+
# @option opts [Boolean] :depth_first depth-first traversal
|
67
|
+
# @option opts [Boolean] :prune_cycle flag indicating whether to exclude cycles in a visit
|
79
68
|
# @option opts [Boolean] :verbose print navigation log messages
|
80
69
|
# @yield [parent] returns an enumerator on the children to visit
|
81
70
|
# @yieldparam parent the current node
|
@@ -86,7 +75,6 @@ module Jinx
|
|
86
75
|
@depth_first_flag = @options[:depth_first]
|
87
76
|
@prune_cycle_flag = @options[:prune_cycle]
|
88
77
|
@lineage = []
|
89
|
-
@cycles = []
|
90
78
|
@visited = {}
|
91
79
|
@verbose = Options.get(:verbose, opts, false)
|
92
80
|
@exclude = Set.new
|
@@ -197,7 +185,7 @@ module Jinx
|
|
197
185
|
# @raise [ArgumentError] if a block is not given to this method
|
198
186
|
def filter
|
199
187
|
raise ArgumentError.new("A filter block is not given to the visitor filter method") unless block_given?
|
200
|
-
|
188
|
+
self.class.new(@options) { |node| yield(node, node_children(node)) }
|
201
189
|
end
|
202
190
|
|
203
191
|
protected
|
@@ -208,8 +196,6 @@ module Jinx
|
|
208
196
|
@lineage.clear
|
209
197
|
# if the visited hash is not shared, then clear it
|
210
198
|
@visited.clear unless @options.has_key?(:visited)
|
211
|
-
# clear the cycles
|
212
|
-
@cycles.clear
|
213
199
|
end
|
214
200
|
|
215
201
|
# Returns the children to visit for the given node.
|
@@ -238,37 +224,42 @@ module Jinx
|
|
238
224
|
result
|
239
225
|
end
|
240
226
|
|
227
|
+
# Returns the nodes which occur within a cycle, excluding the cycle entry point.
|
228
|
+
#
|
241
229
|
# @example
|
242
|
-
#
|
243
|
-
#
|
244
|
-
#
|
245
|
-
#
|
246
|
-
# @return [Array] the non-root nodes in visit cycles
|
230
|
+
# graph.paths #=> a -> b -> a, a -> c -> d -> c
|
231
|
+
# Visitor.new(graph, &navigator).cyclic_nodes(a) #=> [b, d]
|
232
|
+
# @param root the node to visit
|
233
|
+
# @return [Array] the nodes within visit cycles
|
247
234
|
def cyclic_nodes(root)
|
248
235
|
copts = @options.reject { |k, v| k == :prune_cycle }
|
249
|
-
|
236
|
+
cyclic = Set.new
|
237
|
+
cycler = Visitor.new(copts) do |parent|
|
238
|
+
children = @navigator.call(parent)
|
239
|
+
# Look for a cycle back to the child.
|
240
|
+
children.each do |child|
|
241
|
+
index = cycler.lineage.index(child)
|
242
|
+
if index then
|
243
|
+
# The child is also a parent: add the nodes between
|
244
|
+
# the two occurrences of the child in the lineage.
|
245
|
+
cyclic.merge!(cycler.lineage[(index + 1)..-1])
|
246
|
+
end
|
247
|
+
end
|
248
|
+
children
|
249
|
+
end
|
250
250
|
cycler.visit(root)
|
251
|
-
cyclic = cycler.cycles.flatten.uniq
|
252
|
-
cyclic.delete(root)
|
253
251
|
cyclic
|
254
|
-
end
|
252
|
+
end
|
255
253
|
|
256
254
|
def visit_recursive(node, &operator)
|
257
|
-
#
|
255
|
+
# Bail if no node or the node is specifically excluded.
|
258
256
|
return if node.nil? or @exclude.include?(node)
|
259
|
-
#
|
260
|
-
if @visited.has_key?(node)
|
261
|
-
|
262
|
-
|
263
|
-
if index then
|
264
|
-
cycle = @lineage[index..-1] << node
|
265
|
-
@cycles << cycle
|
266
|
-
end
|
267
|
-
return @visited[node]
|
268
|
-
end
|
269
|
-
# return nil if the node has not been visited but has been navigated in a depth-first visit
|
257
|
+
# Return the visited value if the node has already been visited.
|
258
|
+
return @visited[node] if @visited.has_key?(node)
|
259
|
+
# Return nil if the node has not been visited but has been navigated in a
|
260
|
+
# depth-first visit.
|
270
261
|
return if @lineage.include?(node)
|
271
|
-
#
|
262
|
+
# All systems go: visit the node subgraph.
|
272
263
|
visit_node_and_children(node, &operator)
|
273
264
|
end
|
274
265
|
|
data/lib/jinx/import/java.rb
CHANGED
@@ -201,24 +201,49 @@ module Java
|
|
201
201
|
NAME_SPLITTER_REGEX = /^([\w.]+)\.(\w+)$/
|
202
202
|
end
|
203
203
|
|
204
|
-
class
|
205
|
-
# Returns whether this is a
|
204
|
+
class Module
|
205
|
+
# Returns whether this is a Ruby wrapper for a Java class or interface.
|
206
206
|
def java_class?
|
207
|
-
|
207
|
+
respond_to?(:java_class)
|
208
208
|
end
|
209
209
|
|
210
|
-
# Returns the Ruby
|
211
|
-
# * If the
|
212
|
-
#
|
213
|
-
#
|
210
|
+
# Returns the Ruby module for the given argument, as follows:
|
211
|
+
# * If the argument is a primitive Java type, then return the corresponding
|
212
|
+
# JRuby wrapper.
|
213
|
+
# * If the argument is a Ruby module, then return that module.
|
214
|
+
# * If the argument is an unwrapped Java class or interface,
|
215
|
+
# then return the JRuby wrapper for the class string.
|
216
|
+
# * If the argument is a String, then return the JRuby wrapper for the
|
217
|
+
# Java class designated by that String.
|
214
218
|
#
|
215
|
-
# @
|
216
|
-
#
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
219
|
+
# @example
|
220
|
+
# Java.to_ruby(Java::java.lang.Boolean) #=> Java::JavaLang::Boolean
|
221
|
+
# Java.to_ruby(Java::boolean) #=> Java::JavaLang::Boolean
|
222
|
+
# Java.to_ruby('boolean') #=> Java::JavaLang::Boolean
|
223
|
+
#
|
224
|
+
# @param [Module, String] mod the Ruby module or unwrapped Java class, interface or name
|
225
|
+
# @return [Module] the corresponding Ruby class
|
226
|
+
def self.to_ruby(mod)
|
227
|
+
if mod.respond_to?(:java_class) then
|
228
|
+
# A primitive Java type has an empty name and non-capitalized java_class name.
|
229
|
+
jname = mod.name.blank? ? mod.java_class.name : mod.name
|
230
|
+
if jname.blank? then
|
231
|
+
raise ArgumentError.new("Cannot convert the anonymous Java class #{mod} to a JRuby class")
|
232
|
+
end
|
233
|
+
if jname =~ /^[a-z]+$/ then
|
234
|
+
# The effective Java type is the Java wrapper class.
|
235
|
+
to_ruby('java.lang.' + jname.capitalize)
|
236
|
+
elsif Module === mod then
|
237
|
+
mod
|
238
|
+
else
|
239
|
+
to_ruby(jname)
|
240
|
+
end
|
241
|
+
elsif Module === mod then
|
242
|
+
mod
|
243
|
+
elsif String === mod then
|
244
|
+
eval "Java::#{mod}"
|
245
|
+
else
|
246
|
+
raise ArgumentError.new("Cannot convert a #{mod.class} to a JRuby module")
|
222
247
|
end
|
223
248
|
end
|
224
249
|
|
@@ -242,7 +267,7 @@ class Class
|
|
242
267
|
# If the hierarchy flag is set to +false+, then only this class's properties
|
243
268
|
# will be introspected.
|
244
269
|
def property_descriptors(hierarchy=true)
|
245
|
-
return Array::EMPTY_ARRAY
|
270
|
+
return Array::EMPTY_ARRAY unless java_class?
|
246
271
|
info = hierarchy ? Java::JavaBeans::Introspector.getBeanInfo(java_class) : Java::JavaBeans::Introspector.getBeanInfo(java_class, java_class.superclass)
|
247
272
|
info.propertyDescriptors.select { |pd| pd.write_method and property_read_method(pd) }
|
248
273
|
end
|
@@ -314,18 +339,6 @@ class Class
|
|
314
339
|
private
|
315
340
|
|
316
341
|
OBJ_INST_MTHDS = Object.instance_methods
|
317
|
-
|
318
|
-
# @param [String] jname the fully-qualified Java class or interface name
|
319
|
-
# @return [String] the JRuby class or module name
|
320
|
-
# @example
|
321
|
-
# Java.to_ruby_class_name('com.test.Sample') #=> Java::ComTest::Sample
|
322
|
-
def self.to_ruby_name(jname)
|
323
|
-
path = jname.split('.')
|
324
|
-
return "Java::#{jname}" if path.size == 1
|
325
|
-
cname = path[-1]
|
326
|
-
pkg = path[0...-1].map { |s| s.capitalize_first }.join
|
327
|
-
"Java::#{pkg}::#{cname}"
|
328
|
-
end
|
329
342
|
end
|
330
343
|
|
331
344
|
class Array
|
data/lib/jinx/importer.rb
CHANGED
@@ -25,6 +25,20 @@ module Jinx
|
|
25
25
|
# introspected when they are referenced.
|
26
26
|
end
|
27
27
|
|
28
|
+
# Returns whether the given Java class or interface is imported into this
|
29
|
+
# domain module. This method imports the class or interface on demand.
|
30
|
+
#
|
31
|
+
# @param [Module] the class to check
|
32
|
+
# @return [Boolean] whether the class or interface is imported into this
|
33
|
+
# domain module
|
34
|
+
def contains?(klass)
|
35
|
+
# Import a domain class on demand by referencing the class base name in the context
|
36
|
+
# of this module. If the class name can be resolved, then also check that it
|
37
|
+
# was introspected. Primitive classes like String can be resolved, but are not
|
38
|
+
# introspected. Domain classes are introspected when the name is resolved.
|
39
|
+
(!!const_get(klass.name.demodulize) rescue false) and @introspected.include?(klass)
|
40
|
+
end
|
41
|
+
|
28
42
|
# Imports a Java class constant on demand. If the class does not already
|
29
43
|
# include this module's mixin, then the mixin is included in the class.
|
30
44
|
#
|
@@ -51,14 +65,16 @@ module Jinx
|
|
51
65
|
nil
|
52
66
|
end
|
53
67
|
end
|
54
|
-
if klass
|
68
|
+
if klass then
|
69
|
+
logger.debug { "Added #{klass} to the #{self} module." }
|
70
|
+
else
|
55
71
|
# Not a Java class; print a log message and pass along the error.
|
56
72
|
logger.debug { "#{sym} is not recognized as a #{self} Java class." }
|
57
73
|
super
|
58
74
|
end
|
59
75
|
|
60
76
|
# Introspect the Java class meta-data, if necessary.
|
61
|
-
unless
|
77
|
+
unless introspected?(klass) then
|
62
78
|
add_metadata(klass)
|
63
79
|
# Print the class meta-data.
|
64
80
|
logger.info(klass.pp_s)
|
@@ -103,23 +119,30 @@ module Jinx
|
|
103
119
|
end
|
104
120
|
# The introspected classes.
|
105
121
|
@introspected = Set.new
|
122
|
+
# The name => file hash for file definitions that are not in the packages.
|
123
|
+
@unresolved_defs = {}
|
106
124
|
end
|
107
125
|
|
108
|
-
#
|
109
|
-
#
|
126
|
+
# If the given *names* argument is empty, then returns the package names.
|
127
|
+
# Otherwise, sets the package names. The default package conforms to the JRuby convention
|
128
|
+
# for mapping a package name to a module name, e.g. the +MyApp::Domain+ default package
|
110
129
|
# is +myapp.domain+. Clients set the package if it differs from the default.
|
111
130
|
#
|
112
131
|
# @param [<String>] name the package names
|
113
132
|
def packages(*names)
|
114
|
-
@packages = names
|
133
|
+
names.empty? ? @packages : @packages = names
|
115
134
|
end
|
116
135
|
|
117
136
|
# Alias for the common case of a single package.
|
118
137
|
alias :package :packages
|
119
|
-
|
138
|
+
|
139
|
+
# If the given *directories* argument is empty, then return the definition directories.
|
140
|
+
# Otherwise, set the definitions.
|
141
|
+
#
|
120
142
|
# @param [<String>] directories the Ruby class definitions directories
|
143
|
+
# @return [<String>] the definition directories
|
121
144
|
def definitions(*directories)
|
122
|
-
@definitions = directories
|
145
|
+
directories.empty? ? @definitions : @definitions = directories
|
123
146
|
end
|
124
147
|
|
125
148
|
def load_definitions
|
@@ -140,17 +163,26 @@ module Jinx
|
|
140
163
|
srcs = sources(dir)
|
141
164
|
# Introspect and load the classes in reverse class order, i.e. superclass before subclass.
|
142
165
|
klasses = srcs.keys.transitive_closure { |k| [k.superclass] }.select { |k| srcs[k] }.reverse
|
143
|
-
# Introspect the classes
|
144
|
-
klasses.each { |klass| add_metadata(klass) unless
|
166
|
+
# Introspect the classes as necessary.
|
167
|
+
klasses.each { |klass| add_metadata(klass) unless introspected?(klass) }
|
145
168
|
# Load the classes.
|
146
169
|
klasses.each do |klass|
|
147
170
|
file = srcs[klass]
|
148
|
-
|
149
|
-
require file
|
150
|
-
logger.debug { "Loaded #{klass.qp} definition #{file}." }
|
171
|
+
load_definition(klass, file)
|
151
172
|
end
|
152
173
|
logger.debug { "Loaded the class definitions in #{dir}." }
|
153
174
|
end
|
175
|
+
|
176
|
+
def load_definition(klass, file)
|
177
|
+
logger.debug { "Loading the #{klass.qp} definition #{file}..." }
|
178
|
+
begin
|
179
|
+
require file
|
180
|
+
rescue Exception
|
181
|
+
logger.error("Could not load the #{klass} definition #{file} - " + $!)
|
182
|
+
raise
|
183
|
+
end
|
184
|
+
logger.debug { "Loaded the #{klass.qp} definition #{file}." }
|
185
|
+
end
|
154
186
|
|
155
187
|
# @param [String] dir the source directory
|
156
188
|
# @return [{Class => String}] the source class => file hash
|
@@ -161,7 +193,12 @@ module Jinx
|
|
161
193
|
# Ignore files which do not resolve to a class.
|
162
194
|
files.to_compact_hash do |file|
|
163
195
|
name = File.basename(file, ".rb").camelize
|
164
|
-
resolve_class(name)
|
196
|
+
klass = resolve_class(name)
|
197
|
+
if klass.nil? then
|
198
|
+
logger.debug { "The class definition file #{file} does not correspond to a class in the standard #{qp} packages." }
|
199
|
+
@unresolved_defs[name] = file
|
200
|
+
end
|
201
|
+
klass
|
165
202
|
end.invert
|
166
203
|
end
|
167
204
|
|
@@ -196,46 +233,62 @@ module Jinx
|
|
196
233
|
# into this method when the references are introspected below.
|
197
234
|
@introspected << klass
|
198
235
|
# Add the superclass meta-data if necessary.
|
199
|
-
|
200
|
-
|
201
|
-
unless @introspected.include?(sc) or sc == Java::java.lang.Object then
|
202
|
-
add_metadata(sc)
|
203
|
-
end
|
204
|
-
end
|
205
|
-
# Include this resource module into the class.
|
236
|
+
add_superclass_metadata(klass)
|
237
|
+
# Include this resource module into the class, unless this has already occurred.
|
206
238
|
unless klass < self then
|
207
239
|
m = self
|
208
240
|
klass.class_eval { include m }
|
209
241
|
end
|
242
|
+
# Import the class into this resource module, unless this has already occurred.
|
243
|
+
name = klass.name.demodulize
|
244
|
+
unless const_defined?(name) then
|
245
|
+
java_import(klass.java_class.name)
|
246
|
+
end
|
210
247
|
# Add introspection capability to the class.
|
211
248
|
md_mod = @metadata_module || Metadata
|
212
|
-
|
213
249
|
logger.debug { "Extending #{self}::#{klass.qp} with #{md_mod.name}..." }
|
214
250
|
klass.extend(md_mod)
|
215
|
-
|
216
|
-
# Introspect the Java properties.
|
217
|
-
introspect(klass)
|
218
|
-
klass.add_attribute_value_initializer if Class === klass
|
219
251
|
# Set the class domain module.
|
220
252
|
klass.domain_module = self
|
253
|
+
# Introspect the Java properties.
|
254
|
+
klass.introspect
|
255
|
+
# Add the {attribute => value} initializer.
|
256
|
+
klass.add_attribute_value_initializer if Class === klass
|
221
257
|
# Add referenced domain class metadata as necessary.
|
222
|
-
mod = klass.parent_module
|
223
258
|
klass.each_property do |prop|
|
224
259
|
ref = prop.type
|
225
260
|
if ref.nil? then raise MetadataError.new("#{self} #{prop} domain type is unknown.") end
|
226
|
-
|
227
|
-
logger.debug { "Introspecting #{qp} #{prop} reference #{ref.qp}..." }
|
261
|
+
if introspectible?(ref) then
|
262
|
+
logger.debug { "Introspecting the #{klass.qp} #{prop} reference type #{ref.qp}..." }
|
228
263
|
add_metadata(ref)
|
229
264
|
end
|
230
265
|
end
|
266
|
+
# If the class has a definition file but does not resolve to a standard package, then
|
267
|
+
# load it now based on the demodulized class name match.
|
268
|
+
file = @unresolved_defs[name]
|
269
|
+
load_definition(klass, file) if file
|
270
|
+
|
231
271
|
logger.debug("#{self}::#{klass.qp} metadata added.")
|
232
272
|
end
|
233
273
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
274
|
+
def add_superclass_metadata(klass)
|
275
|
+
if Class === klass then
|
276
|
+
sc = klass.superclass
|
277
|
+
add_metadata(sc) unless introspected?(sc) or sc == Java::java.lang.Object
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# @param [Class] the class to check
|
282
|
+
# @return [Boolean] whether the class is an introspected {Resource} class
|
283
|
+
def introspected?(klass)
|
284
|
+
klass < Resource and klass.introspected?
|
285
|
+
end
|
286
|
+
|
287
|
+
# @param [Class] klass the class to check
|
288
|
+
# @return [Boolean] whether the given class has a Java package among this module's
|
289
|
+
# {#packages} and has not yet been introspected
|
290
|
+
def introspectible?(klass)
|
291
|
+
not introspected?(klass) and Class === klass and @packages.include?(klass.java_class.package.name)
|
239
292
|
end
|
240
293
|
end
|
241
294
|
end
|