Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
embeed the javasphinx module
[simgrid.git] / docs / source / _ext / javasphinx / domain.py
1 #
2 # Copyright 2012-2015 Bronto Software, Inc. and contributors
3 #
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
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15 #
16
17 import re
18 import string
19
20 from docutils import nodes
21 from docutils.parsers.rst import Directive, directives
22
23 from sphinx import addnodes, version_info
24 from sphinx.roles import XRefRole
25 from sphinx.locale import l_, _
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
30
31 import javalang
32
33 import javasphinx.extdoc as extdoc
34 import javasphinx.formatter as formatter
35 import javasphinx.util as util
36
37 # Classes in java.lang. These are available without an import.
38 java_dot_lang = set([
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'])
65
66 class JavaObject(ObjectDescription):
67     option_spec = {
68         'noindex': directives.flag,
69         'package': directives.unchanged,
70         'outertype': directives.unchanged
71     }
72
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()
76
77         package = self.env.temp_data.get('java:imports', dict()).get(target, None)
78
79         if not package and target in java_dot_lang:
80             package = 'java.lang'
81
82         if package:
83             ref['java:imported'] = True
84             ref['java:package'] = package
85         else:
86             ref['java:imported'] = False
87             ref['java:package'] = self.get_package()
88
89         return ref
90
91     def _build_type_node(self, typ):
92         if isinstance(typ, javalang.tree.ReferenceType):
93             if typ.dimensions:
94                 dim = '[]' * len(typ.dimensions)
95             else:
96                 dim = ''
97
98             target = typ.name
99             parts = []
100
101             while typ:
102                 ref_node = self._build_ref_node(target)
103                 ref_node += nodes.Text(typ.name, typ.name)
104                 parts.append(ref_node)
105
106                 if typ.arguments:
107                     parts.append(nodes.Text('<', '<'))
108
109                     first = True
110                     for type_arg in typ.arguments:
111                         if first:
112                             first = False
113                         else:
114                             parts.append(nodes.Text(', ', ', '))
115
116                         if type_arg.pattern_type == '?':
117                             parts.append(nodes.Text('?', '?'))
118                         else:
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))
123
124                     parts.append(nodes.Text('>', '>'))
125
126                 typ = typ.sub_type
127
128                 if typ:
129                     target = target + '.' + typ.name
130                     parts.append(nodes.Text('.', '.'))
131                 elif dim:
132                     parts.append(nodes.Text(dim, dim))
133
134             return parts
135         else:
136             type_repr = formatter.output_type(typ).build()
137             return [nodes.Text(type_repr, type_repr)]
138
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))
144         return parts
145
146     def handle_signature(self, sig, signode):
147         handle_name = 'handle_%s_signature' % (self.objtype,)
148         handle = getattr(self, handle_name, None)
149
150         if handle:
151             return handle(sig, signode)
152         else:
153             raise NotImplementedError
154
155     def get_index_text(self, package, type, name):
156         raise NotImplementedError
157
158     def get_package(self):
159         return self.options.get('package', self.env.temp_data.get('java:package'))
160
161     def get_type(self):
162         return self.options.get('outertype', '.'.join(self.env.temp_data.get('java:outertype', [])))
163
164     def add_target_and_index(self, name, sig, signode):
165         package = self.get_package()
166         type = self.get_type();
167
168         fullname = '.'.join(filter(None, (package, type, name)))
169         basename = fullname.partition('(')[0]
170
171         # note target
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)
177
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',
184                     line=self.lineno)
185
186             objects[fullname] = (self.env.docname, self.objtype, basename)
187
188         indextext = self.get_index_text(package, type, name)
189         if indextext:
190             self.indexnode['entries'].append(_create_indexnode(indextext, fullname))
191
192     def before_content(self):
193         self.set_type = False
194
195         if self.objtype == 'type' and self.names:
196             self.set_type = True
197             self.env.temp_data.setdefault('java:outertype', list()).append(self.names[0])
198
199     def after_content(self):
200         if self.set_type:
201             self.env.temp_data['java:outertype'].pop()
202
203 class JavaMethod(JavaObject):
204     doc_field_types = [
205         TypedField('parameter', label=l_('Parameters'),
206                    names=('param', 'parameter', 'arg', 'argument'),
207                    typerolename='type', typenames=('type',)),
208         Field('returnvalue', label=l_('Returns'), has_arg=False,
209               names=('returns', 'return')),
210         GroupedField('throws', names=('throws',), label=l_('Throws'), rolename='type')
211     ]
212
213     def handle_method_signature(self, sig, signode):
214         try:
215             member = javalang.parse.parse_member_signature(sig)
216         except javalang.parser.JavaSyntaxError:
217             raise self.error("syntax error in method signature")
218
219         if not isinstance(member, javalang.tree.MethodDeclaration):
220             raise self.error("expected method declaration")
221
222         mods = formatter.output_modifiers(member.modifiers).build()
223         signode += nodes.Text(mods + ' ', mods + ' ')
224
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(' ', ' ')
229
230         rnode = addnodes.desc_type('', '')
231         rnode += self._build_type_node(member.return_type)
232
233         signode += rnode
234         signode += nodes.Text(' ', ' ')
235         signode += addnodes.desc_name(member.name, member.name)
236
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)
241
242             if parameter.varargs:
243                 param += nodes.Text('...', '')
244
245             param += nodes.emphasis(' ' + parameter.name, ' ' + parameter.name)
246             paramlist += param
247         signode += paramlist
248
249         param_reprs = [formatter.output_type(param.type, with_generics=False).build() for param in member.parameters]
250         return member.name + '(' + ', '.join(param_reprs) + ')'
251
252     def get_index_text(self, package, type, name):
253         return _('%s (Java method)' % (name,))
254
255 class JavaConstructor(JavaObject):
256     doc_field_types = [
257         TypedField('parameter', label=l_('Parameters'),
258                    names=('param', 'parameter', 'arg', 'argument'),
259                    typerolename='type', typenames=('type',)),
260         GroupedField('throws', names=('throws',), label=l_('Throws'))
261     ]
262
263     def handle_constructor_signature(self, sig, signode):
264         try:
265             member = javalang.parse.parse_constructor_signature(sig)
266         except javalang.parser.JavaSyntaxError:
267             raise self.error("syntax error in constructor signature")
268
269         if not isinstance(member, javalang.tree.ConstructorDeclaration):
270             raise self.error("expected constructor declaration")
271
272         mods = formatter.output_modifiers(member.modifiers).build()
273         signode += nodes.Text(mods + ' ', mods + ' ')
274
275         signode += addnodes.desc_name(member.name, member.name)
276
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)
281
282             if parameter.varargs:
283                 param += nodes.Text('...', '')
284
285             param += nodes.emphasis(' ' + parameter.name, ' ' + parameter.name)
286             paramlist += param
287         signode += paramlist
288
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))
291
292     def get_index_text(self, package, type, name):
293         return _('%s (Java constructor)' % (name,))
294
295 class JavaType(JavaObject):
296     doc_field_types = [
297         GroupedField('parameter', names=('param',), label=l_('Parameters'))
298     ]
299
300     declaration_type = None
301
302     def handle_type_signature(self, sig, signode):
303         try:
304             member = javalang.parse.parse_type_signature(sig)
305         except javalang.parser.JavaSyntaxError:
306             raise self.error("syntax error in field signature")
307
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'
316         else:
317             raise self.error("expected type declaration")
318
319         mods = formatter.output_modifiers(member.modifiers).build()
320         signode += nodes.Text(mods + ' ', mods + ' ')
321
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 ')
330
331         signode += addnodes.desc_name(member.name, member.name)
332
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)
336
337         if self.declaration_type == 'class':
338             if member.extends:
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':
347             if member.extends:
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)
356
357         return member.name
358
359     def get_index_text(self, package, type, name):
360         return _('%s (Java %s)' % (name, self.declaration_type))
361
362 class JavaField(JavaObject):
363     def handle_field_signature(self, sig, signode):
364         try:
365             member = javalang.parse.parse_member_signature(sig)
366         except javalang.parser.JavaSyntaxError:
367             raise self.error("syntax error in field signature")
368
369         if not isinstance(member, javalang.tree.FieldDeclaration):
370             raise self.error("expected field declaration")
371
372         mods = formatter.output_modifiers(member.modifiers).build()
373         signode += nodes.Text(mods + ' ', mods + ' ')
374
375         tnode = addnodes.desc_type('', '')
376         tnode += self._build_type_node(member.type)
377
378         signode += tnode
379         signode += nodes.Text(' ', ' ')
380
381         if len(member.declarators) > 1:
382             self.error('only one field may be documented at a time')
383
384         declarator = member.declarators[0]
385         signode += addnodes.desc_name(declarator.name, declarator.name)
386
387         dim = '[]' * len(declarator.dimensions)
388         signode += nodes.Text(dim)
389
390         if declarator.initializer and isinstance(declarator.initializer, javalang.tree.Literal):
391             signode += nodes.Text(' = ' + declarator.initializer.value)
392
393         return declarator.name
394
395     def get_index_text(self, package, type, name):
396         return _('%s (Java field)' % (name,))
397
398 class JavaPackage(Directive):
399     """
400     Directive to mark description of a new package.
401     """
402
403     has_content = False
404     required_arguments = 1
405     optional_arguments = 0
406     final_argument_whitespace = False
407     option_spec = {
408         'noindex': directives.flag,
409     }
410
411     def run(self):
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)
417         ret = []
418
419         if not noindex:
420             targetnode = nodes.target('', '', ids=['package-' + package], ismod=True)
421             self.state.document.note_explicit_target(targetnode)
422
423             # the platform and synopsis aren't printed; in fact, they are only
424             # used in the modindex currently
425             ret.append(targetnode)
426
427             indextext = _('%s (package)') % (package,)
428             inode = addnodes.index(entries=[_create_indexnode(indextext, 'package-' + package)])
429             ret.append(inode)
430
431         return ret
432
433 class JavaImport(Directive):
434     """
435     This directive is just to tell Sphinx the source of a referenced type.
436     """
437
438     has_content = False
439     required_arguments = 2
440     optional_arguments = 0
441     final_argument_whitespace = False
442     option_spec = {}
443
444     def run(self):
445         env = self.state.document.settings.env
446         package, typename = self.arguments
447
448         env.temp_data.setdefault('java:imports', dict())[typename] = package
449         return []
450
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()))
454
455         target = target.lstrip('~')
456
457         # Strip a method component from the target
458         basetype = target
459         if '(' in basetype:
460             basetype = basetype.partition('(')[0]
461             if '.' in basetype:
462                 basetype = basetype.rpartition('.')[0]
463
464         package = env.temp_data.get('java:imports', dict()).get(basetype, None)
465
466         if package:
467             refnode['java:imported'] = True
468             refnode['java:package'] = package
469         else:
470             refnode['java:imported'] = False
471             refnode['java:package'] = env.temp_data.get('java:package')
472
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]
478                 title = title[1:]
479                 dot = title.rfind('.')
480                 if dot != -1:
481                     title = title[dot+1:]
482
483         return title, target
484
485 class JavaDomain(Domain):
486     """Java language domain."""
487     name = 'java'
488     label = 'Java'
489
490     object_types = {
491         'package':     ObjType(l_('package'), 'package', 'ref'),
492         'type':        ObjType(l_('type'), 'type', 'ref'),
493         'field':       ObjType(l_('field'), 'field', 'ref'),
494         'constructor': ObjType(l_('constructor'), 'construct', 'ref'),
495         'method':      ObjType(l_('method'), 'meth', 'ref')
496     }
497
498     directives = {
499         'package':        JavaPackage,
500         'type':           JavaType,
501         'field':          JavaField,
502         'constructor':    JavaConstructor,
503         'method':         JavaMethod,
504         'import':         JavaImport
505     }
506
507     roles = {
508         'package':   JavaXRefRole(),
509         'type':      JavaXRefRole(),
510         'field':     JavaXRefRole(),
511         'construct': JavaXRefRole(),
512         'meth':      JavaXRefRole(),
513         'ref':       JavaXRefRole(),
514     }
515
516     initial_data = {
517         'objects': {},  # fullname -> docname, objtype, basename
518     }
519
520     def clear_doc(self, docname):
521         objects = dict(self.data['objects'])
522
523         for fullname, (fn, _, _) in objects.items():
524             if fn == docname:
525                 del self.data['objects'][fullname]
526
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')
532
533         # Partial function to make building the response easier
534         make_ref = lambda fullname: make_refnode(builder, fromdocname, objects[fullname][0], fullname, contnode, fullname)
535
536         # Check for fully qualified references
537         if target in objects:
538             return make_ref(target)
539
540         # Try with package name prefixed
541         if package:
542             fullname = package + '.' + target
543             if fullname in objects:
544                 return make_ref(fullname)
545
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)
551
552         # Try to find a matching suffix
553         suffix = '.' + target
554         basename_match = None
555         basename_suffix = suffix.partition('(')[0]
556
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
562
563         if basename_match:
564             return make_ref(basename_match)
565
566         # Try creating an external documentation reference
567         ref = extdoc.get_javadoc_ref(self.env, target, target)
568
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)
572
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)
577
578         if ref:
579             ref.append(contnode)
580             return ref
581         else:
582             return None
583
584     def get_objects(self):
585         for refname, (docname, type, _) in self.data['objects'].items():
586             yield (refname, refname, type, docname, refname, 1)
587
588
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, '')
593     else:
594         return ('single', indextext, fullname, '', None)