jaxb2ruby 0.0.1-java

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dc40239981acd3661e8242ccb2902b59ab764bf7
4
+ data.tar.gz: af343089fe56230f70396608b57bb5c7165b11a1
5
+ SHA512:
6
+ metadata.gz: 8bf71fe0df5b94ea3efe7ecd84dc076ada465b339dc20b1c3911ff6144e824329c621df25af4bfb54ca720f7d048aeb636e12eed8356bb52253b9c1bfe7d4efd
7
+ data.tar.gz: b8b1c81d1c8896ff50a33f85e9df9213f265bbdff09e837bd556b1c8bed16ddf9b1e03190bc283c5ea0af9b4c3134faa562232827a186821b904d50afcd2c31a
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ abort "jaxb2ruby must be run with jruby" unless RUBY_PLATFORM == "java"
4
+
5
+ require "optparse"
6
+ require "fileutils"
7
+ require "yaml"
8
+ require "jaxb2ruby"
9
+
10
+ include JAXB2Ruby
11
+
12
+ def mapping_option(option)
13
+ option.inject({}) do |cfg, opt|
14
+ if !opt.include?("=")
15
+ begin
16
+ cfg.merge!(YAML.load_file(opt))
17
+ rescue => e
18
+ abort "cannot load mapping file: #{e}"
19
+ end
20
+ else
21
+ url, klass = opt.split("=", 2)
22
+ abort "mapping option invalid: #{opt}" if klass.nil? or klass.strip.empty?
23
+ cfg[url] = klass
24
+ end
25
+ cfg
26
+ end
27
+ end
28
+
29
+ tmpl = nil
30
+ outdir = "ruby"
31
+ options = {}
32
+ parser = OptionParser.new do |opts|
33
+ opts.banner = "usage: #{File.basename($0)} [options] schema"
34
+
35
+ opts.on("-c", "--classes=MAP1[,MAP2,...]", Array, "XML Schema type to Ruby class mappings", "MAP can be a string in the form type=class or a YAML file of type/class pairs") do |typemap|
36
+ options[:typemap] = mapping_option(typemap)
37
+ end
38
+
39
+ opts.on("-h", "--help", "Show this message") do
40
+ puts opts
41
+ exit
42
+ end
43
+
44
+ opts.on("-I", "--include=DIRECTORY", "Add DIRECTORY to the load path, usefull for using custom template helpers") do |path|
45
+ $LOAD_PATH.unshift(path)
46
+ end
47
+
48
+ opts.on("-J", "--jvm=[ARG1[,ARG2,...]]", Array, "Options to pass to the JVM when calling XJC") do |opt|
49
+ (options[:jvm] ||= []).concat(opt)
50
+ end
51
+
52
+ opts.on("-n", "--namespace=MAP1[,MAP2,...]", Array, "XML namespace to ruby class mappings", "MAP can be a string in the form namespace=class or a YAML file of namespace/class pairs") do |ns|
53
+ options[:namespace] = mapping_option(ns)
54
+ end
55
+
56
+ opts.on("-o", "--output=DIRECTORY", "Directory to output the generated ruby classes, defaults to ruby") do |dir|
57
+ outdir = dir
58
+ end
59
+
60
+ opts.on("-t", "--template=NAME", "Template used to generate the ruby classes", "Can be a path to an ERB template or one of: roxml (default), happymapper, ruby") do |t|
61
+ tmpl = t
62
+ end
63
+
64
+ opts.on("-v", "--version", "jaxb2ruby version") do
65
+ puts "v#{JAXB2Ruby::VERSION}"
66
+ exit
67
+ end
68
+
69
+ opts.on("-w", "--wsdl", "Treat the schema as a WSDL", "Automatically set if the schema has a `.wsdl' extension") do
70
+ options[:wsdl] = true
71
+ end
72
+ end
73
+
74
+ parser.parse!
75
+ schema = ARGV.shift
76
+ abort parser.banner if schema.nil?
77
+
78
+ begin
79
+ template = Template.new(tmpl)
80
+ ruby_classes = Converter.convert(schema, options)
81
+ puts "outputting classes to #{outdir}"
82
+ ruby_classes.each do |klass|
83
+ puts "generating: #{klass.path}"
84
+ FileUtils.mkdir_p(File.join(outdir, klass.directory))
85
+ File.open(File.join(outdir, klass.path), "w") { |io| io.puts template.build(klass) }
86
+ end
87
+ rescue => e
88
+ puts e.backtrace.join("\n")
89
+ abort "class generation failed: #{e}"
90
+ end
@@ -0,0 +1,15 @@
1
+ require "active_support/core_ext/object/blank"
2
+ require "active_support/core_ext/string"
3
+
4
+ require "jaxb2ruby/version"
5
+ require "jaxb2ruby/classes"
6
+ require "jaxb2ruby/converter"
7
+ require "jaxb2ruby/template"
8
+
9
+ module JAXB2Ruby
10
+ RUBY_PKG_SEP = "::"
11
+ JAVA_PKG_SEP = "."
12
+ JAVA_CLASS_SEP = "$"
13
+
14
+ class Error < StandardError; end
15
+ end
@@ -0,0 +1,184 @@
1
+ require "forwardable"
2
+
3
+ module JAXB2Ruby
4
+ class ClassName < String # :nodoc:
5
+ attr :module
6
+ attr :outter_class # if java inner class, if any
7
+ attr :name # module + outter_class + basename
8
+ attr :basename
9
+
10
+ # Turn a java class name into a ruby class name, with accessors for various parts
11
+ def initialize(java_name, rubymod = nil)
12
+ pkg = java_name.split(JAVA_PKG_SEP)
13
+ pkg = rubymod ? [rubymod, ns2mod(pkg[-1])] : pkg.map { |part| ns2mod(part) }
14
+
15
+ parts = pkg.pop.split(JAVA_CLASS_SEP)
16
+ @basename = parts.pop
17
+ @outter_class = parts.join(RUBY_PKG_SEP)
18
+ @module = rubymod || pkg.join(RUBY_PKG_SEP)
19
+ @name = [@module, @outter_class, @basename].reject(&:empty?).join(RUBY_PKG_SEP)
20
+
21
+ super @name
22
+ end
23
+
24
+ private
25
+ def ns2mod(pkg)
26
+ pkg.sub(/\A_/, "V").camelize
27
+ end
28
+ end
29
+
30
+ class Namespace < String # :nodoc:
31
+ counter = 0
32
+ @@prefixes = Hash.new { |h,ns| h[ns] = "ns#{counter+=1}".freeze }
33
+
34
+ attr :name
35
+ attr :prefix
36
+
37
+ def initialize(name)
38
+ @name = name
39
+ @prefix = @@prefixes[name]
40
+ super
41
+ end
42
+ end
43
+
44
+ class Node
45
+ attr :type
46
+ attr :name
47
+ attr :local_name
48
+ attr :namespace
49
+ attr :accessor
50
+ attr :default
51
+
52
+ def initialize(name, options = {})
53
+ @name = @local_name = name
54
+
55
+ @accessor = (options[:accessor] || name).underscore
56
+ # If this conflicts with a Java keyword it will start with an underscore
57
+ @accessor.sub!(/\A_/, "")
58
+
59
+ @namespace = options[:namespace]
60
+ @name = sprintf "%s:%s", @namespace.prefix, @local_name if @namespace
61
+
62
+ @default = options[:default]
63
+ @required = !!options[:required]
64
+ @type = options[:type]
65
+
66
+ # TODO: this isn't used. Future plans?
67
+ @array = !!options[:array]
68
+ @hash = false
69
+
70
+ # Uhhhh, I think this might need some revisiting, esp. with xsd:enumeration
71
+ if @type.is_a?(Array)
72
+ @accessor = @accessor.pluralize
73
+
74
+ if @type.one?
75
+ @array = true
76
+ @type = @type.shift
77
+ else
78
+ @hash = true
79
+ end
80
+ end
81
+ end
82
+
83
+ def hash?
84
+ @hash
85
+ end
86
+
87
+ def array?
88
+ @array
89
+ end
90
+
91
+ def required?
92
+ @required
93
+ end
94
+ end
95
+
96
+ class Attribute < Node; end
97
+
98
+ class Element < Node
99
+ attr :children
100
+ attr :attributes
101
+
102
+ def initialize(name, options = {})
103
+ super
104
+ @text = !!options[:text]
105
+ @root = !!options[:root]
106
+ @nillable = !!options[:nillable]
107
+ @children = options[:children] || []
108
+ @attributes = options[:attributes] || []
109
+ end
110
+
111
+ def nillable?
112
+ @nillable
113
+ end
114
+
115
+ def root?
116
+ @root
117
+ end
118
+
119
+ def text?
120
+ @text
121
+ end
122
+ end
123
+
124
+ class RubyClass
125
+ extend Forwardable
126
+
127
+ attr :module
128
+ attr :outter_class
129
+ attr :superclass
130
+ attr :element
131
+
132
+ def_delegators :@type, :name, :basename, :to_s
133
+
134
+ def initialize(type, element, dependencies = nil, superclass = nil)
135
+ @type = type
136
+ @element = element
137
+ @dependencies = dependencies || []
138
+ @superclass = superclass
139
+
140
+ @module = @type.module.dup unless @type.module.empty?
141
+ @outter_class = @type.outter_class.dup unless @type.outter_class.empty?
142
+
143
+ [@module, @outter_class].each do |v|
144
+ v.extend Enumerable
145
+
146
+ # v may be NilClass
147
+ def v.each(&block)
148
+ ( nil? ? [] : split(RUBY_PKG_SEP) ).each(&block)
149
+ end
150
+ end
151
+ end
152
+
153
+ def filename
154
+ "#{basename.underscore}.rb"
155
+ end
156
+
157
+ def directory
158
+ File.dirname(path)
159
+ end
160
+
161
+ # This class's path, for passing to +require+.
162
+ # <code>Foo::Bar::OneTwo</code> will be turned into <code>foo/bar/one_two</code>.
163
+ #
164
+ def path
165
+ @path ||= make_path(@module.to_a.concat(outter_class.to_a).push(filename))
166
+ end
167
+
168
+ # Paths for all of this class's dependencies, for passing to +require+.
169
+ #
170
+ def requires
171
+ # p @element
172
+ # p @module
173
+ # @dependencies.each { |e| p e }
174
+ # p "-" * 20
175
+
176
+ @requires ||= @dependencies.map { |e| make_path(e.split(RUBY_PKG_SEP)) }.sort.uniq
177
+ end
178
+
179
+ private
180
+ def make_path(modules)
181
+ modules.map { |name| name.underscore }.join("/")
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0"?>
2
+ <jxb:bindings version="1.0"
3
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
4
+ xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
5
+ xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
6
+ jxb:extensionBindingPrefixes="xjc">
7
+ <jxb:globalBindings>
8
+ <!--
9
+ * Creates a type instead of a JAXBElement for xsd:choice elements
10
+ * Pluralizes names (we do this anyways in the Ruby code)
11
+ -->
12
+ <xjc:simple />
13
+ <!-- No inherritance when creating elements with a restriction base type -->
14
+ <!-- <xjc:treatRestrictionLikeNewType /> -->
15
+ </jxb:globalBindings>
16
+ </jxb:bindings>
@@ -0,0 +1,250 @@
1
+ require "find"
2
+ require "fileutils"
3
+ require "java"
4
+
5
+ require "jaxb2ruby/xjc"
6
+ require "jaxb2ruby/type_util"
7
+
8
+ module JAXB2Ruby
9
+ class Converter # :nodoc:
10
+ XML_NULL = "\u0000"
11
+ XML_ANNOT_DEFAULT = "##default"
12
+
13
+ def self.convert(schema, options = {})
14
+ new(schema, options).convert
15
+ end
16
+
17
+ def initialize(schema, options = {})
18
+ raise ArgumentError, "cannot access schema: #{schema}" unless File.file?(schema) and File.readable?(schema)
19
+ @xjc = XJC.new(schema, :wsdl => !!options[:wsdl], :jvm => options[:jvm])
20
+
21
+ @namespace = options[:namespace] || {}
22
+ raise ArgumentError, "namespace mapping must be a Hash" unless @namespace.is_a?(Hash)
23
+
24
+ @typemap = TypeUtil.new(options[:typemap])
25
+ end
26
+
27
+ def convert
28
+ create_java_classes
29
+ create_ruby_classes
30
+ end
31
+
32
+ private
33
+ def create_java_classes
34
+ @classes = @xjc.execute
35
+ end
36
+
37
+ def create_ruby_classes
38
+ java_classes = find_java_classes(@classes)
39
+ raise Error, "no classes were generated from the schema" if java_classes.empty?
40
+
41
+ $CLASSPATH << @classes unless $CLASSPATH.include?(@classes)
42
+ extract_classes(java_classes)
43
+ rescue IOError, SystemCallError => e
44
+ raise Error, "failed to generate ruby class: #{e}"
45
+ end
46
+
47
+ def find_java_classes(root)
48
+ # Without this, parent dir removal below could leave "/" at the start of a non-root path
49
+ # why do we need exapand though..?
50
+ root = File.expand_path(root) << "/" unless root.end_with?("/")
51
+ classes = []
52
+
53
+ Find.find(root) do |path|
54
+ if File.file?(path) && File.extname(path) == ".class"
55
+ path[root] = "" # Want com/example/Class not root/com/example/Class
56
+ classes << path
57
+ end
58
+ end
59
+
60
+ classes.map { |path| java_name_from_path(path) }
61
+ end
62
+
63
+ def java_name_from_path(path)
64
+ klass = path.split(%r{/}).join(".")
65
+ klass[%r{\.class\Z}] = ""
66
+ klass
67
+ end
68
+
69
+ def extract_namespace(annot)
70
+ ns = annot.namespace
71
+ Namespace.new(ns) unless ns.blank? or ns == XML_ANNOT_DEFAULT
72
+ end
73
+
74
+ def find_namespace(klass)
75
+ annot = klass.annotation(javax.xml.bind.annotation.XmlRootElement.java_class) || klass.annotation(javax.xml.bind.annotation.XmlType.java_class)
76
+ return unless annot
77
+
78
+ # if klass is an inner class the namespace will be on the outter class (enclosing_class).
79
+ annot.namespace == XML_ANNOT_DEFAULT && klass.enclosing_class ?
80
+ find_namespace(klass.enclosing_class) :
81
+ annot.namespace
82
+ end
83
+
84
+ def translate_type(klass)
85
+ # Won't work for extract_class() as it expects an instance but this should be split anyways
86
+ return "Object" if klass.java_kind_of?(java.lang.reflect.WildcardType)
87
+
88
+ type = @typemap.java2ruby(klass.name)
89
+ return type if type
90
+ return "String" if klass.enum?
91
+
92
+ # create_class_name(klass)
93
+ modname = @namespace[find_namespace(klass)]
94
+ ClassName.new(klass.name, modname)
95
+ end
96
+
97
+ def resolve_type(field)
98
+ return :ID if field.annotation_present?(javax.xml.bind.annotation.XmlID.java_class)
99
+ return :IDREF if field.annotation_present?(javax.xml.bind.annotation.XmlIDREF.java_class)
100
+
101
+ annot = field.get_annotation(javax.xml.bind.annotation.XmlSchemaType.java_class)
102
+ return @typemap.schema2ruby(annot.name) if annot.respond_to?(:name)
103
+
104
+ # Limited type checking here (but still maybe too much? it's a tad ugly)
105
+ # should be good enough for List<JAXBElement<Object>> and its variants
106
+ if field.type.name == "java.util.List"
107
+ resolved_type = []
108
+ type = field.generic_type
109
+
110
+ if type.java_kind_of?(java.lang.reflect.ParameterizedType)
111
+ type = type.actual_type_arguments.first
112
+
113
+ if type.java_kind_of?(java.lang.reflect.ParameterizedType)
114
+ resolved_type << translate_type(type.actual_type_arguments.first)
115
+ # elsif type.java_kind_of?(java.lang.reflect.WildcardType)
116
+ # type.get_upper_bounds.each do |lower|
117
+ # end
118
+ # type.get_lower_bounds.each do |upper|
119
+ # end
120
+ else
121
+ resolved_type << translate_type(type)
122
+ end
123
+ end
124
+
125
+ return resolved_type
126
+ end
127
+
128
+ translate_type(field.type)
129
+ end
130
+
131
+ # Create a RubyClass for the given Java class.
132
+ def extract_class(klass)
133
+ # Here we expect type to be a ClassName but translate_type can return a String!
134
+ # type = create_class_name(klass)
135
+ type = translate_type(klass)
136
+ element = extract_element(klass)
137
+
138
+ dependencies = []
139
+ #dependencies << type.parent_class if type.parent_class
140
+
141
+ superclass = nil
142
+ if klass.superclass.name != "java.lang.Object"
143
+ # create_class_name(klass.superclass)
144
+ superclass = translate_type(klass.superclass)
145
+ dependencies << superclass
146
+ end
147
+
148
+ (element.children + element.attributes).each do |node|
149
+ # If a node's type isn't predefined, it must be an XML mapped class
150
+ dependencies << node.type if !@typemap.schema_ruby_types.include?(node.type)
151
+ end
152
+ #p "-" * 20
153
+
154
+ RubyClass.new(type, element, dependencies, superclass)
155
+ end
156
+
157
+ # Create elements and attributes from the given Java class' fields
158
+ def extract_elements_nodes(klass)
159
+ nodes = { :attributes => [], :children => [] }
160
+
161
+ klass.declared_fields.each do |field|
162
+ if field.annotation_present?(javax.xml.bind.annotation.XmlValue.java_class) || field.annotation_present?(javax.xml.bind.annotation.XmlMixed.java_class)
163
+ nodes[:text] = true
164
+ next if field.annotation_present?(javax.xml.bind.annotation.XmlValue.java_class)
165
+ end
166
+
167
+ childopts = { :type => resolve_type(field), :accessor => field.name }
168
+ #childopts[:type] = type # unless Array(type).first == "Object"
169
+ childname = childopts[:accessor]
170
+
171
+ if annot = field.get_annotation(javax.xml.bind.annotation.XmlElement.java_class) ||
172
+ field.get_annotation(javax.xml.bind.annotation.XmlElementRef.java_class) ||
173
+ field.get_annotation(javax.xml.bind.annotation.XmlAttribute.java_class)
174
+
175
+ childopts[:namespace] = extract_namespace(annot)
176
+ childopts[:required] = annot.respond_to?(:required?) ? annot.required? : false
177
+ childopts[:nillable] = annot.respond_to?(:nillable?) ? annot.nillable? : false
178
+
179
+ childname = annot.name if annot.name != XML_ANNOT_DEFAULT
180
+
181
+ # Not all implementations support default values for attributes
182
+ if annot.respond_to?(:default_value)
183
+ childopts[:default] = annot.default_value == XML_NULL ? nil : annot.default_value
184
+ end
185
+ end
186
+
187
+ if field.annotation_present?(javax.xml.bind.annotation.XmlAttribute.java_class)
188
+ nodes[:attributes] << Attribute.new(childname, childopts)
189
+ else
190
+ nodes[:children] << Element.new(childname, childopts)
191
+ end
192
+ end
193
+
194
+ nodes
195
+ end
196
+
197
+ # Create an element from a Java class, turning its fields into elements and attributes
198
+ def extract_element(klass)
199
+ #p klass
200
+ options = extract_elements_nodes(klass)
201
+
202
+ if annot = klass.get_annotation(javax.xml.bind.annotation.XmlRootElement.java_class)
203
+ name = annot.name
204
+ options[:root] = true
205
+ end
206
+
207
+
208
+
209
+
210
+ if name.blank?
211
+ annot = klass.get_annotation(javax.xml.bind.annotation.XmlType.java_class)
212
+ name = annot.name
213
+ end
214
+
215
+ name = klass.name if name.blank?
216
+ name = name.split(JAVA_CLASS_SEP).last # might be an inner class
217
+ # Should grab annot.prop_order
218
+ # annot = klass.get_annotation(javax.xml.bind.annotation.XmlType.java_class)
219
+ # annot.prop_order are java props here we have element names
220
+ # element.elements.sort_by! { |e| annot.prop_order.index }
221
+ options[:namespace] = extract_namespace(annot)
222
+
223
+ #p "name2 #{name}"
224
+ #p options
225
+ e=Element.new(name, options)
226
+ #p e.name
227
+ #p e.local_name
228
+ #p e.namespace
229
+ #p "-" * 20
230
+ e
231
+ end
232
+
233
+ def valid_class?(klass)
234
+ # Skip Enum for now, maybe forever!
235
+ # TODO: make sure this is a legit class else we can get a const error.
236
+ # For example, if someone uses a namespace that xjc translates into a /javax?/ package
237
+ !klass.enum? && klass.annotation_present?(javax.xml.bind.annotation.XmlType.java_class)
238
+ end
239
+
240
+ def extract_classes(java_classes)
241
+ ruby_classes = []
242
+ java_classes.each do |name|
243
+ klass = Java.send(name).java_class.to_java
244
+ next unless valid_class?(klass)
245
+ ruby_classes << extract_class(klass)
246
+ end
247
+ ruby_classes
248
+ end
249
+ end
250
+ end