jaxb2ruby 0.0.1-java

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