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)
153 raise NotImplementedError
155 def get_index_text(self, package, type, name):
156 raise NotImplementedError
158 def get_package(self):
159 return self.options.get('package', self.env.temp_data.get('java:package'))
162 return self.options.get('outertype', '.'.join(self.env.temp_data.get('java:outertype', [])))
164 def add_target_and_index(self, name, sig, signode):
165 package = self.get_package()
166 type = self.get_type();
168 fullname = '.'.join(filter(None, (package, type, name)))
169 basename = fullname.partition('(')[0]
172 if fullname not in self.state.document.ids:
173 signode['names'].append(fullname)
174 signode['ids'].append(fullname)
175 signode['first'] = (not self.names)
176 self.state.document.note_explicit_target(signode)
178 objects = self.env.domaindata['java']['objects']
179 if fullname in objects:
180 self.state_machine.reporter.warning(
181 'duplicate object description of %s, ' % fullname +
182 'other instance in ' + self.env.doc2path(objects[fullname][0]) +
183 ', use :noindex: for one of them',
186 objects[fullname] = (self.env.docname, self.objtype, basename)
188 indextext = self.get_index_text(package, type, name)
190 self.indexnode['entries'].append(_create_indexnode(indextext, fullname))
192 def before_content(self):
193 self.set_type = False
195 if self.objtype == 'type' and self.names:
197 self.env.temp_data.setdefault('java:outertype', list()).append(self.names[0])
199 def after_content(self):
201 self.env.temp_data['java:outertype'].pop()
203 class JavaMethod(JavaObject):
205 TypedField('parameter', label=_('Parameters'),
206 names=('param', 'parameter', 'arg', 'argument'),
207 typerolename='type', typenames=('type',)),
208 Field('returnvalue', label=_('Returns'), has_arg=False,
209 names=('returns', 'return')),
210 GroupedField('throws', names=('throws',), label=_('Throws'), rolename='type')
213 def handle_method_signature(self, sig, signode):
215 member = javalang.parse.parse_member_signature(sig)
216 except javalang.parser.JavaSyntaxError:
217 raise self.error("syntax error in method signature")
219 if not isinstance(member, javalang.tree.MethodDeclaration):
220 raise self.error("expected method declaration")
222 mods = formatter.output_modifiers(member.modifiers).build()
223 signode += nodes.Text(mods + ' ', mods + ' ')
225 if member.type_parameters:
226 type_params = formatter.output_type_params(member.type_parameters).build()
227 signode += nodes.Text(type_params, type_params)
228 signode += nodes.Text(' ', ' ')
230 rnode = addnodes.desc_type('', '')
231 rnode += self._build_type_node(member.return_type)
234 signode += nodes.Text(' ', ' ')
235 signode += addnodes.desc_name(member.name, member.name)
237 paramlist = addnodes.desc_parameterlist()
238 for parameter in member.parameters:
239 param = addnodes.desc_parameter('', '', noemph=True)
240 param += self._build_type_node(parameter.type)
242 if parameter.varargs:
243 param += nodes.Text('...', '')
245 param += nodes.emphasis(' ' + parameter.name, ' ' + parameter.name)
249 param_reprs = [formatter.output_type(param.type, with_generics=False).build() for param in member.parameters]
250 return member.name + '(' + ', '.join(param_reprs) + ')'
252 def get_index_text(self, package, type, name):
253 return _('%s (Java method)' % (name,))
255 class JavaConstructor(JavaObject):
257 TypedField('parameter', label=_('Parameters'),
258 names=('param', 'parameter', 'arg', 'argument'),
259 typerolename='type', typenames=('type',)),
260 GroupedField('throws', names=('throws',), label=_('Throws'))
263 def handle_constructor_signature(self, sig, signode):
265 member = javalang.parse.parse_constructor_signature(sig)
266 except javalang.parser.JavaSyntaxError:
267 raise self.error("syntax error in constructor signature")
269 if not isinstance(member, javalang.tree.ConstructorDeclaration):
270 raise self.error("expected constructor declaration")
272 mods = formatter.output_modifiers(member.modifiers).build()
273 signode += nodes.Text(mods + ' ', mods + ' ')
275 signode += addnodes.desc_name(member.name, member.name)
277 paramlist = addnodes.desc_parameterlist()
278 for parameter in member.parameters:
279 param = addnodes.desc_parameter('', '', noemph=True)
280 param += self._build_type_node(parameter.type)
282 if parameter.varargs:
283 param += nodes.Text('...', '')
285 param += nodes.emphasis(' ' + parameter.name, ' ' + parameter.name)
289 param_reprs = [formatter.output_type(param.type, with_generics=False).build() for param in member.parameters]
290 return '%s(%s)' % (member.name, ', '.join(param_reprs))
292 def get_index_text(self, package, type, name):
293 return _('%s (Java constructor)' % (name,))
295 class JavaType(JavaObject):
297 GroupedField('parameter', names=('param',), label=_('Parameters'))
300 declaration_type = None
302 def handle_type_signature(self, sig, signode):
304 member = javalang.parse.parse_type_signature(sig)
305 except javalang.parser.JavaSyntaxError:
306 raise self.error("syntax error in field signature")
308 if isinstance(member, javalang.tree.ClassDeclaration):
309 self.declaration_type = 'class'
310 elif isinstance(member, javalang.tree.InterfaceDeclaration):
311 self.declaration_type = 'interface'
312 elif isinstance(member, javalang.tree.EnumDeclaration):
313 self.declaration_type = 'enum'
314 elif isinstance(member, javalang.tree.AnnotationDeclaration):
315 self.declaration_type = 'annotation'
317 raise self.error("expected type declaration")
319 mods = formatter.output_modifiers(member.modifiers).build()
320 signode += nodes.Text(mods + ' ', mods + ' ')
322 if self.declaration_type == 'class':
323 signode += nodes.Text('class ', 'class ')
324 elif self.declaration_type == 'interface':
325 signode += nodes.Text('interface ', 'interface ')
326 elif self.declaration_type == 'enum':
327 signode += nodes.Text('enum ', 'enum ')
328 elif self.declaration_type == 'annotation':
329 signode += nodes.Text('@interface ', '@interface ')
331 signode += addnodes.desc_name(member.name, member.name)
333 if self.declaration_type in ('class', 'interface') and member.type_parameters:
334 type_params = formatter.output_type_params(member.type_parameters).build()
335 signode += nodes.Text(type_params, type_params)
337 if self.declaration_type == 'class':
339 extends = ' extends '
340 signode += nodes.Text(extends, extends)
341 signode += self._build_type_node(member.extends)
342 if member.implements:
343 implements = ' implements '
344 signode += nodes.Text(implements, implements)
345 signode += self._build_type_node_list(member.implements)
346 elif self.declaration_type == 'interface':
348 extends = ' extends '
349 signode += nodes.Text(extends, extends)
350 signode += self._build_type_node_list(member.extends)
351 elif self.declaration_type == 'enum':
352 if member.implements:
353 implements = ' implements '
354 signode += nodes.Text(implements, implements)
355 signode += self._build_type_node_list(member.implements)
359 def get_index_text(self, package, type, name):
360 return _('%s (Java %s)' % (name, self.declaration_type))
362 class JavaField(JavaObject):
363 def handle_field_signature(self, sig, signode):
365 member = javalang.parse.parse_member_signature(sig)
366 except javalang.parser.JavaSyntaxError:
367 raise self.error("syntax error in field signature")
369 if not isinstance(member, javalang.tree.FieldDeclaration):
370 raise self.error("expected field declaration")
372 mods = formatter.output_modifiers(member.modifiers).build()
373 signode += nodes.Text(mods + ' ', mods + ' ')
375 tnode = addnodes.desc_type('', '')
376 tnode += self._build_type_node(member.type)
379 signode += nodes.Text(' ', ' ')
381 if len(member.declarators) > 1:
382 self.error('only one field may be documented at a time')
384 declarator = member.declarators[0]
385 signode += addnodes.desc_name(declarator.name, declarator.name)
387 dim = '[]' * len(declarator.dimensions)
388 signode += nodes.Text(dim)
390 if declarator.initializer and isinstance(declarator.initializer, javalang.tree.Literal):
391 signode += nodes.Text(' = ' + declarator.initializer.value)
393 return declarator.name
395 def get_index_text(self, package, type, name):
396 return _('%s (Java field)' % (name,))
398 class JavaPackage(Directive):
400 Directive to mark description of a new package.
404 required_arguments = 1
405 optional_arguments = 0
406 final_argument_whitespace = False
408 'noindex': directives.flag,
412 env = self.state.document.settings.env
413 package = self.arguments[0].strip()
414 noindex = 'noindex' in self.options
415 env.temp_data['java:package'] = package
416 env.domaindata['java']['objects'][package] = (env.docname, 'package', package)
420 targetnode = nodes.target('', '', ids=['package-' + package], ismod=True)
421 self.state.document.note_explicit_target(targetnode)
423 # the platform and synopsis aren't printed; in fact, they are only
424 # used in the modindex currently
425 ret.append(targetnode)
427 indextext = _('%s (package)') % (package,)
428 inode = addnodes.index(entries=[_create_indexnode(indextext, 'package-' + package)])
433 class JavaImport(Directive):
435 This directive is just to tell Sphinx the source of a referenced type.
439 required_arguments = 2
440 optional_arguments = 0
441 final_argument_whitespace = False
445 env = self.state.document.settings.env
446 package, typename = self.arguments
448 env.temp_data.setdefault('java:imports', dict())[typename] = package
451 class JavaXRefRole(XRefRole):
452 def process_link(self, env, refnode, has_explicit_title, title, target):
453 refnode['java:outertype'] = '.'.join(env.temp_data.get('java:outertype', list()))
455 target = target.lstrip('~')
457 # Strip a method component from the target
460 basetype = basetype.partition('(')[0]
462 basetype = basetype.rpartition('.')[0]
464 package = env.temp_data.get('java:imports', dict()).get(basetype, None)
467 refnode['java:imported'] = True
468 refnode['java:package'] = package
470 refnode['java:imported'] = False
471 refnode['java:package'] = env.temp_data.get('java:package')
473 if not has_explicit_title:
474 # if the first character is a tilde, don't display the module/class
475 # parts of the contents
476 if title[0:1] == '~':
477 title = title.partition('(')[0]
479 dot = title.rfind('.')
481 title = title[dot+1:]
485 class JavaDomain(Domain):
486 """Java language domain."""
491 'package': ObjType(_('package'), 'package', 'ref'),
492 'type': ObjType(_('type'), 'type', 'ref'),
493 'field': ObjType(_('field'), 'field', 'ref'),
494 'constructor': ObjType(_('constructor'), 'construct', 'ref'),
495 'method': ObjType(_('method'), 'meth', 'ref')
499 'package': JavaPackage,
502 'constructor': JavaConstructor,
503 'method': JavaMethod,
508 'package': JavaXRefRole(),
509 'type': JavaXRefRole(),
510 'field': JavaXRefRole(),
511 'construct': JavaXRefRole(),
512 'meth': JavaXRefRole(),
513 'ref': JavaXRefRole(),
517 'objects': {}, # fullname -> docname, objtype, basename
520 def clear_doc(self, docname):
521 objects = dict(self.data['objects'])
523 for fullname, (fn, _, _) in objects.items():
525 del self.data['objects'][fullname]
527 def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
528 objects = self.data['objects']
529 package = node.get('java:package')
530 imported = node.get('java:imported')
531 type_context = node.get('java:outertype')
533 # Partial function to make building the response easier
534 make_ref = lambda fullname: make_refnode(builder, fromdocname, objects[fullname][0], fullname, contnode, fullname)
536 # Check for fully qualified references
537 if target in objects:
538 return make_ref(target)
540 # Try with package name prefixed
542 fullname = package + '.' + target
543 if fullname in objects:
544 return make_ref(fullname)
546 # Try with package and type prefixed
547 if package and type_context:
548 fullname = package + '.' + type_context + '.' + target
549 if fullname in objects:
550 return make_ref(fullname)
552 # Try to find a matching suffix
553 suffix = '.' + target
554 basename_match = None
555 basename_suffix = suffix.partition('(')[0]
557 for fullname, (_, _, basename) in objects.items():
558 if fullname.endswith(suffix):
559 return make_ref(fullname)
560 elif basename.endswith(basename_suffix):
561 basename_match = fullname
564 return make_ref(basename_match)
566 # Try creating an external documentation reference
567 ref = extdoc.get_javadoc_ref(self.env, target, target)
569 if not ref and target in java_dot_lang:
570 fulltarget = 'java.lang.' + target
571 ref = extdoc.get_javadoc_ref(self.env, fulltarget, fulltarget)
573 # If the target was imported try with the package prefixed
574 if not ref and imported:
575 fulltarget = package + '.' + target
576 ref = extdoc.get_javadoc_ref(self.env, fulltarget, fulltarget)
584 def get_objects(self):
585 for refname, (docname, type, _) in self.data['objects'].items():
586 yield (refname, refname, type, docname, refname, 1)
589 def _create_indexnode(indextext, fullname):
590 # See https://github.com/sphinx-doc/sphinx/issues/2673
591 if version_info < (1, 4):
592 return ('single', indextext, fullname, '')
594 return ('single', indextext, fullname, '', None)