jinx 2.1.1
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/.gitignore +14 -0
- data/.rspec +3 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +27 -0
- data/History.md +6 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +44 -0
- data/Rakefile +41 -0
- data/examples/family/README.md +10 -0
- data/examples/family/ext/build.xml +35 -0
- data/examples/family/ext/src/family/Address.java +68 -0
- data/examples/family/ext/src/family/Child.java +24 -0
- data/examples/family/ext/src/family/DomainObject.java +26 -0
- data/examples/family/ext/src/family/Household.java +36 -0
- data/examples/family/ext/src/family/Parent.java +48 -0
- data/examples/family/ext/src/family/Person.java +42 -0
- data/examples/family/lib/family.rb +15 -0
- data/examples/family/lib/family/address.rb +6 -0
- data/examples/family/lib/family/domain_object.rb +6 -0
- data/examples/family/lib/family/household.rb +6 -0
- data/examples/family/lib/family/parent.rb +16 -0
- data/examples/family/lib/family/person.rb +6 -0
- data/examples/model/README.md +25 -0
- data/examples/model/ext/build.xml +35 -0
- data/examples/model/ext/src/domain/Child.java +192 -0
- data/examples/model/ext/src/domain/Dependent.java +29 -0
- data/examples/model/ext/src/domain/DomainObject.java +26 -0
- data/examples/model/ext/src/domain/Independent.java +83 -0
- data/examples/model/ext/src/domain/Parent.java +129 -0
- data/examples/model/ext/src/domain/Person.java +14 -0
- data/examples/model/lib/model.rb +13 -0
- data/examples/model/lib/model/child.rb +13 -0
- data/examples/model/lib/model/domain_object.rb +6 -0
- data/examples/model/lib/model/independent.rb +11 -0
- data/examples/model/lib/model/parent.rb +17 -0
- data/jinx.gemspec +22 -0
- data/lib/jinx.rb +3 -0
- data/lib/jinx/active_support/README.txt +2 -0
- data/lib/jinx/active_support/core_ext/string.rb +7 -0
- data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/jinx/active_support/inflections.rb +55 -0
- data/lib/jinx/active_support/inflector.rb +398 -0
- data/lib/jinx/cli/application.rb +36 -0
- data/lib/jinx/cli/command.rb +214 -0
- data/lib/jinx/helpers/array.rb +108 -0
- data/lib/jinx/helpers/boolean.rb +42 -0
- data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
- data/lib/jinx/helpers/class.rb +149 -0
- data/lib/jinx/helpers/collection.rb +33 -0
- data/lib/jinx/helpers/collections.rb +11 -0
- data/lib/jinx/helpers/collector.rb +20 -0
- data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
- data/lib/jinx/helpers/enumerable.rb +242 -0
- data/lib/jinx/helpers/enumerate.rb +35 -0
- data/lib/jinx/helpers/error.rb +15 -0
- data/lib/jinx/helpers/file_separator.rb +65 -0
- data/lib/jinx/helpers/filter.rb +52 -0
- data/lib/jinx/helpers/flattener.rb +38 -0
- data/lib/jinx/helpers/hash.rb +12 -0
- data/lib/jinx/helpers/hashable.rb +502 -0
- data/lib/jinx/helpers/inflector.rb +36 -0
- data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
- data/lib/jinx/helpers/lazy_hash.rb +44 -0
- data/lib/jinx/helpers/log.rb +106 -0
- data/lib/jinx/helpers/math.rb +12 -0
- data/lib/jinx/helpers/merge.rb +60 -0
- data/lib/jinx/helpers/module.rb +18 -0
- data/lib/jinx/helpers/multi_enumerator.rb +31 -0
- data/lib/jinx/helpers/options.rb +92 -0
- data/lib/jinx/helpers/os.rb +19 -0
- data/lib/jinx/helpers/partial_order.rb +37 -0
- data/lib/jinx/helpers/pretty_print.rb +207 -0
- data/lib/jinx/helpers/set.rb +8 -0
- data/lib/jinx/helpers/stopwatch.rb +76 -0
- data/lib/jinx/helpers/transformer.rb +24 -0
- data/lib/jinx/helpers/transitive_closure.rb +55 -0
- data/lib/jinx/helpers/uniquifier.rb +50 -0
- data/lib/jinx/helpers/validation.rb +33 -0
- data/lib/jinx/helpers/visitor.rb +370 -0
- data/lib/jinx/import/class_path_modifier.rb +77 -0
- data/lib/jinx/import/java.rb +337 -0
- data/lib/jinx/importer.rb +240 -0
- data/lib/jinx/metadata.rb +155 -0
- data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
- data/lib/jinx/metadata/dependency.rb +244 -0
- data/lib/jinx/metadata/id_alias.rb +23 -0
- data/lib/jinx/metadata/introspector.rb +179 -0
- data/lib/jinx/metadata/inverse.rb +170 -0
- data/lib/jinx/metadata/java_property.rb +169 -0
- data/lib/jinx/metadata/propertied.rb +500 -0
- data/lib/jinx/metadata/property.rb +401 -0
- data/lib/jinx/metadata/property_characteristics.rb +114 -0
- data/lib/jinx/resource.rb +862 -0
- data/lib/jinx/resource/copy_visitor.rb +36 -0
- data/lib/jinx/resource/inversible.rb +90 -0
- data/lib/jinx/resource/match_visitor.rb +180 -0
- data/lib/jinx/resource/matcher.rb +20 -0
- data/lib/jinx/resource/merge_visitor.rb +73 -0
- data/lib/jinx/resource/mergeable.rb +185 -0
- data/lib/jinx/resource/reference_enumerator.rb +49 -0
- data/lib/jinx/resource/reference_path_visitor.rb +38 -0
- data/lib/jinx/resource/reference_visitor.rb +55 -0
- data/lib/jinx/resource/unique.rb +35 -0
- data/lib/jinx/version.rb +3 -0
- data/spec/defaults_spec.rb +30 -0
- data/spec/definitions/model/alias/child.rb +5 -0
- data/spec/definitions/model/base/child.rb +5 -0
- data/spec/definitions/model/base/domain_object.rb +5 -0
- data/spec/definitions/model/base/independent.rb +5 -0
- data/spec/definitions/model/defaults/child.rb +5 -0
- data/spec/definitions/model/dependency/child.rb +5 -0
- data/spec/definitions/model/dependency/parent.rb +6 -0
- data/spec/definitions/model/inverse/child.rb +5 -0
- data/spec/definitions/model/inverse/independent.rb +5 -0
- data/spec/definitions/model/inverse/parent.rb +5 -0
- data/spec/definitions/model/mandatory/child.rb +6 -0
- data/spec/dependency_spec.rb +47 -0
- data/spec/family_spec.rb +64 -0
- data/spec/inverse_spec.rb +53 -0
- data/spec/mandatory_spec.rb +43 -0
- data/spec/metadata_spec.rb +68 -0
- data/spec/resource_spec.rb +30 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/model.rb +19 -0
- data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
- data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
- data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
- data/test/fixtures/mixed/ext/build.xml +35 -0
- data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
- data/test/helper.rb +7 -0
- data/test/lib/jinx/command_test.rb +41 -0
- data/test/lib/jinx/helpers/boolean_test.rb +27 -0
- data/test/lib/jinx/helpers/class_test.rb +60 -0
- data/test/lib/jinx/helpers/collections_test.rb +402 -0
- data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
- data/test/lib/jinx/helpers/inflector_test.rb +11 -0
- data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
- data/test/lib/jinx/helpers/module_test.rb +24 -0
- data/test/lib/jinx/helpers/options_test.rb +66 -0
- data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
- data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
- data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
- data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
- data/test/lib/jinx/helpers/visitor_test.rb +288 -0
- data/test/lib/jinx/import/java_test.rb +78 -0
- data/test/lib/jinx/import/mixed_case_test.rb +16 -0
- metadata +272 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
require 'java'
|
|
2
|
+
require 'ftools'
|
|
3
|
+
require 'date'
|
|
4
|
+
require 'jinx/helpers/class'
|
|
5
|
+
require 'jinx/helpers/inflector'
|
|
6
|
+
require 'jinx/helpers/collections'
|
|
7
|
+
require 'jinx/import/class_path_modifier'
|
|
8
|
+
|
|
9
|
+
module Java
|
|
10
|
+
# the class path modifier for expand_to_class_path
|
|
11
|
+
@cp_mod = Jinx::ClassPathModifier.new
|
|
12
|
+
|
|
13
|
+
# @see ClassPathModifier#expand_to_class_path
|
|
14
|
+
def self.expand_to_class_path(path)
|
|
15
|
+
@cp_mod.expand_to_class_path(path)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param [String] name the properties file name
|
|
19
|
+
# @return [Hash, nil] the properties content, or nil if the file is not on the classpath
|
|
20
|
+
def self.load_properties(name)
|
|
21
|
+
url = JRuby.runtime.jruby_class_loader.findResource(name) || return
|
|
22
|
+
props = JavaUtil::Properties.new
|
|
23
|
+
props.load(url.openStream)
|
|
24
|
+
props
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module JavaUtil
|
|
28
|
+
# Aliases Java Collection methods with the standard Ruby Set counterpart, e.g. +delete+ for +remove+.
|
|
29
|
+
module Collection
|
|
30
|
+
def to_a
|
|
31
|
+
inject(Array.new) { |array, item| array << item }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Removes the given item from this collection.
|
|
35
|
+
def delete(item)
|
|
36
|
+
# can't alias delete to remove, since a Java interface doesn't implement any methods
|
|
37
|
+
remove(item)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Removes the items from this collection for which the block given to this method returns a non-nil, non-false value.
|
|
41
|
+
def delete_if
|
|
42
|
+
removeAll(select { |item| yield item })
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Aliases Java List methods with the standard Ruby Array counterpart, e.g. +merge+ for +addAll+.
|
|
48
|
+
module List
|
|
49
|
+
# Returns whether this List has the same content as the other Java List or Ruby Array.
|
|
50
|
+
def ==(other)
|
|
51
|
+
Array === other ? to_a == other : equals(other)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Removes the given item from this collection.
|
|
55
|
+
def delete(item)
|
|
56
|
+
remove(item)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module Map
|
|
61
|
+
# Returns whether this Set has the same content as the other Java Map or Ruby Hash.
|
|
62
|
+
def ==(other)
|
|
63
|
+
::Hash === other ? (size == other.size and other.all? { |key, value| get(key) == value }) : equals(other)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Merges the other Java Map or Ruby Hash into this Map. Returns this modified Map.
|
|
67
|
+
#
|
|
68
|
+
# If a block is given to this method, then the block determines the mapped value
|
|
69
|
+
# as specified in the Ruby Hash merge method documentation.
|
|
70
|
+
def merge(other)
|
|
71
|
+
other.each do |key, value|
|
|
72
|
+
value = yield(key, get(key), value) if block_given? and containsKey(key)
|
|
73
|
+
put(key, value)
|
|
74
|
+
end
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
alias :merge! :merge
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
module Set
|
|
82
|
+
# Returns whether this Set has the same content as the other Java Set or Ruby Set.
|
|
83
|
+
def ==(other)
|
|
84
|
+
::Set === other ? (size == other.size and all? { |item| other.include?(item) }) : equals(other)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Merges the other Enumerable into this Set. Returns this modified Set.
|
|
88
|
+
#
|
|
89
|
+
# This method conforms to the Ruby Set merge contract rather than the Ruby List and Hash
|
|
90
|
+
# merge contract. Ruby Set merge modifies the Set in-place, whereas Ruby List and Hash
|
|
91
|
+
# merge return a new collection.
|
|
92
|
+
def merge(other)
|
|
93
|
+
return self if other.nil?
|
|
94
|
+
Jinx.fail(ArgumentError, "Merge argument must be enumerable: #{other}") unless Enumerable === other
|
|
95
|
+
other.each { |item| self << item }
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
alias :merge! :merge
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
class HashSet
|
|
103
|
+
alias :base__clear :clear
|
|
104
|
+
private :base__clear
|
|
105
|
+
def clear
|
|
106
|
+
base__clear
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class TreeSet
|
|
112
|
+
alias :base__first :first
|
|
113
|
+
private :base__first
|
|
114
|
+
# Fixes the jRuby {TreeSet#first} to return nil on an empty set rather than raise a Java exception.
|
|
115
|
+
def first
|
|
116
|
+
empty? ? nil : base__first
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class ArrayList
|
|
121
|
+
alias :base__clear :clear
|
|
122
|
+
private :base__clear
|
|
123
|
+
def clear
|
|
124
|
+
base__clear
|
|
125
|
+
self
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
class Date
|
|
130
|
+
# The millisecond-to-day conversion factor.
|
|
131
|
+
MILLIS_PER_DAY = (60 * 60 * 1000) * 24
|
|
132
|
+
|
|
133
|
+
# Converts this Java Date to a Ruby DateTime.
|
|
134
|
+
#
|
|
135
|
+
# @return [DateTime] the Ruby date
|
|
136
|
+
def to_ruby_date
|
|
137
|
+
calendar = java.util.Calendar.instance
|
|
138
|
+
calendar.setTime(self)
|
|
139
|
+
secs = calendar.timeInMillis.to_f / 1000
|
|
140
|
+
# millis since epoch
|
|
141
|
+
time = Time.at(secs)
|
|
142
|
+
# convert UTC timezone millisecond offset to Rational fraction of a day
|
|
143
|
+
offset_millis = calendar.timeZone.getOffset(calendar.timeInMillis).to_f
|
|
144
|
+
if offset_millis.zero? then
|
|
145
|
+
offset = 0
|
|
146
|
+
else
|
|
147
|
+
offset_days = offset_millis / MILLIS_PER_DAY
|
|
148
|
+
offset_fraction = 1 / offset_days
|
|
149
|
+
offset = Rational(1, offset_fraction)
|
|
150
|
+
end
|
|
151
|
+
# convert to DateTime
|
|
152
|
+
DateTime.civil(time.year, time.mon, time.day, time.hour, time.min, time.sec, offset)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Converts a Ruby Date or DateTime to a Java Date.
|
|
156
|
+
#
|
|
157
|
+
# @param [::Date, DateTime] date the Ruby date
|
|
158
|
+
# @return [Date] the Java date
|
|
159
|
+
def self.from_ruby_date(date)
|
|
160
|
+
return if date.nil?
|
|
161
|
+
# DateTime has time attributes, Date doesn't
|
|
162
|
+
if DateTime === date then
|
|
163
|
+
hour, min, sec = date.hour, date.min, date.sec
|
|
164
|
+
else
|
|
165
|
+
hour = min = sec = 0
|
|
166
|
+
end
|
|
167
|
+
# the Ruby time
|
|
168
|
+
rtime = Time.local(sec, min, hour, date.day, date.mon, date.year, nil, nil, nil, nil)
|
|
169
|
+
# millis since epoch
|
|
170
|
+
millis = (rtime.to_f * 1000).truncate
|
|
171
|
+
# the Java date factory
|
|
172
|
+
calendar = java.util.Calendar.instance
|
|
173
|
+
calendar.setTimeInMillis(millis)
|
|
174
|
+
jtime = calendar.getTime
|
|
175
|
+
# the daylight time flag
|
|
176
|
+
isdt = calendar.timeZone.inDaylightTime(jtime)
|
|
177
|
+
return jtime unless isdt
|
|
178
|
+
# adjust the Ruby time for DST
|
|
179
|
+
rtime = Time.local(sec, min, hour, date.day, date.mon, date.year, nil, nil, isdt, nil)
|
|
180
|
+
millis = (rtime.to_f * 1000).truncate
|
|
181
|
+
calendar.setTimeInMillis(millis)
|
|
182
|
+
calendar.getTime
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def self.now
|
|
188
|
+
JavaUtil::Date.from_ruby_date(DateTime.now)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @param [Class, String] the JRuby class or the full Java class name
|
|
192
|
+
# @return (String, String] the package and base for the given name
|
|
193
|
+
def self.split_class_name(name_or_class)
|
|
194
|
+
name = Class === name_or_class ? name_or_class.java_class.name : name_or_class
|
|
195
|
+
match = NAME_SPLITTER_REGEX.match(name)
|
|
196
|
+
match ? match.captures : [nil, name]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
NAME_SPLITTER_REGEX = /^([\w.]+)\.(\w+)$/
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
class Class
|
|
205
|
+
# Returns whether this is a Java wrapper class.
|
|
206
|
+
def java_class?
|
|
207
|
+
method_defined?(:java_class)
|
|
208
|
+
end
|
|
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.
|
|
214
|
+
#
|
|
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)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# @return [Boolean] whether this is a wrapper for an abstract Java class
|
|
226
|
+
def abstract?
|
|
227
|
+
java_class? and Java::JavaLangReflect::Modifier.isAbstract(java_class.modifiers)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Returns whether the given PropertyDescriptor pd corresponds to a transient field in this class, or nil if there is no such field.
|
|
231
|
+
def transient?(pd)
|
|
232
|
+
begin
|
|
233
|
+
field = java_class.declared_field(pd.name)
|
|
234
|
+
rescue Exception
|
|
235
|
+
# should occur only if a property is not a field; not an error
|
|
236
|
+
return
|
|
237
|
+
end
|
|
238
|
+
Java::JavaLangReflect::Modifier.isTransient(field.modifiers) if field
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Returns this class's readable and writable Java PropertyDescriptors, or an empty Array if none.
|
|
242
|
+
# If the hierarchy flag is set to +false+, then only this class's properties
|
|
243
|
+
# will be introspected.
|
|
244
|
+
def property_descriptors(hierarchy=true)
|
|
245
|
+
return Array::EMPTY_ARRAY unless java_class?
|
|
246
|
+
info = hierarchy ? Java::JavaBeans::Introspector.getBeanInfo(java_class) : Java::JavaBeans::Introspector.getBeanInfo(java_class, java_class.superclass)
|
|
247
|
+
info.propertyDescriptors.select { |pd| pd.write_method and property_read_method(pd) }
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Redefines the reserved method corresponding to the given Java property descriptor
|
|
251
|
+
# back to the Object implementation, if necessary.
|
|
252
|
+
# If both this class and Object define a method with the given property name,
|
|
253
|
+
# then a new method is defined with the same body as the previous method.
|
|
254
|
+
# Returns the new method symbol, or nil if the property name is not an occluded
|
|
255
|
+
# Object instance method.
|
|
256
|
+
#
|
|
257
|
+
# This method undoes the JRuby clobbering of Object methods by Java property method
|
|
258
|
+
# wrappers. The method is renamed as follows:
|
|
259
|
+
# * +id+ is changed to :identifier
|
|
260
|
+
# * +type+ is prefixed by the underscore subject class name, e.g. +Specimen.type => :specimen_type+,
|
|
261
|
+
# If the property name is +type+ and the subject class name ends in 'Type', then the property
|
|
262
|
+
# symbol is the underscore subject class name, e.g. +HistologicType.type => :histologic_type+.
|
|
263
|
+
#
|
|
264
|
+
# Raises ArgumentError if symbol is not an Object method.
|
|
265
|
+
def unocclude_reserved_method(pd)
|
|
266
|
+
oldname = pd.name.underscore
|
|
267
|
+
return unless OBJ_INST_MTHDS.include?(oldname)
|
|
268
|
+
oldsym = oldname.to_sym
|
|
269
|
+
undeprecated = case oldsym
|
|
270
|
+
when :id then :object_id
|
|
271
|
+
when :type then :class
|
|
272
|
+
else oldsym
|
|
273
|
+
end
|
|
274
|
+
rsvd_mth = Object.instance_method(undeprecated)
|
|
275
|
+
base = self.qp.underscore
|
|
276
|
+
newname = if oldname == 'id' then
|
|
277
|
+
'identifier'
|
|
278
|
+
elsif base[-oldname.length..-1] == oldname then
|
|
279
|
+
base
|
|
280
|
+
else
|
|
281
|
+
"#{base}_#{oldname}"
|
|
282
|
+
end
|
|
283
|
+
newsym = newname.to_sym
|
|
284
|
+
rdr = property_read_method(pd).name.to_sym
|
|
285
|
+
alias_method(newsym, rdr)
|
|
286
|
+
# alias the writers
|
|
287
|
+
wtr = pd.write_method.name.to_sym
|
|
288
|
+
alias_method("#{newsym}=".to_sym, wtr)
|
|
289
|
+
# alias a camel-case Java-style method if necessary
|
|
290
|
+
altname = newname.camelize
|
|
291
|
+
unless altname == newname then
|
|
292
|
+
alias_method(altname.to_sym, oldsym)
|
|
293
|
+
alias_method("#{altname}=".to_sym, wtr)
|
|
294
|
+
end
|
|
295
|
+
# restore the old method to Object
|
|
296
|
+
define_method(oldsym) { |*args| rsvd_mth.bind(self).call(*args) }
|
|
297
|
+
newsym
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# @quirk Java +java.lang.Boolean+ +is+_name_ is not introspected as a read method,
|
|
301
|
+
# since the type must be primitive, i.e. +boolean is+_name_. The work-around is
|
|
302
|
+
# to explicitly look for the +is+_name_ method in the case of a +java.lang.Boolean+
|
|
303
|
+
# property rather than rely on the Java introspector.
|
|
304
|
+
#
|
|
305
|
+
# @return [Symbol] the property descriptor pd introspected or discovered Java read Method
|
|
306
|
+
def property_read_method(pd)
|
|
307
|
+
return pd.read_method if pd.read_method
|
|
308
|
+
return unless pd.get_property_type == Java::JavaLang::Boolean.java_class
|
|
309
|
+
rdr = java_class.java_method("is#{pd.name.capitalize_first}") rescue nil
|
|
310
|
+
logger.debug { "Discovered #{qp} #{pd.name} property non-introspected reader method #{rdr.name}." } if rdr
|
|
311
|
+
rdr
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
private
|
|
315
|
+
|
|
316
|
+
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
|
+
end
|
|
330
|
+
|
|
331
|
+
class Array
|
|
332
|
+
alias :equal__base :==
|
|
333
|
+
# Overrides the standard == to compare a Java List with a Ruby Array.
|
|
334
|
+
def ==(other)
|
|
335
|
+
Java::JavaUtil::List === other ? other == self : equal__base(other)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
require 'jinx/helpers/transitive_closure'
|
|
2
|
+
require 'jinx/metadata'
|
|
3
|
+
|
|
4
|
+
module Jinx
|
|
5
|
+
# Importer extends a module with Java class import support. Importer is an aspect
|
|
6
|
+
# of a {Metadata} module. Including +Metadata+ in an application domain module
|
|
7
|
+
# extends that module with Importer capability.
|
|
8
|
+
#
|
|
9
|
+
# The Importer module imports a Java class or interface on demand by referencing
|
|
10
|
+
# the class name in the context of the module.
|
|
11
|
+
# The imported class {Metadata} is introspected.
|
|
12
|
+
#
|
|
13
|
+
# Import on demand is induced by a reference to the class.
|
|
14
|
+
# The +family+ example illustrates a domain package extended
|
|
15
|
+
# with metadata capability. The first non-definition reference to +Family::Parent+
|
|
16
|
+
# imports the Java class +family.Parent+ into the JRuby class wrapper
|
|
17
|
+
# +Family+ and introspects the Java property meta-data.
|
|
18
|
+
module Importer
|
|
19
|
+
# Declares that the given {Resource} classes will be dynamically modified.
|
|
20
|
+
# This method introspects the classes, if necessary.
|
|
21
|
+
#
|
|
22
|
+
# @param [<Class>] classes the classes to augment
|
|
23
|
+
def shims(*classes)
|
|
24
|
+
# Nothing to do, since all this method does is ensure that the arguments are
|
|
25
|
+
# introspected when they are referenced.
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Imports a Java class constant on demand. If the class does not already
|
|
29
|
+
# include this module's mixin, then the mixin is included in the class.
|
|
30
|
+
#
|
|
31
|
+
# @param [Symbol, String] sym the missing constant
|
|
32
|
+
# @return [Class] the imported class
|
|
33
|
+
# @raise [NameError] if the symbol is not an importable Java class
|
|
34
|
+
def const_missing(sym)
|
|
35
|
+
# Load the class definitions in the source directory, if necessary.
|
|
36
|
+
# If a load is performed as a result of referencing the given symbol,
|
|
37
|
+
# then dereference the class constant again after the load, since the class
|
|
38
|
+
# might have been loaded or referenced during the load.
|
|
39
|
+
unless defined? @introspected then
|
|
40
|
+
configure_importer
|
|
41
|
+
load_definitions
|
|
42
|
+
return const_get(sym)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Append the symbol to the package to make the Java class name.
|
|
46
|
+
logger.debug { "Detecting whether #{sym} is a #{self} Java class..." }
|
|
47
|
+
klass = @packages.detect_value do |pkg|
|
|
48
|
+
begin
|
|
49
|
+
java_import "#{pkg}.#{sym}"
|
|
50
|
+
rescue NameError
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
if klass.nil? then
|
|
55
|
+
# Not a Java class; print a log message and pass along the error.
|
|
56
|
+
logger.debug { "#{sym} is not recognized as a #{self} Java class." }
|
|
57
|
+
super
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Introspect the Java class meta-data, if necessary.
|
|
61
|
+
unless @introspected.include?(klass) then
|
|
62
|
+
add_metadata(klass)
|
|
63
|
+
# Print the class meta-data.
|
|
64
|
+
logger.info(klass.pp_s)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
klass
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @param [String] the module name to resolve in the context of this module
|
|
71
|
+
# @return [Module] the corresponding module
|
|
72
|
+
def module_for_name(name)
|
|
73
|
+
begin
|
|
74
|
+
# Incrementally resolve the module.
|
|
75
|
+
name.split('::').inject(self) { |ctxt, mod| ctxt.const_get(mod) }
|
|
76
|
+
rescue NameError
|
|
77
|
+
# If the application domain module set the parent module i.v.. then continue
|
|
78
|
+
# the look-up in that parent importer.
|
|
79
|
+
raise unless @parent_importer
|
|
80
|
+
mod = @parent_importer.module_for_name(name)
|
|
81
|
+
if mod then
|
|
82
|
+
logger.debug { "Module #{name} found in #{qp} parent module #{@parent_importer}." }
|
|
83
|
+
end
|
|
84
|
+
mod
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# Initializes this importer on demand. This method is called the first time a class
|
|
91
|
+
# is referenced.
|
|
92
|
+
def configure_importer
|
|
93
|
+
# The default package conforms to the JRuby convention for mapping a package name
|
|
94
|
+
# to a module name.
|
|
95
|
+
@packages ||= [name.split('::').map { |n| n.downcase }.join('.')]
|
|
96
|
+
@packages.each do |pkg|
|
|
97
|
+
begin
|
|
98
|
+
eval "java_package Java::#{pkg}"
|
|
99
|
+
rescue Exception => e
|
|
100
|
+
Jinx.fail(ArgumentError, "#{self} Java package #{pkg} not found - #{$!}")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
# The introspected classes.
|
|
104
|
+
@introspected = Set.new
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Sets the package names. The default package conforms to the JRuby convention for
|
|
108
|
+
# mapping a package name to a module name, e.g. the +MyApp::Domain+ default package
|
|
109
|
+
# is +myapp.domain+. Clients set the package if it differs from the default.
|
|
110
|
+
#
|
|
111
|
+
# @param [<String>] name the package names
|
|
112
|
+
def packages(*names)
|
|
113
|
+
@packages = names
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Alias for the common case of a single package.
|
|
117
|
+
alias :package :packages
|
|
118
|
+
|
|
119
|
+
# @param [<String>] directories the Ruby class definitions directories
|
|
120
|
+
def definitions(*directories)
|
|
121
|
+
@definitions = directories
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def load_definitions
|
|
125
|
+
return if @definitions.nil_or_empty?
|
|
126
|
+
# Load the class definitions in the source directories.
|
|
127
|
+
@definitions.each { |dir| load_dir(File.expand_path(dir)) }
|
|
128
|
+
# Print each introspected class's content.
|
|
129
|
+
@introspected.sort { |k1, k2| k1.name <=> k2.name }.each { |klass| logger.info(klass.pp_s) }
|
|
130
|
+
true
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Loads the Ruby source files in the given directory.
|
|
134
|
+
#
|
|
135
|
+
# @param [String] dir the source directory
|
|
136
|
+
def load_dir(dir)
|
|
137
|
+
logger.debug { "Loading the class definitions in #{dir}..." }
|
|
138
|
+
# Import the classes.
|
|
139
|
+
srcs = sources(dir)
|
|
140
|
+
# Introspect and load the classes in reverse class order, i.e. superclass before subclass.
|
|
141
|
+
klasses = srcs.keys.transitive_closure { |k| [k.superclass] }.select { |k| srcs[k] }.reverse
|
|
142
|
+
# Introspect the classes if necessary.
|
|
143
|
+
klasses.each { |klass| add_metadata(klass) unless @introspected.include?(klass) }
|
|
144
|
+
# Load the classes.
|
|
145
|
+
klasses.each do |klass|
|
|
146
|
+
file = srcs[klass]
|
|
147
|
+
logger.debug { "Loading #{klass.qp} definition #{file}..." }
|
|
148
|
+
require file
|
|
149
|
+
logger.debug { "Loaded #{klass.qp} definition #{file}." }
|
|
150
|
+
end
|
|
151
|
+
logger.debug { "Loaded the class definitions in #{dir}." }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @param [String] dir the source directory
|
|
155
|
+
# @return [{Class => String}] the source class => file hash
|
|
156
|
+
def sources(dir)
|
|
157
|
+
# the domain class definitions
|
|
158
|
+
files = Dir.glob(File.join(dir, "*.rb"))
|
|
159
|
+
# Infer each class symbol from the file base name.
|
|
160
|
+
# Ignore files which do not resolve to a class.
|
|
161
|
+
files.to_compact_hash do |file|
|
|
162
|
+
name = File.basename(file, ".rb").camelize
|
|
163
|
+
resolve_class(name)
|
|
164
|
+
end.invert
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# @param [String] name the demodulized class name
|
|
168
|
+
# @param [String] package the Java package, or nil for all packages
|
|
169
|
+
# @return [Class, nil] the {Resource} class imported into this module,
|
|
170
|
+
# or nil if the class cannot be resolved
|
|
171
|
+
def resolve_class(name, package=nil)
|
|
172
|
+
if const_defined?(name) then
|
|
173
|
+
return const_get(name)
|
|
174
|
+
end
|
|
175
|
+
if package.nil? then
|
|
176
|
+
return @packages.detect_value { |pkg| resolve_class(name, pkg) }
|
|
177
|
+
end
|
|
178
|
+
# Append the class name to the package to make the Java class name.
|
|
179
|
+
full_name = "#{package}.#{name}"
|
|
180
|
+
# If the class is already imported, then java_import returns nil. In that case,
|
|
181
|
+
# evaluate the Java class.
|
|
182
|
+
begin
|
|
183
|
+
java_import(full_name)
|
|
184
|
+
rescue
|
|
185
|
+
module_eval("Java::#{full_name}") rescue nil
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Introspects the given class meta-data.
|
|
190
|
+
#
|
|
191
|
+
# @param [Class] klass the Java class or interface to introspect
|
|
192
|
+
def add_metadata(klass)
|
|
193
|
+
logger.debug("Adding #{self}::#{klass.qp} metadata...")
|
|
194
|
+
# Mark the class as introspected. Do this first to preclude a recursive loop back
|
|
195
|
+
# into this method when the references are introspected below.
|
|
196
|
+
@introspected << klass
|
|
197
|
+
# Add the superclass meta-data if necessary.
|
|
198
|
+
if Class === klass then
|
|
199
|
+
sc = klass.superclass
|
|
200
|
+
unless @introspected.include?(sc) or sc == Java::java.lang.Object then
|
|
201
|
+
add_metadata(sc)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
# Include this resource module into the class.
|
|
205
|
+
unless klass < self then
|
|
206
|
+
m = self
|
|
207
|
+
klass.class_eval { include m }
|
|
208
|
+
end
|
|
209
|
+
# Add introspection capability to the class.
|
|
210
|
+
md_mod = @metadata_module || Metadata
|
|
211
|
+
|
|
212
|
+
logger.debug { "Extending #{self}::#{klass.qp} with #{md_mod.name}..." }
|
|
213
|
+
klass.extend(md_mod)
|
|
214
|
+
|
|
215
|
+
# Introspect the Java properties.
|
|
216
|
+
introspect(klass)
|
|
217
|
+
klass.add_attribute_value_initializer if Class === klass
|
|
218
|
+
# Set the class domain module.
|
|
219
|
+
klass.domain_module = self
|
|
220
|
+
# Add referenced domain class metadata as necessary.
|
|
221
|
+
mod = klass.parent_module
|
|
222
|
+
klass.each_property do |prop|
|
|
223
|
+
ref = prop.type
|
|
224
|
+
if ref.nil? then Jinx.fail(MetadataError, "#{self} #{prop} domain type is unknown.") end
|
|
225
|
+
unless @introspected.include?(ref) or ref.parent_module != mod then
|
|
226
|
+
logger.debug { "Introspecting #{qp} #{prop} reference #{ref.qp}..." }
|
|
227
|
+
add_metadata(ref)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
logger.debug("#{self}::#{klass.qp} metadata added.")
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Introspects the given class.
|
|
234
|
+
#
|
|
235
|
+
# @param [Class] the domain class to introspect
|
|
236
|
+
def introspect(klass)
|
|
237
|
+
klass.introspect
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|