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