├── .gitignore ├── LICENSE ├── README.md ├── build_odin_browser.ps1 ├── build_odin_browser.sh ├── docs ├── css │ └── styles.css ├── index.html └── odin.code.browser │ ├── Director.html │ ├── ExperimentalMain.html │ ├── Main.html │ ├── index.html │ ├── index.json │ ├── indexing │ ├── ImportVisitor.html │ ├── Index.html │ ├── IndexVisitor.html │ ├── Indexer.html │ ├── MethodInfo.html │ ├── SuperClassIndexVisitor.html │ ├── SuperClassIndexer.html │ ├── UrlIndexLoader.html │ └── VariableInfo.html │ ├── options │ └── OdinOptions.html │ ├── rendering │ ├── IndexHtmlRenderer.html │ ├── IndexJsonRenderer.html │ └── source │ │ ├── ApplyIndexVisitor.html │ │ ├── ApplySyntaxHighlightingVisitor.html │ │ ├── ContentRecord.html │ │ ├── PositionType.html │ │ ├── RenderQueueLine.html │ │ ├── RenderingQueue.html │ │ ├── ScopeTracker.html │ │ └── SourceHtmlRenderer.html │ └── util │ └── StringUtil.html ├── pom.xml ├── src ├── main │ └── java │ │ ├── Director.java │ │ ├── ExperimentalMain.java │ │ ├── Main.java │ │ ├── indexing │ │ ├── ImportVisitor.java │ │ ├── Index.java │ │ ├── IndexVisitor.java │ │ ├── Indexer.java │ │ ├── MethodInfo.java │ │ ├── SuperClassIndexVisitor.java │ │ ├── SuperClassIndexer.java │ │ ├── UrlIndexLoader.java │ │ └── VariableInfo.java │ │ ├── options │ │ └── OdinOptions.java │ │ ├── rendering │ │ ├── IndexHtmlRenderer.java │ │ ├── IndexJsonRenderer.java │ │ └── source │ │ │ ├── ApplyIndexVisitor.java │ │ │ ├── ApplySyntaxHighlightingVisitor.java │ │ │ ├── ContentRecord.java │ │ │ ├── PositionType.java │ │ │ ├── RenderQueueLine.java │ │ │ ├── RenderingQueue.java │ │ │ ├── ScopeTracker.java │ │ │ └── SourceHtmlRenderer.java │ │ └── util │ │ └── StringUtil.java └── test │ └── java │ └── StringUtilTest.java ├── webserver.ps1 └── webserver.sh /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.swp 3 | .idea/ 4 | *.iml 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Joseph Mate 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Odin Code Browser 2 | 3 | This project attempts to create a completely static website of your source code, 4 | that navigates all the way down to the depths of the JDK. 5 | The goal is to navigate as well as Intellij. 6 | Try it yourself by navigating from 7 | [Apache Commons Text's 8 | StringEscapeUtils](https://josephmate.github.io/OdinCodeBrowserRepos/commons-text_1.9/org/apache/commons/text/StringEscapeUtils.html#linenum38). 9 | 10 | # Motivation 11 | I used 12 | [grepcode](https://web.archive.org/web/20150318024036/http://www.grepcode.com/) 13 | at least once a week and I am sad to see it go. 14 | It's navigation was almost as good as an IDE. 15 | You could navigate from apache commons all the way down to the JDK. 16 | Occasionally, I would forget what I was originally doing and get lost in the depths of the JDK. 17 | I hope that I can create a spiritual successor by using this tool, 18 | that will live much longer since the source code is available, 19 | and the resulting static web pages are decentralized. 20 | 21 | # Design Philosophy 22 | 1. Each page is static 23 | 2. Works well without javascript 24 | 25 | # How Do I Use This 26 | 27 | ## As a code browser 28 | 1. Find a project you want to browse from: 29 | [jdk8](https://josephmate.github.io/OdinCodeBrowserJdk8/), 30 | [jdk11](https://josephmate.github.io/OdinCodeBrowserJdk11/), 31 | [jdk17](https://josephmate.github.io/OdinCodeBrowserJdk17/), 32 | [Apache Lang3](https://josephmate.github.io/OdinCodeBrowserRepos/commons-lang3_3.11/), 33 | [Apache Collections](https://josephmate.github.io/OdinCodeBrowserRepos/commons-collections_4.4/), 34 | [Apache Text](https://josephmate.github.io/OdinCodeBrowserRepos/commons-text_1.9), 35 | [Github Java Parser](https://josephmate.github.io/OdinCodeBrowserRepos/javaparser-core_3.23.1) 36 | [OdinCodeBrowser](https://josephmate.github.io/OdinCodeBrowser/odin.code.browser) 37 | 2. Find your class (ex: A class Odin uses 38 | [StringEscapeUtils](https://josephmate.github.io/OdinCodeBrowserRepos/commons-text_1.9/org/apache/commons/text/StringEscapeUtils.html)) 39 | 3. Start reading code and navigating by clicking on the links, which are 40 | underlined. 41 | 42 | ## As a code owner 43 | ``` 44 | git clone git@github.com:josephmate/OdinCodeBrowser.git 45 | cd OdinCodeBrowser 46 | args="--inputSourceDirectory " 47 | args="$args --outputDirectory " 48 | args="$args --webPathToCssFile https://josephmate.github.io/OdinCodeBrowser/css/styles.css" 49 | args="$args --webPathToSourceHtmlFiles " 50 | args="$args --languageLevel JAVA_16" 51 | args="$args --urlToDependantIndexJson " 52 | mvn install exec:java \ 53 | -Dexec.mainClass=Main \ 54 | -Dexec.args="$args" 55 | ``` 56 | 57 | Creating an Odin Navigator for my project as an example, with dependencies on 58 | github javaparser, apache commons, apache commons text, and JDK8 that I host on 59 | https://josephmate.github.io/OdinCodeBrowser/ . 60 | ``` 61 | args="--inputSourceDirectory src/main/java" 62 | args="$args --outputDirectory docs/odin.code.browser" 63 | args="$args --webPathToCssFile /OdinCodeBrowser/css/styles.css" 64 | args="$args --webPathToSourceHtmlFiles /OdinCodeBrowser/odin.code.browser" 65 | args="$args --languageLevel JAVA_16" 66 | args="$args --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserJdk8/index.json" 67 | args="$args --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/javaparser-core_3.23.1/index.json" 68 | args="$args --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/commons-lang3_3.11/index.json" 69 | args="$args --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/commons-text_1.9/index.json" 70 | args="$args --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/commons-collections_4.4/index.json" 71 | mvn install exec:java \ 72 | -Dexec.mainClass=Main \ 73 | -Dexec.args="$args" 74 | ``` 75 | 76 | ## Fun and games 77 | 1. Start at a random class in [jdk8](https://josephmate.github.io/OdinCodeBrowserJdk8/) 78 | 2. Using links only, how fast can you get my favourite class: HashMap? 79 | 80 | For example I did 81 | [I18NImpl](https://josephmate.github.io/OdinCodeBrowserJdk8/com/sun/imageio/plugins/common/I18NImpl.html) 82 | -> 83 | [PropertyResourceBundle](https://josephmate.github.io/OdinCodeBrowserJdk8/java/util/PropertyResourceBundle.html#linenum122) 84 | -> 85 | [HashMap](https://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum137) 86 | 87 | However, sometimes you'll pick a class that does not have a path to HashMap :(. 88 | 89 | # Alternatives 90 | Your Java IDE can do a much better job. 91 | As I write this project, I use Intellij to navigate from my project's code to the code of the projects I depend on. 92 | The experience is so much better in the IDE and you should use that. 93 | If you are already using your IDE, keep using it! 94 | 95 | If you don't use an IDE then github provides some support for navigating code without any extra effort. 96 | Just commit your code as you normally do and github updates the navigation. 97 | 98 | The two features together that sets Odin apart from an IDE and GitHub: 99 | 1. [x] sharing links to the code (can't do this in an IDE). Since it's a web page, 100 | this means you can use it on low memory devices like your phone! 101 | 2. [x] can link to the dependant sources as well (github can't do this) 102 | 103 | # Comparison 104 | | Dimension | Odin | IDE | Github | 105 | | ------------------------ | ---- | --- | ------ | 106 | | Shares links | ✅ | ❌ | ✅ | 107 | | Minimal CPU Usage | ✅ | ❌ | ✅ | 108 | | Minimal Memory Usage | ✅ | ❌ | ✅ | 109 | | Minimal Storage Usage | ✅ | ❌ | ✅ | 110 | | Realtime | ❌ | ✅ | ❌ | 111 | | Automatically Applied | ❌ | ✅ | ✅ | 112 | | Static Pages | ✅ | | ? | 113 | | Works without javascript | ✅ | | ❌ | 114 | | Navigate to dependencies | ✅ | ✅ | ❌ | 115 | | Comment on code | ❌ | ✅ | ✅ | 116 | | Navigation Complete | ❌ | ✅ | ❌ | 117 | 118 | 119 | 120 | Explanation of criteria: 121 | 122 | * **Shares links**: In Odin and GitHub you can share links to any line in the code. 123 | An IDE does not let you do that. 124 | * **Minimal CPU/Memory/Storage Usage**: Since Odin and GitHub are not realtime, no cpu, memory or 125 | disk space is needed by the client to efficiently calculate all the indexes. 126 | * **Realtime** As you make changes in an IDE, the indexes are updated and 127 | you can navigate to the new code changes or new dependencies. For GitHub you 128 | need to commit and push before the navigation updates. For Odin you need to 129 | run this tool and publish the generated html files. 130 | * **Automatically Applied**: By rebuilding in the IDE, new code is recognized. 131 | By committing and pushing your changes to GitHub, the navigation is updated. 132 | For Odin, you must manually build and publish the static web pages. You 133 | unfortunately need to manage this. GitHub and an IDE manage this for you with 134 | no effort on your part. 135 | * **Static Pages**: Odin's output is hosted as static web pages that can be 136 | efficiently distributed. This comparison doesn't make sense for an IDE. 137 | * **No javascript**: Odin doesn't use javascript while GitHub needs to. 138 | * **Navigate to dependencies**: IDE and seamlessly navigate to your 139 | dependencies. Odin plans to navigate to dependencies when you build. It will 140 | not be as convenient as an IDE. GitHub only lets you navigate within the same 141 | repository. 142 | * **Navigation Complete**: anything you could possibly want to navigate to or 143 | from in an IDE works. Odin is working on matching an IDE and might reach the 144 | same level one day. GitHub navigation is okay. It gives you a list of options 145 | as a pop. Odin tries to be like an IDE and take you directly to the 146 | definition of the variable or method without asking you which one. For now, 147 | Odin sometimes makes mistakes due to method overloading. 148 | * **Comment on code**: GitHub is probably the best for commenting and discussing 149 | code. You can also do this in an IDE with plugins, but it's not as convenient 150 | as GitHub. Since Odin pages are static without javascript, it's impossible to 151 | have commenting. 152 | 153 | 154 | ## Other Alternatives 155 | 156 | These are alternatives that I do not use on a daily basis so I do not feel qualified in giving a thoughtful review. 157 | However, I'm going to help you find what you're looking for. 158 | 159 | **[woboq](https://code.woboq.org/)** : Looks exactly what I'm trying to achieve, but for C++ instead of Java. 160 | If you're working on open source C/C++ , I recommend checking it out. 161 | 162 | **Opengrok**: Provides much better searching than what Odin can provide. 163 | However, as a result some components cannot be hosted as static files. 164 | If you host Opengrok for your project, I recommend it. 165 | 166 | **[Sourcegraph](https://sourcegraph.com/searh)**: 167 | The search is really good. 168 | It like a super powered Github search. 169 | However, [they aren't indexing OdinCodeBrowser yet.](https://sourcegraph.com/search?q=context:global+repo:%5Egithub%5C.com/josephmate/.*+&patternType=literal). 170 | I'm not sure what triggers the indexing because some of my old side projects are there. 171 | Also, it does not support cross repo navigation. 172 | 173 | # How it Works 174 | 175 | 1. I use [Github's JavaParser](https://github.com/javaparser/javaparser) to 176 | visit nodes in the Java AST. 177 | 2. Through visiting, I build indexes of classes, their methods, their fields, 178 | and their super classes. 179 | 3. Through one more visit, I look to see if there's anything in the index I can create 180 | navigation links to and place these into a 'Rendering Queue'. 181 | 4. I iterate over the code char by checking if I need to insert anything. 182 | 5. All of these are saved as HTML files. 183 | 6. The index is saved as a json file for other repos to use. That way they do 184 | not have to checkout the source code and build the index of all the 185 | dependencies. It also allows the repositories to independently host 186 | their source code and allows dependents to navigate from their code host 187 | on their servers to the code hosted on the dependency's servers. 188 | 189 | # Color Scheme 190 | 191 | The color scheme is based on [vim-dichromatic](https://github.com/romainl/vim-dichromatic) 192 | put together by [Romain Lafourcade](https://github.com/romainl). 193 | I wanted a single color scheme that could be used by anyone, 194 | since it is really difficult for readers to change the styles. 195 | In order for someone to change the style, they will need to install a plugin for 196 | their browser and override Odin's css styles. 197 | Maybe in the future there will be some javascript to select from a list of 198 | styles that writes to your browser's local storage. 199 | 200 | # Future Work 201 | 202 | Below is a checklist of features Odin needs. 203 | 204 | 1. [x] Line numbers with links 205 | 2. [ ] Code is easily copy and pastable 206 | 1. [x] Code is copy and pastable 207 | 2. [ ] Copied code is exactly the same as pasted code 208 | 3. [x] Click on a Type to navigate to the definition of that type 209 | 1. Type 210 | 1. [x] Interface 211 | 1. [x] Annotation 212 | 1. [x] Outer class 213 | 2. [x] Inner class 214 | 3. [x] Enum 215 | 3. [x] Record 216 | 3. [ ] Generics 217 | 3. [ ] Class inside function 218 | 4. [ ] Anonymous class 219 | 1. Type Usage 220 | 1. [x] [Variable type](https://josephmate.github.io/OdinCodeBrowserJdk8/com/oracle/net/Sdp.html#linenum56) 221 | 2. [x] [Method return type](https://josephmate.github.io/OdinCodeBrowserJdk8/com/oracle/net/Sdp.html#linenum104) 222 | 3. [x] [Extends type](http://josephmate.github.io/OdinCodeBrowserJdk8/com/oracle/net/Sdp.html#linenum95) 223 | 4. [x] Implement type 224 | 4. [x] Record 225 | 4. [x] Enum 226 | 3. [ ] Generics 227 | 5. [ ] Annotation (the implementation of the annotation) 228 | 6. [ ] Import 229 | 7. [x] [Types through wildcard imports (ex: List from java.util.*)](https://josephmate.github.io/OdinCodeBrowserJdk8/java/util/concurrent/ScheduledThreadPoolExecutor.html#linenum785) 230 | 8. [x] [Types within same package (ex: ThreadPoolExecutor when in package java.util.concurrent)](https://josephmate.github.io/OdinCodeBrowserJdk8/java/util/concurrent/ScheduledThreadPoolExecutor.html#linenum122) 231 | 8. [x] [Types from java.lang (ex: IllegalArgumentException)](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum448) 232 | 8. [ ] Types within same file 233 | 4. [ ] Click on method to go the definition of that method 234 | 1. [x] [Static and only one function with the name (ex: Objects.hashCode(key))](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum296) 235 | 3. [x] [Functions within the same file (ex: putMapEntries(m, true)](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum784) 236 | 3. [x] [Functions on this (ex: this.getCanonName())](http://josephmate.github.io/OdinCodeBrowserJdk8/java/net/SocketPermission.html#linenum621) 237 | 1. [x] [Object instance and only one function with the name and not in super class (ex: x.getClass())](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum348) 238 | 1. [x] [Object instance and only one function with the name but in some super class](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/concurrent/locks/ReentrantLock.html#linenum131) 239 | 1. [x] [super.method() (super.clone() in HashMap)](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum1318) 240 | 1. [ ] Chained method calls 241 | 2. [ ] Functions are overloaded (different parameters) 242 | 1. [ ] [all arguments are variables (ex: String.lastIndexOf(char vs String))](https://josephmate.github.io/OdinCodeBrowser/odin.code.browser/indexing/ImportVisitor.html#linenum42) 243 | 1. [ ] [length heuristic for generics (ex: Pair.of(A,B) vs Pair.of(Map.Entry))](https://josephmate.github.io/OdinCodeBrowser/odin.code.browser/Director.html#linenum44) 244 | 2. [ ] all args are variables or literals (ex add(a, 1)) 245 | 3. [ ] where any of the arguments are expressions (ex: add(add(1,2), add(a,b))) 246 | 4. [ ] variable arguments (ex: int add(int... vals)) 247 | 3. [ ] import static functions 248 | 3. [ ] Record getter functions 249 | 2. [ ] Super constructors 250 | 2. [ ] Overloaded constructors 251 | 3. [ ] Scoping rules (if names are duplicated in multiple scopes, need to use the closest scope) 252 | 4. [x] [String literal method calls (ex: "true".equals(blah))](https://josephmate.github.io/OdinCodeBrowserJdk8/com/sun/beans/finder/BeanInfoFinder.html) 253 | 4. [x] [class literal method calls (ex: boolean.class.getName())](https://josephmate.github.io/OdinCodeBrowserJdk8/com/sun/beans/finder/PrimitiveTypeMap.html#linenum54) 254 | 5. [ ] Click on variable to the definition of that variable 255 | 1. [x] [from function param](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum345) 256 | 1. [x] [local var](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum568) 257 | 1. [x] [from scope (for/while/if)](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum352) 258 | 1. [x] [field var](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum291) 259 | 1. [x] [static field var](http://josephmate.github.io/OdinCodeBrowserJdk8/java/util/HashMap.html#linenum384) 260 | 1. [ ] Enum value 261 | 1. [ ] this.field 262 | 1. [ ] record.value 263 | 1. [ ] static variable (ex: System.out) 264 | 1. [ ] variable chaining (ex: a.b.c.d) 265 | 6. [ ] Click on method to get a list of implementations 266 | 7. [ ] Click on Override takes you to nearest super class's method that was overridden 267 | 8. [x] [File list in root directory /OdinCodeBrowserJdk8](https://josephmate.github.io/OdinCodeBrowserJdk8/) 268 | 9. [ ] Click on class definition to get all usages 269 | 9. [ ] Click on method definition to get all usages 270 | 9. [ ] Click on variable definition to get all usages 271 | 9. [ ] Click on class/interface definition to get all subtypes 272 | 9. [ ] Click on method definition to get all overrides and impls 273 | 9. [ ] List of functions and fields at the side of the page. 274 | 1. [ ] List of functions within this class, grouped by visibility 275 | 1. [ ] List of fields within this class 276 | 1. [ ] List of functions and fields from super classes 277 | 9. [x] Multi repository support (ex: browsing guava but also linking to JDK8) 278 | 1. [x] Repository exposes it's index file as json 279 | 1. [x] Build loads index files, then builds it own index 280 | 1. [x] Create a demo using current project OdinCodeBrowser -> apache text -> apache commons -> JDK8 281 | 1. Take a look at [SourceHtmlRenderer](https://josephmate.github.io/OdinCodeBrowser/odin.code.browser/rendering/source/SourceHtmlRenderer.html) 282 | 2. In there [I use apache commons text to handle the html escaping for me](https://josephmate.github.io/OdinCodeBrowser/odin.code.browser/SourceHtmlRenderer.html#linenum145) 283 | 3. From SourceHtmlRenderer can navigate to the [StringEscapeUtils.escapeHtml4 implementation](https://josephmate.github.io/OdinCodeBrowserRepos/commons-text_1.9/org/apache/commons/text/StringEscapeUtils.html#linenum660) 284 | 4. escapeHtml4 uses 285 | [CharSequenceTranslator.translate](https://josephmate.github.io/OdinCodeBrowserRepos/commons-text_1.9/org/apache/commons/text/translate/CharSequenceTranslator.html#linenum84) 286 | 5. Ignore not being able to navigate to the overloaded method. Odin doesn't support that yet. 287 | 6. Validate.isTrue which is in apache commons lang3 288 | 7. Again ignore that it went to the wrong overload of Validate.isTrue 289 | 7. Which throws IllegalArgumentException which allows you to navigate to jdk8! 290 | 9. [ ] Nice syntax highlighting somehow without javascript! 291 | 1. [x] Make it look decent on mobile (IE: not tiny text) 292 | 1. [x] Some syntax highlighting 293 | 2. A dark mode syntax highlighting on 294 | 1. [x] pick a colour scheme: [vim-dichromatic](https://github.com/romainl/vim-dichromatic) 295 | 1. [x] comments 296 | 1. [ ] literals 297 | 1. [x] strings 298 | 1. [ ] numbers 299 | 1. [ ] boolean 300 | 1. [ ] enum values 301 | 1. [ ] native types (boolean, char, int, etc.) 302 | 1. [ ] keywords (public, static, record, class, etc) 303 | 1. [ ] this 304 | 1. [ ] super 305 | 1. [ ] class + modifiers 306 | 1. [ ] modifiers of constructors 307 | 1. [ ] modifiers of fields 308 | 1. [ ] modifiers of functions 309 | 1. [ ] modifiers of params 310 | 1. [ ] modifiers of locals 311 | 1. [ ] for 312 | 1. [ ] synchronized 313 | 1. [ ] import 314 | 1. [ ] switch case 315 | 1. [ ] if 316 | 1. [ ] while 317 | 1. [ ] do 318 | 1. [ ] return 319 | 1. [ ] extends 320 | 1. [ ] implements 321 | 1. [x] Types 322 | 1. [x] function calls 323 | 1. [x] variables 324 | 10. [ ] Render javadoc as text like in Intellij 325 | 10. [ ] Link to native code 326 | 10. [ ] navigation comparison between Odin, IDE, and GitHub 327 | 10. [x] Figure out how to support repos with multiple source directories 328 | 10. [ ] Figure out a better release process other than having a developer clone the project and use the `mvn exec:java` goal. 329 | 1. [x] Tried fatJar but does not work well with java 16 330 | 331 | -------------------------------------------------------------------------------- /build_odin_browser.ps1: -------------------------------------------------------------------------------- 1 | $Args = ( 2 | " --inputSourceDirectory src/main/java" + 3 | " --outputDirectory docs/odin.code.browser" + 4 | " --webPathToCssFile /OdinCodeBrowser/css/styles.css" + 5 | " --webPathToSourceHtmlFiles /OdinCodeBrowser/odin.code.browser" + 6 | " --languageLevel JAVA_16" + 7 | " --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserJdk8/index.json" + 8 | " --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/javaparser-core_3.23.1/index.json" + 9 | " --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/commons-lang3_3.11/index.json" + 10 | " --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/commons-text_1.9/index.json" + 11 | " --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/commons-collections_4.4/index.json" 12 | ) 13 | 14 | mvn install exec:java ` 15 | "-Dexec.mainClass=Main" ` 16 | "-Dexec.args=$Args" 17 | -------------------------------------------------------------------------------- /build_odin_browser.sh: -------------------------------------------------------------------------------- 1 | args="--inputSourceDirectory src/main/java" 2 | args="$args --outputDirectory docs/odin.code.browser" 3 | args="$args --webPathToCssFile /OdinCodeBrowser/css/styles.css" 4 | args="$args --webPathToSourceHtmlFiles /OdinCodeBrowser/odin.code.browser" 5 | args="$args --languageLevel JAVA_16" 6 | args="$args --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserJdk8/index.json" 7 | args="$args --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/javaparser-core_3.23.1/index.json" 8 | args="$args --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/commons-lang3_3.11/index.json" 9 | args="$args --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/commons-text_1.9/index.json" 10 | args="$args --urlToDependantIndexJson https://josephmate.github.io/OdinCodeBrowserRepos/commons-collections_4.4/index.json" 11 | mvn install exec:java \ 12 | -Dexec.mainClass=Main \ 13 | -Dexec.args="$args" 14 | -------------------------------------------------------------------------------- /docs/css/styles.css: -------------------------------------------------------------------------------- 1 | .linenum-cell:first-child:after { 2 | content: attr(data-linenum); 3 | } 4 | 5 | .linenum-cell { 6 | background-color: #080808; 7 | text-decoration-color: #87D7D7; 8 | color: #87D7D7; 9 | font-family: "Lucida Console", "Courier New", monospace; 10 | font-size: 12px; 11 | } 12 | 13 | .linenum-cell:visited { 14 | text-decoration-color: #808080; 15 | color: #808080; 16 | } 17 | 18 | .index-link { 19 | background-color: #080808; 20 | text-decoration-color: #87D7D7; 21 | color: #D0D0D0; 22 | font-family: "Lucida Console", "Courier New", monospace; 23 | font-size: 12px; 24 | } 25 | 26 | .index-link:visited { 27 | background-color: #080808; 28 | text-decoration-color: #808080; 29 | color: #808080; 30 | font-family: "Lucida Console", "Courier New", monospace; 31 | font-size: 12px; 32 | } 33 | 34 | body { 35 | background-color: #080808; 36 | color: #D0D0D0; 37 | } 38 | 39 | pre { 40 | margin-bottom: 0px; 41 | font-family: "Lucida Console", "Courier New", monospace; 42 | font-size: 12px; 43 | } 44 | 45 | .comment { 46 | color: #808080; 47 | } 48 | 49 | .string { 50 | color: #AFAF00; 51 | } 52 | 53 | .type { 54 | color: #87D7D7; 55 | } 56 | 57 | .keyword { 58 | color: #AA4499; 59 | } 60 | 61 | .variable { 62 | color: #D0D0D0; 63 | } 64 | 65 | /* 66 | Color theme accessible to all to di/tri-chromia but still looks good to me. 67 | Needed to select something good for all since there's no javascript to let you select a style. 68 | I thought the burden of installing a css overide plugin and creating their own style was too much. 69 | 70 | https://github.com/romainl/vim-dichromatic 71 | https://github.com/romainl/vim-dichromatic/blob/master/colors/dichromatic.vim 72 | BG FG 73 | Normal #080808 #D0D0D0 74 | NonText #585858 75 | Comment #808080 76 | Constant #D75F87 77 | Error #FFFFFF #87005F 78 | Identifier #AF5FAF 79 | Ignore 80 | PreProc #DFDF87 81 | Special #FFFFFF 82 | Statement #AF5FAF 83 | String #AFAF00 84 | Todo reverse 85 | Type #87D7D7 86 | Underlined underline 87 | Number #D75F87 88 | StatusLine #FFFFFF #585858 89 | StatusLineNC #585858 #FFFFFF 90 | VertSplit ctermbg=240 ctermfg=240 cterm=NONE guibg=#585858 guifg=#585858 gui=NONE 91 | TabLine ctermbg=240 ctermfg=15 cterm=NONE guibg=#585858 guifg=#FFFFFF gui=NONE 92 | TabLineFill ctermbg=240 ctermfg=240 cterm=NONE guibg=#585858 guifg=#585858 gui=NONE 93 | TabLineSel ctermbg=15 ctermfg=240 cterm=NONE guibg=#FFFFFF guifg=#585858 gui=NONE 94 | Title ctermbg=NONE ctermfg=72 cterm=NONE guibg=NONE guifg=#5FAF87 gui=NONE 95 | LineNr #87D7D7 96 | Cursor ctermbg=15 ctermfg=232 cterm=NONE guibg=#FFFFFF guifg=#080808 gui=NONE 97 | CursorColumn ctermbg=89 ctermfg=NONE cterm=NONE guibg=#87005F guifg=NONE gui=NONE 98 | CursorLine ctermbg=236 ctermfg=NONE cterm=NONE guibg=#303030 guifg=NONE gui=NONE 99 | CursorLineNr ctermbg=236 ctermfg=NONE cterm=NONE guibg=#303030 guifg=NONE gui=NONE 100 | helpLeadBlank ctermbg=NONE ctermfg=NONE cterm=NONE guibg=NONE guifg=NONE gui=NONE 101 | helpNormal ctermbg=NONE ctermfg=NONE cterm=NONE guibg=NONE guifg=NONE gui=NONE 102 | Visual ctermbg=116 ctermfg=232 cterm=NONE guibg=#87D7D7 guifg=#080808 gui=NONE 103 | VisualNOS ctermbg=133 ctermfg=232 cterm=NONE guibg=#AF5FAF guifg=#080808 gui=NONE 104 | Pmenu ctermbg=186 ctermfg=232 cterm=NONE guibg=#DFDF87 guifg=#080808 gui=NONE 105 | PmenuSbar ctermbg=142 ctermfg=186 cterm=NONE guibg=#AFAF00 guifg=#DFDF87 gui=NONE 106 | PmenuSel ctermbg=133 ctermfg=232 cterm=NONE guibg=#AF5FAF guifg=#080808 gui=NONE 107 | PmenuThumb ctermbg=133 ctermfg=133 cterm=NONE guibg=#AF5FAF guifg=#AF5FAF gui=NONE 108 | FoldColumn ctermbg=NONE ctermfg=72 cterm=NONE guibg=NONE guifg=#5FAF87 gui=NONE 109 | Folded ctermbg=NONE ctermfg=248 cterm=NONE guibg=NONE guifg=#A8A8A8 gui=NONE 110 | WildMenu ctermbg=53 ctermfg=15 cterm=NONE guibg=#00005F guifg=#FFFFFF gui=NONE 111 | SpecialKey ctermbg=NONE ctermfg=186 cterm=NONE guibg=NONE guifg=#DFDF87 gui=NONE 112 | DiffAdd ctermbg=28 ctermfg=232 cterm=NONE guibg=#008700 guifg=#080808 gui=NONE 113 | DiffChange ctermbg=116 ctermfg=232 cterm=NONE guibg=#87D7D7 guifg=#080808 gui=NONE 114 | DiffDelete ctermbg=89 ctermfg=232 cterm=NONE guibg=#87005F guifg=#080808 gui=NONE 115 | DiffText ctermbg=72 ctermfg=232 cterm=NONE guibg=#5FAF87 guifg=#080808 gui=NONE 116 | IncSearch ctermbg=232 ctermfg=133 cterm=reverse guibg=#080808 guifg=#AF5FAF gui=reverse 117 | Search ctermbg=186 ctermfg=232 cterm=NONE guibg=#DFDF87 guifg=#080808 gui=NONE 118 | Directory ctermbg=NONE ctermfg=72 cterm=NONE guibg=NONE guifg=#5FAF87 gui=NONE 119 | MatchParen ctermbg=240 ctermfg=186 cterm=NONE guibg=#585858 guifg=#DFDF87 gui=NONE 120 | SpellBad ctermbg=89 ctermfg=232 cterm=NONE guibg=#87005F guifg=#080808 gui=NONE guisp=#87005F 121 | SpellCap ctermbg=72 ctermfg=232 cterm=NONE guibg=#5FAF87 guifg=#080808 gui=NONE guisp=#5FAF87 122 | SpellLocal ctermbg=142 ctermfg=232 cterm=NONE guibg=#AFAF00 guifg=#080808 gui=NONE guisp=#AFAF00 123 | SpellRare ctermbg=133 ctermfg=232 cterm=NONE guibg=#AF5FAF guifg=#080808 gui=NONE guisp=#AF5FAF 124 | ColorColumn ctermbg=133 ctermfg=NONE cterm=NONE guibg=#AF5FAF guifg=NONE gui=NONE 125 | signColumn ctermbg=NONE ctermfg=186 cterm=NONE guibg=NONE guifg=#DFDF87 gui=NONE 126 | ErrorMsg ctermbg=89 ctermfg=15 cterm=NONE guibg=#87005F guifg=#FFFFFF gui=NONE 127 | ModeMsg ctermbg=142 ctermfg=232 cterm=NONE guibg=#AFAF00 guifg=#080808 gui=NONE 128 | MoreMsg ctermbg=NONE ctermfg=142 cterm=NONE guibg=NONE guifg=#AFAF00 gui=NONE 129 | Question ctermbg=NONE ctermfg=72 cterm=NONE guibg=NONE guifg=#5FAF87 gui=NONE 130 | WarningMsg ctermbg=168 ctermfg=232 cterm=NONE guibg=#D75F87 guifg=#080808 gui=NONE 131 | QuickFixLine ctermbg=15 ctermfg=240 cterm=NONE guibg=#FFFFFF guifg=#585858 gui=NONE 132 | */ 133 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/odin.code.browser/Main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 |
import options.OdinOptions;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import java.io.IOException;
public class Main {
    public static void main(String[] args) throws IOException {
        OdinOptions bean = new OdinOptions();
        CmdLineParser parser = new CmdLineParser(bean);
        try {
            parser.parseArgument(args);
            Director director = new Director(bean);
            director.processFiles();
        } catch(CmdLineException e) {
            System.err.println(e.getMessage());
            System.err.println("java -jar odin-browser.jar Main [options...] arguments...");
            parser.printUsage(System.err);
            return;
        }
    }
}
118 | 119 | Back to index... 120 | 121 | -------------------------------------------------------------------------------- /docs/odin.code.browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/odin.code.browser/indexing/Indexer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 |
package indexing;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ast.CompilationUnit;
import options.OdinOptions;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
public class Indexer {
    private final OdinOptions odinOptions;
    public Indexer(
            OdinOptions odinOptions
    ) {
        this.odinOptions = odinOptions;
    }
    public void indexFiles(
            String inputDirectory,
            Collection<Path> paths,
            Index index
    ) {
        for (Path path: paths) {
            try {
                indexFile(inputDirectory, index, path);
            } catch (Exception e) {
                throw new RuntimeException("Error processing " + path, e);
            }
        }
    }
    private String getFileUrl(
            String inputSourceDirectory,
            Path javaSourceFile
    ) {
        return odinOptions.webPathToSourceHtmlFiles
                + (
                javaSourceFile.toString()
                        .replace('\\', '/')
                        .substring(0, javaSourceFile.toString().length()-5)
                        .replace(inputSourceDirectory, "")
                        + ".html"
        );
    }
    private void indexFile(
            String inputDirectory,
            Index index,
            Path path
    ) throws IOException {
        System.out.println("Indexing " + path);
        final String fileUrl = getFileUrl(inputDirectory, path);
        indexFile(
                index,
                path,
                fileUrl,
                odinOptions.languageLevel
        );
    }
    private void indexFile(
            Index index,
            Path inputFile,
            String fileUrl,
            ParserConfiguration.LanguageLevel languageLevel
    ) throws IOException {
        JavaParser javaParser = new JavaParser(new ParserConfiguration().setLanguageLevel(
                languageLevel));
        CompilationUnit compilationUnit = javaParser.parse(inputFile).getResult().get();
        IndexVisitor indexVisitor = new IndexVisitor(index, fileUrl);
        indexVisitor.visit(compilationUnit, null);
    }
}
318 | 319 | Back to index... 320 | 321 | -------------------------------------------------------------------------------- /docs/odin.code.browser/indexing/MethodInfo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
package indexing;
import java.util.List;
/***
 * @param argumentTypes list of the Fully Qualified Class Name of the method parameters
 * @param returnType the Fully Qualified Class Name of the return type
 * @param filePosition position this varible appears in the file
 */
public record MethodInfo(
        List<String> argumentTypes,
        String returnType,
        Index.FilePosition filePosition
) {
}
70 | 71 | Back to index... 72 | 73 | -------------------------------------------------------------------------------- /docs/odin.code.browser/indexing/SuperClassIndexVisitor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 |
package indexing;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import java.util.Map;
public class SuperClassIndexVisitor extends VoidVisitorAdapter<Void> {
    private final Index index;
    private final Map<String, String> importIndex;
    public SuperClassIndexVisitor(
            Index index,
            Map<String, String> importIndex
    ) {
        this.index = index;
        this.importIndex = importIndex;
    }
    @Override
    public void visit(ClassOrInterfaceDeclaration classOrInterfaceDeclaration, Void arg) {
        if (classOrInterfaceDeclaration.getFullyQualifiedName().isPresent()) {
            String fullyQualifiedClassName = classOrInterfaceDeclaration.getFullyQualifiedName().get();
            boolean found = false;
            for (ClassOrInterfaceType extension : classOrInterfaceDeclaration.getExtendedTypes()) {
                String superClassFullyQualifiedName = importIndex.get(extension.getName().asString());
                if (superClassFullyQualifiedName != null) {
                    found = true;
                    index.addSuperClass(fullyQualifiedClassName, superClassFullyQualifiedName);
                }
            }
            if (!found) {
                index.addSuperClass(fullyQualifiedClassName, "java.lang.Object");
            }
        }
        super.visit(classOrInterfaceDeclaration, arg);
    }
    /* Records and enums cannot extend so we only consider classes */
}
182 | 183 | Back to index... 184 | 185 | -------------------------------------------------------------------------------- /docs/odin.code.browser/indexing/SuperClassIndexer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 |
package indexing;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ast.CompilationUnit;
import options.OdinOptions;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
public class SuperClassIndexer {
    private final Index completeIndex;
    private final OdinOptions odinOptions;
    public SuperClassIndexer(
            Index completeIndex,
            OdinOptions odinOptions
    ) {
        this.completeIndex = completeIndex;
        this.odinOptions = odinOptions;
    }
    public void indexFiles(
            List<Path> files,
            Index localIndex) {
        for (Path file: files) {
            try {
                indexFile(file, localIndex);
            } catch (Exception e) {
                throw new RuntimeException("Error processing " + file, e);
            }
        }
    }
    private void indexFile(
            Path file,
            Index localIndex
    ) throws IOException {
        JavaParser javaParser = new JavaParser(new ParserConfiguration().setLanguageLevel(
                odinOptions.languageLevel));
        CompilationUnit compilationUnit = javaParser.parse(file).getResult().get();
        ImportVisitor importVisitor = new ImportVisitor(completeIndex);
        importVisitor.visit(compilationUnit, null);
        SuperClassIndexVisitor indexVisitor = new SuperClassIndexVisitor(localIndex, importVisitor.imports);
        indexVisitor.visit(compilationUnit, null);
    }
}
218 | 219 | Back to index... 220 | 221 | -------------------------------------------------------------------------------- /docs/odin.code.browser/indexing/VariableInfo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
package indexing;
/***
 * @param filePosition position this varible appears in the file
 * @param type the Fully Qualified Class Name of the type
 */
public record VariableInfo(
        String type,
        Index.FilePosition filePosition
) {
}
54 | 55 | Back to index... 56 | 57 | -------------------------------------------------------------------------------- /docs/odin.code.browser/options/OdinOptions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 |
package options;
import com.github.javaparser.ParserConfiguration;
import org.kohsuke.args4j.Option;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class OdinOptions {
    @Option(name="--inputSourceDirectory",
        required = true,
        usage= """
               The directory contain the source code. For instance,
               if you had a maven project, it would probably be
               <project_root>/src/main/java. You can specifiy multiple
               directories typing multiple --inputSourceDirectory parameters.
               You should specify the all the dependant directories before
               specifying the directory. For instance in jdk17 you type something
               like
               --inputSourceDirectory src/java.base/share/classes
               --inputSourceDirectory src/java.logging/share/classses
               --inputSourceDirectory src/java.xml/share/classses
               --inputSourceDirectory src/java.transactions.xa/share/classses
               --inputSourceDirectory src/java.sql/share/classses
               --inputSourceDirectory src/java.sql.rowset/share/classses
               """)
    public List<String> inputSourceDirectories;
    @Option(name="--outputDirectory",
            required = true,
            usage= """
               The directory where all the html files for the navigable source code will be written.
               Odin will also write the index.html and index.json directly in this directory.
               """)
    public String outputDirectory;
    @Option(name="--webPathToCssFile",
            required = true,
            usage= """
               The path to the css file. For instance, Odin hosts jdk8 at /OdinCodeBrowser/jdk8 but
               the style is in /OdinCodeBrowser/styles.css then this should be /OdinCodeBrowser/styles.css
               that way multiple repos can shares the same style.
               """)
    public String webPathToCssFile;
    @Option(name="--webPathToSourceHtmlFiles",
            required = true,
            usage= """
               The root path to the html versions of the source files generated using --inputSourceDirectory.
               For instance, OdinCodeBrowser hosts the jdk8 source in /OdinCodeBrowser/jdk8 so it uses
               --webPathToSourceHtmlFiles /OdinCodeBrowser/jdk8
               """)
    public String webPathToSourceHtmlFiles;
    @Option(name="--urlToDependantIndexJson",
            required = false,
            usage= """
               Url of the index.json of the dependencies of this repository.
               """)
    public List<String> urlsToDependantIndexJsons = new ArrayList<>();
    @Option(name="--languageLevel",
            required = false,
            usage= """
               Which version of the JDK the source code uses.
               """)
    public ParserConfiguration.LanguageLevel languageLevel = ParserConfiguration.LanguageLevel.JAVA_16;
    @Option(name="--multiRepoRoot",
            required = false,
            usage= """
               If this this website hold multiple repos under a root directory, provide this root directory
               so that the repositories can link back the the repository list.
               """)
    public String multiRepoRoot = null;
}
326 | 327 | Back to index... 328 | 329 | -------------------------------------------------------------------------------- /docs/odin.code.browser/rendering/IndexJsonRenderer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
package rendering;
import com.fasterxml.jackson.databind.ObjectMapper;
import indexing.Index;
import java.io.File;
import java.io.IOException;
/**
 * Creates a json file with the index. Allows other source repositories to point to this repository.
 */
public class IndexJsonRenderer {
    public void render(
            String outputFile,
            Index index
    ) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.writerWithDefaultPrettyPrinter()
                .writeValue(new File(outputFile), index);
    }
}
102 | 103 | Back to index... 104 | 105 | -------------------------------------------------------------------------------- /docs/odin.code.browser/rendering/source/ContentRecord.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
package rendering.source;
record ContentRecord(
        String content,
        PositionType positionType
) {
}
42 | 43 | Back to index... 44 | 45 | -------------------------------------------------------------------------------- /docs/odin.code.browser/rendering/source/PositionType.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
package rendering.source;
enum PositionType {
    BEFORE,
    AFTER
}
34 | 35 | Back to index... 36 | 37 | -------------------------------------------------------------------------------- /docs/odin.code.browser/rendering/source/RenderQueueLine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 |
package rendering.source;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class RenderQueueLine {
    final Map<Integer, List<ContentRecord>> renderQueueLine = new HashMap<>();
    public void add(int col, ContentRecord contentRecord) {
        List<ContentRecord> content = renderQueueLine.get(col);
        if (content == null) {
            content = new ArrayList<>();
            renderQueueLine.put(col, content);
        }
        content.add(contentRecord);
    }
}
94 | 95 | Back to index... 96 | 97 | -------------------------------------------------------------------------------- /docs/odin.code.browser/rendering/source/RenderingQueue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |
package rendering.source;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class RenderingQueue {
    public final Map<Integer, RenderQueueLine> withinLineRenderingQueue = new HashMap<>();
    public final MultiValuedMap<Integer, String> beginningOfLineRenderingQueue = new ArrayListValuedHashMap<>();
    public final MultiValuedMap<Integer, String> endOfLineRenderingQueue = new ArrayListValuedHashMap<>();
    public void add(int line, int col, ContentRecord contentRecord) {
        RenderQueueLine renderQueueLine = withinLineRenderingQueue.get(line);
        if (renderQueueLine == null) {
            renderQueueLine = new RenderQueueLine();
            withinLineRenderingQueue.put(line, renderQueueLine);
        }
        renderQueueLine.add(col, contentRecord);
    }
    public void beginningOfLine(int line, String content) {
        beginningOfLineRenderingQueue.put(line, content);
    }
    public void endOfLine(int line, String content) {
        endOfLineRenderingQueue.put(line, content);
    }
}
134 | 135 | Back to index... 136 | 137 | -------------------------------------------------------------------------------- /docs/odin.code.browser/util/StringUtil.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back to index... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 |
package util;
public class StringUtil {
    private StringUtil() {
    }
    /**
     * ABCDE CDEFG
     * should return
     * ABCDEFG
     * @param startString
     * @param endString
     * @return
     */
    public static String mergeStrings(
            String startString,
            String endString
    ) {
        int max = 0;
        for (int i = 1; i <= endString.length(); i++) {
            String subStr = endString.substring(0, i);
            int posnFound = startString.lastIndexOf(subStr);
            if (posnFound >= 0
                    && startString.lastIndexOf(subStr) == startString.length() - subStr.length()) {
                max = i;
            }
        }
        return startString + endString.substring(max);
    }
}
142 | 143 | Back to index... 144 | 145 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.josephmate.odin 6 | odin.code.browser 7 | 1.0-SNAPSHOT 8 | 9 | 16 10 | 16 11 | 12 | 13 | 14 | com.github.javaparser 15 | javaparser-symbol-solver-core 16 | 3.23.1 17 | 18 | 19 | org.apache.commons 20 | commons-text 21 | 1.9 22 | 23 | 24 | com.fasterxml.jackson.core 25 | jackson-databind 26 | 2.13.0 27 | 28 | 29 | args4j 30 | args4j 31 | 2.33 32 | 33 | 34 | org.apache.commons 35 | commons-collections4 36 | 4.4 37 | 38 | 39 | 40 | org.junit.jupiter 41 | junit-jupiter 42 | RELEASE 43 | test 44 | 45 | 46 | org.junit.jupiter 47 | junit-jupiter 48 | RELEASE 49 | test 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/main/java/Director.java: -------------------------------------------------------------------------------- 1 | import indexing.Index; 2 | import indexing.Indexer; 3 | import indexing.SuperClassIndexer; 4 | import indexing.UrlIndexLoader; 5 | import options.OdinOptions; 6 | import org.apache.commons.lang3.tuple.Pair; 7 | import rendering.IndexHtmlRenderer; 8 | import rendering.IndexJsonRenderer; 9 | import rendering.source.SourceHtmlRenderer; 10 | 11 | import java.io.IOException; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.SortedMap; 18 | import java.util.TreeMap; 19 | import java.util.stream.Collectors; 20 | import java.util.stream.Stream; 21 | 22 | /** 23 | * Figures out which files needs to be processed. 24 | * Two pass process: 25 | *
    26 | *
  1. Provides those files to the Index to create the index.
  2. 27 | *
  3. Gives the index to the render so it can create the html files.
  4. 28 | *
29 | */ 30 | public record Director( 31 | OdinOptions odinOptions 32 | ) { 33 | public void processFiles() throws IOException { 34 | Index completeIndex = new UrlIndexLoader().load(odinOptions.urlsToDependantIndexJsons); 35 | // don't use private methods from dependencies 36 | completeIndex.privateMethodIndex.clear(); 37 | 38 | List>> processOrder = new ArrayList<>(); 39 | for (String inputSourceDirectory : odinOptions().inputSourceDirectories) { 40 | try (Stream stream = Files.walk(Paths.get(inputSourceDirectory))) { 41 | List files = stream.filter(Files::isRegularFile) 42 | .filter(path -> path.toString().endsWith(".java")) 43 | .collect(Collectors.toList()); 44 | processOrder.add(Pair.of(inputSourceDirectory, files)); 45 | } 46 | } 47 | 48 | Index localIndex = new Index(); 49 | for (Pair> currentlyProcessing : processOrder) { 50 | new Indexer(odinOptions) 51 | .indexFiles(currentlyProcessing.getLeft(), 52 | currentlyProcessing.getRight(), 53 | localIndex); 54 | } 55 | // need complete index for super class since we need all the imports 56 | // this index will be missing the super classes which is fine. we don't 57 | // need the dependencies super class mapping to calculate the super class 58 | // mapping. 59 | completeIndex.addAll(localIndex); 60 | 61 | for (Pair> currentlyProcessing : processOrder) { 62 | new SuperClassIndexer(completeIndex, odinOptions) 63 | .indexFiles(currentlyProcessing.getRight(), localIndex); 64 | } 65 | completeIndex.addAll(localIndex); 66 | 67 | for (Pair> currentlyProcessing : processOrder) { 68 | new SourceHtmlRenderer(completeIndex, odinOptions) 69 | .proccessFiles(currentlyProcessing.getLeft(), currentlyProcessing.getRight()); 70 | } 71 | 72 | new IndexHtmlRenderer(odinOptions).render(processOrder); 73 | 74 | new IndexJsonRenderer().render( 75 | odinOptions.outputDirectory + "/index.json", 76 | // only export an index for the sources belonging to this project 77 | localIndex 78 | ); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/ExperimentalMain.java: -------------------------------------------------------------------------------- 1 | import com.github.javaparser.JavaParser; 2 | import com.github.javaparser.ParserConfiguration; 3 | import com.github.javaparser.StaticJavaParser; 4 | import com.github.javaparser.ast.*; 5 | import com.github.javaparser.ast.body.*; 6 | import com.github.javaparser.ast.expr.*; 7 | import com.github.javaparser.ast.stmt.ForStmt; 8 | import com.github.javaparser.ast.stmt.IfStmt; 9 | import com.github.javaparser.ast.stmt.WhileStmt; 10 | import com.github.javaparser.ast.type.ClassOrInterfaceType; 11 | import com.github.javaparser.ast.type.Type; 12 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter; 13 | 14 | import java.io.File; 15 | 16 | public class ExperimentalMain { 17 | public static void main(String [] args) throws Exception { 18 | JavaParser javaParser = new JavaParser(new ParserConfiguration().setLanguageLevel( 19 | ParserConfiguration.LanguageLevel.JAVA_16)); 20 | 21 | CompilationUnit compilationUnit = javaParser.parse(new File(args[0])).getResult().get(); 22 | VoidVisitorAdapter visitor = new Visitor(); 23 | visitor.visit(compilationUnit, null); 24 | 25 | "some test with str" 26 | .substring(0,2) 27 | .substring(0,1); 28 | 29 | (new Builder("hello")) 30 | .blah1(123) 31 | .someField 32 | .blah2() 33 | .arrayField[1] 34 | .blah3(); 35 | } 36 | } 37 | 38 | enum Test { 39 | HELLO, 40 | WORLD 41 | } 42 | 43 | class Builder { 44 | 45 | Builder(String s) { 46 | 47 | } 48 | 49 | Test enumField = Test.HELLO; 50 | 51 | Builder multiA = this, multiB = this; 52 | 53 | int primitiveField = 10; 54 | 55 | int[] primitiveArrayField = new int[]{1, 2, 3}; 56 | 57 | int primitiveArrayFieldWeird[] = new int[]{1, 2, 3}; 58 | 59 | Builder someField = this; 60 | 61 | Builder[] arrayField = new Builder[]{ 62 | this, 63 | this 64 | }; 65 | 66 | Builder blah1(int someArg) { 67 | return this; 68 | } 69 | Builder blah2() { 70 | return this; 71 | } 72 | Builder blah3() { 73 | return this; 74 | } 75 | 76 | } 77 | 78 | class Visitor extends VoidVisitorAdapter { 79 | int tabCount = 0; 80 | 81 | private void print(String message) { 82 | for (int i = 0; i < tabCount; i++) { 83 | System.out.print("\t"); 84 | } 85 | System.out.println(message); 86 | } 87 | 88 | @Override 89 | public void visit(ClassOrInterfaceDeclaration d, Void arg) { 90 | print("start class " + d.getFullyQualifiedName()); 91 | tabCount++; 92 | super.visit(d, arg); 93 | tabCount--; 94 | print("end class " + d.getFullyQualifiedName()); 95 | } 96 | 97 | @Override 98 | public void visit(MethodDeclaration n, Void arg) { 99 | print("start MethodDeclaration " + n.getName()); 100 | tabCount++; 101 | print("MethodDeclaration.getTypeAsString() " + n.getTypeAsString()); 102 | n.getParameters().forEach(p -> print("param: " + p.getTypeAsString())); 103 | 104 | // don't call super since we copied super's code and changed the order to have the parameters first 105 | n.getParameters().forEach(p -> p.accept(this, arg)); 106 | n.getBody().ifPresent(l -> l.accept(this, arg)); 107 | n.getType().accept(this, arg); 108 | n.getModifiers().forEach(p -> p.accept(this, arg)); 109 | n.getName().accept(this, arg); 110 | n.getReceiverParameter().ifPresent(l -> l.accept(this, arg)); 111 | n.getThrownExceptions().forEach(p -> p.accept(this, arg)); 112 | n.getTypeParameters().forEach(p -> p.accept(this, arg)); 113 | n.getAnnotations().forEach(p -> p.accept(this, arg)); 114 | n.getComment().ifPresent(l -> l.accept(this, arg)); 115 | 116 | tabCount--; 117 | print("end MethodDeclaration " + n.getName()); 118 | } 119 | 120 | @Override 121 | public void visit(WhileStmt d, Void arg) { 122 | print("start while " ); 123 | tabCount++; 124 | super.visit(d, arg); 125 | tabCount--; 126 | print("end while "); 127 | } 128 | 129 | @Override 130 | public void visit(ForStmt d, Void arg) { 131 | print("start for "); 132 | tabCount++; 133 | 134 | // don't call super since I don't like the order they use 135 | // instead we do initialization first so we can get the variables before the body: 136 | d.getInitialization().forEach(p -> p.accept(this, arg)); 137 | d.getBody().accept(this, arg); 138 | d.getCompare().ifPresent(l -> l.accept(this, arg)); 139 | d.getUpdate().forEach(p -> p.accept(this, arg)); 140 | d.getComment().ifPresent(l -> l.accept(this, arg)); 141 | 142 | tabCount--; 143 | print("end for "); 144 | } 145 | 146 | @Override 147 | public void visit(IfStmt d, Void arg) { 148 | print("start if "); 149 | tabCount++; 150 | super.visit(d, arg); 151 | tabCount--; 152 | print("end if "); 153 | } 154 | 155 | @Override 156 | public void visit(Parameter d, Void arg) { 157 | print("start param " + d + " " + d.getRange()); 158 | tabCount++; 159 | super.visit(d, arg); 160 | tabCount--; 161 | print("end param " + d + " " + d.getRange()); 162 | } 163 | 164 | @Override 165 | public void visit(VariableDeclarationExpr d, Void arg) { 166 | print("start variable " + d); 167 | tabCount++; 168 | super.visit(d, arg); 169 | tabCount--; 170 | print("end variable " + d); 171 | } 172 | 173 | 174 | @Override 175 | public void visit(ClassOrInterfaceType d, Void arg) { 176 | print("start classType " + d); 177 | tabCount++; 178 | super.visit(d, arg); 179 | tabCount--; 180 | print("end classType " + d); 181 | } 182 | 183 | @Override 184 | public void visit(NameExpr d, Void arg) { 185 | print("start nameExpr " + d); 186 | tabCount++; 187 | super.visit(d, arg); 188 | tabCount--; 189 | print("end nameExpr " + d); 190 | } 191 | 192 | @Override 193 | public void visit(MethodCallExpr n, Void arg) { 194 | print("start MethodCallExpr " + n); 195 | tabCount++; 196 | print("# args: " + n.getArguments().size()); 197 | for (int i = 0; i < n.getArguments().size(); i++) { 198 | Expression expression = n.getArguments().get(i); 199 | print("arg[" + i + "]= (# nodes=" + expression.getChildNodes().size() + ")" + expression); 200 | tabCount++; 201 | if (expression.getChildNodes().size() == 1) { 202 | print("type: " + expression.getChildNodes().get(0).getClass().getSimpleName()); 203 | } 204 | tabCount--; 205 | } 206 | print("==========================="); 207 | super.visit(n, arg); 208 | tabCount--; 209 | print("end MethodCallExpr " + n); 210 | } 211 | 212 | @Override 213 | public void visit(ObjectCreationExpr oce, Void arg) { 214 | print("start ObjectCreationExpr " + oce); 215 | tabCount++; 216 | super.visit(oce, arg); 217 | tabCount--; 218 | print("end ObjectCreationExpr " + oce); 219 | } 220 | @Override 221 | public void visit(ArrayAccessExpr oce, Void arg) { 222 | print("start ArrayAccessExpr " + oce); 223 | tabCount++; 224 | super.visit(oce, arg); 225 | tabCount--; 226 | print("end ArrayAccessExpr " + oce); 227 | } 228 | 229 | public void visit(FieldDeclaration fieldDeclaration, Void arg) { 230 | print("start FieldDeclaration " + fieldDeclaration); 231 | tabCount++; 232 | super.visit(fieldDeclaration, arg); 233 | tabCount--; 234 | print("end FieldDeclaration " + fieldDeclaration); 235 | } 236 | 237 | public void visit(VariableDeclarator vd, Void arg) { 238 | print("start VariableDeclarator " + vd); 239 | tabCount++; 240 | print("start VariableDeclarator.getType().asString() " + vd.getType().asString()); 241 | super.visit(vd, arg); 242 | tabCount--; 243 | print("end VariableDeclarator " + vd); 244 | } 245 | 246 | } -------------------------------------------------------------------------------- /src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | import options.OdinOptions; 2 | import org.kohsuke.args4j.CmdLineException; 3 | import org.kohsuke.args4j.CmdLineParser; 4 | 5 | import java.io.IOException; 6 | 7 | public class Main { 8 | 9 | 10 | 11 | public static void main(String[] args) throws IOException { 12 | OdinOptions bean = new OdinOptions(); 13 | CmdLineParser parser = new CmdLineParser(bean); 14 | 15 | try { 16 | parser.parseArgument(args); 17 | Director director = new Director(bean); 18 | director.processFiles(); 19 | } catch(CmdLineException e) { 20 | System.err.println(e.getMessage()); 21 | System.err.println("java -jar odin-browser.jar Main [options...] arguments..."); 22 | parser.printUsage(System.err); 23 | return; 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/indexing/ImportVisitor.java: -------------------------------------------------------------------------------- 1 | package indexing; 2 | 3 | import com.github.javaparser.ast.ImportDeclaration; 4 | import com.github.javaparser.ast.PackageDeclaration; 5 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter; 6 | import indexing.Index; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * Determines all imports we need to consider including: 13 | *
    14 | *
  • java.util.HashMap
  • 15 | *
  • java.util.*
  • 16 | *
  • java.lang.* (always included)
  • 17 | *
  • current.package.* (always included)
  • 18 | *
19 | */ 20 | public class ImportVisitor extends VoidVisitorAdapter { 21 | 22 | /** 23 | * SimpleName (ex: Object) to Fully Qualified Name (ex: java.lang.Object) 24 | */ 25 | public final Map imports = new HashMap<>(); 26 | private final Index index; 27 | 28 | public ImportVisitor(Index index) { 29 | this.index = index; 30 | for (String fullyQualifiedName : index.getClassIndex().keySet()) { 31 | if (fullyQualifiedName.startsWith("java.lang.")) { 32 | imports.put(getLastToken(fullyQualifiedName), fullyQualifiedName); 33 | } 34 | } 35 | } 36 | 37 | private boolean isInPackage( 38 | String fullyQualifiedName, 39 | String packageName 40 | ) { 41 | return fullyQualifiedName.startsWith(packageName) 42 | && fullyQualifiedName.lastIndexOf('.') <= packageName.length(); 43 | } 44 | 45 | @Override 46 | public void visit(PackageDeclaration packageDeclaration, Void arg) { 47 | String packageName = packageDeclaration.getName().asString(); 48 | for (String fullyQualifiedName : index.getClassIndex().keySet()) { 49 | if (isInPackage(fullyQualifiedName, packageName)) { 50 | imports.put(getLastToken(fullyQualifiedName), fullyQualifiedName); 51 | } 52 | } 53 | super.visit(packageDeclaration, arg); 54 | } 55 | 56 | @Override 57 | public void visit(ImportDeclaration importDeclaration, Void arg) { 58 | if (!importDeclaration.isAsterisk() && !importDeclaration.isStatic()) { 59 | String importName = importDeclaration.getNameAsString(); 60 | imports.put(getLastToken(importName), importName); 61 | } else if (importDeclaration.isAsterisk() && !importDeclaration.isStatic()) { 62 | String importName = importDeclaration.getNameAsString(); 63 | for (String fullyQualifiedName : index.getClassIndex().keySet()) { 64 | if (isInPackage(fullyQualifiedName, importName)) { 65 | imports.put(getLastToken(fullyQualifiedName), fullyQualifiedName); 66 | } 67 | } 68 | } 69 | super.visit(importDeclaration, arg); 70 | } 71 | 72 | private static String getLastToken(String importName) { 73 | String [] tokens = importName.split("\\."); 74 | if (tokens.length <= 0) { 75 | return ""; 76 | } 77 | return tokens[tokens.length-1]; 78 | } 79 | } -------------------------------------------------------------------------------- /src/main/java/indexing/Index.java: -------------------------------------------------------------------------------- 1 | package indexing; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | 5 | import java.util.*; 6 | 7 | public class Index { 8 | 9 | /** 10 | * Fully Qualified Class Name -> File Position 11 | */ 12 | public final Map classIndex = new HashMap<>(); 13 | 14 | /** 15 | * Fully Qualified Class Name -> method name -> File Position 16 | */ 17 | public final Map>> methodIndex = new HashMap<>(); 18 | 19 | /** 20 | * Fully Qualified Class Name -> method name -> File Position 21 | */ 22 | @JsonIgnore 23 | public final Map>> privateMethodIndex = new HashMap<>(); 24 | 25 | /** 26 | * Fully Qualified Class Name -> variable name -> File Position 27 | */ 28 | public final Map> variableIndex = new HashMap<>(); 29 | 30 | public final Map> superClassMap = new HashMap<>(); 31 | 32 | public void addClass( 33 | String fullyQualifiedName, 34 | String fileUrl, 35 | int lineNumber) { 36 | classIndex.put(fullyQualifiedName, new FilePosition(fileUrl, lineNumber)); 37 | } 38 | 39 | public void addMethod( 40 | String fullyQualifiedName, 41 | String methodName, 42 | MethodInfo methodInfo 43 | ) { 44 | Map> methodSubMap = methodIndex.get(fullyQualifiedName); 45 | if (methodSubMap == null) { 46 | methodSubMap = new HashMap<>(); 47 | methodIndex.put(fullyQualifiedName, methodSubMap); 48 | } 49 | 50 | List overloads = methodSubMap.get(methodName); 51 | if (overloads == null) { 52 | overloads = new ArrayList<>(); 53 | methodSubMap.put(methodName, overloads); 54 | } 55 | overloads.add(methodInfo); 56 | } 57 | 58 | public void addPrivateMethod( 59 | String fullyQualifiedName, 60 | String methodName, 61 | MethodInfo methodInfo 62 | ) { 63 | Map> methodSubMap = privateMethodIndex.get(fullyQualifiedName); 64 | if (methodSubMap == null) { 65 | methodSubMap = new HashMap<>(); 66 | privateMethodIndex.put(fullyQualifiedName, methodSubMap); 67 | } 68 | 69 | List overloads = methodSubMap.get(methodName); 70 | if (overloads == null) { 71 | overloads = new ArrayList<>(); 72 | methodSubMap.put(methodName, overloads); 73 | } 74 | overloads.add(methodInfo); 75 | } 76 | 77 | public void addVariable( 78 | String fullyQualifiedName, 79 | String variableName, 80 | VariableInfo variableInfo 81 | ) { 82 | Map variableSubMap = variableIndex.get(fullyQualifiedName); 83 | if (variableSubMap == null) { 84 | variableSubMap = new HashMap<>(); 85 | variableIndex.put(fullyQualifiedName, variableSubMap); 86 | } 87 | variableSubMap.put(variableName, variableInfo); 88 | } 89 | 90 | public void addSuperClass( 91 | String subClassFullyQualifiedName, 92 | String superClassFullyQualifiedName 93 | ) { 94 | List superClasses = superClasses = superClassMap.get(subClassFullyQualifiedName); 95 | if (superClasses == null) { 96 | superClasses = new ArrayList<>(); 97 | superClassMap.put(subClassFullyQualifiedName, superClasses); 98 | } 99 | superClasses.add(superClassFullyQualifiedName); 100 | } 101 | 102 | public FilePosition getClass(String fullyQualifiedName) { 103 | return classIndex.get(fullyQualifiedName); 104 | } 105 | 106 | public List getMethodOverloads( 107 | String fullyQualifiedName, 108 | String methodName) { 109 | Map> methodSubMap = methodIndex.get(fullyQualifiedName); 110 | if (methodSubMap == null) { 111 | return null; 112 | } 113 | 114 | return methodSubMap.get(methodName); 115 | } 116 | 117 | public MethodInfo getMethod( 118 | String fullyQualifiedName, 119 | String methodName, 120 | List argumentTypes 121 | ) { 122 | List overloads = getMethodOverloads(fullyQualifiedName, methodName); 123 | if (overloads == null) { 124 | return null; 125 | } 126 | 127 | for (MethodInfo methodInfo : overloads) { 128 | if (methodInfo.argumentTypes().equals(argumentTypes)) { 129 | return methodInfo; 130 | } 131 | } 132 | 133 | return null; 134 | } 135 | 136 | public List getPrivateMethodOverloads(String fullyQualifiedName, 137 | String methodName) { 138 | Map> methodSubMap = privateMethodIndex.get(fullyQualifiedName); 139 | if (methodSubMap == null) { 140 | return null; 141 | } 142 | 143 | return methodSubMap.get(methodName); 144 | } 145 | 146 | public MethodInfo getPrivateMethod( 147 | String fullyQualifiedName, 148 | String methodName, 149 | List argumentTypes 150 | ) { 151 | List overloads = getPrivateMethodOverloads(fullyQualifiedName, methodName); 152 | if (overloads == null) { 153 | return null; 154 | } 155 | 156 | for (MethodInfo methodInfo : overloads) { 157 | if (methodInfo.argumentTypes().equals(argumentTypes)) { 158 | return methodInfo; 159 | } 160 | } 161 | 162 | return null; 163 | } 164 | 165 | public List getSuperClasses(String fullyQualifiedClassName) { 166 | List result = superClassMap.get(fullyQualifiedClassName); 167 | if (result == null) { 168 | return Collections.emptyList(); 169 | } 170 | return result; 171 | } 172 | 173 | public Map getClassIndex() { 174 | return classIndex; 175 | } 176 | 177 | public Map>> getMethodIndex() { 178 | return methodIndex; 179 | } 180 | 181 | public Map> getVariableIndex() { 182 | return variableIndex; 183 | } 184 | 185 | public Map> getSuperClassMap() { 186 | return superClassMap; 187 | } 188 | 189 | public void addAll(Index otherIndex) { 190 | for (Map.Entry entry : otherIndex.classIndex.entrySet()) { 191 | String fullyQualifiedClassName = entry.getKey(); 192 | Index.FilePosition filePosition = entry.getValue(); 193 | this.addClass( 194 | fullyQualifiedClassName, 195 | filePosition.fileName(), 196 | filePosition.lineNumber() 197 | ); 198 | } 199 | for (Map.Entry> entry : otherIndex.variableIndex.entrySet()) { 200 | String fullyQualifiedClassName = entry.getKey(); 201 | for (Map.Entry entry2: entry.getValue().entrySet()) { 202 | String variableName = entry2.getKey(); 203 | VariableInfo variableInfo = entry2.getValue(); 204 | this.addVariable( 205 | fullyQualifiedClassName, 206 | variableName, 207 | variableInfo 208 | ); 209 | } 210 | } 211 | for (Map.Entry>> entry : otherIndex.methodIndex.entrySet()) { 212 | String fullyQualifiedClassName = entry.getKey(); 213 | for (Map.Entry> entry2: entry.getValue().entrySet()) { 214 | String methodName = entry2.getKey(); 215 | List overloads = entry2.getValue(); 216 | for (MethodInfo overload : overloads) { 217 | this.addMethod( 218 | fullyQualifiedClassName, 219 | methodName, 220 | overload 221 | ); 222 | } 223 | } 224 | } 225 | for (Map.Entry>> entry : otherIndex.privateMethodIndex.entrySet()) { 226 | String fullyQualifiedClassName = entry.getKey(); 227 | for (Map.Entry> entry2: entry.getValue().entrySet()) { 228 | String methodName = entry2.getKey(); 229 | List overloads = entry2.getValue(); 230 | for (MethodInfo overload : overloads) { 231 | this.addMethod( 232 | fullyQualifiedClassName, 233 | methodName, 234 | overload 235 | ); 236 | } 237 | } 238 | } 239 | for (Map.Entry> entry : otherIndex.superClassMap.entrySet()) { 240 | String fullyQualifiedClassName = entry.getKey(); 241 | for (String superClass : entry.getValue()) { 242 | this.addSuperClass( 243 | fullyQualifiedClassName, 244 | superClass 245 | ); 246 | } 247 | } 248 | 249 | this.superClassMap.putAll(otherIndex.getSuperClassMap()); 250 | } 251 | 252 | public record FilePosition ( 253 | String fileName, 254 | int lineNumber) { } 255 | } 256 | 257 | -------------------------------------------------------------------------------- /src/main/java/indexing/IndexVisitor.java: -------------------------------------------------------------------------------- 1 | package indexing; 2 | 3 | import com.github.javaparser.ast.body.*; 4 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | public class IndexVisitor extends VoidVisitorAdapter { 10 | 11 | private final Index index; 12 | private final String fileUrl; 13 | 14 | public IndexVisitor( 15 | Index index, 16 | String fileUrl 17 | ) { 18 | this.index = index; 19 | this.fileUrl = fileUrl; 20 | } 21 | 22 | @Override 23 | public void visit(ClassOrInterfaceDeclaration classOrInterfaceDeclaration, Void arg) { 24 | if (classOrInterfaceDeclaration.getFullyQualifiedName().isPresent()) { 25 | index.addClass( 26 | classOrInterfaceDeclaration.getFullyQualifiedName().get(), 27 | fileUrl, 28 | classOrInterfaceDeclaration.getRange().get().begin.line 29 | ); 30 | } 31 | super.visit(classOrInterfaceDeclaration, arg); 32 | } 33 | 34 | @Override 35 | public void visit(RecordDeclaration recordDeclaration, Void arg) { 36 | if (recordDeclaration.getFullyQualifiedName().isPresent()) { 37 | index.addClass( 38 | recordDeclaration.getFullyQualifiedName().get(), 39 | fileUrl, 40 | recordDeclaration.getRange().get().begin.line 41 | ); 42 | } 43 | super.visit(recordDeclaration, arg); 44 | } 45 | 46 | @Override 47 | public void visit(EnumDeclaration enumDeclaration, Void arg) { 48 | if (enumDeclaration.getFullyQualifiedName().isPresent()) { 49 | index.addClass( 50 | enumDeclaration.getFullyQualifiedName().get(), 51 | fileUrl, 52 | enumDeclaration.getRange().get().begin.line 53 | ); 54 | } 55 | super.visit(enumDeclaration, arg); 56 | } 57 | 58 | @Override 59 | public void visit(MethodDeclaration methodDeclaration, Void arg) { 60 | String methodName = methodDeclaration.getName().asString(); 61 | if (methodDeclaration.getParentNode().isPresent() 62 | && methodDeclaration.getParentNode().get() instanceof ClassOrInterfaceDeclaration 63 | ) { 64 | List argTypes = methodDeclaration.getParameters().stream() 65 | .map(Parameter::getTypeAsString) 66 | .collect(Collectors.toList()); 67 | String returnType = methodDeclaration.getTypeAsString(); 68 | MethodInfo methodInfo = new MethodInfo( 69 | argTypes, 70 | returnType, 71 | new Index.FilePosition( 72 | fileUrl, 73 | methodDeclaration.getRange().get().begin.line 74 | ) 75 | ); 76 | 77 | ClassOrInterfaceDeclaration classOrInterfaceDeclaration = (ClassOrInterfaceDeclaration) methodDeclaration.getParentNode().get(); 78 | if (classOrInterfaceDeclaration.getFullyQualifiedName().isPresent()) { 79 | if (methodDeclaration.isPrivate()) { 80 | index.addPrivateMethod( 81 | classOrInterfaceDeclaration.getFullyQualifiedName().get(), 82 | methodName, 83 | methodInfo 84 | ); 85 | } else { 86 | index.addMethod( 87 | classOrInterfaceDeclaration.getFullyQualifiedName().get(), 88 | methodName, 89 | methodInfo 90 | ); 91 | } 92 | } 93 | } 94 | super.visit(methodDeclaration, arg); 95 | } 96 | 97 | public void visit(FieldDeclaration fieldDeclaration, Void arg) { 98 | if (fieldDeclaration.getParentNode().isPresent() 99 | && fieldDeclaration.getParentNode().get() instanceof ClassOrInterfaceDeclaration 100 | ) { 101 | ClassOrInterfaceDeclaration classOrInterfaceDeclaration = (ClassOrInterfaceDeclaration) fieldDeclaration.getParentNode().get(); 102 | if (classOrInterfaceDeclaration.getFullyQualifiedName().isPresent()) { 103 | if (!fieldDeclaration.isPrivate()) { 104 | for (VariableDeclarator vd : fieldDeclaration.getVariables()) { 105 | index.addVariable( 106 | classOrInterfaceDeclaration.getFullyQualifiedName().get(), 107 | vd.getNameAsString(), 108 | new VariableInfo( 109 | vd.getTypeAsString(), 110 | new Index.FilePosition( 111 | fileUrl, 112 | fieldDeclaration.getRange().get().begin.line 113 | ) 114 | ) 115 | ); 116 | } 117 | } 118 | } 119 | } 120 | super.visit(fieldDeclaration, arg); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/indexing/Indexer.java: -------------------------------------------------------------------------------- 1 | package indexing; 2 | 3 | import com.github.javaparser.JavaParser; 4 | import com.github.javaparser.ParserConfiguration; 5 | import com.github.javaparser.ast.CompilationUnit; 6 | import options.OdinOptions; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Path; 10 | import java.util.Collection; 11 | 12 | public class Indexer { 13 | 14 | private final OdinOptions odinOptions; 15 | 16 | public Indexer( 17 | OdinOptions odinOptions 18 | ) { 19 | this.odinOptions = odinOptions; 20 | } 21 | 22 | public void indexFiles( 23 | String inputDirectory, 24 | Collection paths, 25 | Index index 26 | ) { 27 | for (Path path: paths) { 28 | try { 29 | indexFile(inputDirectory, index, path); 30 | } catch (Exception e) { 31 | throw new RuntimeException("Error processing " + path, e); 32 | } 33 | } 34 | } 35 | 36 | private String getFileUrl( 37 | String inputSourceDirectory, 38 | Path javaSourceFile 39 | ) { 40 | return odinOptions.webPathToSourceHtmlFiles 41 | + ( 42 | javaSourceFile.toString() 43 | .replace('\\', '/') 44 | .substring(0, javaSourceFile.toString().length()-5) 45 | .replace(inputSourceDirectory, "") 46 | + ".html" 47 | ); 48 | } 49 | 50 | private void indexFile( 51 | String inputDirectory, 52 | Index index, 53 | Path path 54 | ) throws IOException { 55 | System.out.println("Indexing " + path); 56 | final String fileUrl = getFileUrl(inputDirectory, path); 57 | indexFile( 58 | index, 59 | path, 60 | fileUrl, 61 | odinOptions.languageLevel 62 | ); 63 | } 64 | 65 | private void indexFile( 66 | Index index, 67 | Path inputFile, 68 | String fileUrl, 69 | ParserConfiguration.LanguageLevel languageLevel 70 | ) throws IOException { 71 | JavaParser javaParser = new JavaParser(new ParserConfiguration().setLanguageLevel( 72 | languageLevel)); 73 | CompilationUnit compilationUnit = javaParser.parse(inputFile).getResult().get(); 74 | IndexVisitor indexVisitor = new IndexVisitor(index, fileUrl); 75 | indexVisitor.visit(compilationUnit, null); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/indexing/MethodInfo.java: -------------------------------------------------------------------------------- 1 | package indexing; 2 | 3 | import java.util.List; 4 | 5 | /*** 6 | * @param argumentTypes list of the Fully Qualified Class Name of the method parameters 7 | * @param returnType the Fully Qualified Class Name of the return type 8 | * @param filePosition position this varible appears in the file 9 | */ 10 | public record MethodInfo( 11 | List argumentTypes, 12 | String returnType, 13 | Index.FilePosition filePosition 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/indexing/SuperClassIndexVisitor.java: -------------------------------------------------------------------------------- 1 | package indexing; 2 | 3 | import com.github.javaparser.ast.body.*; 4 | import com.github.javaparser.ast.type.ClassOrInterfaceType; 5 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter; 6 | 7 | import java.util.Map; 8 | 9 | public class SuperClassIndexVisitor extends VoidVisitorAdapter { 10 | 11 | private final Index index; 12 | private final Map importIndex; 13 | 14 | public SuperClassIndexVisitor( 15 | Index index, 16 | Map importIndex 17 | ) { 18 | this.index = index; 19 | this.importIndex = importIndex; 20 | } 21 | 22 | @Override 23 | public void visit(ClassOrInterfaceDeclaration classOrInterfaceDeclaration, Void arg) { 24 | if (classOrInterfaceDeclaration.getFullyQualifiedName().isPresent()) { 25 | String fullyQualifiedClassName = classOrInterfaceDeclaration.getFullyQualifiedName().get(); 26 | boolean found = false; 27 | for (ClassOrInterfaceType extension : classOrInterfaceDeclaration.getExtendedTypes()) { 28 | String superClassFullyQualifiedName = importIndex.get(extension.getName().asString()); 29 | if (superClassFullyQualifiedName != null) { 30 | found = true; 31 | index.addSuperClass(fullyQualifiedClassName, superClassFullyQualifiedName); 32 | } 33 | } 34 | 35 | if (!found) { 36 | index.addSuperClass(fullyQualifiedClassName, "java.lang.Object"); 37 | } 38 | } 39 | super.visit(classOrInterfaceDeclaration, arg); 40 | } 41 | 42 | /* Records and enums cannot extend so we only consider classes */ 43 | } -------------------------------------------------------------------------------- /src/main/java/indexing/SuperClassIndexer.java: -------------------------------------------------------------------------------- 1 | package indexing; 2 | 3 | import com.github.javaparser.JavaParser; 4 | import com.github.javaparser.ParserConfiguration; 5 | import com.github.javaparser.ast.CompilationUnit; 6 | import options.OdinOptions; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Path; 10 | import java.util.List; 11 | 12 | public class SuperClassIndexer { 13 | 14 | private final Index completeIndex; 15 | private final OdinOptions odinOptions; 16 | 17 | public SuperClassIndexer( 18 | Index completeIndex, 19 | OdinOptions odinOptions 20 | ) { 21 | this.completeIndex = completeIndex; 22 | this.odinOptions = odinOptions; 23 | } 24 | 25 | 26 | public void indexFiles( 27 | List files, 28 | Index localIndex) { 29 | for (Path file: files) { 30 | try { 31 | indexFile(file, localIndex); 32 | } catch (Exception e) { 33 | throw new RuntimeException("Error processing " + file, e); 34 | } 35 | } 36 | } 37 | 38 | private void indexFile( 39 | Path file, 40 | Index localIndex 41 | ) throws IOException { 42 | JavaParser javaParser = new JavaParser(new ParserConfiguration().setLanguageLevel( 43 | odinOptions.languageLevel)); 44 | CompilationUnit compilationUnit = javaParser.parse(file).getResult().get(); 45 | 46 | ImportVisitor importVisitor = new ImportVisitor(completeIndex); 47 | importVisitor.visit(compilationUnit, null); 48 | 49 | SuperClassIndexVisitor indexVisitor = new SuperClassIndexVisitor(localIndex, importVisitor.imports); 50 | indexVisitor.visit(compilationUnit, null); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/indexing/UrlIndexLoader.java: -------------------------------------------------------------------------------- 1 | package indexing; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import util.StringUtil; 5 | 6 | import java.io.IOException; 7 | import java.net.URL; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | public class UrlIndexLoader { 12 | 13 | public Index load(List urls) throws IOException { 14 | Index allExternalIndexes = new Index(); 15 | 16 | ObjectMapper objectMapper = new ObjectMapper(); 17 | for (String url : urls) { 18 | String prefixUrl = url.substring(0, url.length() - "index.json".length()); 19 | 20 | Index externalIndex = objectMapper.readValue(new URL(url), Index.class); 21 | 22 | for (Map.Entry entry : externalIndex.classIndex.entrySet()) { 23 | String fullyQualifiedClassName = entry.getKey(); 24 | Index.FilePosition filePosition = entry.getValue(); 25 | allExternalIndexes.addClass( 26 | fullyQualifiedClassName, 27 | StringUtil.mergeStrings(prefixUrl, filePosition.fileName()), 28 | filePosition.lineNumber() 29 | ); 30 | } 31 | 32 | for (Map.Entry> entry : externalIndex.variableIndex.entrySet()) { 33 | String fullyQualifiedClassName = entry.getKey(); 34 | for (Map.Entry entry2: entry.getValue().entrySet()) { 35 | String variableName = entry2.getKey(); 36 | VariableInfo variableInfo = entry2.getValue(); 37 | allExternalIndexes.addVariable( 38 | fullyQualifiedClassName, 39 | variableName, 40 | new VariableInfo( 41 | variableInfo.type(), 42 | new Index.FilePosition( 43 | StringUtil.mergeStrings(prefixUrl, variableInfo.filePosition().fileName()), 44 | variableInfo.filePosition().lineNumber() 45 | ) 46 | ) 47 | ); 48 | } 49 | } 50 | 51 | for (Map.Entry>> entry : externalIndex.methodIndex.entrySet()) { 52 | String fullyQualifiedClassName = entry.getKey(); 53 | for (Map.Entry> entry2: entry.getValue().entrySet()) { 54 | String methodName = entry2.getKey(); 55 | List overloads = entry2.getValue(); 56 | for(MethodInfo overload : overloads) { 57 | allExternalIndexes.addMethod( 58 | fullyQualifiedClassName, 59 | methodName, 60 | new MethodInfo( 61 | overload.argumentTypes(), 62 | overload.returnType(), 63 | new Index.FilePosition( 64 | StringUtil.mergeStrings(prefixUrl, overload.filePosition().fileName()), 65 | overload.filePosition().lineNumber() 66 | ) 67 | ) 68 | ); 69 | } 70 | } 71 | } 72 | 73 | // purposely skip privateMethodIndex since I don't expect dependants to use 74 | // privates in the dependency 75 | } 76 | 77 | return allExternalIndexes; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/indexing/VariableInfo.java: -------------------------------------------------------------------------------- 1 | package indexing; 2 | 3 | /*** 4 | * @param filePosition position this varible appears in the file 5 | * @param type the Fully Qualified Class Name of the type 6 | */ 7 | public record VariableInfo( 8 | String type, 9 | Index.FilePosition filePosition 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/options/OdinOptions.java: -------------------------------------------------------------------------------- 1 | package options; 2 | 3 | import com.github.javaparser.ParserConfiguration; 4 | import org.kohsuke.args4j.Option; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class OdinOptions { 11 | 12 | @Option(name="--inputSourceDirectory", 13 | required = true, 14 | usage= """ 15 | The directory contain the source code. For instance, 16 | if you had a maven project, it would probably be 17 | /src/main/java. You can specifiy multiple 18 | directories typing multiple --inputSourceDirectory parameters. 19 | You should specify the all the dependant directories before 20 | specifying the directory. For instance in jdk17 you type something 21 | like 22 | --inputSourceDirectory src/java.base/share/classes 23 | --inputSourceDirectory src/java.logging/share/classses 24 | --inputSourceDirectory src/java.xml/share/classses 25 | --inputSourceDirectory src/java.transactions.xa/share/classses 26 | --inputSourceDirectory src/java.sql/share/classses 27 | --inputSourceDirectory src/java.sql.rowset/share/classses 28 | """) 29 | public List inputSourceDirectories; 30 | 31 | @Option(name="--outputDirectory", 32 | required = true, 33 | usage= """ 34 | The directory where all the html files for the navigable source code will be written. 35 | Odin will also write the index.html and index.json directly in this directory. 36 | """) 37 | public String outputDirectory; 38 | 39 | @Option(name="--webPathToCssFile", 40 | required = true, 41 | usage= """ 42 | The path to the css file. For instance, Odin hosts jdk8 at /OdinCodeBrowser/jdk8 but 43 | the style is in /OdinCodeBrowser/styles.css then this should be /OdinCodeBrowser/styles.css 44 | that way multiple repos can shares the same style. 45 | """) 46 | public String webPathToCssFile; 47 | 48 | @Option(name="--webPathToSourceHtmlFiles", 49 | required = true, 50 | usage= """ 51 | The root path to the html versions of the source files generated using --inputSourceDirectory. 52 | For instance, OdinCodeBrowser hosts the jdk8 source in /OdinCodeBrowser/jdk8 so it uses 53 | --webPathToSourceHtmlFiles /OdinCodeBrowser/jdk8 54 | """) 55 | public String webPathToSourceHtmlFiles; 56 | 57 | @Option(name="--urlToDependantIndexJson", 58 | required = false, 59 | usage= """ 60 | Url of the index.json of the dependencies of this repository. 61 | """) 62 | public List urlsToDependantIndexJsons = new ArrayList<>(); 63 | 64 | @Option(name="--languageLevel", 65 | required = false, 66 | usage= """ 67 | Which version of the JDK the source code uses. 68 | """) 69 | public ParserConfiguration.LanguageLevel languageLevel = ParserConfiguration.LanguageLevel.JAVA_16; 70 | 71 | 72 | @Option(name="--multiRepoRoot", 73 | required = false, 74 | usage= """ 75 | If this this website hold multiple repos under a root directory, provide this root directory 76 | so that the repositories can link back the the repository list. 77 | """) 78 | public String multiRepoRoot = null; 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/rendering/IndexHtmlRenderer.java: -------------------------------------------------------------------------------- 1 | package rendering; 2 | 3 | import options.OdinOptions; 4 | import org.apache.commons.lang3.tuple.Pair; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.*; 11 | 12 | /** 13 | * Creates an html file listing out all the class files in the repository. 14 | */ 15 | public class IndexHtmlRenderer { 16 | 17 | private final OdinOptions odinOptions; 18 | 19 | public IndexHtmlRenderer( 20 | OdinOptions odinOptions 21 | ) { 22 | this.odinOptions = odinOptions; 23 | } 24 | 25 | public void render( 26 | Collection>> processingOrder 27 | ) throws IOException { 28 | SortedMap javaFileToHtmlFile = new TreeMap<>(); 29 | for(Pair> currentlyProcessing : processingOrder) { 30 | for (Path path : currentlyProcessing.getRight()) { 31 | javaFileToHtmlFile.put( 32 | path.toString() 33 | .replace('\\', '/') 34 | .replace(currentlyProcessing.getLeft(), ""), 35 | getFileUrl(currentlyProcessing.getLeft(), path) 36 | ); 37 | } 38 | } 39 | final String outputFile = odinOptions.outputDirectory + "/index.html"; 40 | 41 | StringBuilder sb = new StringBuilder(); 42 | sb.append( 43 | String.format( 44 | """ 45 | 46 | 47 | 48 | 49 | 50 | 51 | """, 52 | odinOptions.webPathToCssFile 53 | ) 54 | ); 55 | 56 | if (odinOptions.multiRepoRoot != null) { 57 | sb.append(String.format( 58 | """ 59 | 62 | """, 63 | odinOptions.multiRepoRoot 64 | )); 65 | } 66 | 67 | for (Map.Entry entry : javaFileToHtmlFile.entrySet()) { 68 | sb.append(String.format( 69 | """ 70 | 71 | """, 72 | entry.getValue(), 73 | entry.getKey() 74 | )); 75 | } 76 | 77 | sb.append( 78 | """ 79 | 80 | 81 | """ 82 | ); 83 | Files.writeString(Paths.get(outputFile), sb.toString()); 84 | } 85 | 86 | private String getFileUrl( 87 | String inputSourceDirectory, 88 | Path javaSourceFile 89 | ) { 90 | return odinOptions.webPathToSourceHtmlFiles 91 | + ( 92 | javaSourceFile.toString() 93 | .replace('\\', '/') 94 | .substring(0, javaSourceFile.toString().length()-5) 95 | .replace(inputSourceDirectory, "") 96 | + ".html" 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/rendering/IndexJsonRenderer.java: -------------------------------------------------------------------------------- 1 | package rendering; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import indexing.Index; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | /** 10 | * Creates a json file with the index. Allows other source repositories to point to this repository. 11 | */ 12 | public class IndexJsonRenderer { 13 | 14 | public void render( 15 | String outputFile, 16 | Index index 17 | ) throws IOException { 18 | ObjectMapper objectMapper = new ObjectMapper(); 19 | objectMapper.writerWithDefaultPrettyPrinter() 20 | .writeValue(new File(outputFile), index); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/rendering/source/ApplyIndexVisitor.java: -------------------------------------------------------------------------------- 1 | package rendering.source; 2 | 3 | import com.github.javaparser.ast.Node; 4 | import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; 5 | import com.github.javaparser.ast.body.MethodDeclaration; 6 | import com.github.javaparser.ast.body.Parameter; 7 | import com.github.javaparser.ast.body.VariableDeclarator; 8 | import com.github.javaparser.ast.expr.*; 9 | import com.github.javaparser.ast.stmt.ForStmt; 10 | import com.github.javaparser.ast.stmt.IfStmt; 11 | import com.github.javaparser.ast.stmt.WhileStmt; 12 | import com.github.javaparser.ast.type.ClassOrInterfaceType; 13 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter; 14 | import indexing.Index; 15 | import indexing.MethodInfo; 16 | 17 | import java.util.*; 18 | import java.util.stream.Collectors; 19 | 20 | /** 21 | * Applies the Index to the source code by recording the changes that are need 22 | * in the rendering queue. 23 | */ 24 | public class ApplyIndexVisitor extends VoidVisitorAdapter { 25 | 26 | private final RenderingQueue renderingQueue; 27 | private final Index index; 28 | 29 | /** 30 | * ShortTypes can be used to look up the full class name (fully qualified name). 31 | */ 32 | private final Map imports; 33 | private final ScopeTracker scopeTracker = new ScopeTracker(); 34 | 35 | private final String outputFile; 36 | 37 | public ApplyIndexVisitor( 38 | String outputFile, 39 | Index index, 40 | Map imports, 41 | RenderingQueue renderingQueue 42 | ) { 43 | this.outputFile = outputFile; 44 | this.index = index; 45 | this.imports = imports; 46 | this.renderingQueue = renderingQueue; 47 | } 48 | 49 | private void addLink( 50 | SimpleName simpleName, 51 | Index.FilePosition filePosition, 52 | String cssClass 53 | ) { 54 | if (filePosition != null) { 55 | int lineNum = simpleName.getRange().get().begin.line; 56 | int startCol = simpleName.getRange().get().begin.column; 57 | int endCol = simpleName.getRange().get().end.column; 58 | renderingQueue.add(lineNum, startCol, new ContentRecord( 59 | String.format( 60 | """ 61 | """, 62 | cssClass, 63 | filePosition.fileName(), 64 | filePosition.lineNumber() 65 | ), 66 | PositionType.BEFORE 67 | )); 68 | renderingQueue.add(lineNum, endCol, new ContentRecord( 69 | "", 70 | PositionType.AFTER 71 | )); 72 | } 73 | } 74 | 75 | private String currentClassName = null; 76 | 77 | public void visit(ClassOrInterfaceDeclaration classOrInterfaceDeclaration, Void arg) { 78 | scopeTracker.startScope(); 79 | // need to set the class name before visiting all the nodes 80 | String previousClassName = currentClassName; 81 | if (classOrInterfaceDeclaration.getFullyQualifiedName().isPresent()) { 82 | currentClassName = classOrInterfaceDeclaration.getFullyQualifiedName().get(); 83 | } 84 | super.visit(classOrInterfaceDeclaration, arg); 85 | currentClassName = previousClassName; 86 | scopeTracker.endScope(); 87 | } 88 | 89 | @Override 90 | public void visit(MethodDeclaration n, Void arg) { 91 | scopeTracker.startScope(); 92 | // don't call super since we copied super's code and changed the order to have the parameters first 93 | n.getParameters().forEach(p -> p.accept(this, arg)); 94 | n.getBody().ifPresent(l -> l.accept(this, arg)); 95 | n.getType().accept(this, arg); 96 | n.getModifiers().forEach(p -> p.accept(this, arg)); 97 | n.getName().accept(this, arg); 98 | n.getReceiverParameter().ifPresent(l -> l.accept(this, arg)); 99 | n.getThrownExceptions().forEach(p -> p.accept(this, arg)); 100 | n.getTypeParameters().forEach(p -> p.accept(this, arg)); 101 | n.getAnnotations().forEach(p -> p.accept(this, arg)); 102 | n.getComment().ifPresent(l -> l.accept(this, arg)); 103 | scopeTracker.endScope(); 104 | 105 | } 106 | 107 | @Override 108 | public void visit(WhileStmt d, Void arg) { 109 | scopeTracker.startScope(); 110 | super.visit(d, arg); 111 | scopeTracker.endScope(); 112 | } 113 | 114 | @Override 115 | public void visit(ForStmt n, Void arg) { 116 | scopeTracker.startScope(); 117 | // don't call super since we copied super's code and changed the order to have the parameters first 118 | n.getInitialization().forEach(p -> p.accept(this, arg)); 119 | n.getBody().accept(this, arg); 120 | n.getCompare().ifPresent(l -> l.accept(this, arg)); 121 | n.getUpdate().forEach(p -> p.accept(this, arg)); 122 | n.getComment().ifPresent(l -> l.accept(this, arg)); 123 | scopeTracker.endScope(); 124 | } 125 | 126 | @Override 127 | public void visit(IfStmt d, Void arg) { 128 | scopeTracker.startScope(); 129 | super.visit(d, arg); 130 | scopeTracker.endScope(); 131 | } 132 | 133 | @Override 134 | public void visit(Parameter d, Void arg) { 135 | scopeTracker.addVariable( 136 | d.getNameAsString(), 137 | d.getType().asString(), 138 | d.getRange().get().begin.line 139 | ); 140 | super.visit(d, arg); 141 | } 142 | 143 | @Override 144 | public void visit(VariableDeclarator d, Void arg) { 145 | scopeTracker.addVariable( 146 | d.getNameAsString(), 147 | d.getType().asString(), 148 | d.getRange().get().begin.line 149 | ); 150 | super.visit(d, arg); 151 | } 152 | 153 | @Override 154 | public void visit(ClassOrInterfaceType classOrInterfaceType, Void arg) { 155 | SimpleName simpleName = classOrInterfaceType.getName(); 156 | String className = simpleName.asString(); 157 | if (imports.containsKey(className)) { 158 | String fullyQualifiedName = imports.get(className); 159 | Index.FilePosition filePosition = index.getClass(fullyQualifiedName); 160 | addLink(simpleName, filePosition, "type"); 161 | } 162 | super.visit(classOrInterfaceType, arg); 163 | } 164 | 165 | private String getArgumentType(Expression expression) { 166 | if (expression.getChildNodes().size() != 1) { 167 | return null; 168 | } 169 | Node onlyChild = expression.getChildNodes().get(0); 170 | if (!(onlyChild instanceof SimpleName)) { 171 | return null; 172 | } 173 | 174 | String argument = onlyChild.toString(); 175 | String shortType = scopeTracker.getVariableShortType(argument); 176 | if (shortType == null) { 177 | return null; 178 | } 179 | 180 | return imports.get(shortType); 181 | } 182 | 183 | private List calcParameterTypes(MethodCallExpr methodCallExpr) { 184 | return methodCallExpr.getArguments() 185 | .stream() 186 | .map(this::getArgumentType) 187 | .collect(Collectors.toList()); 188 | } 189 | 190 | public void visit(MethodCallExpr methodCallExpr, Void arg) { 191 | SimpleName methodSimpleName = methodCallExpr.getName(); 192 | List parameterTypes = calcParameterTypes(methodCallExpr); 193 | if (methodCallExpr.getScope().isPresent()) { 194 | Expression scope = methodCallExpr.getScope().get(); 195 | if (scope instanceof StringLiteralExpr) { 196 | handleClassMethod("java.lang.String", methodSimpleName, parameterTypes,false); 197 | } else if (scope instanceof ClassExpr) { 198 | handleClassMethod("java.lang.Class", methodSimpleName, parameterTypes,false); 199 | } else if (scope instanceof NameExpr) { 200 | NameExpr nameExpr = (NameExpr) scope; 201 | // example System.getProperty() 202 | // example variable.getProperty() 203 | // Need some way to distinguish between them 204 | // How about try variable index first, then Class index. 205 | 206 | // trying variable index 207 | final String typeFromVariable = scopeTracker.getVariableShortType(nameExpr.getNameAsString()); 208 | final String fullyQualifiedClassName; 209 | if (typeFromVariable != null) { 210 | fullyQualifiedClassName = imports.get(typeFromVariable); 211 | } else { 212 | // trying Class index 213 | fullyQualifiedClassName = imports.get(nameExpr.getName().asString()); 214 | } 215 | 216 | handleClassMethod(fullyQualifiedClassName, methodSimpleName, parameterTypes, false); 217 | } else if (scope instanceof MethodCallExpr) { 218 | // method call chaining 219 | // example indexMap.entrySet().iterator() 220 | // \ \___ should be Iterable 221 | // \___ should be Map 222 | methodCallExpr.getNameAsString(); 223 | } else if (scope instanceof EnclosedExpr) { 224 | // (new FDBigInteger(r, pow5.offset)).leftShift(p2) 225 | methodCallExpr.getNameAsString(); 226 | } else if (scope instanceof ThisExpr) { 227 | // this.size() 228 | handleClassMethod(currentClassName, methodSimpleName, parameterTypes, true); 229 | } else if (scope instanceof FieldAccessExpr) { 230 | // this.data.clone() 231 | methodCallExpr.getNameAsString(); 232 | } else if (scope instanceof ObjectCreationExpr) { 233 | // new BigInteger(magnitude).shiftLeft(offset * 32) 234 | methodCallExpr.getNameAsString(); 235 | } else if (scope instanceof ArrayAccessExpr) { 236 | // queryArgs[i].startsWith("mode=") 237 | methodCallExpr.getNameAsString(); 238 | } else if (scope instanceof SuperExpr) { 239 | // super.detach() 240 | searchForMethodInClassAndSuperClasses(currentClassName, methodSimpleName, parameterTypes,true); 241 | } else { 242 | System.out.println("Unrecognized expression: " + methodCallExpr); 243 | } 244 | } else { 245 | // method call within the same class 246 | handleClassMethod(currentClassName, methodSimpleName, parameterTypes, true); 247 | } 248 | super.visit(methodCallExpr, arg); 249 | } 250 | 251 | private boolean handleClassMethod( 252 | String fullyQualifiedClassName, 253 | SimpleName methodSimpleName, 254 | List parameterTypes, 255 | boolean includePrivates 256 | ) { 257 | if (fullyQualifiedClassName != null) { 258 | Index.FilePosition filePosition = null; 259 | if (includePrivates) { 260 | List overloads = index.getPrivateMethodOverloads( 261 | fullyQualifiedClassName, 262 | methodSimpleName.asString() 263 | ); 264 | if (overloads != null) { 265 | MethodInfo bestMethodInfo = findBestOverload(overloads, parameterTypes); 266 | if (bestMethodInfo != null) { 267 | filePosition = bestMethodInfo.filePosition(); 268 | } 269 | } 270 | } 271 | addLink(methodSimpleName, filePosition, "type"); 272 | if (filePosition != null) { 273 | return true; 274 | } 275 | 276 | // try class and super classes 277 | return searchForMethodInClassAndSuperClasses( 278 | fullyQualifiedClassName, 279 | methodSimpleName, 280 | parameterTypes, 281 | false 282 | ); 283 | } 284 | return false; 285 | } 286 | 287 | private MethodInfo findBestOverload( 288 | List overloads, 289 | List parameterTypes 290 | ) { 291 | if (overloads == null || overloads.isEmpty()) { 292 | return null; 293 | } 294 | if (parameterTypes == null) { 295 | return overloads.get(overloads.size() - 1); 296 | } 297 | 298 | 299 | return overloads.stream() 300 | .filter(overload -> match(overload.argumentTypes(), parameterTypes)) 301 | .findAny() 302 | .orElse(overloads.get(overloads.size() - 1)); 303 | } 304 | 305 | /** 306 | * Matches parameter types one by one. If either or null, that's treated as a wildcard. 307 | * 308 | * @param parameterTypes1 309 | * @param parameterTypes2 310 | * @return 311 | */ 312 | private boolean match(List parameterTypes1, List parameterTypes2) { 313 | if (parameterTypes1.size() != parameterTypes2.size()) { 314 | return false; 315 | } 316 | for (int i = 0; i < parameterTypes1.size(); i++) { 317 | if (parameterTypes1.get(i) == null) { 318 | continue; 319 | } 320 | if (parameterTypes2.get(i) == null) { 321 | continue; 322 | } 323 | 324 | if (!parameterTypes1.get(i).equals(parameterTypes2.get(i))) { 325 | return false; 326 | } 327 | } 328 | 329 | return true; 330 | } 331 | 332 | private boolean searchForMethodInClassAndSuperClasses( 333 | String fullyQualifiedClassName, 334 | SimpleName methodSimpleName, 335 | List parameterTypes, 336 | boolean skipCurrentClass 337 | ) { 338 | Set visited = new HashSet<>(); 339 | Queue bfsQueue = new ArrayDeque<>(); 340 | if (skipCurrentClass) { 341 | visited.add(fullyQualifiedClassName); 342 | bfsQueue.addAll(index.getSuperClasses(fullyQualifiedClassName)); 343 | } else { 344 | bfsQueue.add(fullyQualifiedClassName); 345 | } 346 | 347 | while (!bfsQueue.isEmpty()) { 348 | String currentFullyQualifiedClassName = bfsQueue.poll(); 349 | visited.add(currentFullyQualifiedClassName); 350 | 351 | List overloads = index.getMethodOverloads( 352 | currentFullyQualifiedClassName, 353 | methodSimpleName.asString() 354 | ); 355 | Index.FilePosition filePosition = null; 356 | if (overloads != null) { 357 | MethodInfo bestMethodInfo = findBestOverload(overloads, parameterTypes); 358 | if (bestMethodInfo != null) { 359 | filePosition = bestMethodInfo.filePosition(); 360 | } 361 | } 362 | addLink(methodSimpleName, filePosition, "type"); 363 | if (filePosition != null) { 364 | return true; 365 | } 366 | 367 | // add super classes to search 368 | for (String fullyQualifiedSuperClassName : index.getSuperClasses(currentFullyQualifiedClassName)) { 369 | if (!visited.contains(fullyQualifiedSuperClassName)) { 370 | bfsQueue.add(fullyQualifiedSuperClassName); 371 | } 372 | } 373 | } 374 | 375 | // exhausted all super classes 376 | return false; 377 | } 378 | 379 | public void visit(NameExpr nameExpr, Void arg) { 380 | // trying variable index 381 | final Integer localVariableLineNum = scopeTracker.getVariableLine(nameExpr.getNameAsString()); 382 | if (localVariableLineNum != null) { 383 | addLink(nameExpr.getName(), new Index.FilePosition( 384 | "", 385 | localVariableLineNum 386 | ), 387 | "variable" 388 | ); 389 | } 390 | } 391 | 392 | } 393 | -------------------------------------------------------------------------------- /src/main/java/rendering/source/ApplySyntaxHighlightingVisitor.java: -------------------------------------------------------------------------------- 1 | package rendering.source; 2 | 3 | import com.github.javaparser.Range; 4 | import com.github.javaparser.ast.comments.BlockComment; 5 | import com.github.javaparser.ast.comments.JavadocComment; 6 | import com.github.javaparser.ast.comments.LineComment; 7 | import com.github.javaparser.ast.expr.StringLiteralExpr; 8 | import com.github.javaparser.ast.expr.TextBlockLiteralExpr; 9 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter; 10 | 11 | import java.util.Optional; 12 | 13 | /** 14 | * Applies syntax highlighting to the source by recording the changes that are need 15 | * in the rendering queue. 16 | */ 17 | public class ApplySyntaxHighlightingVisitor extends VoidVisitorAdapter { 18 | 19 | 20 | private final RenderingQueue renderingQueue; 21 | 22 | public ApplySyntaxHighlightingVisitor( 23 | RenderingQueue renderingQueue 24 | ) { 25 | this.renderingQueue = renderingQueue; 26 | } 27 | 28 | private void highlightToken( 29 | Optional range, 30 | String cssClass 31 | ) { 32 | int startLineNum = range.get().begin.line; 33 | int startCol = range.get().begin.column; 34 | int endLineNum = range.get().end.line; 35 | int endCol = range.get().end.column; 36 | 37 | final String startTag = ""; 38 | final String endTag = ""; 39 | 40 | renderingQueue.add( 41 | startLineNum, 42 | startCol, 43 | new ContentRecord( 44 | startTag, 45 | PositionType.BEFORE 46 | ) 47 | ); 48 | renderingQueue.add( 49 | endLineNum, 50 | endCol, 51 | new ContentRecord( 52 | endTag, 53 | PositionType.AFTER 54 | ) 55 | ); 56 | if (startLineNum != endLineNum) { 57 | renderingQueue.endOfLine(startLineNum, endTag); 58 | for (int i = startLineNum + 1; i < endLineNum; i++) { 59 | renderingQueue.beginningOfLine(i, startTag); 60 | renderingQueue.endOfLine(i, endTag); 61 | } 62 | renderingQueue.endOfLine(endLineNum, endTag); 63 | } 64 | } 65 | 66 | @Override 67 | public void visit(LineComment comment, Void arg) { 68 | highlightToken(comment.getRange(), "comment"); 69 | super.visit(comment, arg); 70 | } 71 | 72 | @Override 73 | public void visit(JavadocComment comment, Void arg) { 74 | highlightToken(comment.getRange(), "comment"); 75 | super.visit(comment, arg); 76 | } 77 | 78 | @Override 79 | public void visit(BlockComment comment, Void arg) { 80 | highlightToken(comment.getRange(), "comment"); 81 | super.visit(comment, arg); 82 | } 83 | 84 | @Override 85 | public void visit(StringLiteralExpr str, Void arg) { 86 | highlightToken(str.getRange(), "string"); 87 | super.visit(str, arg); 88 | } 89 | 90 | @Override 91 | public void visit(TextBlockLiteralExpr str, Void arg) { 92 | highlightToken(str.getRange(), "string"); 93 | super.visit(str, arg); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/rendering/source/ContentRecord.java: -------------------------------------------------------------------------------- 1 | package rendering.source; 2 | 3 | record ContentRecord( 4 | String content, 5 | PositionType positionType 6 | ) { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/rendering/source/PositionType.java: -------------------------------------------------------------------------------- 1 | package rendering.source; 2 | 3 | enum PositionType { 4 | BEFORE, 5 | AFTER 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/rendering/source/RenderQueueLine.java: -------------------------------------------------------------------------------- 1 | package rendering.source; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | class RenderQueueLine { 9 | 10 | final Map> renderQueueLine = new HashMap<>(); 11 | 12 | public void add(int col, ContentRecord contentRecord) { 13 | List content = renderQueueLine.get(col); 14 | if (content == null) { 15 | content = new ArrayList<>(); 16 | renderQueueLine.put(col, content); 17 | } 18 | content.add(contentRecord); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/rendering/source/RenderingQueue.java: -------------------------------------------------------------------------------- 1 | package rendering.source; 2 | 3 | import org.apache.commons.collections4.MultiValuedMap; 4 | import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | class RenderingQueue { 11 | 12 | public final Map withinLineRenderingQueue = new HashMap<>(); 13 | public final MultiValuedMap beginningOfLineRenderingQueue = new ArrayListValuedHashMap<>(); 14 | public final MultiValuedMap endOfLineRenderingQueue = new ArrayListValuedHashMap<>(); 15 | 16 | public void add(int line, int col, ContentRecord contentRecord) { 17 | RenderQueueLine renderQueueLine = withinLineRenderingQueue.get(line); 18 | if (renderQueueLine == null) { 19 | renderQueueLine = new RenderQueueLine(); 20 | withinLineRenderingQueue.put(line, renderQueueLine); 21 | } 22 | renderQueueLine.add(col, contentRecord); 23 | } 24 | 25 | public void beginningOfLine(int line, String content) { 26 | beginningOfLineRenderingQueue.put(line, content); 27 | } 28 | public void endOfLine(int line, String content) { 29 | endOfLineRenderingQueue.put(line, content); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/rendering/source/ScopeTracker.java: -------------------------------------------------------------------------------- 1 | package rendering.source; 2 | 3 | import java.util.*; 4 | 5 | public class ScopeTracker { 6 | 7 | private final Map> variableToShortTypes = new HashMap<>(); 8 | private final Map> variableToLines = new HashMap<>(); 9 | private final Stack> scopeToVariableNames = new Stack<>(); 10 | 11 | public void startScope() { 12 | scopeToVariableNames.add(new ArrayList<>()); 13 | } 14 | 15 | public void addVariable( 16 | String variableName, 17 | String variableType, 18 | int lineNumber) { 19 | if (scopeToVariableNames.isEmpty()) { 20 | return; 21 | } 22 | List variableNames = scopeToVariableNames.peek(); 23 | variableNames.add(variableName); 24 | 25 | Stack types = variableToShortTypes.get(variableName); 26 | if (types == null) { 27 | types = new Stack<>(); 28 | variableToShortTypes.put(variableName, types); 29 | } 30 | types.add(variableType); 31 | 32 | Stack lines = variableToLines.get(variableName); 33 | if (lines == null) { 34 | lines = new Stack<>(); 35 | variableToLines.put(variableName, lines); 36 | } 37 | lines.add(lineNumber); 38 | } 39 | 40 | /** 41 | * @param variableName the name of the variable to search in the scope for. 42 | * @return the short type that still needs to joined against the imports to figure out the full 43 | * class name (fully qualified name). 44 | */ 45 | public String getVariableShortType(String variableName) { 46 | Stack types = variableToShortTypes.get(variableName); 47 | if (types == null) { 48 | return null; 49 | } 50 | if (types.isEmpty()) { 51 | return null; 52 | } 53 | return types.peek(); 54 | } 55 | 56 | public Integer getVariableLine(String variableName) { 57 | Stack lines = variableToLines.get(variableName); 58 | if (lines == null) { 59 | return null; 60 | } 61 | if (lines.isEmpty()) { 62 | return null; 63 | } 64 | return lines.peek(); 65 | } 66 | 67 | public void endScope() { 68 | if (scopeToVariableNames.isEmpty()) { 69 | return; 70 | } 71 | 72 | List variableNamesToRemove = scopeToVariableNames.pop(); 73 | for (String variableNameToRemove : variableNamesToRemove) { 74 | Stack types = variableToShortTypes.get(variableNameToRemove); 75 | if (types != null && !types.isEmpty()) { 76 | types.pop(); 77 | } 78 | Stack lines = variableToLines.get(variableNameToRemove); 79 | if (types != null && !types.isEmpty()) { 80 | types.pop(); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/rendering/source/SourceHtmlRenderer.java: -------------------------------------------------------------------------------- 1 | package rendering.source; 2 | 3 | import com.github.javaparser.JavaParser; 4 | import com.github.javaparser.ParserConfiguration; 5 | import com.github.javaparser.ast.CompilationUnit; 6 | import indexing.ImportVisitor; 7 | import indexing.Index; 8 | import options.OdinOptions; 9 | import org.apache.commons.text.StringEscapeUtils; 10 | 11 | import javax.annotation.Nonnull; 12 | import java.io.IOException; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | import java.nio.file.Paths; 16 | import java.util.*; 17 | import java.util.stream.Collectors; 18 | 19 | public record SourceHtmlRenderer( 20 | Index index, 21 | OdinOptions odinOptions, 22 | String header, 23 | String footer 24 | ){ 25 | 26 | public SourceHtmlRenderer(Index index, 27 | OdinOptions odinOptions) { 28 | this( 29 | index, 30 | odinOptions, 31 | String.format( 32 | """ 33 | 34 | 35 | 36 | 37 | 38 | 39 | Back to index... 40 | 41 | """, 42 | odinOptions.webPathToCssFile, 43 | odinOptions.webPathToSourceHtmlFiles 44 | ), 45 | String.format( 46 | """ 47 |
48 | 49 | Back to index... 50 | 51 | """, 52 | odinOptions.webPathToSourceHtmlFiles 53 | ) 54 | ); 55 | } 56 | 57 | public void proccessFiles( 58 | String inputDirectory, 59 | Collection files 60 | ) throws IOException { 61 | for (Path path : files) { 62 | processFile(inputDirectory, path); 63 | } 64 | } 65 | 66 | private void processFile( 67 | String inputDirectory, 68 | Path path 69 | ) throws IOException { 70 | System.out.println("Rendering " + path); 71 | final String destinationStr = path.toString() 72 | .replace('\\', '/') 73 | .substring(0, path.toString().length()-5) 74 | .replace(inputDirectory, odinOptions.outputDirectory) 75 | + ".html"; 76 | renderFile(path, destinationStr); 77 | } 78 | 79 | private void renderFile( 80 | Path inputFile, 81 | String outputFile 82 | ) throws IOException { 83 | JavaParser javaParser = new JavaParser(new ParserConfiguration().setLanguageLevel( 84 | odinOptions.languageLevel)); 85 | // Get the parse tree from GitHub Java Parser, so we can navigate the parse tree 86 | // with visitors. 87 | CompilationUnit compilationUnit = javaParser.parse(inputFile).getResult().get(); 88 | 89 | // Use a visitor to collect all the imports. The imports are used to detemine what 90 | // methods, classes, and static fields are in scope. 91 | ImportVisitor importVisitor = new ImportVisitor(index); 92 | importVisitor.visit(compilationUnit, null); 93 | 94 | // Accumulate the changes from source code to html in a rendering queue that we apply at the end. 95 | // This allows us to apply successive operations like a link, then a css style. 96 | RenderingQueue renderingQueue = new RenderingQueue(); 97 | 98 | // Use the index with the parse tree to figure out what links need to be added to the source code. 99 | ApplyIndexVisitor applyIndexVisitor = new ApplyIndexVisitor( 100 | outputFile, 101 | index, 102 | importVisitor.imports, 103 | renderingQueue 104 | ); 105 | applyIndexVisitor.visit(compilationUnit, null); 106 | 107 | // Use the parse tree to figure out what css styles need to be applied. This is separates 108 | // linking from styling since methods, classes, variables, other linkables will have 109 | // different colours. 110 | ApplySyntaxHighlightingVisitor syntaxHighlightingVisitor = new ApplySyntaxHighlightingVisitor( 111 | renderingQueue 112 | ); 113 | syntaxHighlightingVisitor.visit(compilationUnit, null); 114 | 115 | // Apply the rendering queue to the code. 116 | Path destinationPath = Paths.get(outputFile); 117 | destinationPath.toFile().getParentFile().mkdirs(); 118 | String code = Files.readString(inputFile); 119 | Files.writeString( 120 | destinationPath, 121 | codeToHtml(code, renderingQueue) 122 | ); 123 | } 124 | 125 | private String codeToHtml( 126 | String code, 127 | RenderingQueue renderingQueue 128 | ) { 129 | StringBuilder sb = new StringBuilder(); 130 | sb.append(header); 131 | 132 | int lineNumber = 1; 133 | Iterable lines = () -> code.lines().iterator(); 134 | for (String line : lines) { 135 | linePrefix(sb, lineNumber); 136 | 137 | Collection beforeTasks = renderingQueue.beginningOfLineRenderingQueue.get(lineNumber); 138 | if (beforeTasks != null) { 139 | for (String content : beforeTasks) { 140 | sb.append(content); 141 | } 142 | } 143 | 144 | renderLine(sb, line, renderingQueue.withinLineRenderingQueue.getOrDefault( 145 | lineNumber, 146 | new RenderQueueLine() 147 | )); 148 | 149 | Collection afterTasksRaw = renderingQueue.beginningOfLineRenderingQueue.get(lineNumber); 150 | if (afterTasksRaw != null) { 151 | List afterTasks = new ArrayList<>(afterTasksRaw); 152 | Collections.reverse(afterTasks); 153 | for (String content : afterTasks) { 154 | sb.append(content); 155 | } 156 | } 157 | 158 | lineSuffix(sb); 159 | lineNumber++; 160 | } 161 | 162 | sb.append(footer); 163 | return sb.toString(); 164 | } 165 | 166 | private void linePrefix(StringBuilder sb, int lineNumber) { 167 | sb.append(String.format( 168 | """ 169 | 170 | 171 |
""",
172 |                 lineNumber,
173 |                 lineNumber,
174 |                 lineNumber
175 |         ));
176 |     }
177 | 
178 |     private void lineSuffix(StringBuilder sb) {
179 |         sb.append(String.format(
180 |                 """
181 |                 
182 | 183 | """ 184 | )); 185 | } 186 | 187 | private void renderLine( 188 | StringBuilder sb, 189 | String line, 190 | @Nonnull RenderQueueLine renderQueueLine 191 | ) { 192 | int currentCol = 1; 193 | for (char c : line.toCharArray()) { 194 | List beforeContentRecords = renderQueueLine.renderQueueLine.getOrDefault(currentCol, Collections.emptyList()).stream() 195 | .filter(contentRecord -> contentRecord.positionType() == PositionType.BEFORE) 196 | .map(ContentRecord::content) 197 | .collect(Collectors.toList()); 198 | for (String before : beforeContentRecords) { 199 | sb.append(before); 200 | } 201 | 202 | sb.append(StringEscapeUtils.escapeHtml4(String.valueOf(c))); 203 | 204 | List afterContentRecords = renderQueueLine.renderQueueLine.getOrDefault(currentCol, Collections.emptyList()).stream() 205 | .filter(contentRecord -> contentRecord.positionType() == PositionType.AFTER) 206 | .map(ContentRecord::content) 207 | // need to reverse so that the 208 | // gets converted to
209 | .collect(Collectors.toList()); 210 | Collections.reverse(afterContentRecords); 211 | for (String after : afterContentRecords) { 212 | sb.append(after); 213 | } 214 | currentCol++; 215 | } 216 | } 217 | } 218 | 219 | -------------------------------------------------------------------------------- /src/main/java/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | public class StringUtil { 4 | 5 | private StringUtil() { 6 | 7 | } 8 | 9 | /** 10 | * ABCDE CDEFG 11 | * should return 12 | * ABCDEFG 13 | * @param startString 14 | * @param endString 15 | * @return 16 | */ 17 | public static String mergeStrings( 18 | String startString, 19 | String endString 20 | ) { 21 | int max = 0; 22 | for (int i = 1; i <= endString.length(); i++) { 23 | String subStr = endString.substring(0, i); 24 | int posnFound = startString.lastIndexOf(subStr); 25 | if (posnFound >= 0 26 | && startString.lastIndexOf(subStr) == startString.length() - subStr.length()) { 27 | max = i; 28 | } 29 | } 30 | return startString + endString.substring(max); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/StringUtilTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.jupiter.api.Test; 2 | import util.StringUtil; 3 | 4 | import static org.junit.jupiter.api.Assertions.*; 5 | 6 | public class StringUtilTest { 7 | 8 | @Test 9 | public void test() { 10 | assertEquals("https://josephmate.github.io/OdinCodeBrowser/jdk8/java/util/List.html#linenum111", 11 | StringUtil.mergeStrings("https://josephmate.github.io/OdinCodeBrowser/jdk8", 12 | "/OdinCodeBrowser/jdk8/java/util/List.html#linenum111")); 13 | } 14 | 15 | @Test 16 | public void test2() { 17 | assertEquals("https://josephmate.github.io/OdinCodeBrowser/jdk8/com/sun/crypto/provider/PBKDF2HmacSHA1Factory.html", 18 | StringUtil.mergeStrings("https://josephmate.github.io/OdinCodeBrowser/jdk8/", 19 | "/OdinCodeBrowser/jdk8/com/sun/crypto/provider/PBKDF2HmacSHA1Factory.html")); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /webserver.ps1: -------------------------------------------------------------------------------- 1 | python3 -m http.server -d docs 2 | -------------------------------------------------------------------------------- /webserver.sh: -------------------------------------------------------------------------------- 1 | mkdir -p target/webroot 2 | ln -s ../../docs target/webroot/OdinCodeBrowser 3 | 4 | python3 -m http.server -d target/webroot 5 | --------------------------------------------------------------------------------