2 # Copyright 2012-2015 Bronto Software, Inc. and contributors
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
20 from docutils import nodes
21 from docutils.parsers.rst import Directive, directives
23 from sphinx import addnodes, version_info
24 from sphinx.roles import XRefRole
25 from sphinx.locale import _
26 from sphinx.domains import Domain, ObjType
27 from sphinx.directives import ObjectDescription
28 from sphinx.util.nodes import make_refnode
29 from sphinx.util.docfields import Field, TypedField, GroupedField
33 import javasphinx.extdoc as extdoc
34 import javasphinx.formatter as formatter
35 import javasphinx.util as util
37 # Classes in java.lang. These are available without an import.
39 'AbstractMethodError', 'Appendable', 'ArithmeticException',
40 'ArrayIndexOutOfBoundsException', 'ArrayStoreException', 'AssertionError',
41 'AutoCloseable', 'Boolean', 'BootstrapMethodError', 'Byte', 'Character',
42 'CharSequence', 'Class', 'ClassCastException', 'ClassCircularityError',
43 'ClassFormatError', 'ClassLoader', 'ClassNotFoundException', 'ClassValue',
44 'Cloneable', 'CloneNotSupportedException', 'Comparable', 'Compiler',
45 'Deprecated', 'Double', 'Enum', 'EnumConstantNotPresentException', 'Error',
46 'Exception', 'ExceptionInInitializerError', 'Float', 'IllegalAccessError',
47 'IllegalAccessException', 'IllegalArgumentException',
48 'IllegalMonitorStateException', 'IllegalStateException',
49 'IllegalThreadStateException', 'IncompatibleClassChangeError',
50 'IndexOutOfBoundsException', 'InheritableThreadLocal', 'InstantiationError',
51 'InstantiationException', 'Integer', 'InternalError', 'InterruptedException',
52 'Iterable', 'LinkageError', 'Long', 'Math', 'NegativeArraySizeException',
53 'NoClassDefFoundError', 'NoSuchFieldError', 'NoSuchFieldException',
54 'NoSuchMethodError', 'NoSuchMethodException', 'NullPointerException', 'Number',
55 'NumberFormatException', 'Object', 'OutOfMemoryError', 'Override', 'Package',
56 'Process', 'ProcessBuilder', 'Readable', 'ReflectiveOperationException',
57 'Runnable', 'Runtime', 'RuntimeException', 'RuntimePermission', 'SafeVarargs',
58 'SecurityException', 'SecurityManager', 'Short', 'StackOverflowError',
59 'StackTraceElement', 'StrictMath', 'String', 'StringBuffer', 'StringBuilder',
60 'StringIndexOutOfBoundsException', 'SuppressWarnings', 'System', 'Thread',
61 'ThreadDeath', 'ThreadGroup', 'ThreadLocal', 'Throwable',
62 'TypeNotPresentException', 'UnknownError', 'UnsatisfiedLinkError',
63 'UnsupportedClassVersionError', 'UnsupportedOperationException', 'VerifyError',
64 'VirtualMachineError', 'Void'])
66 class JavaObject(ObjectDescription):
68 'noindex': directives.flag,
69 'package': directives.unchanged,
70 'outertype': directives.unchanged
73 def _build_ref_node(self, target):
74 ref = addnodes.pending_xref('', refdomain='java', reftype='type', reftarget=target, modname=None, classname=None)
75 ref['java:outertype'] = self.get_type()
77 package = self.env.temp_data.get('java:imports', dict()).get(target, None)
79 if not package and target in java_dot_lang:
83 ref['java:imported'] = True
84 ref['java:package'] = package
86 ref['java:imported'] = False
87 ref['java:package'] = self.get_package()
91 def _build_type_node(self, typ):
92 if isinstance(typ, javalang.tree.ReferenceType):
94 dim = '[]' * len(typ.dimensions)
102 ref_node = self._build_ref_node(target)
103 ref_node += nodes.Text(typ.name, typ.name)
104 parts.append(ref_node)
107 parts.append(nodes.Text('<', '<'))
110 for type_arg in typ.arguments:
114 parts.append(nodes.Text(', ', ', '))
116 if type_arg.pattern_type == '?':
117 parts.append(nodes.Text('?', '?'))
119 if type_arg.pattern_type:
120 s = '? %s ' % (type_arg.pattern_type,)
121 parts.append(nodes.Text(s, s))
122 parts.extend(self._build_type_node(type_arg.type))
124 parts.append(nodes.Text('>', '>'))
129 target = target + '.' + typ.name
130 parts.append(nodes.Text('.', '.'))
132 parts.append(nodes.Text(dim, dim))
136 type_repr = formatter.output_type(typ).build()
137 return [nodes.Text(type_repr, type_repr)]
139 def _build_type_node_list(self, types):
140 parts = self._build_type_node(types[0])
141 for typ in types[1:]:
142 parts.append(nodes.Text(', ', ', '))
143 parts.extend(self._build_type_node(typ))
146 def handle_signature(self, sig, signode):
147 handle_name = 'handle_%s_signature' % (self.objtype,)
148 handle = getattr(self, handle_name, None)
151 return handle(sig, signode)
152 raise NotImplementedError
154 def get_index_text(self, package, typ, name):
155 raise NotImplementedError
157 def get_package(self):
158 return self.options.get('package', self.env.temp_data.get('java:package'))
161 return self.options.get('outertype', '.'.join(self.env.temp_data.get('java:outertype', [])))
163 def add_target_and_index(self, name, sig, signode):
164 package = self.get_package()
165 typ = self.get_type()
167 fullname = '.'.join(filter(None, (package, typ, name)))
168 basename = fullname.partition('(')[0]
171 if fullname not in self.state.document.ids:
172 signode['names'].append(fullname)
173 signode['ids'].append(fullname)
174 signode['first'] = (not self.names)
175 self.state.document.note_explicit_target(signode)
177 objects = self.env.domaindata['java']['objects']
178 if fullname in objects:
179 self.state_machine.reporter.warning(
180 'duplicate object description of %s, ' % fullname +
181 'other instance in ' + self.env.doc2path(objects[fullname][0]) +
182 ', use :noindex: for one of them',
185 objects[fullname] = (self.env.docname, self.objtype, basename)
187 indextext = self.get_index_text(package, typ, name)
189 self.indexnode['entries'].append(_create_indexnode(indextext, fullname))
191 def before_content(self):
192 self.set_type = False
194 if self.objtype == 'type' and self.names:
196 self.env.temp_data.setdefault('java:outertype', list()).append(self.names[0])
198 def after_content(self):
200 self.env.temp_data['java:outertype'].pop()
202 class JavaMethod(JavaObject):
204 TypedField('parameter', label=_('Parameters'),
205 names=('param', 'parameter', 'arg', 'argument'),
206 typerolename='type', typenames=('type',)),
207 Field('returnvalue', label=_('Returns'), has_arg=False,
208 names=('returns', 'return')),
209 GroupedField('throws', names=('throws',), label=_('Throws'), rolename='type')
212 def handle_method_signature(self, sig, signode):
214 member = javalang.parse.parse_member_signature(sig)
215 except javalang.parser.JavaSyntaxError:
216 raise self.error("syntax error in method signature")
218 if not isinstance(member, javalang.tree.MethodDeclaration):
219 raise self.error("expected method declaration")
221 mods = formatter.output_modifiers(member.modifiers).build()
222 signode += nodes.Text(mods + ' ', mods + ' ')
224 if member.type_parameters:
225 type_params = formatter.output_type_params(member.type_parameters).build()
226 signode += nodes.Text(type_params, type_params)
227 signode += nodes.Text(' ', ' ')
229 rnode = addnodes.desc_type('', '')
230 rnode += self._build_type_node(member.return_type)
233 signode += nodes.Text(' ', ' ')
234 signode += addnodes.desc_name(member.name, member.name)
236 paramlist = addnodes.desc_parameterlist()
237 for parameter in member.parameters:
238 param = addnodes.desc_parameter('', '', noemph=True)
239 param += self._build_type_node(parameter.type)
241 if parameter.varargs:
242 param += nodes.Text('...', '')
244 param += nodes.emphasis(' ' + parameter.name, ' ' + parameter.name)
248 param_reprs = [formatter.output_type(param.type, with_generics=False).build() for param in member.parameters]
249 return member.name + '(' + ', '.join(param_reprs) + ')'
251 def get_index_text(self, package, typ, name):
252 return _('%s (Java method)' % (name,))
254 class JavaConstructor(JavaObject):
256 TypedField('parameter', label=_('Parameters'),
257 names=('param', 'parameter', 'arg', 'argument'),
258 typerolename='type', typenames=('type',)),
259 GroupedField('throws', names=('throws',), label=_('Throws'))
262 def handle_constructor_signature(self, sig, signode):
264 member = javalang.parse.parse_constructor_signature(sig)
265 except javalang.parser.JavaSyntaxError:
266 raise self.error("syntax error in constructor signature")
268 if not isinstance(member, javalang.tree.ConstructorDeclaration):
269 raise self.error("expected constructor declaration")
271 mods = formatter.output_modifiers(member.modifiers).build()
272 signode += nodes.Text(mods + ' ', mods + ' ')
274 signode += addnodes.desc_name(member.name, member.name)
276 paramlist = addnodes.desc_parameterlist()
277 for parameter in member.parameters:
278 param = addnodes.desc_parameter('', '', noemph=True)
279 param += self._build_type_node(parameter.type)
281 if parameter.varargs:
282 param += nodes.Text('...', '')
284 param += nodes.emphasis(' ' + parameter.name, ' ' + parameter.name)
288 param_reprs = [formatter.output_type(param.type, with_generics=False).build() for param in member.parameters]
289 return '%s(%s)' % (member.name, ', '.join(param_reprs))
291 def get_index_text(self, package, typ, name):
292 return _('%s (Java constructor)' % (name,))
294 class JavaType(JavaObject):
296 GroupedField('parameter', names=('param',), label=_('Parameters'))
299 declaration_type = None
301 def handle_type_signature(self, sig, signode):
303 member = javalang.parse.parse_type_signature(sig)
304 except javalang.parser.JavaSyntaxError:
305 raise self.error("syntax error in field signature")
307 if isinstance(member, javalang.tree.ClassDeclaration):
308 self.declaration_type = 'class'
309 elif isinstance(member, javalang.tree.InterfaceDeclaration):
310 self.declaration_type = 'interface'
311 elif isinstance(member, javalang.tree.EnumDeclaration):
312 self.declaration_type = 'enum'
313 elif isinstance(member, javalang.tree.AnnotationDeclaration):
314 self.declaration_type = 'annotation'
316 raise self.error("expected type declaration")
318 mods = formatter.output_modifiers(member.modifiers).build()
319 signode += nodes.Text(mods + ' ', mods + ' ')
321 if self.declaration_type == 'class':
322 signode += nodes.Text('class ', 'class ')
323 elif self.declaration_type == 'interface':
324 signode += nodes.Text('interface ', 'interface ')
325 elif self.declaration_type == 'enum':
326 signode += nodes.Text('enum ', 'enum ')
327 elif self.declaration_type == 'annotation':
328 signode += nodes.Text('@interface ', '@interface ')
330 signode += addnodes.desc_name(member.name, member.name)
332 if self.declaration_type in ('class', 'interface') and member.type_parameters:
333 type_params = formatter.output_type_params(member.type_parameters).build()
334 signode += nodes.Text(type_params, type_params)
336 if self.declaration_type == 'class':
338 extends = ' extends '
339 signode += nodes.Text(extends, extends)
340 signode += self._build_type_node(member.extends)
341 if member.implements:
342 implements = ' implements '
343 signode += nodes.Text(implements, implements)
344 signode += self._build_type_node_list(member.implements)
345 elif self.declaration_type == 'interface':
347 extends = ' extends '
348 signode += nodes.Text(extends, extends)
349 signode += self._build_type_node_list(member.extends)
350 elif self.declaration_type == 'enum':
351 if member.implements:
352 implements = ' implements '
353 signode += nodes.Text(implements, implements)
354 signode += self._build_type_node_list(member.implements)
358 def get_index_text(self, package, typ, name):
359 return _('%s (Java %s)' % (name, self.declaration_typ))
361 class JavaField(JavaObject):
362 def handle_field_signature(self, sig, signode):
364 member = javalang.parse.parse_member_signature(sig)
365 except javalang.parser.JavaSyntaxError:
366 raise self.error("syntax error in field signature")
368 if not isinstance(member, javalang.tree.FieldDeclaration):
369 raise self.error("expected field declaration")
371 mods = formatter.output_modifiers(member.modifiers).build()
372 signode += nodes.Text(mods + ' ', mods + ' ')
374 tnode = addnodes.desc_type('', '')
375 tnode += self._build_type_node(member.type)
378 signode += nodes.Text(' ', ' ')
380 if len(member.declarators) > 1:
381 self.error('only one field may be documented at a time')
383 declarator = member.declarators[0]
384 signode += addnodes.desc_name(declarator.name, declarator.name)
386 dim = '[]' * len(declarator.dimensions)
387 signode += nodes.Text(dim)
389 if declarator.initializer and isinstance(declarator.initializer, javalang.tree.Literal):
390 signode += nodes.Text(' = ' + declarator.initializer.value)
392 return declarator.name
394 def get_index_text(self, package, typ, name):
395 return _('%s (Java field)' % (name,))
397 class JavaPackage(Directive):
399 Directive to mark description of a new package.
403 required_arguments = 1
404 optional_arguments = 0
405 final_argument_whitespace = False
407 'noindex': directives.flag,
411 env = self.state.document.settings.env
412 package = self.arguments[0].strip()
413 noindex = 'noindex' in self.options
414 env.temp_data['java:package'] = package
415 env.domaindata['java']['objects'][package] = (env.docname, 'package', package)
419 targetnode = nodes.target('', '', ids=['package-' + package], ismod=True)
420 self.state.document.note_explicit_target(targetnode)
422 # the platform and synopsis aren't printed; in fact, they are only
423 # used in the modindex currently
424 ret.append(targetnode)
426 indextext = _('%s (package)') % (package,)
427 inode = addnodes.index(entries=[_create_indexnode(indextext, 'package-' + package)])
432 class JavaImport(Directive):
434 This directive is just to tell Sphinx the source of a referenced type.
438 required_arguments = 2
439 optional_arguments = 0
440 final_argument_whitespace = False
444 env = self.state.document.settings.env
445 package, typename = self.arguments
447 env.temp_data.setdefault('java:imports', dict())[typename] = package
450 class JavaXRefRole(XRefRole):
451 def process_link(self, env, refnode, has_explicit_title, title, target):
452 refnode['java:outertype'] = '.'.join(env.temp_data.get('java:outertype', list()))
454 target = target.lstrip('~')
456 # Strip a method component from the target
459 basetype = basetype.partition('(')[0]
461 basetype = basetype.rpartition('.')[0]
463 package = env.temp_data.get('java:imports', dict()).get(basetype, None)
466 refnode['java:imported'] = True
467 refnode['java:package'] = package
469 refnode['java:imported'] = False
470 refnode['java:package'] = env.temp_data.get('java:package')
472 if not has_explicit_title:
473 # if the first character is a tilde, don't display the module/class
474 # parts of the contents
475 if title[0:1] == '~':
476 title = title.partition('(')[0]
478 dot = title.rfind('.')
480 title = title[dot+1:]
484 class JavaDomain(Domain):
485 """Java language domain."""
490 'package': ObjType(_('package'), 'package', 'ref'),
491 'type': ObjType(_('type'), 'type', 'ref'),
492 'field': ObjType(_('field'), 'field', 'ref'),
493 'constructor': ObjType(_('constructor'), 'construct', 'ref'),
494 'method': ObjType(_('method'), 'meth', 'ref')
498 'package': JavaPackage,
501 'constructor': JavaConstructor,
502 'method': JavaMethod,
507 'package': JavaXRefRole(),
508 'type': JavaXRefRole(),
509 'field': JavaXRefRole(),
510 'construct': JavaXRefRole(),
511 'meth': JavaXRefRole(),
512 'ref': JavaXRefRole(),
516 'objects': {}, # fullname -> docname, objtype, basename
519 def clear_doc(self, docname):
520 objects = dict(self.data['objects'])
522 for fullname, (fn, _, _) in objects.items():
524 del self.data['objects'][fullname]
526 def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
527 objects = self.data['objects']
528 package = node.get('java:package')
529 imported = node.get('java:imported')
530 type_context = node.get('java:outertype')
532 # Partial function to make building the response easier
533 make_ref = lambda fullname: make_refnode(builder, fromdocname, objects[fullname][0], fullname, contnode, fullname)
535 # Check for fully qualified references
536 if target in objects:
537 return make_ref(target)
539 # Try with package name prefixed
541 fullname = package + '.' + target
542 if fullname in objects:
543 return make_ref(fullname)
545 # Try with package and type prefixed
546 if package and type_context:
547 fullname = package + '.' + type_context + '.' + target
548 if fullname in objects:
549 return make_ref(fullname)
551 # Try to find a matching suffix
552 suffix = '.' + target
553 basename_match = None
554 basename_suffix = suffix.partition('(')[0]
556 for fullname, (_, _, basename) in objects.items():
557 if fullname.endswith(suffix):
558 return make_ref(fullname)
559 if basename.endswith(basename_suffix):
560 basename_match = fullname
563 return make_ref(basename_match)
565 # Try creating an external documentation reference
566 ref = extdoc.get_javadoc_ref(self.env, target, target)
568 if not ref and target in java_dot_lang:
569 fulltarget = 'java.lang.' + target
570 ref = extdoc.get_javadoc_ref(self.env, fulltarget, fulltarget)
572 # If the target was imported try with the package prefixed
573 if not ref and imported:
574 fulltarget = package + '.' + target
575 ref = extdoc.get_javadoc_ref(self.env, fulltarget, fulltarget)
582 def get_objects(self):
583 for refname, (docname, typ, _) in self.data['objects'].items():
584 yield (refname, refname, typ, docname, refname, 1)
587 def _create_indexnode(indextext, fullname):
588 # See https://github.com/sphinx-doc/sphinx/issues/2673
589 if version_info < (1, 4):
590 return ('single', indextext, fullname, '')
591 return ('single', indextext, fullname, '', None)