Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
include a copy of javasphinx so that we can fix it on need
authorMartin Quinson <martin.quinson@ens-rennes.fr>
Sun, 13 Sep 2020 13:52:58 +0000 (15:52 +0200)
committerMartin Quinson <martin.quinson@ens-rennes.fr>
Sun, 13 Sep 2020 13:52:58 +0000 (15:52 +0200)
17 files changed:
docs/Build.sh
docs/requirements.txt
docs/source/_ext/javasphinx/.gitignore [new file with mode: 0644]
docs/source/_ext/javasphinx/LICENSE [new file with mode: 0644]
docs/source/_ext/javasphinx/MANIFEST.in [new file with mode: 0644]
docs/source/_ext/javasphinx/README.md [new file with mode: 0644]
docs/source/_ext/javasphinx/doc/conf.py [new file with mode: 0644]
docs/source/_ext/javasphinx/doc/index.rst [new file with mode: 0644]
docs/source/_ext/javasphinx/javasphinx/__init__.py [new file with mode: 0644]
docs/source/_ext/javasphinx/javasphinx/apidoc.py [new file with mode: 0755]
docs/source/_ext/javasphinx/javasphinx/compiler.py [new file with mode: 0644]
docs/source/_ext/javasphinx/javasphinx/domain.py [new file with mode: 0644]
docs/source/_ext/javasphinx/javasphinx/extdoc.py [new file with mode: 0644]
docs/source/_ext/javasphinx/javasphinx/formatter.py [new file with mode: 0644]
docs/source/_ext/javasphinx/javasphinx/htmlrst.py [new file with mode: 0644]
docs/source/_ext/javasphinx/javasphinx/util.py [new file with mode: 0644]
docs/source/_ext/javasphinx/setup.py [new file with mode: 0644]

index 0bb46f2..2b42bfd 100755 (executable)
@@ -21,7 +21,17 @@ if [ "x$1" != 'xjava' ] && [ -e source/java ] ; then
   echo "javasphinx not rerun: 'java' was not provided as an argument"
 else
   rm -rf source/java
-  javasphinx-apidoc --force -o source/java/ ../src/bindings/java/org/simgrid/msg
+  
+  # Use that script without installing javasphinx: javasphinx-apidoc --force -o source/java/ ../src/bindings/java/org/simgrid/msg
+  PYTHONPATH=${PYTHONPATH}:source/_ext/javasphinx python3 - --force -o source/java/ ../src/bindings/java/org/simgrid/msg <<EOF
+import re
+import sys
+from javasphinx.apidoc import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())
+EOF
+
   rm -f source/java/packages.rst # api_generated/source_java_packages.rst
   rm -f source/java/org/simgrid/msg/package-index.rst # api_generated/source_java_org_simgrid_msg_package-index.rst
   for f in source/java/org/simgrid/msg/* ; do
index 4302bfe..0f9b45d 100644 (file)
@@ -2,4 +2,3 @@ breathe
 sphinx>=1.8.0
 sphinx_rtd_theme
 sphinx_tabs
-javasphinx
diff --git a/docs/source/_ext/javasphinx/.gitignore b/docs/source/_ext/javasphinx/.gitignore
new file mode 100644 (file)
index 0000000..03ecc2b
--- /dev/null
@@ -0,0 +1,5 @@
+*.pyc
+dist/
+*.egg-info/
+.vscode
+.DS_Store
\ No newline at end of file
diff --git a/docs/source/_ext/javasphinx/LICENSE b/docs/source/_ext/javasphinx/LICENSE
new file mode 100644 (file)
index 0000000..d645695
--- /dev/null
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/docs/source/_ext/javasphinx/MANIFEST.in b/docs/source/_ext/javasphinx/MANIFEST.in
new file mode 100644 (file)
index 0000000..9561fb1
--- /dev/null
@@ -0,0 +1 @@
+include README.rst
diff --git a/docs/source/_ext/javasphinx/README.md b/docs/source/_ext/javasphinx/README.md
new file mode 100644 (file)
index 0000000..a523ad2
--- /dev/null
@@ -0,0 +1,12 @@
+
+# javasphinx
+
+[![Documentation Status](https://readthedocs.org/projects/bronto-javasphinx/badge/?version=latest)](http://bronto-javasphinx.readthedocs.io/en/latest/?badge=latest)
+
+**This project is no longer maintained and should be used for historical purposes only.**
+
+javasphinx is an extension to the Sphinx documentation system which adds support for documenting Java projects. It includes a Java domain for writing documentation manually and a javasphinx-apidoc utility which will automatically generate API documentation from existing Javadoc markup.
+
+javasphinx is available in the Python Package Index (PyPi) under the name _javasphinx_ and can be installed using tools such as `pip` or `easy_install`.
+
+Documentation for javasphinx is available at http://bronto-javasphinx.readthedocs.io
diff --git a/docs/source/_ext/javasphinx/doc/conf.py b/docs/source/_ext/javasphinx/doc/conf.py
new file mode 100644 (file)
index 0000000..de52125
--- /dev/null
@@ -0,0 +1,25 @@
+#
+# Copyright 2012-2015 Bronto Software, Inc. and contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+project = 'javasphinx'
+version = '0.9.15'
+release = version
+
+extensions = ['javasphinx']
+
+master_doc = 'index'
+copyright = u'2012-2017, Bronto Software Inc. and contributors'
+primary_domain = 'rst'
diff --git a/docs/source/_ext/javasphinx/doc/index.rst b/docs/source/_ext/javasphinx/doc/index.rst
new file mode 100644 (file)
index 0000000..8780a23
--- /dev/null
@@ -0,0 +1,219 @@
+
+#######################
+javasphinx User's Guide
+#######################
+
+Welcome to the javasphinx user's guide.
+
+Introduction
+============
+
+javasphinx is a Sphinx_ extension that provides a Sphinx domain_ for documenting
+Java projects and a ``javasphinx-apidoc`` command line tool for automatically
+generating API documentation from existing Java source code and Javadoc
+documentation.
+
+.. _Sphinx: http://sphinx-doc.org
+.. _domain: http://sphinx-doc.org/domains.html
+
+Installing
+==========
+
+javasphinx is available in the Python Package Index (PyPi) and can be installed
+using tools such as ``pip`` or ``easy_install``,
+
+.. code-block:: sh
+
+   $ pip install javasphinx
+
+or,
+
+.. code-block:: sh
+
+   $ easy_install -U javasphinx
+
+Configuration
+=============
+
+To enable javasphinx for your existing Sphinx configuration add ``'javasphinx'``
+to the list of extensions in your conf.py file. javasphinx can be configured to
+cross link to external sources of documentation using the ``javadoc_url_map``
+option,
+
+.. code-block:: python
+
+   javadoc_url_map = {
+       'com.netflix.curator' : ('http://netflix.github.com/curator/doc', 'javadoc'),
+       'org.springframework' : ('http://static.springsource.org/spring/docs/3.1.x/javadoc-api/', 'javadoc'),
+       'org.springframework.data.redis' : ('http://static.springsource.org/spring-data/data-redis/docs/current/api/', 'javadoc')
+   }
+
+Each key in the map should be a Java package. Each value is a tuple of the form
+``(base_url, doc_type)`` where ``base_url`` is the base URL of the documentation
+source, and ``doc_type`` is one of,
+
+``javadoc``
+  For documentation generated by the Javadoc tool *before* version 8.
+
+``javadoc8``
+  For documentation generated by the Javadoc tool after version 8. This is
+  required due to changes in how method anchors are generated (see JDK-8144118_).
+
+``sphinx``
+  For external documentation generated by javasphinx.
+
+When comparing referenced types to the list of available packages the longest
+match will be used. Entries for ``java``, ``javax``, ``org.xml``, and
+``org.w3c`` packages pointing to http://docs.oracle.com/javase/8/docs/api are
+included automatically and do not need to be defined explicitly.
+
+.. _JDK-8144118: https://bugs.openjdk.java.net/browse/JDK-8144118
+
+Java domain
+===========
+
+Directives
+----------
+
+The Java domain uses the name **java** and provides the following directives,
+
+.. rst:directive:: .. java:type:: type-signature
+
+   Describe a Java type. The signature can represent either a class, interface,
+   enum or annotation declaration.
+
+   Use the ``param`` field to document type parameters.
+
+   Example,
+
+   .. code-block:: rst
+
+      .. java:type:: public interface List<E> extends Collection<E>, Iterable<E>
+
+         An ordered collection (also known as a *sequence*)
+
+         :param E: type of item stored by the list
+
+   produces,
+
+      .. java:type:: public interface List<E> extends Collection<E>, Iterable<E>
+
+         An ordered collection (also known as a *sequence*)
+
+         :param E: type of item stored by the list
+
+.. rst:directive:: .. java:field:: field-signature
+
+   Describe a Java field.
+
+.. rst:directive:: .. java:method:: method-signature
+
+   Describe a Java method.
+
+   Use the ``param`` field to document parameters.
+
+   Use the ``throws`` field to document exceptions thrown by the method.
+
+   Use the ``return`` field to document the return type
+
+.. rst:directive:: .. java:constructor:: constructor-signature
+
+   Describe a Java constructor.
+
+   Use the ``param`` field to document parameters.
+
+   Use the ``throws`` field to document exceptions thrown by the constructor.
+
+.. rst:directive:: .. java:package:: package
+
+   Provide package-level documentation and also sets the active package for the
+   type, method, field, constructors, and references that follow.
+
+   Use the ``:noindex:`` option if the directive is only being used to specify
+   the active package. Only one directive for a given package should exclude
+   ``:noindex:``.
+
+.. rst:directive:: .. java:import:: package type
+
+   Declare the given type as being provided by the given package. This
+   information helps javasphinx create cross references for types in type,
+   method, and field declarations. It also allows explicit cross references
+   (using the ``java:ref`` role) to exclude the package qualification.
+
+The method, construct, field, and type directives all accept the following
+standard options,
+
+.. describe:: package
+
+   Specify the package the declaration is within. Can be used instead of, or to
+   override, a ``java:package`` directive.
+
+.. describe:: outertype
+
+   Specify the class/interface the documented object is contained within. This
+   option should be provided for any constructor, method, or field directive
+   that isn't nested within a corresponding type directive.
+
+Roles
+-----
+
+The following roles are provided,
+
+.. rst:role:: java:ref
+
+   This role can be used to create a cross reference to any object type within
+   the Java domain. Aliases for this role include ``java:meth``, ``java:type``,
+   ``java:field``, ``java:package``, and ``java:construct``.
+
+   An explicit title can be provided by using the standard ``title <reference>``
+   syntax.
+
+.. rst:role:: java:extdoc
+
+   This role can be used to explicitly link to an externally documented
+   type. The reference must be fully qualified and supports an explicit title
+   using the ``title <reference>`` syntax.
+
+   The ``java:ref`` role will also create external references as a fall-back if
+   it can't find a matching local declaration so using this role is not strictly
+   necessary.
+
+javasphinx-apidoc
+=================
+
+The ``javasphinx-apidoc`` tool is the counterpoint to the ``sphinx-apidoc`` tool
+within the Java domain. It can be used to generate reST source from existing
+Java source code which has been marked up with Javadoc-style comments. The
+generated reST is then processed alongside hand-written documentation by Sphinx.
+
+At minimum a source and destination directory must be provided. The input
+directory will be scanned for .java files and documentation will be generated
+for all non-private types and members. A separate output file will be generated
+for each type (including inner classes). Each file is put within a directory
+corresponding to its package (with periods replaced by directory separators) and
+with the basename of the file deriving from the type name. Inner types are
+placed in files with a basename using a hyphen to separate inner and outer
+types, e.g. ``OuterType-InnerType.rst``.
+
+By default ``javasphinx-apidoc`` will not override existing files. Two options
+can change this behavior,
+
+.. option:: -f, --force
+
+   All existing output files will be rewritten. If a cache directory is
+   specified it will be rebuilt.
+
+.. option:: -u, --update
+
+   Updated source files will have their corresponding output files
+   updated. Unchanged files will be left alone. Most projects will want to use
+   this option.
+
+For larger projects it is recommended to use a cache directory. This can speed
+up subsequent runs by an order of magnitude or more. Specify a directory to
+store cached output using the :option:`-c` option,
+
+.. option:: -c, --cache-dir
+
+   Specify a directory to cache intermediate documentation representations. This
+   directory will be created if it does not already exist.
diff --git a/docs/source/_ext/javasphinx/javasphinx/__init__.py b/docs/source/_ext/javasphinx/javasphinx/__init__.py
new file mode 100644 (file)
index 0000000..c6b9cb9
--- /dev/null
@@ -0,0 +1,24 @@
+#
+# Copyright 2012-2015 Bronto Software, Inc. and contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from .domain import JavaDomain
+from .extdoc import javadoc_role
+
+def setup(app):
+    app.add_domain(JavaDomain)
+
+    app.add_config_value('javadoc_url_map', dict(), '')
+    app.add_role('java:extdoc', javadoc_role)
diff --git a/docs/source/_ext/javasphinx/javasphinx/apidoc.py b/docs/source/_ext/javasphinx/javasphinx/apidoc.py
new file mode 100755 (executable)
index 0000000..b0de46f
--- /dev/null
@@ -0,0 +1,352 @@
+#
+# Copyright 2012-2015 Bronto Software, Inc. and contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from __future__ import print_function, unicode_literals
+
+try:
+   import cPickle as pickle
+except:
+   import pickle
+
+import hashlib
+import logging
+import sys
+import os
+import os.path
+
+from optparse import OptionParser
+
+import javalang
+
+import javasphinx.compiler as compiler
+import javasphinx.util as util
+
+def encode_output(s):
+   if isinstance(s, str):
+      return s
+   else:
+      return s.encode('utf-8')
+
+def find_source_files(input_path, excludes):
+    """ Get a list of filenames for all Java source files within the given
+    directory.
+
+    """
+
+    java_files = []
+
+    input_path = os.path.normpath(os.path.abspath(input_path))
+
+    for dirpath, dirnames, filenames in os.walk(input_path):
+        if is_excluded(dirpath, excludes):
+            del dirnames[:]
+            continue
+
+        for filename in filenames:
+            if filename.endswith(".java"):
+                java_files.append(os.path.join(dirpath, filename))
+
+    return java_files
+
+def write_toc(packages, opts):
+    doc = util.Document()
+    doc.add_heading(opts.toc_title, '=')
+
+    toc = util.Directive('toctree')
+    toc.add_option('maxdepth', '2')
+    doc.add_object(toc)
+
+    for package in sorted(packages.keys()):
+        toc.add_content("%s/package-index\n" % package.replace('.', '/'))
+
+    filename = 'packages.' + opts.suffix
+    fullpath = os.path.join(opts.destdir, filename)
+
+    if os.path.exists(fullpath) and not (opts.force or opts.update):
+        sys.stderr.write(fullpath + ' already exists. Use -f to overwrite.\n')
+        sys.exit(1)
+
+    f = open(fullpath, 'w')
+    f.write(encode_output(doc.build()))
+    f.close()
+
+def write_documents(packages, documents, sources, opts):
+    package_contents = dict()
+
+    # Write individual documents
+    for fullname, (package, name, document) in documents.items():
+        if is_package_info_doc(name):
+            continue
+
+        package_path = package.replace('.', os.sep)
+        filebasename = name.replace('.', '-')
+        filename = filebasename + '.' + opts.suffix
+        dirpath = os.path.join(opts.destdir, package_path)
+        fullpath = os.path.join(dirpath, filename)
+
+        if not os.path.exists(dirpath):
+            os.makedirs(dirpath)
+        elif os.path.exists(fullpath) and not (opts.force or opts.update):
+            sys.stderr.write(fullpath + ' already exists. Use -f to overwrite.\n')
+            sys.exit(1)
+
+        # Add to package indexes
+        package_contents.setdefault(package, list()).append(filebasename)
+
+        if opts.update and os.path.exists(fullpath):
+            # If the destination file is newer than the source file than skip
+            # writing it out
+            source_mod_time = os.stat(sources[fullname]).st_mtime
+            dest_mod_time = os.stat(fullpath).st_mtime
+
+            if source_mod_time < dest_mod_time:
+                continue
+
+        f = open(fullpath, 'w')
+        f.write(encode_output(document))
+        f.close()
+
+    # Write package-index for each package
+    for package, classes in package_contents.items():
+        doc = util.Document()
+        doc.add_heading(package, '=')
+
+        #Adds the package documentation (if any)
+        if packages[package] != '':
+            documentation = packages[package]
+            doc.add_line("\n%s" % documentation)
+
+        doc.add_object(util.Directive('java:package', package))
+
+        toc = util.Directive('toctree')
+        toc.add_option('maxdepth', '1')
+
+        classes.sort()
+        for filebasename in classes:
+            toc.add_content(filebasename + '\n')
+        doc.add_object(toc)
+
+        package_path = package.replace('.', os.sep)
+        filename = 'package-index.' + opts.suffix
+        dirpath = os.path.join(opts.destdir, package_path)
+        fullpath = os.path.join(dirpath, filename)
+
+        if not os.path.exists(dirpath):
+            os.makedirs(dirpath)
+        elif os.path.exists(fullpath) and not (opts.force or opts.update):
+            sys.stderr.write(fullpath + ' already exists. Use -f to overwrite.\n')
+            sys.exit(1)
+
+        f = open(fullpath, 'w')
+        f.write(encode_output(doc.build()))
+        f.close()
+
+def get_newer(a, b):
+    if not os.path.exists(a):
+        return b
+
+    if not os.path.exists(b):
+        return a
+
+    a_mtime = int(os.stat(a).st_mtime)
+    b_mtime = int(os.stat(b).st_mtime)
+
+    if a_mtime < b_mtime:
+        return b
+
+    return a
+
+def format_syntax_error(e):
+    rest = ""
+    if e.at.position:
+        value = e.at.value
+        pos = e.at.position
+        rest = ' at %s line %d, character %d' % (value, pos[0], pos[1])
+    return e.description + rest
+
+def generate_from_source_file(doc_compiler, source_file, cache_dir):
+    if cache_dir:
+        fingerprint = hashlib.md5(source_file.encode()).hexdigest()
+        cache_file = os.path.join(cache_dir, 'parsed-' + fingerprint + '.p')
+
+        if get_newer(source_file, cache_file) == cache_file:
+            return pickle.load(open(cache_file, 'rb'))
+    else:
+        cache_file = None
+
+    f = open(source_file)
+    source = f.read()
+    f.close()
+
+    try:
+        ast = javalang.parse.parse(source)
+    except javalang.parser.JavaSyntaxError as e:
+        util.error('Syntax error in %s: %s', source_file, format_syntax_error(e))
+    except Exception:
+        util.unexpected('Unexpected exception while parsing %s', source_file)
+
+    documents = {}
+    try:
+        if source_file.endswith("package-info.java"):
+            if ast.package is not None:
+                documentation = doc_compiler.compile_docblock(ast.package)
+                documents[ast.package.name] = (ast.package.name, 'package-info', documentation)
+        else:
+            documents = doc_compiler.compile(ast)
+    except Exception:
+        util.unexpected('Unexpected exception while compiling %s', source_file)
+
+    if cache_file:
+        dump_file = open(cache_file, 'wb')
+        pickle.dump(documents, dump_file)
+        dump_file.close()
+
+    return documents
+
+def generate_documents(source_files, cache_dir, verbose, member_headers, parser):
+    documents = {}
+    sources = {}
+    doc_compiler = compiler.JavadocRestCompiler(None, member_headers, parser)
+
+    for source_file in source_files:
+        if verbose:
+            print('Processing', source_file)
+
+        this_file_documents = generate_from_source_file(doc_compiler, source_file, cache_dir)
+        for fullname in this_file_documents:
+            sources[fullname] = source_file
+
+        documents.update(this_file_documents)
+
+    #Existing packages dict, where each key is a package name
+    #and each value is the package documentation (if any)
+    packages = {}
+
+    #Gets the name of the package where the document was declared
+    #and adds it to the packages dict with no documentation.
+    #Package documentation, if any, will be collected from package-info.java files.
+    for package, name, _ in documents.values():
+        packages[package] = ""
+
+    #Gets packages documentation from package-info.java documents (if any).
+    for package, name, content in documents.values():
+        if is_package_info_doc(name):
+            packages[package] = content
+
+    return packages, documents, sources
+
+def normalize_excludes(rootpath, excludes):
+    f_excludes = []
+    for exclude in excludes:
+        if not os.path.isabs(exclude) and not exclude.startswith(rootpath):
+            exclude = os.path.join(rootpath, exclude)
+        f_excludes.append(os.path.normpath(exclude) + os.path.sep)
+    return f_excludes
+
+def is_excluded(root, excludes):
+    sep = os.path.sep
+    if not root.endswith(sep):
+        root += sep
+    for exclude in excludes:
+        if root.startswith(exclude):
+            return True
+    return False
+
+def is_package_info_doc(document_name):
+    ''' Checks if the name of a document represents a package-info.java file. '''
+    return document_name == 'package-info'
+
+
+def main(argv=sys.argv):
+    logging.basicConfig(level=logging.WARN)
+
+    parser = OptionParser(
+        usage="""\
+usage: %prog [options] -o <output_path> <input_path> [exclude_paths, ...]
+
+Look recursively in <input_path> for Java sources files and create reST files
+for all non-private classes, organized by package under <output_path>. A package
+index (package-index.<ext>) will be created for each package, and a top level
+table of contents will be generated named packages.<ext>.
+
+Paths matching any of the given exclude_paths (interpreted as regular
+expressions) will be skipped.
+
+Note: By default this script will not overwrite already created files.""")
+
+    parser.add_option('-o', '--output-dir', action='store', dest='destdir',
+                      help='Directory to place all output', default='')
+    parser.add_option('-f', '--force', action='store_true', dest='force',
+                      help='Overwrite all files')
+    parser.add_option('-c', '--cache-dir', action='store', dest='cache_dir',
+                      help='Directory to stored cachable output')
+    parser.add_option('-u', '--update', action='store_true', dest='update',
+                      help='Overwrite new and changed files', default=False)
+    parser.add_option('-T', '--no-toc', action='store_true', dest='notoc',
+                      help='Don\'t create a table of contents file')
+    parser.add_option('-t', '--title', dest='toc_title', default='Javadoc',
+                      help='Title to use on table of contents')
+    parser.add_option('--no-member-headers', action='store_false', default=True, dest='member_headers',
+                      help='Don\'t generate headers for class members')
+    parser.add_option('-s', '--suffix', action='store', dest='suffix',
+                      help='file suffix (default: rst)', default='rst')
+    parser.add_option('-I', '--include', action='append', dest='includes',
+                      help='Additional input paths to scan', default=[])
+    parser.add_option('-p', '--parser', dest='parser_lib', default='lxml',
+                      help='Beautiful Soup---html parser library option.')
+    parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
+                      help='verbose output')
+
+    (opts, args) = parser.parse_args(argv[1:])
+
+    if not args:
+        parser.error('A source path is required.')
+
+    rootpath, excludes = args[0], args[1:]
+
+    input_paths = opts.includes
+    input_paths.append(rootpath)
+
+    if not opts.destdir:
+        parser.error('An output directory is required.')
+
+    if opts.suffix.startswith('.'):
+        opts.suffix = opts.suffix[1:]
+
+    for input_path in input_paths:
+        if not os.path.isdir(input_path):
+            sys.stderr.write('%s is not a directory.\n' % (input_path,))
+            sys.exit(1)
+
+    if not os.path.isdir(opts.destdir):
+        os.makedirs(opts.destdir)
+
+    if opts.cache_dir and not os.path.isdir(opts.cache_dir):
+        os.makedirs(opts.cache_dir)
+
+    excludes = normalize_excludes(rootpath, excludes)
+    source_files = []
+
+    for input_path in input_paths:
+        source_files.extend(find_source_files(input_path, excludes))
+
+    packages, documents, sources = generate_documents(source_files, opts.cache_dir, opts.verbose,
+                                                      opts.member_headers, opts.parser_lib)
+
+    write_documents(packages, documents, sources, opts)
+
+    if not opts.notoc:
+        write_toc(packages, opts)
diff --git a/docs/source/_ext/javasphinx/javasphinx/compiler.py b/docs/source/_ext/javasphinx/javasphinx/compiler.py
new file mode 100644 (file)
index 0000000..807d027
--- /dev/null
@@ -0,0 +1,345 @@
+#
+# Copyright 2012-2015 Bronto Software, Inc. and contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import javalang
+
+import javasphinx.formatter as formatter
+import javasphinx.util as util
+import javasphinx.htmlrst as htmlrst
+
+class JavadocRestCompiler(object):
+    """ Javadoc to ReST compiler. Builds ReST documentation from a Java syntax
+    tree. """
+
+    def __init__(self, filter=None, member_headers=True, parser='lxml'):
+        if filter:
+            self.filter = filter
+        else:
+            self.filter = self.__default_filter
+
+        self.converter = htmlrst.Converter(parser)
+
+        self.member_headers = member_headers
+
+    def __default_filter(self, node):
+        """Excludes private members and those tagged "@hide" / "@exclude" in their
+        docblocks.
+
+        """
+
+        if not isinstance(node, javalang.tree.Declaration):
+            return False
+
+        if 'private' in node.modifiers:
+            return False
+
+        if isinstance(node, javalang.tree.Documented) and node.documentation:
+            doc = javalang.javadoc.parse(node.documentation)
+            if 'hide' in doc.tags or 'exclude' in doc.tags:
+                return False
+
+        return True
+
+    def __html_to_rst(self, s):
+        return self.converter.convert(s)
+
+    def __output_doc(self, documented):
+        if not isinstance(documented, javalang.tree.Documented):
+            raise ValueError('node not documented')
+
+        output = util.Document()
+
+        if not documented.documentation:
+            return output
+
+        doc = javalang.javadoc.parse(documented.documentation)
+
+        if doc.description:
+            output.add(self.__html_to_rst(doc.description))
+            output.clear()
+
+        if doc.authors:
+            output.add_line(':author: %s' % (self.__html_to_rst(', '.join(doc.authors)),))
+
+        for name, value in doc.params:
+            output.add_line(':param %s: %s' % (name, self.__html_to_rst(value)))
+
+        for exception in doc.throws:
+            description = doc.throws[exception]
+            output.add_line(':throws %s: %s' % (exception, self.__html_to_rst(description)))
+
+        if doc.return_doc:
+            output.add_line(':return: %s' % (self.__html_to_rst(doc.return_doc),))
+
+        if doc.tags.get('see'):
+            output.clear()
+
+            see_also = ', '.join(self.__output_see(see) for see in doc.tags['see'])
+            output.add_line('**See also:** %s' % (see_also,))
+
+        return output
+
+    def __output_see(self, see):
+        """ Convert the argument to a @see tag to rest """
+
+        if see.startswith('<a href'):
+            # HTML link -- <a href="...">...</a>
+            return self.__html_to_rst(see)
+        elif '"' in see:
+            # Plain text
+            return see
+        else:
+            # Type reference (default)
+            return ':java:ref:`%s`' % (see.replace('#', '.').replace(' ', ''),)
+
+    def compile_type(self, declaration):
+        signature = util.StringBuilder()
+        formatter.output_declaration(declaration, signature)
+
+        doc = self.__output_doc(declaration)
+
+        directive = util.Directive('java:type', signature.build())
+        directive.add_content(doc)
+
+        return directive
+
+    def compile_enum_constant(self, enum, constant):
+        signature = util.StringBuilder()
+
+        for annotation in constant.annotations:
+            formatter.output_annotation(annotation, signature)
+
+        # All enum constants are public, static, and final
+        signature.append('public static final ')
+        signature.append(enum)
+        signature.append(' ')
+        signature.append(constant.name)
+
+        doc = self.__output_doc(constant)
+
+        directive = util.Directive('java:field', signature.build())
+        directive.add_content(doc)
+
+        return directive
+
+    def compile_field(self, field):
+        signature = util.StringBuilder()
+
+        for annotation in field.annotations:
+            formatter.output_annotation(annotation, signature)
+
+        formatter.output_modifiers(field.modifiers, signature)
+        signature.append(' ')
+
+        formatter.output_type(field.type, signature)
+        signature.append(' ')
+        signature.append(field.declarators[0].name)
+
+        doc = self.__output_doc(field)
+
+        directive = util.Directive('java:field', signature.build())
+        directive.add_content(doc)
+
+        return directive
+
+    def compile_constructor(self, constructor):
+        signature = util.StringBuilder()
+
+        for annotation in constructor.annotations:
+            formatter.output_annotation(annotation, signature)
+
+        formatter.output_modifiers(constructor.modifiers, signature)
+        signature.append(' ')
+
+        if constructor.type_parameters:
+            formatter.output_type_params(constructor.type_parameters, signature)
+            signature.append(' ')
+
+        signature.append(constructor.name)
+
+        signature.append('(')
+        formatter.output_list(formatter.output_formal_param, constructor.parameters, signature, ', ')
+        signature.append(')')
+
+        if constructor.throws:
+            signature.append(' throws ')
+            formatter.output_list(formatter.output_exception, constructor.throws, signature, ', ')
+
+        doc = self.__output_doc(constructor)
+
+        directive = util.Directive('java:constructor', signature.build())
+        directive.add_content(doc)
+
+        return directive
+
+    def compile_method(self, method):
+        signature = util.StringBuilder()
+
+        for annotation in method.annotations:
+            formatter.output_annotation(annotation, signature)
+
+        formatter.output_modifiers(method.modifiers, signature)
+        signature.append(' ')
+
+        if method.type_parameters:
+            formatter.output_type_params(method.type_parameters, signature)
+            signature.append(' ')
+
+        formatter.output_type(method.return_type, signature)
+        signature.append(' ')
+
+        signature.append(method.name)
+
+        signature.append('(')
+        formatter.output_list(formatter.output_formal_param, method.parameters, signature, ', ')
+        signature.append(')')
+
+        if method.throws:
+            signature.append(' throws ')
+            formatter.output_list(formatter.output_exception, method.throws, signature, ', ')
+
+        doc = self.__output_doc(method)
+
+        directive = util.Directive('java:method', signature.build())
+        directive.add_content(doc)
+
+        return directive
+
+    def compile_type_document(self, imports_block, package, name, declaration):
+        """ Compile a complete document, documenting a type and its members """
+
+        outer_type = name.rpartition('.')[0]
+
+        document = util.Document()
+        document.add(imports_block)
+        document.add_heading(name, '=')
+
+        method_summary = util.StringBuilder()
+        document.add_object(method_summary)
+
+        package_dir = util.Directive('java:package', package)
+        package_dir.add_option('noindex')
+        document.add_object(package_dir)
+
+        # Add type-level documentation
+        type_dir = self.compile_type(declaration)
+        if outer_type:
+            type_dir.add_option('outertype', outer_type)
+        document.add_object(type_dir)
+
+        if isinstance(declaration, javalang.tree.EnumDeclaration):
+            enum_constants = list(declaration.body.constants)
+            enum_constants.sort(key=lambda c: c.name)
+
+            document.add_heading('Enum Constants')
+            for enum_constant in enum_constants:
+                if self.member_headers:
+                    document.add_heading(enum_constant.name, '^')
+                c = self.compile_enum_constant(name, enum_constant)
+                c.add_option('outertype', name)
+                document.add_object(c)
+
+        fields = list(filter(self.filter, declaration.fields))
+        if fields:
+            document.add_heading('Fields', '-')
+            fields.sort(key=lambda f: f.declarators[0].name)
+            for field in fields:
+                if self.member_headers:
+                    document.add_heading(field.declarators[0].name, '^')
+                f = self.compile_field(field)
+                f.add_option('outertype', name)
+                document.add_object(f)
+
+        constructors = list(filter(self.filter, declaration.constructors))
+        if constructors:
+            document.add_heading('Constructors', '-')
+            constructors.sort(key=lambda c: c.name)
+            for constructor in constructors:
+                if self.member_headers:
+                    document.add_heading(constructor.name, '^')
+                c = self.compile_constructor(constructor)
+                c.add_option('outertype', name)
+                document.add_object(c)
+
+        methods = list(filter(self.filter, declaration.methods))
+        if methods:
+            document.add_heading('Methods', '-')
+            methods.sort(key=lambda m: m.name)
+            for method in methods:
+                if self.member_headers:
+                    document.add_heading(method.name, '^')
+                m = self.compile_method(method)
+                m.add_option('outertype', name)
+                document.add_object(m)
+
+        return document
+
+    def compile(self, ast):
+        """ Compile autodocs for the given Java syntax tree. Documents will be
+        returned documenting each separate type. """
+
+        documents = {}
+
+        imports = util.StringBuilder()
+        for imp in ast.imports:
+            if imp.static or imp.wildcard:
+                continue
+
+            package_parts = []
+            cls_parts = []
+
+            for part in imp.path.split('.'):
+                if cls_parts or part[0].isupper():
+                    cls_parts.append(part)
+                else:
+                    package_parts.append(part)
+
+
+            # If the import's final part wasn't capitalized,
+            # append it to the class parts anyway so sphinx doesn't complain.
+            if cls_parts == []:
+                cls_parts.append(package_parts.pop())
+
+            package = '.'.join(package_parts)
+            cls = '.'.join(cls_parts)
+
+            imports.append(util.Directive('java:import', package + ' ' + cls).build())
+        import_block = imports.build()
+
+        if not ast.package:
+            raise ValueError('File must have package declaration')
+
+        package = ast.package.name
+        type_declarations = []
+        for path, node in ast.filter(javalang.tree.TypeDeclaration):
+            if not self.filter(node):
+                continue
+
+            classes = [n.name for n in path if isinstance(n, javalang.tree.TypeDeclaration)]
+            classes.append(node.name)
+
+            name = '.'.join(classes)
+            type_declarations.append((package, name, node))
+
+        for package, name, declaration in type_declarations:
+            full_name = package + '.' + name
+            document = self.compile_type_document(import_block, package, name, declaration)
+            documents[full_name] = (package, name, document.build())
+        return documents
+
+    def compile_docblock(self, documented):
+        ''' Compiles a single, standalone docblock. '''
+        return self.__output_doc(documented).build()
diff --git a/docs/source/_ext/javasphinx/javasphinx/domain.py b/docs/source/_ext/javasphinx/javasphinx/domain.py
new file mode 100644 (file)
index 0000000..275f6c8
--- /dev/null
@@ -0,0 +1,594 @@
+#
+# Copyright 2012-2015 Bronto Software, Inc. and contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import re
+import string
+
+from docutils import nodes
+from docutils.parsers.rst import Directive, directives
+
+from sphinx import addnodes, version_info
+from sphinx.roles import XRefRole
+from sphinx.locale import _
+from sphinx.domains import Domain, ObjType
+from sphinx.directives import ObjectDescription
+from sphinx.util.nodes import make_refnode
+from sphinx.util.docfields import Field, TypedField, GroupedField
+
+import javalang
+
+import javasphinx.extdoc as extdoc
+import javasphinx.formatter as formatter
+import javasphinx.util as util
+
+# Classes in java.lang. These are available without an import.
+java_dot_lang = set([
+    'AbstractMethodError', 'Appendable', 'ArithmeticException',
+    'ArrayIndexOutOfBoundsException', 'ArrayStoreException', 'AssertionError',
+    'AutoCloseable', 'Boolean', 'BootstrapMethodError', 'Byte', 'Character',
+    'CharSequence', 'Class', 'ClassCastException', 'ClassCircularityError',
+    'ClassFormatError', 'ClassLoader', 'ClassNotFoundException', 'ClassValue',
+    'Cloneable', 'CloneNotSupportedException', 'Comparable', 'Compiler',
+    'Deprecated', 'Double', 'Enum', 'EnumConstantNotPresentException', 'Error',
+    'Exception', 'ExceptionInInitializerError', 'Float', 'IllegalAccessError',
+    'IllegalAccessException', 'IllegalArgumentException',
+    'IllegalMonitorStateException', 'IllegalStateException',
+    'IllegalThreadStateException', 'IncompatibleClassChangeError',
+    'IndexOutOfBoundsException', 'InheritableThreadLocal', 'InstantiationError',
+    'InstantiationException', 'Integer', 'InternalError', 'InterruptedException',
+    'Iterable', 'LinkageError', 'Long', 'Math', 'NegativeArraySizeException',
+    'NoClassDefFoundError', 'NoSuchFieldError', 'NoSuchFieldException',
+    'NoSuchMethodError', 'NoSuchMethodException', 'NullPointerException', 'Number',
+    'NumberFormatException', 'Object', 'OutOfMemoryError', 'Override', 'Package',
+    'Process', 'ProcessBuilder', 'Readable', 'ReflectiveOperationException',
+    'Runnable', 'Runtime', 'RuntimeException', 'RuntimePermission', 'SafeVarargs',
+    'SecurityException', 'SecurityManager', 'Short', 'StackOverflowError',
+    'StackTraceElement', 'StrictMath', 'String', 'StringBuffer', 'StringBuilder',
+    'StringIndexOutOfBoundsException', 'SuppressWarnings', 'System', 'Thread',
+    'ThreadDeath', 'ThreadGroup', 'ThreadLocal', 'Throwable',
+    'TypeNotPresentException', 'UnknownError', 'UnsatisfiedLinkError',
+    'UnsupportedClassVersionError', 'UnsupportedOperationException', 'VerifyError',
+    'VirtualMachineError', 'Void'])
+
+class JavaObject(ObjectDescription):
+    option_spec = {
+        'noindex': directives.flag,
+        'package': directives.unchanged,
+        'outertype': directives.unchanged
+    }
+
+    def _build_ref_node(self, target):
+        ref = addnodes.pending_xref('', refdomain='java', reftype='type', reftarget=target, modname=None, classname=None)
+        ref['java:outertype'] = self.get_type()
+
+        package = self.env.temp_data.get('java:imports', dict()).get(target, None)
+
+        if not package and target in java_dot_lang:
+            package = 'java.lang'
+
+        if package:
+            ref['java:imported'] = True
+            ref['java:package'] = package
+        else:
+            ref['java:imported'] = False
+            ref['java:package'] = self.get_package()
+
+        return ref
+
+    def _build_type_node(self, typ):
+        if isinstance(typ, javalang.tree.ReferenceType):
+            if typ.dimensions:
+                dim = '[]' * len(typ.dimensions)
+            else:
+                dim = ''
+
+            target = typ.name
+            parts = []
+
+            while typ:
+                ref_node = self._build_ref_node(target)
+                ref_node += nodes.Text(typ.name, typ.name)
+                parts.append(ref_node)
+
+                if typ.arguments:
+                    parts.append(nodes.Text('<', '<'))
+
+                    first = True
+                    for type_arg in typ.arguments:
+                        if first:
+                            first = False
+                        else:
+                            parts.append(nodes.Text(', ', ', '))
+
+                        if type_arg.pattern_type == '?':
+                            parts.append(nodes.Text('?', '?'))
+                        else:
+                            if type_arg.pattern_type:
+                                s = '? %s ' % (type_arg.pattern_type,)
+                                parts.append(nodes.Text(s, s))
+                            parts.extend(self._build_type_node(type_arg.type))
+
+                    parts.append(nodes.Text('>', '>'))
+
+                typ = typ.sub_type
+
+                if typ:
+                    target = target + '.' + typ.name
+                    parts.append(nodes.Text('.', '.'))
+                elif dim:
+                    parts.append(nodes.Text(dim, dim))
+
+            return parts
+        else:
+            type_repr = formatter.output_type(typ).build()
+            return [nodes.Text(type_repr, type_repr)]
+
+    def _build_type_node_list(self, types):
+        parts = self._build_type_node(types[0])
+        for typ in types[1:]:
+            parts.append(nodes.Text(', ', ', '))
+            parts.extend(self._build_type_node(typ))
+        return parts
+
+    def handle_signature(self, sig, signode):
+        handle_name = 'handle_%s_signature' % (self.objtype,)
+        handle = getattr(self, handle_name, None)
+
+        if handle:
+            return handle(sig, signode)
+        else:
+            raise NotImplementedError
+
+    def get_index_text(self, package, type, name):
+        raise NotImplementedError
+
+    def get_package(self):
+        return self.options.get('package', self.env.temp_data.get('java:package'))
+
+    def get_type(self):
+        return self.options.get('outertype', '.'.join(self.env.temp_data.get('java:outertype', [])))
+
+    def add_target_and_index(self, name, sig, signode):
+        package = self.get_package()
+        type = self.get_type();
+
+        fullname = '.'.join(filter(None, (package, type, name)))
+        basename = fullname.partition('(')[0]
+
+        # note target
+        if fullname not in self.state.document.ids:
+            signode['names'].append(fullname)
+            signode['ids'].append(fullname)
+            signode['first'] = (not self.names)
+            self.state.document.note_explicit_target(signode)
+
+            objects = self.env.domaindata['java']['objects']
+            if fullname in objects:
+                self.state_machine.reporter.warning(
+                    'duplicate object description of %s, ' % fullname +
+                    'other instance in ' + self.env.doc2path(objects[fullname][0]) +
+                    ', use :noindex: for one of them',
+                    line=self.lineno)
+
+            objects[fullname] = (self.env.docname, self.objtype, basename)
+
+        indextext = self.get_index_text(package, type, name)
+        if indextext:
+            self.indexnode['entries'].append(_create_indexnode(indextext, fullname))
+
+    def before_content(self):
+        self.set_type = False
+
+        if self.objtype == 'type' and self.names:
+            self.set_type = True
+            self.env.temp_data.setdefault('java:outertype', list()).append(self.names[0])
+
+    def after_content(self):
+        if self.set_type:
+            self.env.temp_data['java:outertype'].pop()
+
+class JavaMethod(JavaObject):
+    doc_field_types = [
+        TypedField('parameter', label=_('Parameters'),
+                   names=('param', 'parameter', 'arg', 'argument'),
+                   typerolename='type', typenames=('type',)),
+        Field('returnvalue', label=_('Returns'), has_arg=False,
+              names=('returns', 'return')),
+        GroupedField('throws', names=('throws',), label=_('Throws'), rolename='type')
+    ]
+
+    def handle_method_signature(self, sig, signode):
+        try:
+            member = javalang.parse.parse_member_signature(sig)
+        except javalang.parser.JavaSyntaxError:
+            raise self.error("syntax error in method signature")
+
+        if not isinstance(member, javalang.tree.MethodDeclaration):
+            raise self.error("expected method declaration")
+
+        mods = formatter.output_modifiers(member.modifiers).build()
+        signode += nodes.Text(mods + ' ', mods + ' ')
+
+        if member.type_parameters:
+            type_params = formatter.output_type_params(member.type_parameters).build()
+            signode += nodes.Text(type_params, type_params)
+            signode += nodes.Text(' ', ' ')
+
+        rnode = addnodes.desc_type('', '')
+        rnode += self._build_type_node(member.return_type)
+
+        signode += rnode
+        signode += nodes.Text(' ', ' ')
+        signode += addnodes.desc_name(member.name, member.name)
+
+        paramlist = addnodes.desc_parameterlist()
+        for parameter in member.parameters:
+            param = addnodes.desc_parameter('', '', noemph=True)
+            param += self._build_type_node(parameter.type)
+
+            if parameter.varargs:
+                param += nodes.Text('...', '')
+
+            param += nodes.emphasis(' ' + parameter.name, ' ' + parameter.name)
+            paramlist += param
+        signode += paramlist
+
+        param_reprs = [formatter.output_type(param.type, with_generics=False).build() for param in member.parameters]
+        return member.name + '(' + ', '.join(param_reprs) + ')'
+
+    def get_index_text(self, package, type, name):
+        return _('%s (Java method)' % (name,))
+
+class JavaConstructor(JavaObject):
+    doc_field_types = [
+        TypedField('parameter', label=_('Parameters'),
+                   names=('param', 'parameter', 'arg', 'argument'),
+                   typerolename='type', typenames=('type',)),
+        GroupedField('throws', names=('throws',), label=_('Throws'))
+    ]
+
+    def handle_constructor_signature(self, sig, signode):
+        try:
+            member = javalang.parse.parse_constructor_signature(sig)
+        except javalang.parser.JavaSyntaxError:
+            raise self.error("syntax error in constructor signature")
+
+        if not isinstance(member, javalang.tree.ConstructorDeclaration):
+            raise self.error("expected constructor declaration")
+
+        mods = formatter.output_modifiers(member.modifiers).build()
+        signode += nodes.Text(mods + ' ', mods + ' ')
+
+        signode += addnodes.desc_name(member.name, member.name)
+
+        paramlist = addnodes.desc_parameterlist()
+        for parameter in member.parameters:
+            param = addnodes.desc_parameter('', '', noemph=True)
+            param += self._build_type_node(parameter.type)
+
+            if parameter.varargs:
+                param += nodes.Text('...', '')
+
+            param += nodes.emphasis(' ' + parameter.name, ' ' + parameter.name)
+            paramlist += param
+        signode += paramlist
+
+        param_reprs = [formatter.output_type(param.type, with_generics=False).build() for param in member.parameters]
+        return '%s(%s)' % (member.name, ', '.join(param_reprs))
+
+    def get_index_text(self, package, type, name):
+        return _('%s (Java constructor)' % (name,))
+
+class JavaType(JavaObject):
+    doc_field_types = [
+        GroupedField('parameter', names=('param',), label=_('Parameters'))
+    ]
+
+    declaration_type = None
+
+    def handle_type_signature(self, sig, signode):
+        try:
+            member = javalang.parse.parse_type_signature(sig)
+        except javalang.parser.JavaSyntaxError:
+            raise self.error("syntax error in field signature")
+
+        if isinstance(member, javalang.tree.ClassDeclaration):
+            self.declaration_type = 'class'
+        elif isinstance(member, javalang.tree.InterfaceDeclaration):
+            self.declaration_type = 'interface'
+        elif isinstance(member, javalang.tree.EnumDeclaration):
+            self.declaration_type = 'enum'
+        elif isinstance(member, javalang.tree.AnnotationDeclaration):
+            self.declaration_type = 'annotation'
+        else:
+            raise self.error("expected type declaration")
+
+        mods = formatter.output_modifiers(member.modifiers).build()
+        signode += nodes.Text(mods + ' ', mods + ' ')
+
+        if self.declaration_type == 'class':
+            signode += nodes.Text('class ', 'class ')
+        elif self.declaration_type == 'interface':
+            signode += nodes.Text('interface ', 'interface ')
+        elif self.declaration_type == 'enum':
+            signode += nodes.Text('enum ', 'enum ')
+        elif self.declaration_type == 'annotation':
+            signode += nodes.Text('@interface ', '@interface ')
+
+        signode += addnodes.desc_name(member.name, member.name)
+
+        if self.declaration_type in ('class', 'interface') and member.type_parameters:
+            type_params = formatter.output_type_params(member.type_parameters).build()
+            signode += nodes.Text(type_params, type_params)
+
+        if self.declaration_type == 'class':
+            if member.extends:
+                extends = ' extends '
+                signode += nodes.Text(extends, extends)
+                signode += self._build_type_node(member.extends)
+            if member.implements:
+                implements = ' implements '
+                signode += nodes.Text(implements, implements)
+                signode += self._build_type_node_list(member.implements)
+        elif self.declaration_type == 'interface':
+            if member.extends:
+                extends = ' extends '
+                signode += nodes.Text(extends, extends)
+                signode += self._build_type_node_list(member.extends)
+        elif self.declaration_type == 'enum':
+            if member.implements:
+                implements = ' implements '
+                signode += nodes.Text(implements, implements)
+                signode += self._build_type_node_list(member.implements)
+
+        return member.name
+
+    def get_index_text(self, package, type, name):
+        return _('%s (Java %s)' % (name, self.declaration_type))
+
+class JavaField(JavaObject):
+    def handle_field_signature(self, sig, signode):
+        try:
+            member = javalang.parse.parse_member_signature(sig)
+        except javalang.parser.JavaSyntaxError:
+            raise self.error("syntax error in field signature")
+
+        if not isinstance(member, javalang.tree.FieldDeclaration):
+            raise self.error("expected field declaration")
+
+        mods = formatter.output_modifiers(member.modifiers).build()
+        signode += nodes.Text(mods + ' ', mods + ' ')
+
+        tnode = addnodes.desc_type('', '')
+        tnode += self._build_type_node(member.type)
+
+        signode += tnode
+        signode += nodes.Text(' ', ' ')
+
+        if len(member.declarators) > 1:
+            self.error('only one field may be documented at a time')
+
+        declarator = member.declarators[0]
+        signode += addnodes.desc_name(declarator.name, declarator.name)
+
+        dim = '[]' * len(declarator.dimensions)
+        signode += nodes.Text(dim)
+
+        if declarator.initializer and isinstance(declarator.initializer, javalang.tree.Literal):
+            signode += nodes.Text(' = ' + declarator.initializer.value)
+
+        return declarator.name
+
+    def get_index_text(self, package, type, name):
+        return _('%s (Java field)' % (name,))
+
+class JavaPackage(Directive):
+    """
+    Directive to mark description of a new package.
+    """
+
+    has_content = False
+    required_arguments = 1
+    optional_arguments = 0
+    final_argument_whitespace = False
+    option_spec = {
+        'noindex': directives.flag,
+    }
+
+    def run(self):
+        env = self.state.document.settings.env
+        package = self.arguments[0].strip()
+        noindex = 'noindex' in self.options
+        env.temp_data['java:package'] = package
+        env.domaindata['java']['objects'][package] = (env.docname, 'package', package)
+        ret = []
+
+        if not noindex:
+            targetnode = nodes.target('', '', ids=['package-' + package], ismod=True)
+            self.state.document.note_explicit_target(targetnode)
+
+            # the platform and synopsis aren't printed; in fact, they are only
+            # used in the modindex currently
+            ret.append(targetnode)
+
+            indextext = _('%s (package)') % (package,)
+            inode = addnodes.index(entries=[_create_indexnode(indextext, 'package-' + package)])
+            ret.append(inode)
+
+        return ret
+
+class JavaImport(Directive):
+    """
+    This directive is just to tell Sphinx the source of a referenced type.
+    """
+
+    has_content = False
+    required_arguments = 2
+    optional_arguments = 0
+    final_argument_whitespace = False
+    option_spec = {}
+
+    def run(self):
+        env = self.state.document.settings.env
+        package, typename = self.arguments
+
+        env.temp_data.setdefault('java:imports', dict())[typename] = package
+        return []
+
+class JavaXRefRole(XRefRole):
+    def process_link(self, env, refnode, has_explicit_title, title, target):
+        refnode['java:outertype'] = '.'.join(env.temp_data.get('java:outertype', list()))
+
+        target = target.lstrip('~')
+
+        # Strip a method component from the target
+        basetype = target
+        if '(' in basetype:
+            basetype = basetype.partition('(')[0]
+            if '.' in basetype:
+                basetype = basetype.rpartition('.')[0]
+
+        package = env.temp_data.get('java:imports', dict()).get(basetype, None)
+
+        if package:
+            refnode['java:imported'] = True
+            refnode['java:package'] = package
+        else:
+            refnode['java:imported'] = False
+            refnode['java:package'] = env.temp_data.get('java:package')
+
+        if not has_explicit_title:
+            # if the first character is a tilde, don't display the module/class
+            # parts of the contents
+            if title[0:1] == '~':
+                title = title.partition('(')[0]
+                title = title[1:]
+                dot = title.rfind('.')
+                if dot != -1:
+                    title = title[dot+1:]
+
+        return title, target
+
+class JavaDomain(Domain):
+    """Java language domain."""
+    name = 'java'
+    label = 'Java'
+
+    object_types = {
+        'package':     ObjType(_('package'), 'package', 'ref'),
+        'type':        ObjType(_('type'), 'type', 'ref'),
+        'field':       ObjType(_('field'), 'field', 'ref'),
+        'constructor': ObjType(_('constructor'), 'construct', 'ref'),
+        'method':      ObjType(_('method'), 'meth', 'ref')
+    }
+
+    directives = {
+        'package':        JavaPackage,
+        'type':           JavaType,
+        'field':          JavaField,
+        'constructor':    JavaConstructor,
+        'method':         JavaMethod,
+        'import':         JavaImport
+    }
+
+    roles = {
+        'package':   JavaXRefRole(),
+        'type':      JavaXRefRole(),
+        'field':     JavaXRefRole(),
+        'construct': JavaXRefRole(),
+        'meth':      JavaXRefRole(),
+        'ref':       JavaXRefRole(),
+    }
+
+    initial_data = {
+        'objects': {},  # fullname -> docname, objtype, basename
+    }
+
+    def clear_doc(self, docname):
+        objects = dict(self.data['objects'])
+
+        for fullname, (fn, _, _) in objects.items():
+            if fn == docname:
+                del self.data['objects'][fullname]
+
+    def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
+        objects = self.data['objects']
+        package = node.get('java:package')
+        imported = node.get('java:imported')
+        type_context = node.get('java:outertype')
+
+        # Partial function to make building the response easier
+        make_ref = lambda fullname: make_refnode(builder, fromdocname, objects[fullname][0], fullname, contnode, fullname)
+
+        # Check for fully qualified references
+        if target in objects:
+            return make_ref(target)
+
+        # Try with package name prefixed
+        if package:
+            fullname = package + '.' + target
+            if fullname in objects:
+                return make_ref(fullname)
+
+        # Try with package and type prefixed
+        if package and type_context:
+            fullname = package + '.' + type_context + '.' + target
+            if fullname in objects:
+                return make_ref(fullname)
+
+        # Try to find a matching suffix
+        suffix = '.' + target
+        basename_match = None
+        basename_suffix = suffix.partition('(')[0]
+
+        for fullname, (_, _, basename) in objects.items():
+            if fullname.endswith(suffix):
+                return make_ref(fullname)
+            elif basename.endswith(basename_suffix):
+                basename_match = fullname
+
+        if basename_match:
+            return make_ref(basename_match)
+
+        # Try creating an external documentation reference
+        ref = extdoc.get_javadoc_ref(self.env, target, target)
+
+        if not ref and target in java_dot_lang:
+            fulltarget = 'java.lang.' + target
+            ref = extdoc.get_javadoc_ref(self.env, fulltarget, fulltarget)
+
+        # If the target was imported try with the package prefixed
+        if not ref and imported:
+            fulltarget = package + '.' + target
+            ref = extdoc.get_javadoc_ref(self.env, fulltarget, fulltarget)
+
+        if ref:
+            ref.append(contnode)
+            return ref
+        else:
+            return None
+
+    def get_objects(self):
+        for refname, (docname, type, _) in self.data['objects'].items():
+            yield (refname, refname, type, docname, refname, 1)
+
+
+def _create_indexnode(indextext, fullname):
+    # See https://github.com/sphinx-doc/sphinx/issues/2673
+    if version_info < (1, 4):
+        return ('single', indextext, fullname, '')
+    else:
+        return ('single', indextext, fullname, '', None)
diff --git a/docs/source/_ext/javasphinx/javasphinx/extdoc.py b/docs/source/_ext/javasphinx/javasphinx/extdoc.py
new file mode 100644 (file)
index 0000000..1586bc2
--- /dev/null
@@ -0,0 +1,124 @@
+#
+# Copyright 2012-2015 Bronto Software, Inc. and contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import re
+
+from docutils import nodes, utils
+from sphinx.util.nodes import split_explicit_title
+
+def get_javadoc_ref(app, rawtext, text):
+    javadoc_url_map = app.config.javadoc_url_map
+
+    # Add default Java SE sources
+    if not javadoc_url_map.get("java"):
+        javadoc_url_map["java"] = ("http://docs.oracle.com/javase/8/docs/api", 'javadoc8')
+    if not javadoc_url_map.get("javax"):
+        javadoc_url_map["javax"] = ("http://docs.oracle.com/javase/8/docs/api", 'javadoc8')
+    if not javadoc_url_map.get("org.xml"):
+        javadoc_url_map["org.xml"] = ("http://docs.oracle.com/javase/8/docs/api", 'javadoc8')
+    if not javadoc_url_map.get("org.w3c"):
+        javadoc_url_map["org.w3c"] = ("http://docs.oracle.com/javase/8/docs/api", 'javadoc8')
+
+    source = None
+    package = ''
+    method = None
+
+    if '(' in text:
+        # If the javadoc contains a line like this:
+        # {@link #sort(List)}
+        # there is no package so the text.rindex will fail
+        try:
+            split_point = text.rindex('.', 0, text.index('('))
+            method = text[split_point + 1:]
+            text = text[:split_point]
+        except ValueError:
+            pass
+
+    for pkg, (baseurl, ext_type) in javadoc_url_map.items():
+        if text.startswith(pkg + '.') and len(pkg) > len(package):
+            source = baseurl, ext_type
+            package = pkg
+
+    if not source:
+        return None
+
+    baseurl, ext_type = source
+
+    package_parts = []
+    cls_parts = []
+
+    for part in text.split('.'):
+        if cls_parts or part[0].isupper():
+            cls_parts.append(part)
+        else:
+            package_parts.append(part)
+
+    package = '.'.join(package_parts)
+    cls = '.'.join(cls_parts)
+
+    if not baseurl.endswith('/'):
+        baseurl = baseurl + '/'
+
+    if ext_type == 'javadoc':
+        if not cls:
+            cls = 'package-summary'
+        source = baseurl + package.replace('.', '/') + '/' + cls + '.html'
+        if method:
+            source = source + '#' + method
+    elif ext_type == 'javadoc8':
+        if not cls:
+            cls = 'package-summary'
+        source = baseurl + package.replace('.', '/') + '/' + cls + '.html'
+        if method:
+            source = source + '#' + re.sub(r'[()]', '-', method)
+    elif ext_type == 'sphinx':
+        if not cls:
+            cls = 'package-index'
+        source = baseurl + package.replace('.', '/') + '/' + cls.replace('.', '-') + '.html'
+        if method:
+            source = source + '#' + package + '.' + cls + '.' + method
+    else:
+        raise ValueError('invalid target specifier ' + ext_type)
+
+    title = '.'.join(filter(None, (package, cls, method)))
+    node = nodes.reference(rawtext, '')
+    node['refuri'] = source
+    node['reftitle'] = title
+
+    return node
+
+def javadoc_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
+    """ Role for linking to external Javadoc """
+
+    has_explicit_title, title, target = split_explicit_title(text)
+    title = utils.unescape(title)
+    target = utils.unescape(target)
+
+    if not has_explicit_title:
+        target = target.lstrip('~')
+
+        if title[0] == '~':
+            title = title[1:].rpartition('.')[2]
+
+    app = inliner.document.settings.env.app
+    ref = get_javadoc_ref(app, rawtext, target)
+
+    if not ref:
+         raise ValueError("no Javadoc source found for %s in javadoc_url_map" % (target,))
+
+    ref.append(nodes.Text(title, title))
+
+    return [ref], []
diff --git a/docs/source/_ext/javasphinx/javasphinx/formatter.py b/docs/source/_ext/javasphinx/javasphinx/formatter.py
new file mode 100644 (file)
index 0000000..51b4ce7
--- /dev/null
@@ -0,0 +1,163 @@
+#
+# Copyright 2012-2015 Bronto Software, Inc. and contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Convert Java syntax tree nodes to string representations.
+
+"""
+
+import javalang
+
+from .util import StringBuilder
+
+# The order for displaying modifiers
+__modifiers_order = ('public', 'protected', 'private', 'static', 'abstract', 'final',
+                     'native', 'synchronized', 'transient', 'volatile', 'strictfp')
+
+def formatter(f):
+    def _f(node, output=None, **kwargs):
+        if output is None:
+            output = StringBuilder()
+
+        f(node, output, **kwargs)
+        return output
+    return _f
+
+def output_list(f, items, output=None, sep=', '):
+    if items:
+        f(items[0], output)
+        for item in items[1:]:
+            output.append(sep)
+            f(item, output)
+
+@formatter
+def output_annotation(annotation, output):
+    output.append('@')
+    output.append(annotation.name)
+    output.append(' ')
+
+@formatter
+def output_type(type, output, with_generics=True):
+    if not type:
+        output.append('void')
+        return
+
+    if type.dimensions:
+        dim = '[]' * len(type.dimensions)
+    else:
+        dim = ''
+
+    if isinstance(type, javalang.tree.BasicType):
+        output.append(type.name)
+    else:
+        while type:
+            output.append(type.name)
+
+            if with_generics:
+                output_type_args(type.arguments, output)
+
+            type = type.sub_type
+
+            if type:
+                output.append('.')
+    output.append(dim)
+
+@formatter
+def output_exception(exception, output):
+    output.append(exception)
+
+@formatter
+def output_type_arg(type_arg, output):
+    if type_arg.pattern_type == '?':
+        output.append('?')
+    else:
+        if type_arg.pattern_type:
+            output.append('? ')
+            output.append(type_arg.pattern_type)
+            output.append(' ')
+
+        output_type(type_arg.type, output)
+
+@formatter
+def output_type_args(type_args, output):
+    if type_args:
+        output.append('<')
+        output_list(output_type_arg, type_args, output, ', ')
+        output.append('>')
+
+@formatter
+def output_type_param(type_param, output):
+    output.append(type_param.name)
+
+    if type_param.extends:
+        output.append(' extends ')
+        output_list(output_type, type_param.extends, output, ' & ')
+
+@formatter
+def output_type_params(type_params, output):
+    if type_params:
+        output.append('<')
+        output_list(output_type_param, type_params, output, ', ')
+        output.append('>')
+
+@formatter
+def output_declaration(declaration, output):
+    for annotation in declaration.annotations:
+        output_annotation(annotation, output)
+
+    output_modifiers(declaration.modifiers, output)
+    output.append(' ')
+
+    if isinstance(declaration, javalang.tree.ClassDeclaration):
+        output.append('class ')
+    elif isinstance(declaration, javalang.tree.EnumDeclaration):
+        output.append('enum ')
+    elif isinstance(declaration, javalang.tree.InterfaceDeclaration):
+        output.append('interface ')
+    elif isinstance(declaration, javalang.tree.AnnotationDeclaration):
+        output.append('@interface ')
+
+    output.append(declaration.name)
+
+    if isinstance(declaration, (javalang.tree.ClassDeclaration, javalang.tree.InterfaceDeclaration)):
+        output_type_params(declaration.type_parameters, output)
+
+    if isinstance(declaration, javalang.tree.ClassDeclaration) and declaration.extends:
+        output.append(' extends ')
+        output_type(declaration.extends, output)
+
+    if isinstance(declaration, javalang.tree.InterfaceDeclaration) and declaration.extends:
+        output.append(' extends ')
+        output_list(output_type, declaration.extends, output, ', ')
+
+    if isinstance(declaration, (javalang.tree.ClassDeclaration, javalang.tree.EnumDeclaration)) and declaration.implements:
+        output.append(' implements ')
+        output_list(output_type, declaration.implements, output, ', ')
+
+@formatter
+def output_formal_param(param, output):
+    output_type(param.type, output)
+
+    if param.varargs:
+        output.append('...')
+
+    output.append(' ')
+    output.append(param.name)
+
+@formatter
+def output_modifiers(modifiers, output):
+    ordered_modifiers = [mod for mod in __modifiers_order if mod in modifiers]
+    output_list(lambda mod, output: output.append(mod), ordered_modifiers, output, ' ')
diff --git a/docs/source/_ext/javasphinx/javasphinx/htmlrst.py b/docs/source/_ext/javasphinx/javasphinx/htmlrst.py
new file mode 100644 (file)
index 0000000..b34f1f2
--- /dev/null
@@ -0,0 +1,419 @@
+#
+# Copyright 2013-2015 Bronto Software, Inc. and contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from __future__ import unicode_literals
+from builtins import str
+
+import collections
+import re
+
+from xml.sax.saxutils import escape as html_escape
+from bs4 import BeautifulSoup
+
+Cell = collections.namedtuple('Cell', ['type', 'rowspan', 'colspan', 'contents'])
+
+class Converter(object):
+    def __init__(self, parser):
+        self._unknown_tags = set()
+        self._clear = '\n\n..\n\n'
+
+        # Regular expressions
+        self._preprocess_anchors = re.compile(r'<a\s+name\s*=\s*["\']?(.+?)["\']?\s*>')
+        self._post_process_empty_lines = re.compile(r'^\s+$', re.MULTILINE)
+        self._post_process_compress_lines = re.compile(r'\n{3,}')
+        self._whitespace_with_newline = re.compile(r'[\s\n]+')
+        self._whitespace = re.compile(r'\s+')
+        self._html_tag = re.compile(r'<.*?>')
+
+        self._preprocess_entity = re.compile(r'&(nbsp|lt|gt|amp)([^;]|[\n])')
+        self._parser = parser
+
+    # --------------------------------------------------------------------------
+    # ---- reST Utility Methods ----
+
+    def _unicode(self, s):
+        if isinstance(s, unicode):
+            return s
+        else:
+            return unicode(s, 'utf8')
+
+    def _separate(self, s):
+        return u'\n\n' + s + u'\n\n'
+
+    def _escape_inline(self, s):
+        return '\\ ' + s + '\\ '
+
+    def _inline(self, tag, s):
+        # Seems fishy if our inline markup spans lines. We will instead just return
+        # the string as is
+        if '\n' in s:
+            return s
+
+        s = s.strip()
+
+        if not s:
+            return s
+
+        return self._escape_inline(tag + s.strip() + tag)
+
+    def _role(self, role, s, label=None):
+        if label:
+            return self._escape_inline(':%s:`%s <%s>`' % (role, label, s))
+        else:
+            return self._escape_inline(':%s:`%s`' % (role, s))
+
+    def _directive(self, directive, body=None):
+        header = '\n\n.. %s::\n\n' % (directive,)
+
+        if body:
+            return header + self._left_justify(body, 3) + '\n\n'
+        else:
+            return header + '\n'
+
+    def _hyperlink(self, target, label):
+        return self._escape_inline('`%s <%s>`_' % (label, target))
+
+    def _listing(self, marker, items):
+        items = [self._left_justify(item, len(marker) + 1) for item in items]
+        items = [marker + item[len(marker):] for item in items]
+        return self._separate('..') + self._separate('\n'.join(items))
+
+    def _left_justify(self, s, indent=0):
+        lines = [l.rstrip() for l in s.split('\n')]
+        indents = [len(l) - len(l.lstrip()) for l in lines if l]
+
+        if not indents:
+            return s
+
+        shift = indent - min(indents)
+
+        if shift < 0:
+            return '\n'.join(l[-shift:] for l in lines)
+        else:
+            prefix = ' ' * shift
+            return '\n'.join(prefix + l for l in lines)
+
+    def _compress_whitespace(self, s, replace=' ', newlines=True):
+        if newlines:
+            return self._whitespace_with_newline.sub(replace, s)
+        else:
+            return self._whitespace.sub(replace, s)
+
+    # --------------------------------------------------------------------------
+    # ---- DOM Tree Processing ----
+
+    def _process_table_cells(self, table):
+        """ Compile all the table cells.
+
+        Returns a list of rows. The rows may have different lengths because of
+        column spans.
+
+        """
+
+        rows = []
+
+        for i, tr in enumerate(table.find_all('tr')):
+            row = []
+
+            for c in tr.contents:
+                cell_type = getattr(c, 'name', None)
+
+                if cell_type not in ('td', 'th'):
+                    continue
+
+                rowspan = int(c.attrs.get('rowspan', 1))
+                colspan = int(c.attrs.get('colspan', 1))
+                contents = self._process_children(c).strip()
+
+                if cell_type == 'th' and i > 0:
+                    contents = self._inline('**', contents)
+
+                row.append(Cell(cell_type, rowspan, colspan, contents))
+
+            rows.append(row)
+
+        return rows
+
+    def _process_table(self, node):
+        rows = self._process_table_cells(node)
+
+        if not rows:
+            return ''
+
+        table_num_columns = max(sum(c.colspan for c in row) for row in rows)
+
+        normalized = []
+
+        for row in rows:
+            row_num_columns = sum(c.colspan for c in row)
+
+            if row_num_columns < table_num_columns:
+                cell_type = row[-1].type if row else 'td'
+                row.append(Cell(cell_type, 1, table_num_columns - row_num_columns, ''))
+
+        col_widths = [0] * table_num_columns
+        row_heights = [0] * len(rows)
+
+        for i, row in enumerate(rows):
+            j = 0
+            for cell in row:
+                current_w = sum(col_widths[j:j + cell.colspan])
+                required_w = max(len(l) for l in cell.contents.split('\n'))
+
+                if required_w > current_w:
+                    additional = required_w - current_w
+                    col_widths[j] += additional - (cell.colspan - 1) * (additional // cell.colspan)
+                    for jj in range(j + 1, j + cell.colspan):
+                        col_widths[jj] += (additional // cell.colspan)
+
+                current_h = row_heights[i]
+                required_h = len(cell.contents.split('\n'))
+
+                if required_h > current_h:
+                    row_heights[i] = required_h
+
+                j += cell.colspan
+
+        row_sep = '+' + '+'.join('-' * (l + 2) for l in col_widths) + '+'
+        header_sep = '+' + '+'.join('=' * (l + 2) for l in col_widths) + '+'
+        lines = [row_sep]
+
+        for i, row in enumerate(rows):
+            for y in range(0, row_heights[i]):
+                line = []
+                j = 0
+                for c in row:
+                    w = sum(n + 3 for n in col_widths[j:j+c.colspan]) - 2
+                    h = row_heights[i]
+
+                    line.append('| ')
+                    cell_lines = c.contents.split('\n')
+                    content = cell_lines[y] if y < len(cell_lines) else ''
+                    line.append(content.ljust(w))
+
+                    j += c.colspan
+
+                line.append('|')
+                lines.append(''.join(line))
+
+            if i == 0 and all(c.type == 'th' for c in row):
+                lines.append(header_sep)
+            else:
+                lines.append(row_sep)
+
+        return self._separate('\n'.join(lines))
+
+    def _process_children(self, node):
+        parts = []
+        is_newline = False
+
+        for c in node.contents:
+            part = self._process(c)
+
+            if is_newline:
+                part = part.lstrip()
+
+            if part:
+                parts.append(part)
+                is_newline = part.endswith('\n')
+
+        return ''.join(parts)
+
+    def _process_text(self, node):
+        return ''.join(node.strings)
+
+    def _process(self, node):
+        if isinstance(node, str):
+            return self._compress_whitespace(node)
+
+        simple_tags = {
+            'b'      : lambda s: self._inline('**', s),
+            'strong' : lambda s: self._inline('**', s),
+            'i'      : lambda s: self._inline('*', s),
+            'em'     : lambda s: self._inline('*', s),
+            'tt'     : lambda s: self._inline('``', s),
+            'code'   : lambda s: self._inline('``', s),
+            'h1'     : lambda s: self._inline('**', s),
+            'h2'     : lambda s: self._inline('**', s),
+            'h3'     : lambda s: self._inline('**', s),
+            'h4'     : lambda s: self._inline('**', s),
+            'h5'     : lambda s: self._inline('**', s),
+            'h6'     : lambda s: self._inline('**', s),
+            'sub'    : lambda s: self._role('sub', s),
+            'sup'    : lambda s: self._role('sup', s),
+            'hr'     : lambda s: self._separate('') # Transitions not allowed
+            }
+
+        if node.name in simple_tags:
+            return simple_tags[node.name](self._process_text(node))
+
+        if node.name == 'p':
+            return self._separate(self._process_children(node).strip())
+
+        if node.name == 'pre':
+            return self._directive('parsed-literal', self._process_text(node))
+
+        if node.name == 'a':
+            if 'name' in node.attrs:
+                return self._separate('.. _' + node['name'] + ':')
+            elif 'href' in node.attrs:
+                target = node['href']
+                label = self._compress_whitespace(self._process_text(node).strip('\n'))
+
+                if target.startswith('#'):
+                    return self._role('ref', target[1:], label)
+                elif target.startswith('@'):
+                    return self._role('java:ref', target[1:], label)
+                else:
+                    return self._hyperlink(target, label)
+
+        if node.name == 'ul':
+            items = [self._process(n) for n in node.find_all('li', recursive=False)]
+            return self._listing('*', items)
+
+        if node.name == 'ol':
+            items = [self._process(n) for n in node.find_all('li', recursive=False)]
+            return self._listing('#.', items)
+
+        if node.name == 'li':
+            s = self._process_children(node)
+            s = s.strip()
+
+            # If it's multiline clear the end to correcly support nested lists
+            if '\n' in s:
+                s = s + '\n\n'
+
+            return s
+
+        if node.name == 'table':
+            return self._process_table(node)
+
+        self._unknown_tags.add(node.name)
+
+        return self._process_children(node)
+
+    # --------------------------------------------------------------------------
+    # ---- HTML Preprocessing ----
+
+    def _preprocess_inline_javadoc_replace(self, tag, f, s):
+        parts = []
+
+        start = '{@' + tag
+        start_length = len(start)
+
+        i = s.find(start)
+        j = 0
+
+        while i != -1:
+            parts.append(s[j:i])
+
+            # Find a closing bracket such that the brackets are balanced between
+            # them. This is necessary since code examples containing { and } are
+            # commonly wrapped in {@code ...} tags
+
+            try:
+                j = s.find('}', i + start_length) + 1
+                while s.count('{', i, j) != s.count('}', i, j):
+                    j = s.index('}', j) + 1
+            except ValueError:
+                raise ValueError('Unbalanced {} brackets in ' + tag + ' tag')
+
+            parts.append(f(s[i + start_length:j - 1].strip()))
+            i = s.find(start, j)
+
+        parts.append(s[j:])
+
+        return ''.join(parts)
+
+    def _preprocess_replace_javadoc_link(self, s):
+        s = self._compress_whitespace(s)
+
+        target = None
+        label = ''
+
+        if ' ' not in s:
+            target = s
+        else:
+            i = s.find(' ')
+
+            while s.count('(', 0, i) != s.count(')', 0, i):
+                i = s.find(' ', i + 1)
+
+                if i == -1:
+                    i = len(s)
+                    break
+
+            target = s[:i]
+            label = s[i:]
+
+        if target[0] == '#':
+            target = target[1:]
+
+        target = target.replace('#', '.').replace(' ', '').strip()
+
+        # Strip HTML tags from the target
+        target = self._html_tag.sub('', target)
+
+        label = label.strip()
+
+        return '<a href="@%s">%s</a>' % (target, label)
+
+    def _preprocess_close_anchor_tags(self, s):
+        # Add closing tags to all anchors so they are better handled by the parser
+        return self._preprocess_anchors.sub(r'<a name="\1"></a>', s)
+
+    def _preprocess_fix_entities(self, s):
+        return self._preprocess_entity.sub(r'&\1;\2', s)
+
+    def _preprocess(self, s_html):
+        to_tag = lambda t: lambda m: '<%s>%s</%s>' % (t, html_escape(m), t)
+        s_html = self._preprocess_inline_javadoc_replace('code', to_tag('code'), s_html)
+        s_html = self._preprocess_inline_javadoc_replace('literal', to_tag('span'), s_html)
+        s_html = self._preprocess_inline_javadoc_replace('docRoot', lambda m: '', s_html)
+        s_html = self._preprocess_inline_javadoc_replace('linkplain', self._preprocess_replace_javadoc_link, s_html)
+        s_html = self._preprocess_inline_javadoc_replace('link', self._preprocess_replace_javadoc_link, s_html)
+
+        # Make sure all anchor tags are closed
+        s_html = self._preprocess_close_anchor_tags(s_html)
+
+        # Fix up some entitities without closing ;
+        s_html = self._preprocess_fix_entities(s_html)
+
+        return s_html
+
+    # --------------------------------------------------------------------------
+    # ---- Conversion entry point ----
+
+    def convert(self, s_html):
+        if not isinstance(s_html, str):
+            s_html = str(s_html, 'utf8')
+
+        s_html = self._preprocess(s_html)
+
+        if not s_html.strip():
+            return ''
+
+        soup = BeautifulSoup(s_html, self._parser)
+        top = soup.html.body
+
+        result = self._process_children(top)
+
+        # Post processing
+        result = self._post_process_empty_lines.sub('', result)
+        result = self._post_process_compress_lines.sub('\n\n', result)
+        result = result.strip()
+
+        return result
diff --git a/docs/source/_ext/javasphinx/javasphinx/util.py b/docs/source/_ext/javasphinx/javasphinx/util.py
new file mode 100644 (file)
index 0000000..2de85d5
--- /dev/null
@@ -0,0 +1,119 @@
+#
+# Copyright 2012-2015 Bronto Software, Inc. and contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from __future__ import unicode_literals
+from builtins import str
+
+import logging
+import re
+import sys
+
+class StringBuilder(list):
+    def build(self):
+        return str(self)
+
+    def __str__(self):
+        return ''.join(self)
+
+class Directive(object):
+
+    def __init__(self, type, argument=''):
+        self.type = type
+        self.argument = argument
+
+        self.options = []
+        self.content = []
+
+    def add_option(self, name, value=''):
+        self.options.append((name, value))
+
+    def add_content(self, o):
+        assert o is not None
+        self.content.append(o)
+
+    def build(self):
+        doc = Document()
+        doc.add_line('.. %s:: %s' % (self.type, self.argument))
+
+        for name, value in self.options:
+            doc.add_line('   :%s: %s\n' % (name, value))
+
+        content = Document()
+
+        for obj in self.content:
+            content.add_object(obj)
+
+        doc.clear()
+        for line in content.build().splitlines():
+            doc.add_line('   ' + line)
+        doc.clear()
+
+        return doc.build()
+
+class Document(object):
+    remove_trailing_whitespace_re = re.compile('[ \t]+$', re.MULTILINE)
+    collapse_empty_lines_re = re.compile('\n' + '{3,}', re.DOTALL)
+
+    def __init__(self):
+        self.content = []
+
+    def add_object(self, o):
+        assert o is not None
+
+        self.content.append(o)
+
+    def add(self, s):
+        self.add_object(s)
+
+    def add_line(self, s):
+        self.add(s)
+        self.add('\n')
+
+    def add_heading(self, s, t='-'):
+        self.add_line(s)
+        self.add_line(t * len(s))
+
+    def clear(self):
+        self.add('\n\n')
+
+    def build(self):
+        output = StringBuilder()
+
+        for obj in self.content:
+            if isinstance(obj, Directive):
+                output.append('\n\n')
+                output.append(obj.build())
+                output.append('\n\n')
+            elif isinstance(obj, Document):
+                output.append(obj.build())
+            else:
+                output.append(str(obj))
+
+        output.append('\n\n')
+
+        output = str(output)
+        output = self.remove_trailing_whitespace_re.sub('', output)
+        output = self.collapse_empty_lines_re.sub('\n\n', output)
+
+        return output
+
+def error(s, *args, **kwargs):
+    logging.error(s, *args, **kwargs)
+    sys.exit(1)
+
+def unexpected(s, *args, **kwargs):
+    logging.exception(s, *args, **kwargs)
+    sys.exit(1)
diff --git a/docs/source/_ext/javasphinx/setup.py b/docs/source/_ext/javasphinx/setup.py
new file mode 100644 (file)
index 0000000..3e4f362
--- /dev/null
@@ -0,0 +1,59 @@
+#
+# Copyright 2012-2015 Bronto Software, Inc. and contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from setuptools import setup
+
+setup(
+    name = "javasphinx",
+    packages = ["javasphinx"],
+    version = "0.9.15",
+    author = "Chris Thunes",
+    author_email = "cthunes@brewtab.com",
+    url = "http://github.com/bronto/javasphinx",
+    description = "Sphinx extension for documenting Java projects",
+    license = "Apache 2.0",
+    classifiers = [
+        "Programming Language :: Python",
+        "Development Status :: 4 - Beta",
+        "Operating System :: OS Independent",
+        "License :: OSI Approved :: Apache Software License",
+        "Intended Audience :: Developers",
+        "Topic :: Software Development :: Libraries"
+        ],
+    install_requires=[
+        "javalang>=0.10.1",
+        "lxml",
+        "beautifulsoup4",
+        "future",
+        "docutils",
+        "sphinx"
+    ],
+    entry_points={
+        'console_scripts': [
+            'javasphinx-apidoc = javasphinx.apidoc:main'
+            ]
+        },
+    long_description = """\
+==========
+javasphinx
+==========
+
+javasphinx is an extension to the Sphinx documentation system which adds support
+for documenting Java projects. It includes a Java domain for writing
+documentation manually and a javasphinx-apidoc utility which will automatically
+generate API documentation from existing Javadoc markup.
+"""
+)