jinx 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|