jinx 2.1.2 → 2.1.3
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/History.md +4 -0
- data/LEGAL +0 -1
- data/README.md +0 -4
- data/lib/jinx/helpers/log.rb +3 -1
- data/lib/jinx/helpers/string_uniquifier.rb +39 -0
- data/lib/jinx/helpers/transitive_closure.rb +0 -2
- data/lib/jinx/helpers/uid.rb +19 -0
- data/lib/jinx/helpers/visitor.rb +31 -21
- data/lib/jinx/metadata/introspector.rb +2 -2
- data/lib/jinx/metadata/propertied.rb +10 -10
- data/lib/jinx/metadata/property.rb +0 -95
- data/lib/jinx/metadata/property_characteristics.rb +2 -1
- data/lib/jinx/resource/match_visitor.rb +6 -2
- data/lib/jinx/resource/merge_visitor.rb +1 -1
- data/lib/jinx/resource/mergeable.rb +22 -15
- data/lib/jinx/resource/reference_visitor.rb +10 -10
- data/lib/jinx/resource/unique.rb +14 -17
- data/lib/jinx/resource/uniquifier_cache.rb +34 -0
- data/lib/jinx/version.rb +1 -1
- data/test/lib/jinx/helpers/string_uniquifier_test.rb +11 -0
- metadata +7 -5
- data/lib/jinx/helpers/uniquifier.rb +0 -83
- data/test/lib/jinx/helpers/uniquifier_test.rb +0 -11
data/History.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
This history lists major release themes. See the GitHub commits (https://github.com/jinx/core)
|
2
2
|
for change details.
|
3
3
|
|
4
|
+
2.1.3 / 2012-08-18
|
5
|
+
------------------
|
6
|
+
* Pull UID out of uniquifier.
|
7
|
+
|
4
8
|
2.1.2 / 2012-06-12
|
5
9
|
------------------
|
6
10
|
* Fine-tune introspection.
|
data/LEGAL
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
JINX: JRuby Java introspector
|
2
2
|
=============================
|
3
|
-
|
4
3
|
**Home**: [http://github.com/jinx/core](http://github.com/jinx/core)
|
5
4
|
**Git**: [http://github.com/jinx/core](http://github.com/jinx/core)
|
6
5
|
**Author**: OHSU Knight Cancer Institute
|
@@ -9,12 +8,10 @@ JINX: JRuby Java introspector
|
|
9
8
|
|
10
9
|
Synopsis
|
11
10
|
--------
|
12
|
-
|
13
11
|
Jinx introspects a Java application domain package.
|
14
12
|
|
15
13
|
Feature List
|
16
14
|
------------
|
17
|
-
|
18
15
|
1. Introspects Java properties.
|
19
16
|
|
20
17
|
2. Annotates the introspected object model with additional meta-data.
|
@@ -39,6 +36,5 @@ See the [example](http://github.com/jinx/core/tree/master/examples/family) for a
|
|
39
36
|
|
40
37
|
Copyright
|
41
38
|
---------
|
42
|
-
|
43
39
|
Jinx © 2012 by [Oregon Health & Science University](http://www.ohsu.edu/xd/health/services/cancer/index.cfm).
|
44
40
|
Jinx is licensed under the MIT license. Please see the LICENSE and LEGAL files for more information.
|
data/lib/jinx/helpers/log.rb
CHANGED
@@ -66,7 +66,9 @@ module Jinx
|
|
66
66
|
# @option opts [Boolean] :debug whether to include debug messages in the log file
|
67
67
|
# @return [MultilineLogger] the global logger
|
68
68
|
def open(dev=nil, opts=nil)
|
69
|
-
|
69
|
+
if open? then
|
70
|
+
raise RuntimeError.new("The logger has already opened the log#{' file ' + @dev if String === @dev}")
|
71
|
+
end
|
70
72
|
dev, opts = nil, dev if Hash === dev
|
71
73
|
dev ||= default_log_file(Options.get(:app, opts))
|
72
74
|
FileUtils.mkdir_p(File.dirname(dev)) if String === dev
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'jinx/helpers/uid'
|
2
|
+
|
3
|
+
module Jinx
|
4
|
+
# A mix-in to convert a String to a unique value.
|
5
|
+
module StringUniquifier
|
6
|
+
# Returns a relatively unique String for the given base String. Each call returns
|
7
|
+
# a distinct value.
|
8
|
+
#
|
9
|
+
# This method is useful to transform a String object key to a unique value for
|
10
|
+
# testing purposes.
|
11
|
+
#
|
12
|
+
# The unique value is comprised of a prefix and suffix. The prefix is the base value
|
13
|
+
# with spaces replaced by an underscore. The suffix is given by a {Jinx::UID.generate}
|
14
|
+
# qualifier converted to digits and lower-case letters, excluding the digits 0, 1 and
|
15
|
+
# the characters l, o to avoid confusion.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# Jinx::StringUniquifier.uniquify('Groucho') #=> Groucho_wiafye6e
|
19
|
+
#
|
20
|
+
# @param value [String] the value to make unique
|
21
|
+
# @return [String, nil] the new unique value
|
22
|
+
# @raise [ArgumentError] if value is not a String
|
23
|
+
def self.uniquify(value)
|
24
|
+
raise ArgumentError.new("#{value.qp} is not a String") unless String === value
|
25
|
+
s = ''
|
26
|
+
n = UID.generate
|
27
|
+
while n > 0 do
|
28
|
+
n, m = n.divmod(32)
|
29
|
+
s << CHARS[m]
|
30
|
+
end
|
31
|
+
[value.gsub(' ', '_'), s].join('_')
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# The qualifier character range.
|
37
|
+
CHARS = 'abcdefghijkmnpqrstuvwxyz23456789'
|
38
|
+
end
|
39
|
+
end
|
@@ -22,8 +22,6 @@ class Object
|
|
22
22
|
# @children = []
|
23
23
|
# parent.children << self if parent
|
24
24
|
# end
|
25
|
-
#
|
26
|
-
# def to_s;
|
27
25
|
# end
|
28
26
|
# a = Node.new('a'); b = Node.new('b', a), c = Node.new('c', a); d = Node.new('d', c)
|
29
27
|
# a.transitive_closure { |node| node.children }.to_a.join(", ") #=> a, b, c, d
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Jinx
|
2
|
+
# A unique identifier generator.
|
3
|
+
module UID
|
4
|
+
# Returns a relatively unique integer. Successive calls to this method
|
5
|
+
# within the same time zone spaced more than a millisecond apart return different
|
6
|
+
# integers. Each generated qualifier is greater than the previous by an unspecified
|
7
|
+
# amount.
|
8
|
+
def self.generate
|
9
|
+
# the first date that this method could be called
|
10
|
+
@first ||= Date.new(2011, 12, 01)
|
11
|
+
# days as integer + milliseconds as fraction since the first date
|
12
|
+
diff = DateTime.now - @first
|
13
|
+
# shift a tenth of a milli up into the integer portion
|
14
|
+
decimillis = diff * 24 * 60 * 60 * 10000
|
15
|
+
# truncate the fraction
|
16
|
+
decimillis.truncate
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/jinx/helpers/visitor.rb
CHANGED
@@ -193,7 +193,7 @@ module Jinx
|
|
193
193
|
# @return [Visitor] the filter visitor
|
194
194
|
# @yield [parent, children] the filter to select which of the children to visit next
|
195
195
|
# @yieldparam parent the currently visited node
|
196
|
-
# @yieldparam children the nodes slated by this
|
196
|
+
# @yieldparam [Array] children the nodes slated by this visitor to visit next
|
197
197
|
# @raise [ArgumentError] if a block is not given to this method
|
198
198
|
def filter
|
199
199
|
raise ArgumentError.new("A filter block is not given to the visitor filter method") unless block_given?
|
@@ -229,20 +229,28 @@ module Jinx
|
|
229
229
|
# Visits the root node and all descendants.
|
230
230
|
def visit_root(node, &operator)
|
231
231
|
clear
|
232
|
-
|
233
|
-
|
234
|
-
|
232
|
+
# Exclude cycles if the prune cycles flag is set.
|
233
|
+
@exclude.merge!(cyclic_nodes(node)) if @prune_cycle_flag
|
234
|
+
# Visit the root node.
|
235
|
+
result = visit_recursive(node, &operator)
|
236
|
+
# Reset the exclusions if the prune cycles flag is set.
|
237
|
+
@exclude.clear if @prune_cycle_flag
|
238
|
+
result
|
235
239
|
end
|
236
|
-
|
237
|
-
#
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
240
|
+
|
241
|
+
# @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
|
247
|
+
def cyclic_nodes(root)
|
248
|
+
copts = @options.reject { |k, v| k == :prune_cycle }
|
249
|
+
cycler = Visitor.new(copts, &@navigator)
|
250
|
+
cycler.visit(root)
|
251
|
+
cyclic = cycler.cycles.flatten.uniq
|
252
|
+
cyclic.delete(root)
|
253
|
+
cyclic
|
246
254
|
end
|
247
255
|
|
248
256
|
def visit_recursive(node, &operator)
|
@@ -298,7 +306,9 @@ module Jinx
|
|
298
306
|
@visitor = visitor
|
299
307
|
@root = node
|
300
308
|
end
|
301
|
-
|
309
|
+
|
310
|
+
# @yield [node] operates on the visited node
|
311
|
+
# @yieldparam node the visited node
|
302
312
|
def each
|
303
313
|
@visitor.visit(@root) { |node| yield(node) }
|
304
314
|
end
|
@@ -314,8 +324,8 @@ module Jinx
|
|
314
324
|
|
315
325
|
# Visits the given pair of nodes.
|
316
326
|
#
|
317
|
-
#
|
318
|
-
#
|
327
|
+
# @param [(Object, Object), <(Object, Object)>] nodes the node pair
|
328
|
+
# @raise [ArgumentError] if the arguments do not consist of either two nodes or one two-item array
|
319
329
|
def visit(*nodes)
|
320
330
|
if nodes.size == 1 then
|
321
331
|
nodes = nodes.first
|
@@ -324,10 +334,10 @@ module Jinx
|
|
324
334
|
super(nodes)
|
325
335
|
end
|
326
336
|
|
327
|
-
#
|
328
|
-
#
|
329
|
-
#
|
330
|
-
#
|
337
|
+
# @param (see #visit)
|
338
|
+
# @param [(Object, Object), <(Object, Object)>] nodes
|
339
|
+
# @return [Enumerable] the result of applying the given block to each matched node starting at the given root nodes
|
340
|
+
# @raise [ArgumentError] if the arguments do not consist of either two nodes or one two-item array
|
331
341
|
def to_enum(*nodes)
|
332
342
|
if nodes.size == 1 then
|
333
343
|
nodes = nodes.first
|
@@ -17,9 +17,9 @@ module Jinx
|
|
17
17
|
# Adds an optional {attribute=>value} constructor parameter to this class.
|
18
18
|
def add_attribute_value_initializer
|
19
19
|
class << self
|
20
|
-
def new(
|
20
|
+
def new(params=nil)
|
21
21
|
obj = super()
|
22
|
-
obj.merge_attributes(
|
22
|
+
obj.merge_attributes(params) if params
|
23
23
|
obj
|
24
24
|
end
|
25
25
|
end
|
@@ -246,7 +246,7 @@ module Jinx
|
|
246
246
|
@local_prop_hash = {}
|
247
247
|
@prop_hash = append_ancestor_enum(@local_prop_hash) { |par| par.property_hash }
|
248
248
|
@attributes = Enumerable::Enumerator.new(@prop_hash, :each_key)
|
249
|
-
@
|
249
|
+
@local_mndty_attrs = Set.new
|
250
250
|
@local_defaults = {}
|
251
251
|
@defaults = append_ancestor_enum(@local_defaults) { |par| par.defaults }
|
252
252
|
end
|
@@ -393,7 +393,7 @@ module Jinx
|
|
393
393
|
|
394
394
|
# @param [Symbol] attribute the mandatory attribute
|
395
395
|
def add_mandatory_attribute(attribute)
|
396
|
-
@
|
396
|
+
@local_mndty_attrs << standard_attribute(attribute)
|
397
397
|
end
|
398
398
|
|
399
399
|
# Marks the given attribute with flags supported by {Property#qualify}.
|
@@ -414,16 +414,16 @@ module Jinx
|
|
414
414
|
# An attribute declared in a superclass Resource is hidden from this Resource but retained in
|
415
415
|
# the declaring Resource.
|
416
416
|
def remove_attribute(attribute)
|
417
|
-
|
417
|
+
sa = standard_attribute(attribute)
|
418
418
|
# if the attribute is local, then delete it, otherwise filter out the superclass attribute
|
419
|
-
|
420
|
-
if
|
419
|
+
sp = @local_prop_hash.delete(sa)
|
420
|
+
if sp then
|
421
421
|
# clear the inverse, if any
|
422
|
-
clear_inverse(
|
422
|
+
clear_inverse(sp)
|
423
423
|
# remove from the mandatory attributes, if necessary
|
424
|
-
@
|
424
|
+
@local_mndty_attrs.delete(sa)
|
425
425
|
# remove from the attribute => metadata hash
|
426
|
-
@local_std_prop_hash.delete_if { |aliaz, pa| pa ==
|
426
|
+
@local_std_prop_hash.delete_if { |aliaz, pa| pa == sa }
|
427
427
|
else
|
428
428
|
# Filter the superclass hashes.
|
429
429
|
anc_prop_hash = @prop_hash.components[1]
|
@@ -478,8 +478,8 @@ module Jinx
|
|
478
478
|
#
|
479
479
|
# @see #mandatory_attributes
|
480
480
|
def collect_mandatory_attributes
|
481
|
-
@
|
482
|
-
append_ancestor_enum(@
|
481
|
+
@local_mndty_attrs.merge!(default_mandatory_local_attributes)
|
482
|
+
append_ancestor_enum(@local_mndty_attrs) { |par| par.mandatory_attributes }
|
483
483
|
end
|
484
484
|
|
485
485
|
def default_mandatory_local_attributes
|
@@ -77,15 +77,6 @@ module Jinx
|
|
77
77
|
@inv_prop.attribute if @inv_prop
|
78
78
|
end
|
79
79
|
|
80
|
-
# An attribute is unidirectional if both of the following is true:
|
81
|
-
# * there is no distinct {#inverse} attribute
|
82
|
-
# * the attribute is not a {#dependent?} with more than one owner
|
83
|
-
#
|
84
|
-
# @return [Boolean] whether this attribute does not have an inverse
|
85
|
-
def unidirectional?
|
86
|
-
inverse.nil? and not (dependent? and type.owner_attributes.size > 1)
|
87
|
-
end
|
88
|
-
|
89
80
|
# @param [Class] the attribute return type
|
90
81
|
def type=(klass)
|
91
82
|
return if klass == @type
|
@@ -135,11 +126,6 @@ module Jinx
|
|
135
126
|
@inv_prop.qualify(:disjoint) if disjoint?
|
136
127
|
logger.debug { "Assigned #{@declarer.qp}.#{self} attribute inverse to #{type.qp}.#{attribute}." }
|
137
128
|
end
|
138
|
-
|
139
|
-
# @return [Boolean] whether this property has an inverse
|
140
|
-
def bidirectional?
|
141
|
-
!!@inv_prop
|
142
|
-
end
|
143
129
|
|
144
130
|
# @return [Property, nil] the property for the {#inverse} attribute, if any
|
145
131
|
def inverse_property
|
@@ -156,91 +142,10 @@ module Jinx
|
|
156
142
|
if @restrictions then @restrictions.each { |prop| prop.qualify(*flags) } end
|
157
143
|
end
|
158
144
|
|
159
|
-
# @return [Boolean] whether the subject attribute encapsulates a Java attribute
|
160
|
-
def java_property?
|
161
|
-
JavaProperty === self
|
162
|
-
end
|
163
|
-
|
164
|
-
# @return [Boolean] whether the subject attribute returns a domain object or collection of domain objects
|
165
|
-
def domain?
|
166
|
-
# the type must be a Ruby class rather than a Java Class, and include the Domain mix-in
|
167
|
-
Class === type and type < Resource
|
168
|
-
end
|
169
|
-
|
170
|
-
# @return [Boolean] whether the subject attribute is not a domain object attribute
|
171
|
-
def nondomain?
|
172
|
-
not domain?
|
173
|
-
end
|
174
|
-
|
175
|
-
# @return [Boolean] whether the subject attribute return type is a collection
|
176
|
-
def collection?
|
177
|
-
@flags.include?(:collection)
|
178
|
-
end
|
179
|
-
|
180
|
-
# Returns whether the subject attribute is a dependent on a parent. See the Jinx configuration
|
181
|
-
# documentation for a dependency description.
|
182
|
-
#
|
183
|
-
# @return [Boolean] whether the attribute references a dependent
|
184
|
-
def dependent?
|
185
|
-
@flags.include?(:dependent)
|
186
|
-
end
|
187
|
-
|
188
|
-
# Returns whether the subject attribute must have a value when it is saved
|
189
|
-
#
|
190
|
-
# @return [Boolean] whether the attribute is mandatory
|
191
|
-
def mandatory?
|
192
|
-
@declarer.mandatory_attributes.include?(attribute)
|
193
|
-
end
|
194
|
-
|
195
|
-
# An attribute is derived if the attribute value is set by setting another attribute, e.g. if this
|
196
|
-
# attribute is the inverse of a dependent owner attribute.
|
197
|
-
#
|
198
|
-
# @return [Boolean] whether this attribute is derived from another attribute
|
199
|
-
def derived?
|
200
|
-
dependent? and !!inverse
|
201
|
-
end
|
202
|
-
|
203
145
|
# @return [Boolean] this attribute's inverse attribute if the inverse is a derived attribute, or nil otherwise
|
204
146
|
def derived_inverse
|
205
147
|
@inv_prop.attribute if @inv_prop and @inv_prop.derived?
|
206
148
|
end
|
207
|
-
|
208
|
-
# An independent attribute is a reference to one or more non-dependent Resource objects.
|
209
|
-
# An {#owner?} attribute is independent.
|
210
|
-
#
|
211
|
-
# @return [Boolean] whether the subject attribute is a non-dependent domain attribute
|
212
|
-
def independent?
|
213
|
-
domain? and not dependent?
|
214
|
-
end
|
215
|
-
|
216
|
-
# @return [Boolean] whether this attribute is a collection with a collection inverse
|
217
|
-
def many_to_many?
|
218
|
-
return false unless collection?
|
219
|
-
inv_prop = inverse_property
|
220
|
-
inv_prop and inv_prop.collection?
|
221
|
-
end
|
222
|
-
|
223
|
-
# @return [Boolean] whether the subject attribute is a dependency owner
|
224
|
-
def owner?
|
225
|
-
@flags.include?(:owner)
|
226
|
-
end
|
227
|
-
|
228
|
-
# @return [Boolean] whether this is a dependent attribute which has exactly one owner value
|
229
|
-
# chosen from several owner attributes
|
230
|
-
def disjoint?
|
231
|
-
@flags.include?(:disjoint)
|
232
|
-
end
|
233
|
-
|
234
|
-
# @return [Boolean] whether this attribute is a dependent which does not have a Java
|
235
|
-
# inverse owner attribute
|
236
|
-
def unidirectional_java_dependent?
|
237
|
-
dependent? and java_property? and not bidirectional_java_association?
|
238
|
-
end
|
239
|
-
|
240
|
-
# @return [Boolean] whether this is a Java attribute which has a Java inverse
|
241
|
-
def bidirectional_java_association?
|
242
|
-
inverse and java_property? and inverse_property.java_property?
|
243
|
-
end
|
244
149
|
|
245
150
|
# Creates a new declarer attribute which restricts this attribute.
|
246
151
|
# This method should only be called by a {Resource} class, since the class is responsible
|
@@ -27,7 +27,8 @@ module Jinx
|
|
27
27
|
JavaProperty === self
|
28
28
|
end
|
29
29
|
|
30
|
-
# @return [Boolean] whether the subject attribute returns a domain object or collection
|
30
|
+
# @return [Boolean] whether the subject attribute returns a domain object or a collection
|
31
|
+
# of domain objects
|
31
32
|
def domain?
|
32
33
|
# the type must be a Ruby class rather than a Java Class, and include the Domain mix-in
|
33
34
|
Class === type and type < Resource
|
@@ -27,8 +27,12 @@ module Jinx
|
|
27
27
|
@copier = opts.delete(:copier)
|
28
28
|
# the source => target matches
|
29
29
|
@matches = {}
|
30
|
-
# Apply a filter to the
|
31
|
-
|
30
|
+
# Apply a filter to the visited reference so that only a matched reference is visited.
|
31
|
+
# the reference filter
|
32
|
+
flt = opts[:filter]
|
33
|
+
opts[:filter] = Proc.new do |src|
|
34
|
+
(flt.nil? or flt.call(src)) and !!@matches[src]
|
35
|
+
end
|
32
36
|
# the class => {id => target} hash
|
33
37
|
@id_mtchs = LazyHash.new { Hash.new }
|
34
38
|
# Match the source references before navigating from the source to its references, since
|
@@ -23,21 +23,19 @@ module Jinx
|
|
23
23
|
# {Propertied#mergeable_attributes}.
|
24
24
|
#
|
25
25
|
# The merge is performed by calling {#merge_attribute} on each attribute with the matches
|
26
|
-
# and
|
26
|
+
# and filter block given to this method.
|
27
27
|
#
|
28
28
|
# @param [Mergeable, {Symbol => Object}] other the source domain object or value hash to merge from
|
29
29
|
# @param [<Symbol>, nil] attributes the attributes to merge (default {Propertied#nondomain_attributes})
|
30
30
|
# @param [{Resource => Resource}, nil] the optional merge source => target reference matches
|
31
|
-
# @yield [
|
32
|
-
# @yieldparam
|
33
|
-
# @yieldparam oldval the current merge attribute value
|
34
|
-
# @yieldparam newval the new merge attribute value
|
31
|
+
# @yield [value] the optional filter block
|
32
|
+
# @yieldparam value the source merge attribute value
|
35
33
|
# @return [Mergeable] self
|
36
34
|
# @raise [ArgumentError] if none of the following are true:
|
37
35
|
# * other is a Hash
|
38
36
|
# * attributes is non-nil
|
39
37
|
# * the other class responds to +mergeable_attributes+
|
40
|
-
def merge_attributes(other, attributes=nil, matches=nil, &
|
38
|
+
def merge_attributes(other, attributes=nil, matches=nil, &filter)
|
41
39
|
return self if other.nil? or other.equal?(self)
|
42
40
|
attributes = [attributes] if Symbol === attributes
|
43
41
|
attributes ||= self.class.mergeable_attributes
|
@@ -45,11 +43,13 @@ module Jinx
|
|
45
43
|
# if the source object is not a hash, then convert it to an attribute => value hash
|
46
44
|
vh = Hashable === other ? other : other.value_hash(attributes)
|
47
45
|
# merge the value hash
|
48
|
-
vh.each { |pa, value| merge_attribute(pa, value, matches, &
|
46
|
+
vh.each { |pa, value| merge_attribute(pa, value, matches, &filter) }
|
49
47
|
self
|
50
48
|
end
|
51
49
|
|
52
|
-
|
50
|
+
def merge(*args, &filter)
|
51
|
+
merge_attributes(*args, &filter)
|
52
|
+
end
|
53
53
|
|
54
54
|
alias :merge! :merge
|
55
55
|
|
@@ -77,19 +77,26 @@ module Jinx
|
|
77
77
|
def merge_attribute(attribute, newval, matches=nil)
|
78
78
|
# the previous value
|
79
79
|
oldval = send(attribute)
|
80
|
-
#
|
81
|
-
if newval
|
82
|
-
|
83
|
-
|
84
|
-
|
80
|
+
# Filter the newval into the srcval.
|
81
|
+
srcval = if newval and block_given? then
|
82
|
+
if newval.collection? then
|
83
|
+
newval.select { |v| yield(v) }
|
84
|
+
elsif yield(newval) then
|
85
|
+
newval
|
86
|
+
end
|
87
|
+
else
|
88
|
+
newval
|
85
89
|
end
|
86
90
|
|
91
|
+
# If there is no point in merging, then bail.
|
92
|
+
return oldval if srcval.nil_or_empty? or mergeable__equal?(oldval, newval)
|
93
|
+
|
87
94
|
# Discriminate between a domain and non-domain attribute.
|
88
95
|
prop = self.class.property(attribute)
|
89
96
|
if prop.domain? then
|
90
|
-
merge_domain_property_value(prop, oldval,
|
97
|
+
merge_domain_property_value(prop, oldval, srcval, matches)
|
91
98
|
else
|
92
|
-
merge_nondomain_property_value(prop, oldval,
|
99
|
+
merge_nondomain_property_value(prop, oldval, srcval)
|
93
100
|
end
|
94
101
|
end
|
95
102
|
|
@@ -14,31 +14,31 @@ module Jinx
|
|
14
14
|
# The required selector block given to this initializer determines which attributes to
|
15
15
|
# visit. The references to visit next thus consist of the current domain object's selector
|
16
16
|
# attributes' values. If the :filter option is set, then the given filter block is applied
|
17
|
-
# to
|
17
|
+
# to each selected attribute reference to determine which domain objects will be visited.
|
18
18
|
#
|
19
19
|
# @param opts (see Visitor#initialize)
|
20
|
-
# @option opts [Proc] :filter an optional filter on the
|
20
|
+
# @option opts [Proc] :filter an optional filter on the reference to visit
|
21
21
|
# @yield [obj] returns the {AttributeEnumerator} of attributes to visit next from the
|
22
22
|
# current domain object
|
23
23
|
# @yieldparam [Resource] obj the current domain object
|
24
24
|
def initialize(opts=nil, &selector)
|
25
25
|
raise ArgumentError.new("Reference visitor missing domain reference selector") unless block_given?
|
26
26
|
# the property selector
|
27
|
-
@
|
27
|
+
@selector = selector
|
28
28
|
# the reference filter
|
29
|
-
|
29
|
+
@filter = Options.get(:filter, opts)
|
30
30
|
# Initialize the Visitor with a reference enumerator which selects the reference
|
31
31
|
# attributes and applies the optional filter if necessary.
|
32
32
|
@ref_enums = {}
|
33
|
-
super do |
|
33
|
+
super do |obj|
|
34
34
|
# the reference property filter
|
35
|
-
ras = attributes_to_visit(
|
35
|
+
ras = attributes_to_visit(obj)
|
36
36
|
if ras then
|
37
|
-
logger.debug { "#{qp} visiting #{
|
37
|
+
logger.debug { "#{qp} visiting #{obj} attributes #{ras.pp_s(:single_line)}..." } if @verbose
|
38
38
|
# an enumerator on the reference properties
|
39
|
-
enum = ReferenceEnumerator.new(
|
39
|
+
enum = ReferenceEnumerator.new(obj, ras.properties)
|
40
40
|
# If there is a reference filter, then apply it to the enum references.
|
41
|
-
|
41
|
+
@filter ? enum.filter(&@filter) : enum
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
@@ -48,7 +48,7 @@ module Jinx
|
|
48
48
|
# @param [Resource] obj the visiting object
|
49
49
|
# @return [Propertied::Filter] the attributes to visit
|
50
50
|
def attributes_to_visit(obj)
|
51
|
-
@
|
51
|
+
@selector.call(obj)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
data/lib/jinx/resource/unique.rb
CHANGED
@@ -1,32 +1,29 @@
|
|
1
|
-
require 'jinx/
|
1
|
+
require 'jinx/resource/uniquifier_cache'
|
2
2
|
|
3
3
|
module Jinx
|
4
4
|
# The Unique mix-in makes values unique within the scope of a Resource class.
|
5
5
|
module Unique
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
raise TypeError.new("Cannot uniquify #{qp} non-String value #{value}")
|
12
|
-
end
|
13
|
-
Uniquifier.instance.uniquify(self, value)
|
14
|
-
end
|
15
|
-
|
16
|
-
# Makes the secondary key unique by replacing each String key attribute value
|
17
|
-
# with a unique value.
|
6
|
+
# Replaces each String secondary and alternate key property with a unique
|
7
|
+
# value. Successive calls to this method for domain objects of the same class
|
8
|
+
# replace the same String key property values with the same unique value.
|
9
|
+
#
|
10
|
+
# @return [Resource] self
|
18
11
|
def uniquify
|
19
12
|
uniquify_attributes(self.class.secondary_key_attributes)
|
20
13
|
uniquify_attributes(self.class.alternate_key_attributes)
|
14
|
+
self
|
21
15
|
end
|
22
|
-
|
23
|
-
|
24
|
-
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# Makes this domain object's String values for the given attributes unique.
|
20
|
+
#
|
21
|
+
# @param [<Symbol>] the key attributes to uniquify
|
25
22
|
def uniquify_attributes(attributes)
|
26
23
|
attributes.each do |ka|
|
27
24
|
oldval = send(ka)
|
28
25
|
next unless String === oldval
|
29
|
-
newval =
|
26
|
+
newval = UniquifierCache.instance.get(self, oldval)
|
30
27
|
set_property_value(ka, newval)
|
31
28
|
logger.debug { "Reset #{qp} #{ka} from #{oldval} to unique value #{newval}." }
|
32
29
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'jinx/helpers/lazy_hash'
|
3
|
+
require 'jinx/helpers/string_uniquifier'
|
4
|
+
|
5
|
+
module Jinx
|
6
|
+
# A utility class to cache key value qualifiers.
|
7
|
+
class UniquifierCache
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@cache = Jinx::LazyHash.new { Hash.new }
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the unique value generated for the given object and value.
|
15
|
+
# Successive calls to this method for domain objects of the same class
|
16
|
+
# and a given value return the same result.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# Jinx::UniquifierCache.instance.get(person, 'Groucho') #=> Groucho_wy874e6e
|
20
|
+
# Jinx::UniquifierCache.instance.get(person.copy, 'Groucho') #=> Groucho_wy87ye6e
|
21
|
+
#
|
22
|
+
# @param [Resource] obj the domain object containing the value
|
23
|
+
# @param [String] value the value to make unique
|
24
|
+
# @return [String] the unique value
|
25
|
+
def get(obj, value)
|
26
|
+
@cache[obj.class][value] ||= StringUniquifier.uniquify(value)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Clears all cache entries.
|
30
|
+
def clear
|
31
|
+
@cache.clear
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/jinx/version.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../../helper'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'jinx/helpers/string_uniquifier'
|
4
|
+
|
5
|
+
class StringUniquifierTest < Test::Unit::TestCase
|
6
|
+
def test_uniquify
|
7
|
+
u1 = Jinx::StringUniquifier.uniquify('Groucho')
|
8
|
+
u2 = Jinx::StringUniquifier.uniquify('Groucho')
|
9
|
+
assert_not_equal(u1, u2, "Consecutive uniquifier calls not unique")
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: jinx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 2.1.
|
5
|
+
version: 2.1.3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- OHSU
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-07-17 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -142,9 +142,10 @@ files:
|
|
142
142
|
- lib/jinx/helpers/pretty_print.rb
|
143
143
|
- lib/jinx/helpers/set.rb
|
144
144
|
- lib/jinx/helpers/stopwatch.rb
|
145
|
+
- lib/jinx/helpers/string_uniquifier.rb
|
145
146
|
- lib/jinx/helpers/transformer.rb
|
146
147
|
- lib/jinx/helpers/transitive_closure.rb
|
147
|
-
- lib/jinx/helpers/
|
148
|
+
- lib/jinx/helpers/uid.rb
|
148
149
|
- lib/jinx/helpers/validation.rb
|
149
150
|
- lib/jinx/helpers/visitor.rb
|
150
151
|
- lib/jinx/import/class_path_modifier.rb
|
@@ -171,6 +172,7 @@ files:
|
|
171
172
|
- lib/jinx/resource/reference_path_visitor.rb
|
172
173
|
- lib/jinx/resource/reference_visitor.rb
|
173
174
|
- lib/jinx/resource/unique.rb
|
175
|
+
- lib/jinx/resource/uniquifier_cache.rb
|
174
176
|
- lib/jinx/version.rb
|
175
177
|
- spec/defaults_spec.rb
|
176
178
|
- spec/definitions/model/alias/child.rb
|
@@ -212,8 +214,8 @@ files:
|
|
212
214
|
- test/lib/jinx/helpers/partial_order_test.rb
|
213
215
|
- test/lib/jinx/helpers/pretty_print_test.rb
|
214
216
|
- test/lib/jinx/helpers/stopwatch_test.rb
|
217
|
+
- test/lib/jinx/helpers/string_uniquifier_test.rb
|
215
218
|
- test/lib/jinx/helpers/transitive_closure_test.rb
|
216
|
-
- test/lib/jinx/helpers/uniquifier_test.rb
|
217
219
|
- test/lib/jinx/helpers/visitor_test.rb
|
218
220
|
- test/lib/jinx/import/java_test.rb
|
219
221
|
- test/lib/jinx/import/mixed_case_test.rb
|
@@ -265,8 +267,8 @@ test_files:
|
|
265
267
|
- test/lib/jinx/helpers/partial_order_test.rb
|
266
268
|
- test/lib/jinx/helpers/pretty_print_test.rb
|
267
269
|
- test/lib/jinx/helpers/stopwatch_test.rb
|
270
|
+
- test/lib/jinx/helpers/string_uniquifier_test.rb
|
268
271
|
- test/lib/jinx/helpers/transitive_closure_test.rb
|
269
|
-
- test/lib/jinx/helpers/uniquifier_test.rb
|
270
272
|
- test/lib/jinx/helpers/visitor_test.rb
|
271
273
|
- test/lib/jinx/import/java_test.rb
|
272
274
|
- test/lib/jinx/import/mixed_case_test.rb
|
@@ -1,83 +0,0 @@
|
|
1
|
-
require 'singleton'
|
2
|
-
require 'jinx/helpers/lazy_hash'
|
3
|
-
|
4
|
-
module Jinx
|
5
|
-
# A utility class to generate value qualifiers.
|
6
|
-
class Uniquifier
|
7
|
-
include Singleton
|
8
|
-
|
9
|
-
# Returns a relatively unique integral qualifier. Successive calls to this method
|
10
|
-
# within the same time zone spaced more than a millisecond apart return different
|
11
|
-
# integers. Each generated qualifier is greater than the previous by an unspecified
|
12
|
-
# amount.
|
13
|
-
def self.qualifier
|
14
|
-
# the first date that this method could be called
|
15
|
-
@first ||= Date.new(2011, 12, 01)
|
16
|
-
# days as integer + milliseconds as fraction since the first date
|
17
|
-
diff = DateTime.now - @first
|
18
|
-
# shift a tenth of a milli up into the integer portion
|
19
|
-
decimillis = diff * 24 * 60 * 60 * 10000
|
20
|
-
# truncate the fraction
|
21
|
-
decimillis.truncate
|
22
|
-
end
|
23
|
-
|
24
|
-
def initialize
|
25
|
-
@cache = Jinx::LazyHash.new { Hash.new }
|
26
|
-
end
|
27
|
-
|
28
|
-
# Returns a relatively unique String for the given base String object or
|
29
|
-
# (object, String value) pair. In the former case, each call returns a distinct value.
|
30
|
-
# In the latter case, successive calls of the same String value for the same object
|
31
|
-
# class return the same unique value.
|
32
|
-
#
|
33
|
-
# This method is useful to transform a String object key to a unique value for testing
|
34
|
-
# purposes.
|
35
|
-
#
|
36
|
-
# The unique value is comprised of a prefix and suffix. The prefix is the base value
|
37
|
-
# with spaces replaced by an underscore. The suffix is a {Jinx::Uniquifier.qualifier}
|
38
|
-
# converted to digits and lower-case letters, excluding the digits 0, 1 and characters
|
39
|
-
# l, o to avoid confusion.
|
40
|
-
#
|
41
|
-
# @example
|
42
|
-
# Jinx::Uniquifier.instance.uniquify('Groucho') #=> Groucho_wiafye6e
|
43
|
-
# Jinx::Uniquifier.instance.uniquify('Groucho') #=> Groucho_uqafye6e
|
44
|
-
# Jinx::Uniquifier.instance.uniquify('Groucho Marx') #=> Groucho_ay23ye6e
|
45
|
-
# Jinx::Uniquifier.instance.uniquify(person, 'Groucho') #=> Groucho_wy874e6e
|
46
|
-
# Jinx::Uniquifier.instance.uniquify(person, 'Groucho') #=> Groucho_wy87ye6e
|
47
|
-
#
|
48
|
-
# @param obj the object containing the value to uniquify
|
49
|
-
# @param value [String, nil] the value to make unique, or nil if the containing object is a String
|
50
|
-
# @return [String, nil] the new unique value, or nil if the containing object is a String
|
51
|
-
# and the given value is nil
|
52
|
-
def uniquify(obj, value=nil)
|
53
|
-
if String === obj then
|
54
|
-
to_unique(obj)
|
55
|
-
elsif value then
|
56
|
-
@cache[obj.class][value] ||= to_unique(value)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def clear
|
61
|
-
@cache.clear
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
CHARS = 'abcdefghijkmnpqrstuvwxyz23456789'
|
67
|
-
|
68
|
-
# @param value [String, nil] the value to make unique
|
69
|
-
# @return [String] the new unique value, or nil if the given value is nil
|
70
|
-
# @raise [ArgumentError] if value is neither a String nor nil
|
71
|
-
def to_unique(value)
|
72
|
-
return if value.nil?
|
73
|
-
raise ArgumentError.new("#{value.qp} is not a String") unless String === value
|
74
|
-
s = ''
|
75
|
-
n = Jinx::Uniquifier.qualifier
|
76
|
-
while n > 0 do
|
77
|
-
n, m = n.divmod(32)
|
78
|
-
s << CHARS[m]
|
79
|
-
end
|
80
|
-
[value.gsub(' ', '_'), s].join('_')
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
@@ -1,11 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../../../helper'
|
2
|
-
require 'test/unit'
|
3
|
-
require 'jinx/helpers/uniquifier'
|
4
|
-
|
5
|
-
class UniquifierTest < Test::Unit::TestCase
|
6
|
-
def test_uniquify
|
7
|
-
u1 = Jinx::Uniquifier.instance.uniquify('Groucho')
|
8
|
-
u2 = Jinx::Uniquifier.instance.uniquify('Groucho')
|
9
|
-
assert_not_equal(u1, u2, "Consequetive uniquifier calls not unique")
|
10
|
-
end
|
11
|
-
end
|