cadove 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/README +34 -0
- data/lib/cadove.rb +92 -0
- data/lib/cadove/models.rb +5 -0
- data/lib/cadove/models/XMI/document.rb +143 -0
- data/lib/cadove/models/analytics/comparison.rb +155 -0
- data/lib/cadove/models/helpers.rb +7 -0
- data/lib/cadove/models/indifferent_access.rb +149 -0
- data/lib/cadove/models/java/code.rb +108 -0
- data/lib/cadove/models/java/javalib/qdox-1.10.jar +0 -0
- data/lib/cadove/models/reports/reports.rb +60 -0
- data/spec/cadove_spec.rb +18 -0
- data/spec/java_stubs/org/foo/Animal.java +3 -0
- data/spec/java_stubs/org/foo/Cat.java +3 -0
- data/spec/java_stubs/org/foo/Dog.java +18 -0
- data/spec/models/XMI/document_spec.rb +127 -0
- data/spec/models/analytics/comparison_spec.rb +105 -0
- data/spec/models/java/code_spec.rb +48 -0
- data/spec/spec_helper.rb +1 -0
- metadata +82 -0
data/CHANGELOG
ADDED
data/README
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
caDOVe (Domain Object Verifier)
|
2
|
+
===============================
|
3
|
+
|
4
|
+
About
|
5
|
+
-----
|
6
|
+
caDOVe helps with checking if a Java code base and a UML model
|
7
|
+
of the domain objects are in sync. This is especially useful for
|
8
|
+
verifying the silver level compatibility requirement,
|
9
|
+
"bla bla bla bla bla".
|
10
|
+
|
11
|
+
Currently...
|
12
|
+
- only Hibernate annotated Java classes.
|
13
|
+
- only XMI representations of UML are supported.
|
14
|
+
|
15
|
+
Installation
|
16
|
+
------------
|
17
|
+
1. Install the RJB rubygem (http://rjb.rubyforge.org/)
|
18
|
+
2. Set JAVA_HOME in your environment
|
19
|
+
|
20
|
+
Usage Example
|
21
|
+
-------------
|
22
|
+
require 'cadove'
|
23
|
+
|
24
|
+
CaDOVe.configure do
|
25
|
+
left 'nbia.xmi'
|
26
|
+
right 'nbia/domain'
|
27
|
+
|
28
|
+
ignore_classes %w(Boolean Date Double Integer Long String)
|
29
|
+
|
30
|
+
matching_classes(
|
31
|
+
:Image => :CTImage
|
32
|
+
)
|
33
|
+
end.class_comparison.attribute_comparison
|
34
|
+
|
data/lib/cadove.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/cadove/models'
|
2
|
+
|
3
|
+
module CaDOVe
|
4
|
+
class << self
|
5
|
+
attr_writer :left, :right, :ignore_classes, :matching_classes
|
6
|
+
|
7
|
+
def configure(&block)
|
8
|
+
Configurator.module_eval &block
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
###### CONFIGURATION PROPERTY ACCESSORS
|
13
|
+
%w(left right).each do |attribute|
|
14
|
+
required_accessor = <<-EOS
|
15
|
+
def #{attribute}
|
16
|
+
raise ConfigurationError.new("Bcsec::#{attribute} not set") unless @#{attribute}
|
17
|
+
@#{attribute}
|
18
|
+
end
|
19
|
+
EOS
|
20
|
+
class_eval(required_accessor)
|
21
|
+
end
|
22
|
+
|
23
|
+
def ignore_classes
|
24
|
+
@ignore_classes ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
def matching_classes
|
28
|
+
@matching_clases ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def class_comparison
|
32
|
+
Reports::ClassReport.new(comparison, left_type, right_type).display
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def attribute_comparison
|
37
|
+
Reports::AttributeReport.new(comparison, left_type, right_type).display
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def left_representation
|
43
|
+
@left_representation ||= xmi?(left) ? XMI::XMIDocument.new(left) : Java::JavaSourceTree.new(left)
|
44
|
+
end
|
45
|
+
|
46
|
+
def right_representation
|
47
|
+
@right_representation ||= xmi?(right) ? XMI::XMIDocument.new(right) : Java::JavaSourceTree.new(right)
|
48
|
+
end
|
49
|
+
|
50
|
+
def comparison
|
51
|
+
@comparison ||= Analytics::Comparison.new(
|
52
|
+
left_representation.hashify,
|
53
|
+
right_representation.hashify,
|
54
|
+
:ignore_classes => @ignore_classes,
|
55
|
+
:matching_classes => @matching_classes
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def xmi?(path)
|
60
|
+
path =~ /.*\.xmi$/i
|
61
|
+
end
|
62
|
+
|
63
|
+
%w(left right).each do |side|
|
64
|
+
type_accessor = <<-EOS
|
65
|
+
def #{side}_type
|
66
|
+
xmi?(#{side}) ? :xmi : :java
|
67
|
+
end
|
68
|
+
EOS
|
69
|
+
class_eval(type_accessor)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module Configurator
|
74
|
+
class << self
|
75
|
+
#########
|
76
|
+
protected
|
77
|
+
|
78
|
+
SIMPLE_TERMS = %w(left right ignore_classes matching_classes)
|
79
|
+
|
80
|
+
# DSL terms for simple top-level attributes
|
81
|
+
SIMPLE_TERMS.each do |attribute|
|
82
|
+
simple_setter = <<-EOS
|
83
|
+
def #{attribute}(value)
|
84
|
+
CaDOVe.#{attribute} = value
|
85
|
+
end
|
86
|
+
EOS
|
87
|
+
class_eval(simple_setter)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
class ConfigurationError < Exception; end
|
92
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require File.dirname(__FILE__) + '/../helpers'
|
3
|
+
|
4
|
+
module CaDOVe
|
5
|
+
module XMI
|
6
|
+
class XMIDocument
|
7
|
+
def initialize(input, opts = {})
|
8
|
+
opts[:input_type] ||= :file_path
|
9
|
+
val =
|
10
|
+
if opts[:input_type] == :text
|
11
|
+
input
|
12
|
+
elsif opts[:input_type] == :file_path
|
13
|
+
File.new(input)
|
14
|
+
end
|
15
|
+
|
16
|
+
@xml_doc = REXML::Document.new(val)
|
17
|
+
end
|
18
|
+
|
19
|
+
def classes
|
20
|
+
package = UMLPackage.logical_model(@xml_doc) || UMLPackage.all(@xml_doc).first
|
21
|
+
raise "No packages found in the XMI document" unless package
|
22
|
+
@classes ||= package.classes
|
23
|
+
end
|
24
|
+
|
25
|
+
def hashify
|
26
|
+
all = {}
|
27
|
+
classes.each do |clazz|
|
28
|
+
all.merge!(clazz.hashify)
|
29
|
+
end
|
30
|
+
all
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class UMLPackage
|
35
|
+
def initialize(xml)
|
36
|
+
@xml = xml
|
37
|
+
@name = name
|
38
|
+
end
|
39
|
+
|
40
|
+
# def self.logical_model(xml)
|
41
|
+
# packages = all(xml)
|
42
|
+
# logical = packages.size == 1 ? packages : packages.select{|p| p.name =~ /Logical Model/i}
|
43
|
+
# logical.first
|
44
|
+
# end
|
45
|
+
|
46
|
+
def self.all(xml)
|
47
|
+
xml.elements.collect('.//UML:Package') do |p|
|
48
|
+
new(p)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.method_missing(name, *args)
|
53
|
+
package_name = name.to_s.gsub(/_/, ' ')
|
54
|
+
all(args[0]).select{|p| p.name =~ /#{package_name}/i}.first
|
55
|
+
end
|
56
|
+
|
57
|
+
def name
|
58
|
+
@name ||= @xml.attributes['name']
|
59
|
+
end
|
60
|
+
|
61
|
+
def classes
|
62
|
+
UMLClass.all(@xml)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class UMLClass
|
67
|
+
def initialize(xml)
|
68
|
+
@xml = xml
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.all(xml)
|
72
|
+
xml.elements.collect('.//UML:Class') do |c|
|
73
|
+
new(c)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def name
|
78
|
+
@name ||= @xml.attributes['name']
|
79
|
+
end
|
80
|
+
|
81
|
+
def hashify
|
82
|
+
field_and_type = attributes.collect{|f| [f.name, f.type] }
|
83
|
+
{"#{name}" => field_and_type}
|
84
|
+
end
|
85
|
+
|
86
|
+
def attributes
|
87
|
+
@attributes ||= UMLAttribute.all(@xml)
|
88
|
+
end
|
89
|
+
|
90
|
+
def associations
|
91
|
+
@associations ||= UMLAssociation.all(@xml, name)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class UMLAttribute
|
96
|
+
include CaDOVe::Helpers
|
97
|
+
|
98
|
+
def initialize(xml)
|
99
|
+
@xml = xml
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.all(class_element)
|
103
|
+
class_element.elements.collect('.//UML:Attribute') do |a|
|
104
|
+
new(a)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def name
|
109
|
+
@name ||= @xml.attributes['name']
|
110
|
+
end
|
111
|
+
|
112
|
+
def type
|
113
|
+
@type ||= remove_package_name(REXML::XPath.first(@xml, ".//UML:TaggedValue[@tag='type']").attributes['value'])
|
114
|
+
end
|
115
|
+
|
116
|
+
def key
|
117
|
+
"#{name} (#{type})"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class UMLAssociation
|
122
|
+
def initialize(xml)
|
123
|
+
@xml = xml
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.all(class_element, class_name)
|
127
|
+
class_element.root.elements.collect("//UML:Association") do |a|
|
128
|
+
if REXML::XPath.first(a, ".//UML:TaggedValue[@tag='ea_sourceName']") && REXML::XPath.first(a, ".//UML:TaggedValue[@tag='ea_sourceName']").attributes['value'] == class_name
|
129
|
+
new(a)
|
130
|
+
end
|
131
|
+
end.compact
|
132
|
+
end
|
133
|
+
|
134
|
+
def name
|
135
|
+
REXML::XPath.first(@xml, ".//UML:TaggedValue[@tag='ea_targetName']").attributes['value']
|
136
|
+
end
|
137
|
+
|
138
|
+
def key
|
139
|
+
"#{name} (#{type})"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module CaDOVe
|
2
|
+
module Analytics
|
3
|
+
class Comparison
|
4
|
+
def initialize(left, right, opts={})
|
5
|
+
@left = left
|
6
|
+
@right = right
|
7
|
+
@opts = opts
|
8
|
+
end
|
9
|
+
|
10
|
+
def classes
|
11
|
+
@classes ||= ClassMatch.all(@left, @right, @opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
def intersecting_classes
|
15
|
+
@intersecting_classes ||= classes.select{|c| c.intersecting_class? }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ClassMatch
|
20
|
+
def initialize(key, left_attributes, right_attributes)
|
21
|
+
@key = key
|
22
|
+
@left_attributes = left_attributes
|
23
|
+
@right_attributes = right_attributes
|
24
|
+
end
|
25
|
+
attr_reader :key
|
26
|
+
|
27
|
+
# TODO: Clean this mess up
|
28
|
+
def self.all(left, right, opts={})
|
29
|
+
opts ||= {}
|
30
|
+
ignore_classes = opts[:ignore_classes] || []
|
31
|
+
matching_classes = opts[:matching_classes] || {}
|
32
|
+
|
33
|
+
left_keys = left.keys.reject{|k| ignore_classes.include?(k)}
|
34
|
+
right_keys = right.keys.reject{|k| ignore_classes.include?(k)}
|
35
|
+
ClassMatchKey.align(left_keys, right_keys, matching_classes).collect do |key|
|
36
|
+
ClassMatch.new(
|
37
|
+
key,
|
38
|
+
left["#{key.left}"],
|
39
|
+
right["#{key.right}"]
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def attributes
|
45
|
+
@attributes ||= AttributeMatch.all(@left_attributes, @right_attributes)
|
46
|
+
end
|
47
|
+
|
48
|
+
def left_match?
|
49
|
+
!@left_attributes.nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
def right_match?
|
53
|
+
!@right_attributes.nil?
|
54
|
+
end
|
55
|
+
|
56
|
+
def intersecting_class?
|
57
|
+
left_match? && right_match?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class ClassMatchKey
|
62
|
+
def initialize(left, right)
|
63
|
+
@left = left
|
64
|
+
@right = right
|
65
|
+
end
|
66
|
+
attr_reader :left, :right
|
67
|
+
|
68
|
+
def self.align(left_keys, right_keys, override={})
|
69
|
+
override = override.with_indifferent_access
|
70
|
+
|
71
|
+
matches = []
|
72
|
+
left_keys.each do |left_key|
|
73
|
+
found =
|
74
|
+
if m = override_match(left_key, right_keys, override)
|
75
|
+
right_keys.delete(m)
|
76
|
+
elsif right_keys.include?(left_key)
|
77
|
+
right_keys.delete(left_key)
|
78
|
+
end
|
79
|
+
|
80
|
+
matches << new(left_key, found)
|
81
|
+
end
|
82
|
+
|
83
|
+
right_keys.each do |right_key|
|
84
|
+
matches << new(nil, right_key)
|
85
|
+
end
|
86
|
+
|
87
|
+
matches.sort
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.override_match(left_key, right_keys, override={})
|
91
|
+
if override.has_key?(left_key) && right_keys.include?(override[left_key].to_s)
|
92
|
+
override[left_key].to_s
|
93
|
+
elsif override.invert.has_key?(left_key) && right_keys.include?(override.invert[left_key].to_s)
|
94
|
+
override.invert[left_key].to_s
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def <=>(other)
|
99
|
+
display <=> other.display
|
100
|
+
end
|
101
|
+
|
102
|
+
def display
|
103
|
+
return if left.nil? && right.nil?
|
104
|
+
|
105
|
+
if left && right.nil?
|
106
|
+
left
|
107
|
+
elsif right && left.nil?
|
108
|
+
right
|
109
|
+
elsif left == right
|
110
|
+
left
|
111
|
+
elsif left != right
|
112
|
+
"#{left} => #{right}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class AttributeMatch
|
118
|
+
def initialize(name, type, left_match, right_match)
|
119
|
+
@name = name
|
120
|
+
@type = type
|
121
|
+
@left_match = left_match
|
122
|
+
@right_match = right_match
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.all(left_attributes, right_attributes)
|
126
|
+
combined = (left_attributes | right_attributes).sort{|a,b|a[0] <=> b[0]} # Sort by name
|
127
|
+
|
128
|
+
combined.collect do |name, type|
|
129
|
+
AttributeMatch.new(
|
130
|
+
name,
|
131
|
+
type,
|
132
|
+
left_attributes.include?( [name, type] ),
|
133
|
+
right_attributes.include?( [name, type] )
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def key
|
139
|
+
[@name, @type]
|
140
|
+
end
|
141
|
+
|
142
|
+
def left_match?
|
143
|
+
@left_match
|
144
|
+
end
|
145
|
+
|
146
|
+
def right_match?
|
147
|
+
@right_match
|
148
|
+
end
|
149
|
+
|
150
|
+
def display
|
151
|
+
"#{@name} (#{@type})"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# This class has dubious semantics and we only have it so that
|
2
|
+
# people can write params[:key] instead of params['key']
|
3
|
+
# and they get the same value for both keys.
|
4
|
+
|
5
|
+
class HashWithIndifferentAccess < Hash
|
6
|
+
def initialize(constructor = {})
|
7
|
+
if constructor.is_a?(Hash)
|
8
|
+
super()
|
9
|
+
update(constructor)
|
10
|
+
else
|
11
|
+
super(constructor)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def default(key = nil)
|
16
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
17
|
+
self[key]
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
24
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
25
|
+
|
26
|
+
# Assigns a new value to the hash:
|
27
|
+
#
|
28
|
+
# hash = HashWithIndifferentAccess.new
|
29
|
+
# hash[:key] = "value"
|
30
|
+
#
|
31
|
+
def []=(key, value)
|
32
|
+
regular_writer(convert_key(key), convert_value(value))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Updates the instantized hash with values from the second:
|
36
|
+
#
|
37
|
+
# hash_1 = HashWithIndifferentAccess.new
|
38
|
+
# hash_1[:key] = "value"
|
39
|
+
#
|
40
|
+
# hash_2 = HashWithIndifferentAccess.new
|
41
|
+
# hash_2[:key] = "New Value!"
|
42
|
+
#
|
43
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
44
|
+
#
|
45
|
+
def update(other_hash)
|
46
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :merge!, :update
|
51
|
+
|
52
|
+
# Checks the hash for a key matching the argument passed in:
|
53
|
+
#
|
54
|
+
# hash = HashWithIndifferentAccess.new
|
55
|
+
# hash["key"] = "value"
|
56
|
+
# hash.key? :key # => true
|
57
|
+
# hash.key? "key" # => true
|
58
|
+
#
|
59
|
+
def key?(key)
|
60
|
+
super(convert_key(key))
|
61
|
+
end
|
62
|
+
|
63
|
+
alias_method :include?, :key?
|
64
|
+
alias_method :has_key?, :key?
|
65
|
+
alias_method :member?, :key?
|
66
|
+
|
67
|
+
# Fetches the value for the specified key, same as doing hash[key]
|
68
|
+
def fetch(key, *extras)
|
69
|
+
super(convert_key(key), *extras)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns an array of the values at the specified indices:
|
73
|
+
#
|
74
|
+
# hash = HashWithIndifferentAccess.new
|
75
|
+
# hash[:a] = "x"
|
76
|
+
# hash[:b] = "y"
|
77
|
+
# hash.values_at("a", "b") # => ["x", "y"]
|
78
|
+
#
|
79
|
+
def values_at(*indices)
|
80
|
+
indices.collect {|key| self[convert_key(key)]}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns an exact copy of the hash.
|
84
|
+
def dup
|
85
|
+
HashWithIndifferentAccess.new(self)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
|
89
|
+
# Does not overwrite the existing hash.
|
90
|
+
def merge(hash)
|
91
|
+
self.dup.update(hash)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
|
95
|
+
# This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
|
96
|
+
def reverse_merge(other_hash)
|
97
|
+
super other_hash.with_indifferent_access
|
98
|
+
end
|
99
|
+
|
100
|
+
def invert
|
101
|
+
super.with_indifferent_access
|
102
|
+
end
|
103
|
+
|
104
|
+
# Removes a specified key from the hash.
|
105
|
+
def delete(key)
|
106
|
+
super(convert_key(key))
|
107
|
+
end
|
108
|
+
|
109
|
+
def stringify_keys!; self end
|
110
|
+
def symbolize_keys!; self end
|
111
|
+
def to_options!; self end
|
112
|
+
|
113
|
+
# Convert to a Hash with String keys.
|
114
|
+
def to_hash
|
115
|
+
Hash.new(default).merge(self)
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
def convert_key(key)
|
120
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
121
|
+
end
|
122
|
+
|
123
|
+
def convert_value(value)
|
124
|
+
case value
|
125
|
+
when Hash
|
126
|
+
value.with_indifferent_access
|
127
|
+
when Array
|
128
|
+
value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
|
129
|
+
else
|
130
|
+
value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
module CaDOVe #:nodoc:
|
136
|
+
module CoreExtensions #:nodoc:
|
137
|
+
module Hash #:nodoc:
|
138
|
+
module IndifferentAccess #:nodoc:
|
139
|
+
def with_indifferent_access
|
140
|
+
hash = HashWithIndifferentAccess.new(self)
|
141
|
+
hash.default = self.default
|
142
|
+
hash
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
Hash.send(:include, CaDOVe::CoreExtensions::Hash::IndifferentAccess)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'rjb'
|
2
|
+
require File.dirname(__FILE__) + '/../helpers'
|
3
|
+
|
4
|
+
module CaDOVe
|
5
|
+
module Java
|
6
|
+
Rjb::load(File.dirname(__FILE__) + '/javalib/qdox-1.10.jar')
|
7
|
+
|
8
|
+
class JavaSourceTree
|
9
|
+
def initialize(directory)
|
10
|
+
file = Rjb::import('java.io.File').
|
11
|
+
new_with_sig('Ljava.lang.String;', directory)
|
12
|
+
|
13
|
+
builder = Rjb::import('com.thoughtworks.qdox.JavaDocBuilder').new
|
14
|
+
builder._invoke('addSourceTree', 'Ljava.io.File;', file)
|
15
|
+
|
16
|
+
@sources = builder.getSources
|
17
|
+
end
|
18
|
+
|
19
|
+
def classes
|
20
|
+
JavaClass.all(@sources)
|
21
|
+
end
|
22
|
+
|
23
|
+
def hashify
|
24
|
+
all = {}
|
25
|
+
classes.each do |clazz|
|
26
|
+
all.merge!(clazz.hashify)
|
27
|
+
end
|
28
|
+
all
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class JavaClass
|
33
|
+
def initialize(clazz)
|
34
|
+
@clazz = clazz
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.all(sources, including_abstract=false)
|
38
|
+
classes = sources.collect { |s| s.getClasses }.flatten
|
39
|
+
results = classes.collect { |c| new(c) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def name
|
43
|
+
@name ||= @clazz.getName
|
44
|
+
end
|
45
|
+
|
46
|
+
def hashify
|
47
|
+
field_and_type = fields.collect{|f| [f.name, f.type] }
|
48
|
+
{"#{name}" => field_and_type}
|
49
|
+
end
|
50
|
+
|
51
|
+
def fields
|
52
|
+
JavaField.all_non_associated(@clazz)
|
53
|
+
end
|
54
|
+
alias_method :attributes, :fields
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
class JavaField
|
59
|
+
include CaDOVe::Helpers
|
60
|
+
def initialize(field)
|
61
|
+
@field = field
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.all(clazz)
|
65
|
+
clazz.getFields.collect do |field|
|
66
|
+
new(field)
|
67
|
+
end.reject{|f| is_constant?(f.name) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.all_non_associated(clazz)
|
71
|
+
all(clazz).select{|f| is_simple_type(f.type) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.is_simple_type(class_name)
|
75
|
+
java_lang_and_other = %w(Boolean Byte Character Double Float Integer Long Math Number Short String Date Timestamp)
|
76
|
+
primitives = %w(boolean byte char double float integer long short)
|
77
|
+
result |= java_lang_and_other.include?(class_name)
|
78
|
+
result |= primitives.include?(class_name)
|
79
|
+
result
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.is_constant?(name)
|
83
|
+
name =~ /^([A-Z]|_)+$/
|
84
|
+
end
|
85
|
+
|
86
|
+
def name
|
87
|
+
@name ||= @field.getName
|
88
|
+
end
|
89
|
+
|
90
|
+
def type
|
91
|
+
@type ||= remove_package_name(@field.getType.getValue)
|
92
|
+
end
|
93
|
+
|
94
|
+
def ==(other)
|
95
|
+
return true if self.equal?(other)
|
96
|
+
return false if self.class != Java::JavaMethod
|
97
|
+
|
98
|
+
if (self.name != other.name)
|
99
|
+
return false
|
100
|
+
elsif (self.type != other.type)
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
|
104
|
+
true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
Binary file
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# TODO: Move these into a plain text file
|
2
|
+
module CaDOVe
|
3
|
+
module Reports
|
4
|
+
class ClassReport
|
5
|
+
def initialize(comparison, left_type, right_type)
|
6
|
+
@comparison = comparison
|
7
|
+
@left_type = left_type
|
8
|
+
@right_type = right_type
|
9
|
+
end
|
10
|
+
|
11
|
+
def display
|
12
|
+
puts Time.new.strftime('%b %d, %Y %H:%M:%S %Z')
|
13
|
+
puts "\n"
|
14
|
+
puts " #{header(@left_type)} | #{header(@right_type)} | Class Name"
|
15
|
+
puts "--------------------------------"
|
16
|
+
@comparison.classes.each do |clazz|
|
17
|
+
puts " #{clazz.left_match? ? 'X' : ' '} | #{clazz.right_match? ? 'X' : ' '} | #{clazz.key.display}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def header(name)
|
22
|
+
name.to_s.upcase
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class AttributeReport
|
27
|
+
def initialize(comparison, left_type, right_type)
|
28
|
+
@comparison = comparison
|
29
|
+
@left_type = left_type
|
30
|
+
@right_type = right_type
|
31
|
+
end
|
32
|
+
|
33
|
+
def display
|
34
|
+
puts Time.new.strftime('%b %d, %Y %H:%M:%S %Z')
|
35
|
+
puts "\n"
|
36
|
+
|
37
|
+
puts "########################"
|
38
|
+
@comparison.intersecting_classes.each do |clazz|
|
39
|
+
unless clazz.attributes.empty?
|
40
|
+
puts ""
|
41
|
+
puts "#{clazz.key.display}"
|
42
|
+
puts ""
|
43
|
+
puts " #{header(@left_type)} | #{header(@right_type)} | Attributes"
|
44
|
+
puts "--------------------------------------"
|
45
|
+
|
46
|
+
clazz.attributes.each do |a|
|
47
|
+
puts " #{a.left_match? ? 'X' : ' '} | #{a.right_match? ? 'X' : ' '} | #{a.display}"
|
48
|
+
end
|
49
|
+
puts ""
|
50
|
+
puts "########################"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def header(name)
|
56
|
+
name.to_s.upcase
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/spec/cadove_spec.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe CaDOVe do
|
4
|
+
it "should configure the left and right properties" do
|
5
|
+
CaDOVe.configure do
|
6
|
+
left 'hello'
|
7
|
+
right 'world'
|
8
|
+
end
|
9
|
+
|
10
|
+
CaDOVe.left.should == 'hello'
|
11
|
+
CaDOVe.right.should == 'world'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should throws an exception if a required configuration option is accessed without being set" do
|
15
|
+
CaDOVe.left = nil
|
16
|
+
lambda { CaDOVe.left }.should raise_error(CaDOVe::ConfigurationError)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
package org.foo;
|
2
|
+
|
3
|
+
class Dog {
|
4
|
+
String color;
|
5
|
+
boolean rabid;
|
6
|
+
Father father;
|
7
|
+
List<Enemies> enemies;
|
8
|
+
|
9
|
+
public String getColor(){}
|
10
|
+
public boolean isRabid(){}
|
11
|
+
|
12
|
+
public Father getFather() {}
|
13
|
+
public List<Enemies> getEnemies() {}
|
14
|
+
|
15
|
+
public String setIgnoreSetters(){}
|
16
|
+
private String getIgnorePrivateMethods(){}
|
17
|
+
protected String getIgnoreProtectedMethods(){}
|
18
|
+
}
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
module CaDOVe::XMI
|
4
|
+
describe UMLClass do
|
5
|
+
before(:each) do
|
6
|
+
@xmi =
|
7
|
+
xmi_document do
|
8
|
+
uml_package('Data Model') do
|
9
|
+
uml_class('DontFindMe')
|
10
|
+
end +
|
11
|
+
uml_package('Logical Model') do
|
12
|
+
uml_class('Dog') do
|
13
|
+
uml_attribute('color', 'String') +
|
14
|
+
uml_attribute('rabid', 'boolean')
|
15
|
+
end +
|
16
|
+
uml_class('Cat')
|
17
|
+
end +
|
18
|
+
|
19
|
+
uml_association('Dog', 'Friend') +
|
20
|
+
uml_association('Dog', 'Enemy')
|
21
|
+
end
|
22
|
+
|
23
|
+
@doc = XMIDocument.new(@xmi, :input_type => :text)
|
24
|
+
@dog = @doc.classes.select{ |c| c.name == 'Dog' }.first
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have two classes in the document" do
|
28
|
+
classes = @doc.classes
|
29
|
+
@doc.should have(2).classes
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should include the class Cat and Dog" do
|
33
|
+
names = @doc.classes.collect { |c| c.name }
|
34
|
+
names.should include("Cat")
|
35
|
+
names.should include("Dog")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should have the correct attributes for Dog" do
|
39
|
+
@dog.should have(2).attributes
|
40
|
+
names = @dog.attributes.collect { |c| [c.name, c.type] }
|
41
|
+
names.should include( %w(color String) )
|
42
|
+
names.should include( %w(rabid boolean) )
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should have the correct correct associations for Dog" do
|
46
|
+
@dog.should have(2).associations
|
47
|
+
names = @dog.associations.collect { |c| c.name }
|
48
|
+
names.should include('Friend')
|
49
|
+
names.should include('Enemy')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return the only model available if only one exists" do
|
53
|
+
single_pkg_xmi =
|
54
|
+
xmi_document do
|
55
|
+
uml_package('My Package') do
|
56
|
+
uml_class('Foo')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
single_pkg_doc = XMIDocument.new(single_pkg_xmi, :input_type => :text)
|
61
|
+
single_pkg_doc.should have(1).classes
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should hashify dog" do
|
65
|
+
doggy_hash = @dog.hashify
|
66
|
+
doggy_hash['Dog'].should include( %w(color String) )
|
67
|
+
doggy_hash['Dog'].should include( %w(rabid boolean) )
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should hashify the document tree" do
|
71
|
+
@doc.hashify.keys.should include('Cat')
|
72
|
+
@doc.hashify.keys.should include('Dog')
|
73
|
+
|
74
|
+
@doc.hashify['Dog'].should include( %w(color String) )
|
75
|
+
@doc.hashify['Dog'].should include( %w(rabid boolean) )
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def xmi_document(&block)
|
80
|
+
<<-XMI
|
81
|
+
<xmi:XMI xmlns:UML="http://schema.omg.org/spec/UML/2.0" xmlns:xmi="http://schema.omg.org/spec/XMI/2.1">
|
82
|
+
#{yield if block_given?}
|
83
|
+
</xmi:XMI>
|
84
|
+
XMI
|
85
|
+
end
|
86
|
+
|
87
|
+
def uml_package(name, &block)
|
88
|
+
<<-XMI
|
89
|
+
<UML:Package name="#{name}">
|
90
|
+
#{yield if block_given?}
|
91
|
+
</UML:Package>
|
92
|
+
XMI
|
93
|
+
end
|
94
|
+
|
95
|
+
def uml_class(name, &block)
|
96
|
+
<<-XMI
|
97
|
+
<UML:Class name='#{name}'>
|
98
|
+
#{yield if block_given?}
|
99
|
+
</UML:Class>
|
100
|
+
XMI
|
101
|
+
end
|
102
|
+
|
103
|
+
def uml_attribute(name, type)
|
104
|
+
<<-XMI
|
105
|
+
<UML:Attribute name='#{name}' visibility='private'>
|
106
|
+
<UML:ModelElement.taggedValue>
|
107
|
+
<UML:TaggedValue tag='foo' value='bar'/>
|
108
|
+
<UML:TaggedValue tag='type' value='#{type}'/>
|
109
|
+
<UML:TaggedValue tag='derived' value='0'/>
|
110
|
+
</UML:ModelElement.taggedValue>
|
111
|
+
</UML:Attribute>
|
112
|
+
XMI
|
113
|
+
end
|
114
|
+
|
115
|
+
def uml_association(src, dst)
|
116
|
+
<<-XMI
|
117
|
+
<UML:Association visibility='public'>
|
118
|
+
<UML:ModelElement.taggedValue>
|
119
|
+
<UML:TaggedValue tag='style' value='3'/>
|
120
|
+
<UML:TaggedValue tag='ea_sourceName' value='#{src}'/>
|
121
|
+
<UML:TaggedValue tag='ea_targetName' value='#{dst}'/>
|
122
|
+
</UML:ModelElement.taggedValue>
|
123
|
+
</UML:Association>
|
124
|
+
XMI
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
module CaDOVe::Analytics
|
4
|
+
describe Comparison do
|
5
|
+
before(:each) do
|
6
|
+
@left = {
|
7
|
+
'Dog' => [
|
8
|
+
%w(name String),
|
9
|
+
%w(rabid boolean)
|
10
|
+
]
|
11
|
+
}
|
12
|
+
|
13
|
+
@right = {
|
14
|
+
'Dog' => [
|
15
|
+
%w(name String),
|
16
|
+
%w(color String)
|
17
|
+
],
|
18
|
+
'Rabbit' => [ %w(color String) ]
|
19
|
+
}
|
20
|
+
|
21
|
+
@comparison = Comparison.new(@left, @right)
|
22
|
+
@dog = @comparison.classes.select{|c| c.key.left == 'Dog' }.first
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should have two class" do
|
26
|
+
@comparison.should have(2).classes
|
27
|
+
@comparison.classes.collect{|c| [c.key.left, c.key.right]}.should == [
|
28
|
+
['Dog', 'Dog'],
|
29
|
+
[nil, 'Rabbit']
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should have a left and right match" do
|
34
|
+
@dog.should be_left_match
|
35
|
+
@dog.should be_right_match
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
it "should have one intersecting class" do
|
40
|
+
@comparison.should have(1).intersecting_classes
|
41
|
+
@comparison.intersecting_classes.collect{|c| [c.key.left, c.key.right]}.should == [['Dog', 'Dog']]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should have a dog match with 3 attributes" do
|
45
|
+
keys = @dog.attributes.collect{|a| a.key}
|
46
|
+
keys.should include( %w(name String) )
|
47
|
+
keys.should include( %w(rabid boolean))
|
48
|
+
keys.should include( %w(color String) )
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should have a dog match having an 'name' attribute having a left and right match" do
|
52
|
+
name = @dog.attributes.select{|a| a.key[0] == 'name'}.first
|
53
|
+
name.should be_left_match
|
54
|
+
name.should be_right_match
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should have a dog match having an 'rabid' attribute having only a left match" do
|
58
|
+
name = @dog.attributes.select{|a| a.key[0] == 'rabid'}.first
|
59
|
+
name.should be_left_match
|
60
|
+
name.should_not be_right_match
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should ignore the Rabbit class" do
|
64
|
+
ignore_on = Comparison.new(@left, @right, :ignore_classes => %w(Rabbit) )
|
65
|
+
ignore_on.should have(1).classes
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should manually match 'Rabbit' to 'Hare'" do
|
69
|
+
@left['Hare'] = [
|
70
|
+
%w(color String)
|
71
|
+
]
|
72
|
+
|
73
|
+
manual_matching = Comparison.new(@left, @right, :matching_classes => {:Hare => :Rabbit})
|
74
|
+
manual_matching.should have(2).intersecting_classes
|
75
|
+
names = manual_matching.intersecting_classes.collect{|c| [c.key.left, c.key.right]}
|
76
|
+
names.should include(['Hare', 'Rabbit'])
|
77
|
+
names.should include(['Dog', 'Dog'])
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should manually match 'Rabbit' to 'Hare' with inverse matching class" do
|
81
|
+
@left['Hare'] = [
|
82
|
+
%w(color String)
|
83
|
+
]
|
84
|
+
|
85
|
+
manual_matching = Comparison.new(@left, @right, :matching_classes => {:Rabbit => :Hare})
|
86
|
+
manual_matching.should have(2).intersecting_classes
|
87
|
+
names = manual_matching.intersecting_classes.collect{|c| [c.key.left, c.key.right]}
|
88
|
+
names.should include(['Hare', 'Rabbit'])
|
89
|
+
names.should include(['Dog', 'Dog'])
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should align keys" do
|
93
|
+
aligned = ClassMatchKey.align(['a', 'b'], ['b', 'c'], :a => :c).collect{|k| [k.left, k.right]}
|
94
|
+
aligned.size.should be(2)
|
95
|
+
aligned.should include(['b', 'b'])
|
96
|
+
aligned.should include(['a', 'c'])
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should sort keys" do
|
100
|
+
keys = [ClassMatchKey.new('b', 'b'), ClassMatchKey.new('a', 'a')].sort
|
101
|
+
keys[0].left.should == 'a'
|
102
|
+
keys[1].left.should == 'b'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
module CaDOVe::Java
|
4
|
+
describe JavaSourceTree do
|
5
|
+
before(:each) do
|
6
|
+
@code = JavaSourceTree.new(
|
7
|
+
File.dirname(__FILE__) + '/../../java_stubs'
|
8
|
+
)
|
9
|
+
|
10
|
+
@dog = @code.classes.select { |c| c.name == "Dog" }.first
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have three classes in the document" do
|
14
|
+
@code.should have(3).classes
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should include only classes Animal, Cat and Dog" do
|
18
|
+
names = @code.classes.collect { |c| c.name }
|
19
|
+
names.should include("Animal")
|
20
|
+
names.should include("Cat")
|
21
|
+
names.should include("Dog")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should have the two fields" do
|
25
|
+
@dog.should have(2).fields
|
26
|
+
fields = @dog.fields.collect{ |f| [f.name, f.type] }
|
27
|
+
fields.should include( %w(color String) )
|
28
|
+
fields.should include( %w(rabid boolean) )
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it "should hashify Dog" do
|
33
|
+
doggy_hash = @dog.hashify
|
34
|
+
doggy_hash['Dog'].should include( %w(color String) )
|
35
|
+
doggy_hash['Dog'].should include( %w(rabid boolean) )
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should hashify the source tree" do
|
39
|
+
@code.hashify.keys.should include('Cat')
|
40
|
+
@code.hashify.keys.should include('Dog')
|
41
|
+
|
42
|
+
@code.hashify['Dog'].should include( %w(color String) )
|
43
|
+
@code.hashify['Dog'].should include( %w(rabid boolean) )
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should have only associations"
|
47
|
+
end
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../lib/cadove'
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cadove
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Dzak
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-11 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rjb
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email:
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/cadove/models/analytics/comparison.rb
|
35
|
+
- lib/cadove/models/helpers.rb
|
36
|
+
- lib/cadove/models/indifferent_access.rb
|
37
|
+
- lib/cadove/models/java/code.rb
|
38
|
+
- lib/cadove/models/java/javalib/qdox-1.10.jar
|
39
|
+
- lib/cadove/models/reports/reports.rb
|
40
|
+
- lib/cadove/models/XMI/document.rb
|
41
|
+
- lib/cadove/models.rb
|
42
|
+
- lib/cadove.rb
|
43
|
+
- CHANGELOG
|
44
|
+
- README
|
45
|
+
- spec/cadove_spec.rb
|
46
|
+
- spec/java_stubs/org/foo/Animal.java
|
47
|
+
- spec/java_stubs/org/foo/Cat.java
|
48
|
+
- spec/java_stubs/org/foo/Dog.java
|
49
|
+
- spec/models/analytics/comparison_spec.rb
|
50
|
+
- spec/models/java/code_spec.rb
|
51
|
+
- spec/models/XMI/document_spec.rb
|
52
|
+
- spec/spec_helper.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage:
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.3.5
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Domain Object Verifier compares Java domain objects to their corresponding XMI representations.
|
81
|
+
test_files: []
|
82
|
+
|