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.
Files changed (40) hide show
  1. data/Gemfile.lock +27 -0
  2. data/History.md +4 -0
  3. data/lib/jinx/helpers/class.rb +16 -12
  4. data/lib/jinx/helpers/collection.rb +277 -29
  5. data/lib/jinx/helpers/collections.rb +35 -2
  6. data/lib/jinx/helpers/conditional_enumerator.rb +2 -2
  7. data/lib/jinx/helpers/filter.rb +8 -2
  8. data/lib/jinx/helpers/flattener.rb +2 -2
  9. data/lib/jinx/helpers/hash.rb +3 -2
  10. data/lib/jinx/helpers/{hashable.rb → hasher.rb} +125 -77
  11. data/lib/jinx/helpers/module.rb +1 -1
  12. data/lib/jinx/helpers/multi_enumerator.rb +25 -9
  13. data/lib/jinx/helpers/options.rb +4 -3
  14. data/lib/jinx/helpers/partial_order.rb +16 -8
  15. data/lib/jinx/helpers/pretty_print.rb +14 -4
  16. data/lib/jinx/helpers/transformer.rb +3 -1
  17. data/lib/jinx/helpers/transitive_closure.rb +3 -3
  18. data/lib/jinx/helpers/visitor.rb +33 -42
  19. data/lib/jinx/import/java.rb +40 -27
  20. data/lib/jinx/importer.rb +86 -33
  21. data/lib/jinx/metadata/attribute_enumerator.rb +5 -11
  22. data/lib/jinx/metadata/dependency.rb +65 -30
  23. data/lib/jinx/metadata/id_alias.rb +1 -0
  24. data/lib/jinx/metadata/introspector.rb +21 -9
  25. data/lib/jinx/metadata/inverse.rb +14 -11
  26. data/lib/jinx/metadata/java_property.rb +15 -26
  27. data/lib/jinx/metadata/propertied.rb +80 -19
  28. data/lib/jinx/metadata/property.rb +13 -8
  29. data/lib/jinx/metadata/property_characteristics.rb +2 -2
  30. data/lib/jinx/resource.rb +62 -32
  31. data/lib/jinx/resource/inversible.rb +4 -0
  32. data/lib/jinx/resource/match_visitor.rb +0 -1
  33. data/lib/jinx/resource/mergeable.rb +16 -6
  34. data/lib/jinx/resource/reference_enumerator.rb +1 -2
  35. data/lib/jinx/version.rb +1 -1
  36. data/test/lib/jinx/helpers/collections_test.rb +29 -14
  37. data/test/lib/jinx/helpers/visitor_test.rb +7 -20
  38. data/test/lib/jinx/import/mixed_case_test.rb +17 -3
  39. metadata +4 -4
  40. data/lib/jinx/helpers/enumerable.rb +0 -245
@@ -13,6 +13,6 @@ class Module
13
13
  # A::B.parent_module #=> A
14
14
  # @return [Module] this module's definition context
15
15
  def parent_module
16
- Kernel.module_with_name(name.split('::')[0..-2].join('::'))
16
+ module_with_name(name.split('::')[0..-2].join('::'))
17
17
  end
18
18
  end
@@ -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#+, MultiEnumerator reflects changes to the
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
- def initialize(*enums)
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 Enumerators in sequence.
29
+ # Iterates over each of this MultiEnumerator's enumerators in sequence.
27
30
  def each
28
- @components.each { |enum| enum.each { |item| yield item } }
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
@@ -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 options.
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, options)
84
- options.detect_value do |opt|
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 which restricted scope. Classes which include PartialOrder
3
- # are required to implement the <=> operator with the following semantics:
4
- # * _a_ <=> _b_ returns -1, 0, or 1 if a and b are comparable, nil otherwise
5
- # A PartialOrder thus relaxes comparison symmetry, e.g.
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
- # Example:
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 a Queued
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 Hashable
158
- # qp, short for quick-print, prints this Hashable with a filter that calls qp on each key and value.
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
- # @return [String] the formatted +strftime+
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.
@@ -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, :cycles
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
- # The options is a _symbol_, _symbol_ => _value_ hash or nil. A _symbol_ argument is the same
66
- # as +{+_symbol_ +=> true}+. Supported options include the following:
67
- # * +:depth_first+: +true+, +false+ or a Proc. If the value is a Proc, then
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
- Visitor.new(@options) { |node| yield(node, node_children(node)) }
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
- # visitor.visit(a)
243
- # visit.to_enum #=> [a, b, c, u, v, x, y, z]
244
- # visit.cycles #=> [[a, b, u, x, a], [c, z, c]]
245
- # visit.cyclic_nodes #=> [b, u, x, c, z]
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
- cycler = Visitor.new(copts, &@navigator)
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
- # bail if no node or the node is specifically excluded
255
+ # Bail if no node or the node is specifically excluded.
258
256
  return if node.nil? or @exclude.include?(node)
259
- # return the visited value if the node has already been visited
260
- if @visited.has_key?(node) then
261
- #capture a cycle
262
- index = @lineage.index(node)
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
- # all systems go: visit the node graph
262
+ # All systems go: visit the node subgraph.
272
263
  visit_node_and_children(node, &operator)
273
264
  end
274
265
 
@@ -201,24 +201,49 @@ module Java
201
201
  NAME_SPLITTER_REGEX = /^([\w.]+)\.(\w+)$/
202
202
  end
203
203
 
204
- class Class
205
- # Returns whether this is a Java wrapper class.
204
+ class Module
205
+ # Returns whether this is a Ruby wrapper for a Java class or interface.
206
206
  def java_class?
207
- method_defined?(:java_class)
207
+ respond_to?(:java_class)
208
208
  end
209
209
 
210
- # Returns the Ruby class for the given class, as follows:
211
- # * If the given class is already a Ruby class, then return the class.
212
- # * If the class argument is a Java class or a Java class name, then
213
- # the Ruby class is the JRuby wrapper for the Java class.
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
- # @param [Class, String] class_or_name the class or class name
216
- # @return [Class] the corresponding Ruby class
217
- def self.to_ruby(class_or_name)
218
- case class_or_name
219
- when Class then class_or_name
220
- when String then eval to_ruby_name(class_or_name)
221
- else to_ruby(class_or_name.name)
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 unless java_class?
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
@@ -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.nil? then
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 @introspected.include?(klass) then
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
- # Sets the package names. The default package conforms to the JRuby convention for
109
- # mapping a package name to a module name, e.g. the +MyApp::Domain+ default package
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 if necessary.
144
- klasses.each { |klass| add_metadata(klass) unless @introspected.include?(klass) }
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
- logger.debug { "Loading #{klass.qp} definition #{file}..." }
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
- if Class === klass then
200
- sc = klass.superclass
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
- unless @introspected.include?(ref) or ref.parent_module != mod then
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
- # Introspects the given class.
235
- #
236
- # @param [Class] the domain class to introspect
237
- def introspect(klass)
238
- klass.introspect
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