├── .classpath ├── .gitattributes ├── .gitignore ├── .project ├── LICENSE.txt ├── README.md ├── RELEASES.txt ├── project.clj ├── resources ├── icons │ ├── LICENSE.txt │ ├── alphab_sort_co.gif │ ├── details_view.gif │ ├── field_default_obj.gif │ ├── field_private_obj.gif │ ├── field_protected_obj.gif │ ├── field_public_obj.gif │ ├── file_obj.gif │ ├── filter_history.gif │ ├── genericvariable_obj.gif │ ├── insp_sbook.gif │ ├── javaassist_co.gif │ ├── javadoc.gif │ ├── methdef_obj.gif │ ├── methpri_obj.gif │ ├── methpro_obj.gif │ ├── methpub_obj.gif │ ├── nav_refresh.gif │ ├── package_obj.gif │ ├── quickfix_obj.gif │ ├── resume_co.gif │ ├── runlast_co.gif │ └── toggle_breadcrumb.gif └── images │ ├── inspector-jay.png │ ├── screenshot-edit.png │ ├── screenshot-edit.pspimage │ └── screenshot.PNG ├── src └── inspector_jay │ ├── core.clj │ ├── gui │ ├── gui.clj │ ├── node_properties.clj │ └── utils.clj │ └── model │ ├── tree_model.clj │ └── tree_node.clj └── test └── inspector_jay └── core_test.clj /.classpath: -------------------------------------------------------------------------------- 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 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /doc 4 | /classes 5 | /checkouts 6 | /bin 7 | /.settings 8 | pom.xml 9 | pom.xml.asc 10 | *.jar 11 | *.class 12 | .lein-deps-sum 13 | .lein-failures 14 | .lein-plugins 15 | .lein-repl-history 16 | Thumbs.db 17 | .nrepl-port -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | inspector-jay 4 | 5 | 6 | 7 | 8 | 9 | ccw.builder 10 | 11 | 12 | 13 | 14 | ccw.leiningen.builder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.jdt.core.javanature 26 | ccw.leiningen.nature 27 | ccw.nature 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015 Tim Molderez 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the inspector-jay developer team nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE INSPECTOR-JAY DEVELOPER TEAM BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Inspector Jay 2 | ============= 3 | 4 | Inspector Jay is an inspection utility that can help you to debug and/or understand Clojure/Java code. Just pass an object or data structure to the `inspect` function and you can quickly examine its contents in a simple graphical user interface. 5 | 6 | - Inspect just about anything reachable from an object by navigating Inspector Jay's tree structure. 7 | - Examine the field values of objects and the return values of invoked methods. 8 | - Similar to `clojure.inspector`, Clojure data structures and Java collections can also be examined. 9 | - Inspector Jay can be used in both Clojure and Java applications. 10 | 11 | ![Inspector Jay logo](https://raw.github.com/timmolderez/inspector-jay/master/resources/images/screenshot-edit.png) 12 | 13 | ### Installation 14 | 15 | If you're using [**Leiningen**](https://github.com/technomancy/leiningen), add `[inspector-jay "0.3"]` to the dependencies in your `project.clj` file. 16 | 17 | If you're using [**Maven**](http://maven.apache.org/), add the following dependency to your `pom.xml` file: 18 | 19 | ```xml 20 | 21 | inspector-jay 22 | inspector-jay 23 | 0.3 24 | 25 | ``` 26 | 27 | Finally, you can also [**download Inspector Jay** as a stand-alone .jar](http://timmolderez.be/builds/inspector-jay/) file. 28 | 29 | To find out what's new in the latest version of Inspector Jay, have a look at the [release notes](https://github.com/timmolderez/inspector-jay/blob/master/RELEASES.txt). 30 | 31 | ### Usage 32 | 33 | If you're using **Clojure**, give these examples a try in the REPL: 34 | 35 | ```clojure 36 | (use '(inspector-jay core)) 37 | (inspect (new java.io.File ".")) 38 | (inspect [[1 :2] {:three 3.0 :four "4"}]) 39 | (inspect (new javax.swing.JFrame) :inherited false :private true) 40 | ``` 41 | 42 | Note that calls to the `inspect` function can be configured with various keyword arguments. The complete list of configuration options is available in [gui.clj/default-options](https://github.com/timmolderez/inspector-jay/blob/master/src/inspector_jay/gui/gui.clj#L41). 43 | 44 | If you're using **Java**, you can call Inspector Jay as follows: 45 | 46 | ```java 47 | inspectorjay.InspectoryJay.inspect(new java.io.File(".")); 48 | ``` 49 | 50 | ### License 51 | 52 | Inspector Jay is released under the [BSD 3-Clause license](http://opensource.org/licenses/BSD-3-Clause). 53 | -------------------------------------------------------------------------------- /RELEASES.txt: -------------------------------------------------------------------------------- 1 | Release notes 2 | ------------- 3 | 4 | v0.3 5 | ---- 6 | 7 | * New feature - Inspectors can now be configured with keyword arguments (see gui.clj/default-options) 8 | * New feature - An inspector can now optionally pause the current thread. Execution is resumed when either 9 | clicking the resume button, or closing the inspector. 10 | For example: (inspect an-object :pause true) 11 | * New feature - When calling inspect, it can be added to a window with a specific name. 12 | This is useful to create groups of inspector tabs. 13 | For example: (inspect 1 :window-name "Numbers")(inspect 2 :window-name "Numbers")(inspect "Hello" :window-name "Strings") 14 | This creates two windows named "Numbers" and "Strings", where the Numbers window has two inspector tabs. 15 | * New feature - The tree node that was last selected can be accessed via the last-selected-value function. 16 | This is useful, for example, when you need quick access to a certain node in the REPL. 17 | * Bug fixed - Exception when creating inspectors in rapid succession. 18 | 19 | v0.2.5 20 | ------ 21 | 22 | * New feature - Open the selected node in a new inspector 23 | * New feature - Use the :vars option to pass variables into an inspector. 24 | For example: (inspect an-object :vars {:foo (new Object) :bar 314}) 25 | When entering parameter values in the inspector, you can access this map as the "vars" variable. 26 | * New feature - Tabs can be closed with Ctrl+w 27 | * Change - A maximum number of tabs is enforced. Whenever you open a tab too many, the first one is closed. 28 | * Change - Now using the core.memoize library, so our memoization cache is cleared when closing an inspector 29 | * Bug fixed - Exception when inspecting a collection with 1 item, or items with a nil value 30 | 31 | v0.2 32 | ---- 33 | 34 | * New feature - Support for tabs; whenever inspect is called, the inspector is opened as a new tab 35 | * New feature - Added filtering menu (show/hide public/protected/private/static/inherited members) 36 | * New feature - Added search-as-you-type text field 37 | * New feature - Sort members alphabetically 38 | * New feature - Toggle horizontal/vertical layout 39 | * New feature - Re-invoke methods that have already been called (so you can e.g. enter different parameter values) 40 | * New feature - Added refresh button (so you can see changes to the object's state) 41 | * Bug fixed - Exception when inspecting an empty list or nil 42 | * Bug fixed - Exceptions caught when entering faulty code in parameter text boxes 43 | * Change - Javadoc button opens documentation for declaring class, rather than type of a method's return value 44 | 45 | v0.1.5 46 | ------ 47 | 48 | * New feature - Initial support for calling methods with parameters 49 | * New feature - Info panel now lists all elements of collections instead of the collection itself 50 | * Bug fixed - Method nodes returning null are now correctly recognized as leaf nodes 51 | * Bug fixed - Return value was retrieved too early for methods returning a collection 52 | * Change - Native members are now shown as well 53 | * Change - Compiles to Java 6 (instead of Java 7) 54 | 55 | v0.1 56 | ---- 57 | 58 | * Initial release 59 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject inspector-jay "0.3" 2 | :description "Graphical inspector for Java objects and Clojure data structures" 3 | :url "https://github.com/timmolderez/inspector-jay" 4 | :license {:name "BSD 3-Clause License" 5 | :url "http://opensource.org/licenses/BSD-3-Clause"} 6 | :dependencies [[org.clojure/clojure "1.6.0"] 7 | [org.clojure/core.memoize "0.5.6"] 8 | [seesaw "1.4.5" 9 | :exclusions [com.miglayout/miglayout 10 | com.jgoodies/forms 11 | org.swinglabs.swingx/swingx-core 12 | org.fife.ui/rsyntaxtextarea]] 13 | [net.java.balloontip/balloontip "1.2.4.1"]] 14 | :plugins [[codox "0.8.11"]] 15 | 16 | ; Compile these namespaces to Java 17 | :aot [inspector-jay.core] 18 | 19 | ; Don't include these files in the jar 20 | :jar-exclusions [#"images/*̀"] 21 | 22 | ; Java compiler options 23 | :javac-options ["-target" "1.6" "-source" "1.6" "-Xlint:-options"]) -------------------------------------------------------------------------------- /resources/icons/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and 10 | b) in the case of each subsequent Contributor: 11 | i) changes to the Program, and 12 | ii) additions to the Program; 13 | where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. 14 | "Contributor" means any person or entity that distributes the Program. 15 | 16 | "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. 17 | 18 | "Program" means the Contributions distributed in accordance with this Agreement. 19 | 20 | "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 21 | 22 | 2. GRANT OF RIGHTS 23 | 24 | a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. 25 | b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. 26 | c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. 27 | d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 28 | 3. REQUIREMENTS 29 | 30 | A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: 31 | 32 | a) it complies with the terms and conditions of this Agreement; and 33 | b) its license agreement: 34 | i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; 35 | ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; 36 | iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and 37 | iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. 38 | When the Program is made available in source code form: 39 | 40 | a) it must be made available under this Agreement; and 41 | b) a copy of this Agreement must be included with each copy of the Program. 42 | Contributors may not remove or alter any copyright notices contained within the Program. 43 | 44 | Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 45 | 46 | 4. COMMERCIAL DISTRIBUTION 47 | 48 | Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. 49 | 50 | For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 51 | 52 | 5. NO WARRANTY 53 | 54 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 55 | 56 | 6. DISCLAIMER OF LIABILITY 57 | 58 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 59 | 60 | 7. GENERAL 61 | 62 | If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 63 | 64 | If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. 65 | 66 | All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. 67 | 68 | Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. 69 | 70 | This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. -------------------------------------------------------------------------------- /resources/icons/alphab_sort_co.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/alphab_sort_co.gif -------------------------------------------------------------------------------- /resources/icons/details_view.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/details_view.gif -------------------------------------------------------------------------------- /resources/icons/field_default_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/field_default_obj.gif -------------------------------------------------------------------------------- /resources/icons/field_private_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/field_private_obj.gif -------------------------------------------------------------------------------- /resources/icons/field_protected_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/field_protected_obj.gif -------------------------------------------------------------------------------- /resources/icons/field_public_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/field_public_obj.gif -------------------------------------------------------------------------------- /resources/icons/file_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/file_obj.gif -------------------------------------------------------------------------------- /resources/icons/filter_history.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/filter_history.gif -------------------------------------------------------------------------------- /resources/icons/genericvariable_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/genericvariable_obj.gif -------------------------------------------------------------------------------- /resources/icons/insp_sbook.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/insp_sbook.gif -------------------------------------------------------------------------------- /resources/icons/javaassist_co.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/javaassist_co.gif -------------------------------------------------------------------------------- /resources/icons/javadoc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/javadoc.gif -------------------------------------------------------------------------------- /resources/icons/methdef_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/methdef_obj.gif -------------------------------------------------------------------------------- /resources/icons/methpri_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/methpri_obj.gif -------------------------------------------------------------------------------- /resources/icons/methpro_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/methpro_obj.gif -------------------------------------------------------------------------------- /resources/icons/methpub_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/methpub_obj.gif -------------------------------------------------------------------------------- /resources/icons/nav_refresh.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/nav_refresh.gif -------------------------------------------------------------------------------- /resources/icons/package_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/package_obj.gif -------------------------------------------------------------------------------- /resources/icons/quickfix_obj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/quickfix_obj.gif -------------------------------------------------------------------------------- /resources/icons/resume_co.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/resume_co.gif -------------------------------------------------------------------------------- /resources/icons/runlast_co.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/runlast_co.gif -------------------------------------------------------------------------------- /resources/icons/toggle_breadcrumb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/icons/toggle_breadcrumb.gif -------------------------------------------------------------------------------- /resources/images/inspector-jay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/images/inspector-jay.png -------------------------------------------------------------------------------- /resources/images/screenshot-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/images/screenshot-edit.png -------------------------------------------------------------------------------- /resources/images/screenshot-edit.pspimage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/images/screenshot-edit.pspimage -------------------------------------------------------------------------------- /resources/images/screenshot.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmolderez/inspector-jay/0035beae482c49e0f215a54e17baf405e42f2398/resources/images/screenshot.PNG -------------------------------------------------------------------------------- /src/inspector_jay/core.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2013-2015 Tim Molderez. 2 | ; 3 | ; All rights reserved. This program and the accompanying materials 4 | ; are made available under the terms of the 3-Clause BSD License 5 | ; which accompanies this distribution, and is available at 6 | ; http://www.opensource.org/licenses/BSD-3-Clause 7 | 8 | (ns inspector-jay.core 9 | "Inspector Jay is a graphical inspector that lets you examine Java/Clojure objects and data structures." 10 | {:author "Tim Molderez"} 11 | (:gen-class 12 | :name inspectorjay.InspectorJay 13 | :prefix java- 14 | :methods [#^{:static true} [inspect [Object] Object]]) 15 | (:require [inspector-jay.gui 16 | [gui :as gui] 17 | [utils :as utils]])) 18 | 19 | (defn inspect 20 | "Displays an inspector window for a given object. 21 | The return value of inspect is the object itself, so you can plug in this function anywhere you like. 22 | See gui/default-options for more information on all available keyword arguments." 23 | ^Object [^Object object & {:as args}] 24 | (if (not= object nil) 25 | (apply gui/inspector-window object (utils/map-to-keyword-args args))) 26 | object) 27 | 28 | (defn last-selected-value 29 | "Retrieve the value of the tree node that was last selected. 30 | See gui/last-selected-value for more information." 31 | [] 32 | (gui/last-selected-value)) 33 | 34 | (defn java-inspect 35 | "Java wrapper for the inspect function. 36 | When using Java, you can call this function as follows: 37 | inspectorjay.InspectorJay.inspect(anObject);" 38 | [object] 39 | (inspect object)) 40 | 41 | (defn java-inspectorPanel 42 | "Java wrapper for the inspector-panel function. 43 | Rather than opening an inspector window, this method only returns the inspector's JPanel. 44 | You can use it to embed Inspector Jay in your own applications." 45 | [object] 46 | (gui/inspector-panel object)) -------------------------------------------------------------------------------- /src/inspector_jay/gui/gui.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2013-2015 Tim Molderez. 2 | ; 3 | ; All rights reserved. This program and the accompanying materials 4 | ; are made available under the terms of the 3-Clause BSD License 5 | ; which accompanies this distribution, and is available at 6 | ; http://www.opensource.org/licenses/BSD-3-Clause 7 | 8 | (ns inspector-jay.gui.gui 9 | "Defines Inspector Jay's graphical user interface" 10 | {:author "Tim Molderez"} 11 | (:require 12 | [clojure.string :as s] 13 | [clojure.java 14 | [io :as io] 15 | [javadoc :as jdoc]] 16 | [seesaw 17 | [core :as seesaw] 18 | [color :as color] 19 | [border :as border] 20 | [font :as font]] 21 | [inspector-jay.gui 22 | [utils :as utils] 23 | [node-properties :as nprops]] 24 | [inspector-jay.model 25 | [tree-node :as node] 26 | [tree-model :as model]]) 27 | (:import 28 | [javax.swing Box JTextArea KeyStroke JFrame JPanel JTree JToolBar JComponent JToolBar$Separator UIManager JSplitPane JTabbedPane] 29 | [javax.swing.tree DefaultTreeCellRenderer] 30 | [javax.swing.event TreeSelectionListener TreeExpansionListener TreeWillExpandListener] 31 | [javax.swing.tree ExpandVetoException] 32 | [java.awt Rectangle Toolkit] 33 | [java.awt.event KeyEvent ActionListener ActionEvent InputEvent WindowEvent] 34 | [net.java.balloontip BalloonTip CustomBalloonTip] 35 | [net.java.balloontip.positioners LeftAbovePositioner LeftBelowPositioner] 36 | [net.java.balloontip.styles IsometricBalloonStyle] 37 | [net.java.balloontip.utils TimingUtils])) 38 | 39 | (seesaw/native!) ; Use the OS's native look and feel 40 | 41 | (def 42 | ^{:doc "When a new inspector is opened, these are the default options. 43 | You can customize these options via keyword arguments, 44 | e.g. (inspect an-object :sorted false :pause true)"} 45 | default-options 46 | {; Tree filtering options 47 | :sorted true ; Alphabetically sort members 48 | :methods true ; Show/hide methods 49 | :fields true 50 | :public true 51 | :protected false 52 | :private false 53 | :static true 54 | :inherited true ; Show/hide inherited members 55 | 56 | ; Miscellaneous options 57 | :window-name nil ; If given a name, the inspector is opened as a new tab in the window with that name. 58 | ; If that window does not exist yet, it will be created. This is useful if you want to 59 | ; divide your inspectors into groups. 60 | :new-window false ; If true, the inspector is opened in a new window. 61 | ; Otherwise, the inspector is opened as a new tab. 62 | ; This option is ignored if :window-name is used. 63 | :pause false ; If true, the current thread will be paused when opening an inspector. 64 | ; Execution will resume either when clicking the resume button, or closing the inspector tab/window. 65 | :vars nil ; You can use this to pass in any extra information to an inspector window. 66 | ; This is useful when invoking a method in the inspector, and you need to fill in some argument values. 67 | ; The value of :vars will then be available in the inspector under the variable named vars. 68 | }) 69 | 70 | (def 71 | ^{:doc "Inspectory Jay GUI options"} 72 | gui-options 73 | {:width 1024 74 | :height 768 75 | :font (font/font :name :sans-serif :style #{:plain}) 76 | :crumb-length 32 77 | :max-tabs 64 78 | :btip-style `(new IsometricBalloonStyle (UIManager/getColor "Panel.background") (color/color "#268bd2") 5) 79 | :btip-error-style `(new IsometricBalloonStyle (UIManager/getColor "Panel.background") (color/color "#d32e27") 3) 80 | :btip-positioner `(new LeftAbovePositioner 8 5)}) 81 | 82 | (declare inspector-window) ; Forward declaration 83 | 84 | ; Global variable! List of all open Inspector Jay windows (Each element is a map with keys :window and :name) 85 | (def jay-windows (atom [])) 86 | 87 | ; Global variable! Keeps track of the last selected tree node. (across all tabs/windows) 88 | (def last-selected-node (atom nil)) 89 | 90 | (defn- error 91 | "Show an error message near a given component" 92 | [^String message ^JComponent component] 93 | (TimingUtils/showTimedBalloon (new BalloonTip 94 | component 95 | (seesaw/label message) 96 | (eval (gui-options :btip-error-style)) 97 | (eval (gui-options :btip-positioner)) 98 | nil) 99 | 3000) 100 | (-> (Toolkit/getDefaultToolkit) .beep)) 101 | 102 | (defn- tree-renderer 103 | "Returns a cell renderer which defines what each tree node should look like" 104 | ^DefaultTreeCellRenderer [] 105 | (proxy [DefaultTreeCellRenderer] [] 106 | (getTreeCellRendererComponent [tree value selected expanded leaf row hasFocus] 107 | (proxy-super getTreeCellRendererComponent tree value selected expanded leaf row hasFocus) 108 | (-> this (.setText (nprops/to-string value))) 109 | (-> this (.setIcon (nprops/get-icon value))) 110 | this))) 111 | 112 | (defn last-selected-value 113 | "Retrieve the value of the tree node that was last selected. 114 | (An exception is thrown if we can't automatically obtain this value .. e.g. in case the 115 | value is not available yet and the last selected node is a method with parameters.)" 116 | [] 117 | (.getValue @last-selected-node)) 118 | 119 | (defn- tree-selection-listener 120 | "Whenever a tree node is selected, update the detailed information panel, the breadcrumbs, 121 | and the last-selected-value variable." 122 | ^TreeSelectionListener [info-panel crumbs-panel] 123 | (proxy [TreeSelectionListener] [] 124 | (valueChanged [event] 125 | (let [new-path (-> event .getNewLeadSelectionPath)] 126 | (if (not= new-path nil) 127 | (let [new-node (-> new-path .getLastPathComponent)] 128 | (swap! last-selected-node (fn [x] new-node)) 129 | (seesaw/config! info-panel :text (nprops/to-string-verbose new-node)) 130 | (seesaw/config! crumbs-panel :text 131 | (str 132 | "" 133 | (s/join (interpose " > " 134 | (map (fn [x] (nprops/to-string-breadcrumb x (:crumb-length gui-options))) 135 | (-> new-path .getPath)))) 136 | "")))))))) 137 | 138 | (defn- tree-expansion-listener 139 | "Updates the detailed information panel whenever a node is expanded." 140 | ^TreeExpansionListener [info-panel] 141 | (proxy [TreeExpansionListener] [] 142 | (treeExpanded [event] 143 | (seesaw/config! info-panel :text (nprops/to-string-verbose (-> event .getPath .getLastPathComponent)))) 144 | (treeCollapsed [event]))) 145 | 146 | (defn- tree-will-expand-listener 147 | "Displays a dialog if the user needs to enter some actual parameters to invoke a method." 148 | ^TreeWillExpandListener [shared-vars] 149 | (proxy [TreeWillExpandListener] [] 150 | (treeWillExpand [event] 151 | (let [jtree (-> event .getSource) 152 | node (-> event .getPath .getLastPathComponent)] 153 | (if (not (-> node .isValueAvailable)) 154 | (if (= 0 (count (-> node .getMethod .getParameterTypes))) 155 | ; No parameters needed; we can simply call .getValue to make the value available 156 | (-> node .getValue) 157 | ; Otherwise we'll need to ask the user to enter some parameter values 158 | (let [raw-types (-> node .getMethod .getParameterTypes) 159 | ; Swap Java's primitive types for their corresponding wrappers 160 | param-types (for [x raw-types] 161 | (let [type (-> x .toString)] 162 | (cond 163 | (= type "byte") java.lang.Byte 164 | (= type "short") java.lang.Short 165 | (= type "int") java.lang.Integer 166 | (= type "long") java.lang.Long 167 | (= type "float") java.lang.Float 168 | (= type "double") java.lang.Double 169 | (= type "boolean") java.lang.Boolean 170 | (= type "char") java.lang.Character 171 | :else x))) 172 | param-boxes (for [x param-types] 173 | (seesaw/text :tip (-> x .getSimpleName) :columns 32 )) 174 | params (seesaw/grid-panel :rows (inc (count param-types)) :columns 1) 175 | ok-button (seesaw/button :text "Call") 176 | cancel-button (seesaw/button :text "Cancel") 177 | buttons (seesaw/flow-panel) 178 | border-panel (seesaw/border-panel :center params :south buttons)] 179 | (-> buttons (.add ok-button)) 180 | (-> buttons (.add cancel-button)) 181 | (-> params (.add (seesaw/label "Enter parameter values to call this method:"))) 182 | (doseq [x param-boxes] 183 | (-> params (.add x))) 184 | (let [; Form to enter paramter values 185 | btip (new CustomBalloonTip 186 | jtree 187 | border-panel 188 | (-> jtree (.getPathBounds (-> event .getPath))) 189 | (eval (gui-options :btip-style)) 190 | (eval (gui-options :btip-positioner)) 191 | nil) 192 | ; Button to submit the form, and invoke the requested method 193 | ok-handler (fn [e] 194 | (try (let [args (for [i (range 0 (count param-boxes))] 195 | ; Try to evaluate the expression to obtain the current parameter's value 196 | (try 197 | (let [value (node/eval-arg (-> (nth param-boxes i) .getText) shared-vars) 198 | type (nth param-types i)] 199 | (cond 200 | ; Do primitive type conversion when necessary 201 | (= type java.lang.Short) (short value) ; Convert long to short 202 | (= type java.lang.Float) (float value) ; Convert double to float 203 | (= type java.lang.Integer) (int value) ; Convert long to int 204 | :else value)) 205 | (catch Exception e 206 | (do 207 | (error (-> e .getMessage) (nth param-boxes i)) 208 | (throw (Exception.))))))] 209 | (doseq [x args] x) ; If something went wrong with the arguments, this should trigger the exception before attempting an invocation.. 210 | (-> node (.invokeMethod args)) 211 | (-> btip .closeBalloon) 212 | (-> jtree (.expandPath (-> event .getPath))) 213 | (-> jtree (.setSelectionPath (-> event .getPath))) 214 | (-> jtree .requestFocus)) 215 | (catch Exception e ))) 216 | cancel-handler (fn [e] 217 | (-> btip .closeBalloon) 218 | (-> jtree .requestFocus)) 219 | key-handler (fn [e] 220 | (cond 221 | (= (-> e .getKeyCode) KeyEvent/VK_ENTER) (ok-handler e) 222 | (= (-> e .getKeyCode) KeyEvent/VK_ESCAPE) (cancel-handler e)))] 223 | (-> (first param-boxes) .requestFocus) 224 | (doseq [x param-boxes] 225 | (seesaw/listen x :key-pressed key-handler)) 226 | (seesaw/listen cancel-button :action (fn [e] 227 | (-> btip .closeBalloon) 228 | (-> jtree .requestFocus))) 229 | (seesaw/listen ok-button :action ok-handler)) 230 | (throw (new ExpandVetoException event))))))) ; Deny expanding the tree node; it will only be expanded once the value is available 231 | (treeWillCollapse [event]))) 232 | 233 | (defn- open-javadoc 234 | "Search Javadoc for the selected node (if present)" 235 | [jtree] 236 | (let [selection (-> jtree .getLastSelectedPathComponent)] 237 | (if (not= selection nil) 238 | (jdoc/javadoc (nprops/get-javadoc-class selection))))) 239 | 240 | (defn- inspect-node 241 | "Open the currently selected node in a new inspector" 242 | [jtree inspect-button] 243 | (let [selection (-> jtree .getLastSelectedPathComponent)] 244 | (if (-> selection .hasValue) 245 | (let 246 | [window (seesaw/to-root jtree) 247 | name (some (fn [w] (when (= (:window w) window) (:name w))) @jay-windows)] 248 | (inspector-window (-> selection .getValue) :window-name name)) 249 | (if (= (-> selection .getKind) :method) 250 | (error "Can't inspect the return value of this method. (Have you already called it?)" inspect-button) 251 | (error "Can't inspect this node; it doesn't have a value." inspect-button)))) 252 | ) 253 | 254 | (defn- reinvoke 255 | "(Re)invoke the selected method node" 256 | [jtree] 257 | (let [selection (-> jtree .getLastSelectedPathComponent) 258 | selection-path (-> jtree .getSelectionPath) 259 | selection-row (-> jtree (.getRowForPath selection-path))] 260 | (if (= (-> selection .getKind) :method) 261 | (do 262 | (-> jtree (.collapsePath selection-path)) 263 | (-> jtree .getModel (.valueForPathChanged selection-path (node/object-node (new Object)))) 264 | (-> jtree (.expandRow selection-row)) 265 | (-> jtree (.setSelectionRow selection-row)))))) 266 | 267 | (defn- resume-execution 268 | "Wake up any threads waiting for a lock object" 269 | [lock] 270 | (locking lock (.notify lock))) 271 | 272 | (defn- search-tree 273 | "Search a JTree for the first visible node whose value contains 'key', starting from the node at row 'start-row'. 274 | If 'forward' is true, we search going forward; otherwise backwards. 275 | If a matching node is found, its path is returned; otherwise nil is returned." 276 | [^JTree tree ^String key start-row forward include-current] 277 | (let [max-rows (-> tree .getRowCount) 278 | ukey (-> key .toUpperCase) 279 | increment (if forward 1 -1) 280 | start (if include-current start-row (mod (+ start-row increment max-rows) max-rows))] 281 | ; Keep looking through all rows until we either find a match, or we're back at the start 282 | (loop [row start] 283 | (let [path (-> tree (.getPathForRow row)) 284 | node (nprops/to-string (-> path .getLastPathComponent)) 285 | next-row (mod (+ row increment max-rows) max-rows)] 286 | (if (-> node .toUpperCase (.contains ukey)) 287 | path 288 | (if (not= next-row start) 289 | (recur next-row) 290 | nil)))))) 291 | 292 | (defn- search-tree-and-select 293 | "Search the inspector tree and select the node that was found, if any" 294 | [^JTree tree text forward include-current] 295 | (let [start-row (if (-> tree .isSelectionEmpty) 0 (-> tree .getLeadSelectionRow)) 296 | next-match (search-tree tree text start-row forward include-current)] 297 | (if (not= next-match nil) 298 | (doto tree 299 | (.setSelectionPath next-match) 300 | (.scrollPathToVisible next-match)) 301 | (-> (Toolkit/getDefaultToolkit) .beep)))) 302 | 303 | (defn- tool-panel 304 | "Create the toolbar of the Inspector Jay window" 305 | ^JToolBar [^Object object ^JTree jtree ^JSplitPane split-pane tree-options] 306 | (let [iconSize [24 :by 24] 307 | sort-button (seesaw/toggle :icon (seesaw/icon (io/resource "icons/alphab_sort_co.gif")) :size iconSize :selected? (tree-options :sorted)) 308 | filter-button (seesaw/button :icon (seesaw/icon (io/resource "icons/filter_history.gif")) :size iconSize) 309 | filter-methods (seesaw/checkbox :text "Methods" :selected? (tree-options :methods)) 310 | filter-fields (seesaw/checkbox :text "Fields" :selected? (tree-options :fields)) 311 | filter-public (seesaw/checkbox :text "Public" :selected? (tree-options :public)) 312 | filter-protected (seesaw/checkbox :text "Protected" :selected? (tree-options :protected)) 313 | filter-private (seesaw/checkbox :text "Private" :selected? (tree-options :private)) 314 | filter-static (seesaw/checkbox :text "Static" :selected? (tree-options :static)) 315 | filter-inherited (seesaw/checkbox :text "Inherited" :selected? (tree-options :inherited)) 316 | ; Function to refresh the inspector tree given the current options in the filter menu 317 | update-filters (fn [e] 318 | (-> jtree (.setModel (model/tree-model object {:sorted (-> sort-button .isSelected) 319 | :methods (-> filter-methods .isSelected) 320 | :fields (-> filter-fields .isSelected) 321 | :public (-> filter-public .isSelected) 322 | :protected (-> filter-protected .isSelected) 323 | :private (-> filter-private .isSelected) 324 | :static (-> filter-static .isSelected) 325 | :inherited (-> filter-inherited .isSelected)}))) 326 | (-> jtree (.setSelectionPath (-> jtree (.getPathForRow 0))))) 327 | filter-panel (seesaw/vertical-panel :items [filter-methods filter-fields 328 | (seesaw/separator) filter-public filter-protected filter-private 329 | (seesaw/separator) filter-static filter-inherited]) 330 | pane-button (seesaw/toggle :icon (seesaw/icon (io/resource "icons/details_view.gif")) :size iconSize :selected? true) 331 | inspect-button (seesaw/button :icon (seesaw/icon (io/resource "icons/insp_sbook.gif")) :size iconSize) 332 | doc-button (seesaw/button :icon (seesaw/icon (io/resource "icons/javadoc.gif")) :size iconSize) 333 | invoke-button (seesaw/button :icon (seesaw/icon (io/resource "icons/runlast_co.gif")) :size iconSize) 334 | refresh-button (seesaw/button :icon (seesaw/icon (io/resource "icons/nav_refresh.gif")) :size iconSize) 335 | filter-tip (delay (new BalloonTip ; Only create the filter menu once needed 336 | filter-button 337 | filter-panel 338 | (eval (gui-options :btip-style)) 339 | (eval (gui-options :btip-positioner)) 340 | nil)) 341 | resume-button (if (:pause tree-options) 342 | (seesaw/button :icon (seesaw/icon (io/resource "icons/resume_co.gif")) :size iconSize)) 343 | search-txt (seesaw/text :columns 20 :text "Search...") 344 | toolbar-items (remove nil? [sort-button filter-button pane-button inspect-button doc-button invoke-button refresh-button resume-button 345 | (Box/createHorizontalGlue) search-txt (Box/createHorizontalStrut 2)]) 346 | toolbar (seesaw/toolbar :items toolbar-items)] 347 | (doto toolbar 348 | (.setBorder (border/empty-border :thickness 1)) 349 | (.setFloatable false)) 350 | (-> search-txt (.setMaximumSize (-> search-txt .getPreferredSize))) 351 | ; Ditch the redundant dotted rectangle when a button is focused 352 | (-> sort-button (.setFocusPainted false)) 353 | (-> filter-button (.setFocusPainted false)) 354 | (-> pane-button (.setFocusPainted false)) 355 | (-> inspect-button (.setFocusPainted false)) 356 | (-> doc-button (.setFocusPainted false)) 357 | (-> invoke-button (.setFocusPainted false)) 358 | (-> refresh-button (.setFocusPainted false)) 359 | ; Set tooltips 360 | (-> sort-button (.setToolTipText "Sort alphabetically")) 361 | (-> filter-button (.setToolTipText "Filtering options...")) 362 | (-> pane-button (.setToolTipText "Toggle horizontal/vertical layout")) 363 | (-> inspect-button (.setToolTipText "Open selected node in new inspector")) 364 | (-> doc-button (.setToolTipText "Search Javadoc (F1)")) 365 | (-> invoke-button (.setToolTipText "(Re)invoke selected method (F4)")) 366 | (-> refresh-button (.setToolTipText "Refresh tree")) 367 | (-> search-txt (.setToolTipText "Search visible tree nodes (F3 / Shift-F3)")) 368 | ; Resume button 369 | (if (:pause tree-options) 370 | (do 371 | (-> resume-button (.setFocusPainted false)) 372 | (-> resume-button (.setToolTipText "Resume execution")) 373 | (seesaw/listen resume-button :action (fn [e] 374 | (resume-execution (.getParent toolbar)) 375 | (-> resume-button (.setEnabled false)) 376 | (-> resume-button (.setToolTipText "Resume execution (already resumed)")))))) 377 | ; Sort button 378 | (seesaw/listen sort-button :action update-filters) 379 | ; Open/close filter options menu 380 | (seesaw/listen filter-button :action (fn [e] 381 | (if (not (realized? filter-tip)) 382 | ; If opened for the first time, add a listener that hides the menu on mouse exit 383 | (seesaw/listen @filter-tip :mouse-exited (fn [e] 384 | (if (not (-> @filter-tip (.contains (-> e .getPoint)))) 385 | (-> @filter-tip (.setVisible false))))) 386 | (if (-> @filter-tip .isVisible) 387 | (-> @filter-tip (.setVisible false)) 388 | (-> @filter-tip (.setVisible true)))))) 389 | ; Filter checkboxes 390 | (seesaw/listen filter-methods :action update-filters) 391 | (seesaw/listen filter-fields :action update-filters) 392 | (seesaw/listen filter-public :action update-filters) 393 | (seesaw/listen filter-protected :action update-filters) 394 | (seesaw/listen filter-private :action update-filters) 395 | (seesaw/listen filter-static :action update-filters) 396 | (seesaw/listen filter-inherited :action update-filters) 397 | ; Toggle horizontal/vertical layout 398 | (seesaw/listen pane-button :action (fn [e] 399 | (if (-> pane-button .isSelected) 400 | (-> split-pane (.setOrientation 0)) 401 | (-> split-pane (.setOrientation 1))))) 402 | ; Open selected node in new inspector 403 | (seesaw/listen inspect-button :action (fn [e] (inspect-node jtree inspect-button))) 404 | ; Open javadoc of selected tree node 405 | (seesaw/listen doc-button :action (fn [e] (open-javadoc jtree))) 406 | ; (Re)invoke the selected method 407 | (seesaw/listen invoke-button :action (fn [e] (reinvoke jtree))) 408 | ; Refresh the tree 409 | (seesaw/listen refresh-button :action update-filters) 410 | ; Clear search field initially 411 | (seesaw/listen search-txt :focus-gained (fn [e] 412 | (-> search-txt (.setText "")) 413 | (-> search-txt (.removeFocusListener (last(-> search-txt (.getFocusListeners))))))) 414 | ; When typing in the search field, look for matches 415 | (seesaw/listen search-txt #{:remove-update :insert-update} (fn [e] 416 | (search-tree-and-select jtree (-> search-txt .getText) true true))) 417 | toolbar)) 418 | 419 | (defn- get-jtree 420 | "Retrieve the object tree from an inspector Jay panel" 421 | [^JPanel panel] 422 | (let [split-pane (nth (-> panel .getComponents) 2) 423 | scroll-pane (nth (-> split-pane .getComponents) 2) 424 | viewport (nth (-> scroll-pane .getComponents) 0) 425 | jtree (-> viewport .getView)] 426 | jtree)) 427 | 428 | (defn- get-search-field 429 | "Retrieve the search field from an inspector Jay panel" 430 | [^JPanel panel] 431 | (let [toolbar (nth (-> panel .getComponents) 0) 432 | idx (- (count (-> toolbar .getComponents)) 2) ; It's the second to last component in the toolbar.. 433 | search-field (nth (-> toolbar .getComponents) idx)] 434 | search-field)) 435 | 436 | (defn- get-selected-tab 437 | "Retrieve the currently selected tab of an inspector Jay window" 438 | ^JPanel [^JFrame window] 439 | (let [content (-> window .getContentPane)] 440 | (if (instance? JTabbedPane content) 441 | (-> content .getSelectedComponent) 442 | content))) 443 | 444 | (defn- close-tab 445 | "Close the currently selected tab in an Inspector Jay window" 446 | [window tab] 447 | (let [content (-> window .getContentPane) 448 | isTabbed (instance? JTabbedPane content)] 449 | (if (not isTabbed) 450 | ; Close the window if there only is one object opened 451 | (-> window (.dispatchEvent (new WindowEvent window WindowEvent/WINDOW_CLOSING))) 452 | ; Close the tab 453 | (do 454 | (resume-execution (-> content (.getComponentAt (-> content .getSelectedIndex)))) 455 | (-> content (.removeTabAt (-> content .getSelectedIndex))) 456 | ; Go back to an untabbed interface if there's only one tab left 457 | (if (= 1 (-> content .getTabCount)) 458 | (-> window (.setContentPane (-> content (.getSelectedComponent))))))))) 459 | 460 | (defn- bind-keys 461 | "Attach various key bindings to an Inspector Jay window" 462 | [frame] 463 | (let 464 | [f1-key (KeyStroke/getKeyStroke KeyEvent/VK_F1 0) 465 | f3-key (KeyStroke/getKeyStroke KeyEvent/VK_F3 0) 466 | f4-key (KeyStroke/getKeyStroke KeyEvent/VK_F4 0) 467 | shift-f3-key (KeyStroke/getKeyStroke KeyEvent/VK_F3 InputEvent/SHIFT_DOWN_MASK) 468 | ctrl-f-key (KeyStroke/getKeyStroke KeyEvent/VK_F (-> (Toolkit/getDefaultToolkit) .getMenuShortcutKeyMask)) ; Cmd on a Mac, Ctrl elsewhere 469 | ctrl-w-key (KeyStroke/getKeyStroke KeyEvent/VK_W (-> (Toolkit/getDefaultToolkit) .getMenuShortcutKeyMask))] 470 | ; Search javadoc for the currently selected node and open it in a browser window 471 | (-> frame .getRootPane (.registerKeyboardAction 472 | (proxy [ActionListener] [] 473 | (actionPerformed [e] 474 | (open-javadoc (get-jtree (get-selected-tab frame))))) 475 | f1-key JComponent/WHEN_IN_FOCUSED_WINDOW)) 476 | ; (Re)invoke selected method 477 | (-> frame .getRootPane (.registerKeyboardAction 478 | (proxy [ActionListener] [] 479 | (actionPerformed [e] 480 | (reinvoke (get-jtree (get-selected-tab frame))))) 481 | f4-key JComponent/WHEN_IN_FOCUSED_WINDOW)) 482 | ; Find next 483 | (-> frame .getRootPane (.registerKeyboardAction 484 | (proxy [ActionListener] [] 485 | (actionPerformed [e] 486 | (let [tab (get-selected-tab frame)] 487 | (search-tree-and-select (get-jtree tab) (-> (get-search-field tab) .getText) true false)))) 488 | f3-key JComponent/WHEN_IN_FOCUSED_WINDOW)) 489 | ; Find previous 490 | (-> frame .getRootPane (.registerKeyboardAction 491 | (proxy [ActionListener] [] 492 | (actionPerformed [e] 493 | (let [tab (get-selected-tab frame)] 494 | (search-tree-and-select (get-jtree tab) (-> (get-search-field tab) .getText) false false)))) 495 | shift-f3-key JComponent/WHEN_IN_FOCUSED_WINDOW)) 496 | ; Go to search field (or back to the tree) 497 | (-> frame .getRootPane (.registerKeyboardAction 498 | (proxy [ActionListener] [] 499 | (actionPerformed [e] 500 | (let [tab (get-selected-tab frame) 501 | search-field (get-search-field tab)] 502 | (if (-> search-field .hasFocus) 503 | (-> (get-jtree tab) .requestFocus) 504 | (-> search-field .requestFocus))))) 505 | ctrl-f-key JComponent/WHEN_IN_FOCUSED_WINDOW)) 506 | ; Close the current tab 507 | (-> frame .getRootPane (.registerKeyboardAction 508 | (proxy [ActionListener] [] 509 | (actionPerformed [e] 510 | (close-tab frame (get-selected-tab frame)))) 511 | ctrl-w-key JComponent/WHEN_IN_FOCUSED_WINDOW)))) 512 | 513 | (defn inspector-panel 514 | "Create and show an Inspector Jay window to inspect a given object. 515 | See default-options for more information on all available keyword arguments." 516 | ^JPanel [^Object object & {:as args}] 517 | (let [obj-info (seesaw/text :multi-line? true :editable? false :font (gui-options :font)) 518 | obj-tree (seesaw/tree :model (model/tree-model object args)) 519 | crumbs (seesaw/label :icon (seesaw/icon (io/resource "icons/toggle_breadcrumb.gif"))) 520 | obj-info-scroll (seesaw/scrollable obj-info) 521 | obj-tree-scroll (seesaw/scrollable obj-tree) 522 | split-pane (seesaw/top-bottom-split obj-info-scroll obj-tree-scroll :divider-location 1/5) 523 | toolbar (tool-panel object obj-tree split-pane args) 524 | main-panel (seesaw/border-panel :north toolbar :south crumbs :center split-pane)] 525 | (-> split-pane (.setDividerSize 9)) 526 | (-> obj-info-scroll (.setBorder (border/empty-border))) 527 | (-> obj-tree-scroll (.setBorder (border/empty-border))) 528 | (doto obj-tree 529 | (.setCellRenderer (tree-renderer)) 530 | (.addTreeSelectionListener (tree-selection-listener obj-info crumbs)) 531 | (.addTreeExpansionListener (tree-expansion-listener obj-info)) 532 | (.addTreeWillExpandListener (tree-will-expand-listener (:vars args))) 533 | (.setSelectionPath (-> obj-tree (.getPathForRow 0)))) 534 | main-panel)) 535 | 536 | (defn- close-window 537 | "Clean up when closing a window" 538 | [window] 539 | ; Resume any threads that might've been paused by inspectors in this window 540 | (let [content (-> window .getContentPane) 541 | isTabbed (instance? JTabbedPane content)] 542 | (if (not isTabbed) 543 | (resume-execution content) 544 | (doseq [x (range 0 (.getTabCount content))] 545 | (resume-execution (.getComponentAt content x))))) 546 | ; Clean up any resources associated with the window 547 | (swap! jay-windows (fn [x] (remove (fn [w] (= (:window w) window)) x))) 548 | (if (empty? @jay-windows) ; If all windows are closed, reset last-selected-node 549 | (swap! last-selected-node (fn [x] nil))) 550 | (node/clear-memoization-caches)) 551 | 552 | (defn- window-title 553 | "Compose a window's title" 554 | [window-name object] 555 | (let [prefix (if (nil? window-name) "Object inspector : " (str window-name " : "))] 556 | (str prefix (.toString object)))) 557 | 558 | (defn inspector-window 559 | "Show an Inspector Jay window to inspect a given object. 560 | See default-options for more information on all available keyword arguments." 561 | [object & {:as args}] 562 | (let [merged-args (merge default-options args) 563 | win-name (:window-name merged-args) 564 | panel (apply inspector-panel object (utils/map-to-keyword-args merged-args))] 565 | (seesaw/invoke-later 566 | (if (or ; Create a new window if: there are no windows yet, or :new-window is used, or a window with the given name hasn't been created yet. 567 | (= 0 (count @jay-windows)) 568 | (and (:new-window merged-args) (nil? win-name)) 569 | (and (not (nil? win-name)) (not-any? (fn [w] (= win-name (:name w))) @jay-windows))) 570 | ; Create a new window 571 | (let [window (seesaw/frame :title (window-title win-name object) 572 | :size [(gui-options :width) :by (gui-options :height)] 573 | :on-close :dispose)] 574 | (swap! jay-windows (fn [x] (conj x {:window window :name win-name}))) 575 | (seesaw/config! window :content panel) 576 | (bind-keys window) 577 | ; When the window is closed, remove the window from the list and clear caches 578 | (seesaw/listen window :window-closed (fn [e] (close-window window))) 579 | (-> window seesaw/show!) 580 | (-> (get-jtree panel) .requestFocus)) 581 | 582 | ; Otherwise, add a new tab 583 | (let [window (if (nil? win-name) 584 | (:window (last @jay-windows)) ; The most recently created window 585 | (some 586 | (fn [w] (when 587 | (= win-name (:name w)) 588 | (:window w))) @jay-windows)) 589 | content (-> window .getContentPane) 590 | isTabbed (instance? JTabbedPane content)] 591 | ; If the tabbed pane has not been created yet 592 | (if (not isTabbed) 593 | (let [tabs (seesaw/tabbed-panel) 594 | title (utils/truncate (-> (get-jtree content) .getModel .getRoot .getValue .toString) 20)] 595 | (-> tabs (.setBorder (border/empty-border :top 1 :left 2 :bottom 1 :right 0))) 596 | (-> tabs (.add title content)) 597 | (-> window (.setContentPane tabs)) 598 | ; When switching tabs, adjust the window title, and focus on the tree 599 | (seesaw/listen tabs :change (fn [e] 600 | (let [tree (get-jtree (get-selected-tab window)) 601 | title (window-title 602 | win-name 603 | (-> tree .getModel .getRoot .getValue))] 604 | (-> window (.setTitle title)) 605 | (-> tree .requestFocus)))))) 606 | (let [tabs (-> window .getContentPane)] 607 | ; Add the new tab 608 | (doto tabs 609 | (.add (utils/truncate (.toString object) 20) panel) 610 | (.setSelectedIndex (-> window .getContentPane (.indexOfComponent panel)))) 611 | ; Close the first tab, if going beyond the maximum number of tabs 612 | (if (> (-> tabs .getTabCount) (:max-tabs gui-options)) 613 | (close-tab window (-> tabs (.getComponentAt 0)))) 614 | (-> (get-jtree panel) .requestFocus))))) 615 | ; If desired, pause the current thread (while the GUI keeps running in a seperate thread) 616 | (if (:pause args) 617 | (locking panel (.wait panel))))) -------------------------------------------------------------------------------- /src/inspector_jay/gui/node_properties.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2013-2015 Tim Molderez. 2 | ; 3 | ; All rights reserved. This program and the accompanying materials 4 | ; are made available under the terms of the 3-Clause BSD License 5 | ; which accompanies this distribution, and is available at 6 | ; http://www.opensource.org/licenses/BSD-3-Clause 7 | 8 | (ns inspector-jay.gui.node-properties 9 | "Defines various visual properties of Inspector Jay's tree nodes" 10 | {:author "Tim Molderez"} 11 | (:require 12 | [clojure.string :as s] 13 | [clojure.java.io :as io] 14 | [inspector-jay.gui.utils :as utils] 15 | [inspector-jay.model 16 | [tree-node :as node] 17 | [tree-model :as model]] 18 | [seesaw.core :as seesaw]) 19 | (:import 20 | [java.lang.reflect Modifier])) 21 | 22 | (defmulti to-string 23 | "Retrieve a short description of a tree node" 24 | (fn [node] (-> node .getKind))) 25 | 26 | (defmulti to-string-breadcrumb 27 | "Retrieve a string to describe a tree node in a path of breadcrumbs" 28 | (fn [node crumb-length] (-> node .getKind))) 29 | 30 | (defmulti to-string-verbose 31 | "Retrieve a detailed description of a tree node" 32 | (fn [node] (-> node .getKind))) 33 | 34 | (defmulti to-string-value 35 | "Retrieve a node's value as a string" 36 | (fn [node] (-> node .getCollectionKind))) 37 | 38 | (defmulti get-icon 39 | "Retrieve the icon associated with a tree node" 40 | (fn [node] (-> node .getKind))) 41 | 42 | (defmulti get-javadoc-class 43 | "Retrieve the class that should be used when looking for a node's javadoc" 44 | (fn [node] (-> node .getKind))) 45 | 46 | (defmethod to-string :default [node] 47 | (-> node .getValue .toString)) 48 | (defmethod to-string :nil [node] 49 | "nil") 50 | (defmethod to-string :method [node] 51 | (str 52 | (-> node .getMethod .getName) 53 | "(" 54 | (s/join (interpose ", " (map (memfn getSimpleName) (-> node .getMethod .getParameterTypes)))) 55 | ") : " 56 | (-> node .getMethod .getReturnType .getSimpleName))) 57 | (defmethod to-string :field [node] 58 | (str 59 | (-> node .getField .getName) 60 | " : " 61 | (-> node .getValue))) 62 | 63 | (defmethod to-string-breadcrumb :default [node crumb-length] 64 | (utils/truncate (-> node .getValue .toString) crumb-length)) 65 | (defmethod to-string-breadcrumb :nil [node crumb-length] 66 | "nil") 67 | (defmethod to-string-breadcrumb :method [node crumb-length] 68 | (utils/truncate (str 69 | (-> node .getMethod .getName) 70 | "(" 71 | (s/join (interpose ", " (map (memfn getSimpleName) (-> node .getMethod .getParameterTypes)))) 72 | ")") 73 | crumb-length)) 74 | (defmethod to-string-breadcrumb :field [node crumb-length] 75 | (utils/truncate (str (-> node .getField .getName)) 76 | crumb-length)) 77 | 78 | (defmethod to-string-verbose :default [node] 79 | (str 80 | (-> node .getValue .getClass) 81 | "\n\n" 82 | (to-string-value node))) 83 | (defmethod to-string-verbose :nil [node] 84 | "nil") 85 | (defmethod to-string-verbose :method [node] 86 | (str 87 | (-> node .getMethod .toString) 88 | (if (-> node .hasValue) 89 | (str 90 | "\n\n" 91 | (-> node .getValue .getClass .getName) " (dynamic type)" 92 | "\n\n" 93 | (to-string-value node))))) 94 | (defmethod to-string-verbose :field [node] 95 | (str 96 | (-> node .getField) 97 | (if (-> node .hasValue) 98 | (str 99 | "\n\n" 100 | (-> node .getValue .getClass .getName) " (dynamic type)" 101 | "\n\n" 102 | (to-string-value node))))) 103 | 104 | (defmethod to-string-value :atom [node] 105 | (-> node .getValue .toString)) 106 | (defmethod to-string-value :sequence [node] 107 | (utils/to-string-sequence (-> node .getValue))) 108 | (defmethod to-string-value :collection [node] 109 | (utils/to-string-sequence (seq(-> node .getValue)))) 110 | 111 | (defmethod get-icon :default [node] 112 | (seesaw/icon (io/resource "icons/genericvariable_obj.gif"))) 113 | (defmethod get-icon :method [node] 114 | (let 115 | [mod (-> node .getMethod .getModifiers)] 116 | (cond 117 | (Modifier/isPublic mod) (seesaw/icon (io/resource "icons/methpub_obj.gif")) 118 | (Modifier/isPrivate mod) (seesaw/icon (io/resource "icons/methpri_obj.gif")) 119 | (Modifier/isProtected mod) (seesaw/icon (io/resource "icons/methpro_obj.gif")) 120 | :else (seesaw/icon (io/resource "icons/methdef_obj.gif"))))) 121 | (defmethod get-icon :field [node] 122 | (let 123 | [mod (-> node .getField .getModifiers)] 124 | (cond 125 | (Modifier/isPublic mod) (seesaw/icon (io/resource "icons/field_public_obj.gif")) 126 | (Modifier/isPrivate mod) (seesaw/icon (io/resource "icons/field_private_obj.gif")) 127 | (Modifier/isProtected mod) (seesaw/icon (io/resource "icons/field_protected_obj.gif")) 128 | :else (seesaw/icon (io/resource "icons/field_default_obj.gif"))))) 129 | 130 | (defmethod get-javadoc-class :default [node] 131 | (-> node .getValueClass)) 132 | (defmethod get-javadoc-class :method [node] 133 | (-> node .getMethod .getDeclaringClass)) 134 | (defmethod get-javadoc-class :field [node] 135 | (-> node .getField .getDeclaringClass)) 136 | -------------------------------------------------------------------------------- /src/inspector_jay/gui/utils.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2013-2015 Tim Molderez. 2 | ; 3 | ; All rights reserved. This program and the accompanying materials 4 | ; are made available under the terms of the 3-Clause BSD License 5 | ; which accompanies this distribution, and is available at 6 | ; http://www.opensource.org/licenses/BSD-3-Clause 7 | 8 | (ns inspector-jay.gui.utils 9 | "Some utility functions" 10 | {:author "Tim Molderez"} 11 | (:require 12 | [clojure.string :as s])) 13 | 14 | (defn truncate 15 | "Returns a truncated string. If the string is longer than length, we only return the first 'length' characters and append an ellipsis to it." 16 | ^String [string length] 17 | (if (> (count string) length) 18 | (str (subs string 0 length) "...") 19 | string)) 20 | 21 | (defn to-string-sequence 22 | "Create a string, listing the elements of a sequence" 23 | ^String [sequence] 24 | (let [n (count sequence) 25 | indexWidth (if (> n 1) 26 | (int (java.lang.Math/ceil (java.lang.Math/log10 n))) 27 | 1)] 28 | (s/join 29 | (for [x (range 0 n)] 30 | (format (str "[%0" indexWidth "d] %s\n") x (nth sequence x)))))) 31 | 32 | (defn map-to-keyword-args 33 | "Convert a map to a list of keyword arguments. 34 | Used to pass in keyword arguments via clojure.core/apply" 35 | [args-map] 36 | (mapcat identity (vec args-map))) -------------------------------------------------------------------------------- /src/inspector_jay/model/tree_model.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2013-2015 Tim Molderez. 2 | ; 3 | ; All rights reserved. This program and the accompanying materials 4 | ; are made available under the terms of the 3-Clause BSD License 5 | ; which accompanies this distribution, and is available at 6 | ; http://www.opensource.org/licenses/BSD-3-Clause 7 | 8 | (ns inspector-jay.model.tree-model 9 | "Defines the tree model of the object inspector" 10 | {:author "Tim Molderez"} 11 | (:require 12 | [inspector-jay.model.tree-node :as node]) 13 | (:import 14 | [java.util Collection Map RandomAccess] 15 | [javax.swing.tree TreeModel] 16 | [javax.swing.event TreeModelEvent] 17 | [java.lang.reflect Method])) 18 | 19 | (defmulti is-leaf 20 | "Is this tree node a leaf?" 21 | (fn [node] (-> node .getCollectionKind))) 22 | (defmulti get-child 23 | "Get the child of a node at a certain index" 24 | (fn [node index opts] (-> node .getCollectionKind))) 25 | (defmulti get-child-count 26 | "Get the number of children of a node" 27 | (fn [node opts] (-> node .getCollectionKind))) 28 | 29 | (defmethod is-leaf :default [node] 30 | (not (-> node .mightHaveValue))) 31 | (defmethod is-leaf :sequence [node] 32 | (if (-> node .hasValue) 33 | (= 0 (count (-> node .getValue))) 34 | false)) 35 | (defmethod is-leaf :collection [node] 36 | (if (-> node .hasValue) 37 | (= 0 (count (-> node .getValue))) 38 | false)) 39 | 40 | (defmethod get-child :default [node index opts] 41 | (if (opts :methods) 42 | (if (< index (count (-> node (.getMethods opts)))) 43 | ; Methods always come first; if the index does not go beyond the number of methods, we're retrieving a method 44 | (let [meth (nth (-> node (.getMethods opts)) index)] (node/method-node meth (-> node .getValue))) 45 | ; Otherwise, we must be retrieving a field 46 | (let [field-index (- index (count (-> node (.getMethods opts)))) 47 | field (nth (-> node (.getFields opts)) field-index)] 48 | (node/field-node field (-> node .getValue)))) 49 | ; If methods are hidden, we must be retrieving a field 50 | (let [field (nth (-> node (.getFields opts)) index)] (node/field-node field (-> node .getValue))))) 51 | (defmethod get-child :sequence [node index opts] 52 | (node/object-node (nth (-> node .getValue) index))) 53 | (defmethod get-child :collection [node index opts] 54 | (node/object-node (nth (seq (-> node .getValue)) index))) 55 | 56 | (defmethod get-child-count :default [node opts] 57 | (if (-> node .hasValue) 58 | (+ (if (opts :methods) (count (-> node (.getMethods opts))) 0) 59 | (if (opts :fields) (count (-> node (.getFields opts))) 0)) 60 | 0)) 61 | (defmethod get-child-count :sequence [node opts] 62 | (if (-> node .hasValue) 63 | (count (-> node .getValue)) 64 | 0)) 65 | (defmethod get-child-count :collection [node opts] 66 | (if (-> node .hasValue) 67 | (count (-> node .getValue)) 68 | 0)) 69 | 70 | (defn tree-model 71 | "Define a tree model around root, which is the object we want to inspect" 72 | ^TreeModel [^Object root filter-options] 73 | (let [listeners (new java.util.Vector)] 74 | (proxy [TreeModel] [] 75 | (getRoot [] (node/object-node root)) 76 | (addTreeModelListener [treeModelListener] 77 | (-> listeners (.add treeModelListener))) 78 | (getChild [parent index] 79 | (get-child parent index filter-options)) 80 | (getChildCount [parent] 81 | (get-child-count parent filter-options)) 82 | (isLeaf [node] 83 | (is-leaf node)) 84 | (valueForPathChanged [path newValue] 85 | (let [e (new TreeModelEvent newValue path)] 86 | (doseq [listener listeners] 87 | (-> listener (.treeStructureChanged e))))) 88 | (getIndexOfChild [parent child] -1) 89 | (removeTreeModelListener [treeModelListener] 90 | (-> listeners (.remove treeModelListener)))))) -------------------------------------------------------------------------------- /src/inspector_jay/model/tree_node.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2013-2015 Tim Molderez. 2 | ; 3 | ; All rights reserved. This program and the accompanying materials 4 | ; are made available under the terms of the 3-Clause BSD License 5 | ; which accompanies this distribution, and is available at 6 | ; http://www.opensource.org/licenses/BSD-3-Clause 7 | 8 | (ns inspector-jay.model.tree-node 9 | "Defines the data structure of a tree node" 10 | {:author "Tim Molderez"} 11 | (:import 12 | [java.lang.reflect Modifier Method Field InvocationTargetException] 13 | [clojure.lang Delay]) 14 | (:require 15 | [clojure.core.memoize :as memo])) 16 | 17 | (def ^:dynamic meth-args) ; This declaration is needed so we can make method arguments available inside the delay-function that invokes a method (see method-node) 18 | (def ^:dynamic vars) ; This declaration is needed to make shared variables available when evaluating a method argument (see eval-arg) 19 | 20 | (defn invoke-method 21 | "Call a method on an object via reflection, and return its return value. 22 | If the call produces an exception, it is caught and returned as the return value instead." 23 | [method object & args] 24 | (try 25 | (-> method (.invoke object (to-array args))) 26 | (catch InvocationTargetException e (-> e .getCause)))) 27 | 28 | (def get-visible-fields 29 | (memo/memo (fn [cls opts] 30 | "Retrieve all fields that are visible to any instances of class cls. 31 | More specifically, all fields declared directly in cls, and all public/protected fields found in its ancestor classes. 32 | (This function is memoized, as it may trigger a lot of reflective calls.)" 33 | (let 34 | ; All fields declared directly in cls 35 | [declFields (filter (fn [aField] 36 | (and 37 | (if (opts :static) true (not (Modifier/isStatic (-> aField .getModifiers)))) 38 | (if (opts :private) true (not (Modifier/isPrivate (-> aField .getModifiers)))) 39 | (if (opts :protected) true (not (Modifier/isProtected (-> aField .getModifiers)))) 40 | (if (opts :public) true (not (Modifier/isPublic (-> aField .getModifiers)))))) 41 | (-> cls .getDeclaredFields)) 42 | ; All fields declared in the ancestor classes of cls 43 | ancestorFields (if (or (= java.lang.Object cls) (not (opts :inherited))) 44 | [] 45 | (get-visible-fields (-> cls .getSuperclass) opts)) 46 | ; Remove all private and hidden fields from ancestorFields 47 | filteredAncestors (filter (fn [aField] 48 | (and 49 | (not (Modifier/isPrivate (-> aField .getModifiers))) 50 | (every? (fn [dField] (not= (-> dField .getName) (-> aField .getName))) 51 | declFields))) 52 | ancestorFields)] 53 | (concat 54 | (if (opts :sorted) 55 | (sort-by (memfn getName) declFields) 56 | declFields) 57 | filteredAncestors))))) 58 | 59 | (def get-visible-methods 60 | (memo/memo (fn [cls opts] 61 | "Retrieve all methods that are visible to any instances of class cls. 62 | More specifically, all methods declared directly in cls, and all public/protected methods found in its ancestor classes. 63 | (This function is memoized, as it may trigger a lot of reflective calls.)" 64 | (let 65 | ; All methods declared directly in cls 66 | [declMethods (filter (fn [aMethod] 67 | (and 68 | (if (opts :static) true (not (Modifier/isStatic (-> aMethod .getModifiers)))) 69 | (if (opts :private) true (not (Modifier/isPrivate (-> aMethod .getModifiers)))) 70 | (if (opts :protected) true (not (Modifier/isProtected (-> aMethod .getModifiers)))) 71 | (if (opts :public) true (not (Modifier/isPublic (-> aMethod .getModifiers)))))) 72 | (-> cls .getDeclaredMethods)) 73 | ; All methods declared in the ancestor classes of cls 74 | ancestorMethods (if (or (= java.lang.Object cls) (not (opts :inherited))) 75 | [] 76 | (get-visible-methods (-> cls .getSuperclass) opts)) 77 | ; Remove all private and hidden methods from ancestorMethods 78 | filteredAncestors (filter (fn [aMethod] 79 | (and 80 | (not (Modifier/isPrivate (-> aMethod .getModifiers))) 81 | (every? (fn [dMethod] 82 | (and 83 | (not= (-> dMethod .getName) (-> aMethod .getName)) 84 | (not= (-> dMethod .getParameterTypes) (-> aMethod .getParameterTypes)))) 85 | declMethods))) 86 | ancestorMethods)] 87 | (concat 88 | (if (opts :sorted) 89 | (sort-by (memfn getName) declMethods) 90 | declMethods) 91 | filteredAncestors))))) 92 | 93 | (defn clear-memoization-caches 94 | "Clear the caches that store the list of fields and methods per class" 95 | [] 96 | (memo/memo-clear! get-visible-methods) 97 | (memo/memo-clear! get-visible-fields)) 98 | 99 | (defprotocol ITreeNode 100 | (getValue [this] 101 | "Retrieve the Java object contained by this node. (may be nil) In case the object is not available yet, it will be made available now. 102 | (This typically is the case if the object is the return value of a method that has not been invoked yet.)") 103 | (hasValue [this] 104 | "Is the object in this node available, and does it have a non-nil value?") 105 | (mightHaveValue [this] 106 | "Might this node contain a non-nil object? 107 | (If the object is not available yet, we assume it might have a non-nil value..)") 108 | (isValueAvailable [this] 109 | "Is the object contained by this node available?") 110 | (getValueClass [this] 111 | "Retrieve the type of the object contained by this node.") 112 | (getMethod [this] 113 | "Retrieve the method that produces the node's value as its return value. (may be nil)") 114 | (invokeMethod [this args] 115 | "Retrieve this node's value by invoking its method") 116 | (getField [this] 117 | "Retrieve the field associated with this node's value. (may be nil)") 118 | (getMethods [this opts] 119 | "Retrieve the methods in the node value's class. (if this value is nil, nil is returned)") 120 | (getFields [this opts] 121 | "Retrieve the fields in the node value's class. (if this value is nil, nil is returned)") 122 | (getKind [this] 123 | "Retrieve a keyword that represents this node: 124 | :object This is a generic object node, not associated with a method or a field. 125 | :method The object contained in this node is the return value of a method call. 126 | :field The object contained in this node is the value of a field.") 127 | (getCollectionKind [this] 128 | "Determine whether the object in this node represents some kind of collection: 129 | :atom The object is not a collection. 130 | :sequence The object is can be sequenced. (supports the nth and count functions) 131 | :collection The object is any other kind of collection, e.g. a set or map. (supports the seq and count functions)")) 132 | 133 | (deftype TreeNode [data] 134 | ITreeNode 135 | (getValue [this] 136 | (force (data :value))) 137 | (hasValue [this] 138 | (if (instance? Delay (data :value)) 139 | (and 140 | (realized? (data :value)) 141 | (not= (deref (data :value)) nil)) 142 | (not= (data :value) nil))) 143 | (mightHaveValue [this] 144 | (and 145 | (contains? data :value)) 146 | (not= (data :value) nil)) 147 | (isValueAvailable [this] 148 | (if (instance? Delay (data :value)) 149 | (realized? (data :value)) 150 | true)) 151 | (getValueClass [this] 152 | (case (-> this .getKind) 153 | :object (-> (data :value) .getClass) 154 | :method (-> (data :method) .getReturnType) 155 | :field (-> (data :field) .getType) 156 | :nil Object)) 157 | (getMethod [this] 158 | (data :method)) 159 | (invokeMethod [this args] 160 | (binding [meth-args args] ; Makes the method arguments available to the value's delay-function 161 | (-> this .getValue))) 162 | (getField [this] 163 | (data :field)) 164 | (getMethods [this opts] 165 | (if (not= (-> this .getValue) nil) 166 | (get-visible-methods (-> this .getValue .getClass) opts) 167 | nil)) 168 | (getFields [this opts] 169 | (if (not= (-> this .getValue) nil) 170 | (get-visible-fields (-> this .getValue .getClass) opts) 171 | nil)) 172 | (getKind [this] 173 | (cond 174 | (contains? data :method) :method 175 | (contains? data :field) :field 176 | (not= (data :value) nil) :object 177 | :else :nil)) 178 | (getCollectionKind [this] 179 | (let [cls (-> this .getValueClass)] 180 | (cond 181 | ; A :sequence is anything that supports the nth function.. 182 | (-> clojure.lang.Sequential (.isAssignableFrom cls)) :sequence 183 | (-> java.util.RandomAccess (.isAssignableFrom cls)) :sequence 184 | (-> cls .isArray) :sequence 185 | ; All collections support the count and seq functions.. 186 | (-> java.util.Collection (.isAssignableFrom cls)) :collection 187 | (-> java.util.Map (.isAssignableFrom cls)) :collection 188 | :else :atom)))) 189 | 190 | (defn object-node 191 | "Create a new generic object node." 192 | ^TreeNode [^Object object] 193 | (new TreeNode {:value object})) 194 | 195 | (defn method-node 196 | "Create a method node, given a method and a receiver object. 197 | The object contained by this node is the return value of invoking the method." 198 | ^TreeNode [^Method method ^Object receiver] 199 | (let [] 200 | (-> method (.setAccessible true)) ; Enable access to private methods 201 | (new TreeNode {:method method :value (delay 202 | (if (sequential? meth-args) 203 | (apply invoke-method method receiver meth-args) 204 | (invoke-method method receiver)))}))) 205 | 206 | (defn field-node 207 | "Create a field node, given a field and a receiver object. 208 | The object contained by this node is the field's value." 209 | ^TreeNode [^Field field ^Object receiver] 210 | (-> field (.setAccessible true)) ; Enable access to private fields 211 | (new TreeNode {:field field :value (-> field (.get receiver))})) 212 | 213 | (defn eval-arg 214 | "Evaluate an expression to obtain the value of a method argument. 215 | The value of shared-vars is made available in the expression as the vars variable." 216 | [arg shared-vars] 217 | (binding [*ns* (the-ns 'inspector-jay.model.tree-node) ; Make sure the namespace corresponds to this file; otherwise the declaration of vars won't be found! 218 | vars shared-vars] 219 | (eval (read-string arg)))) -------------------------------------------------------------------------------- /test/inspector_jay/core_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2013-2015 Tim Molderez. 2 | ; 3 | ; All rights reserved. This program and the accompanying materials 4 | ; are made available under the terms of the 3-Clause BSD License 5 | ; which accompanies this distribution, and is available at 6 | ; http://www.opensource.org/licenses/BSD-3-Clause 7 | 8 | (ns inspector-jay.core-test 9 | "Inspector Jay unit tests" 10 | (:use clojure.test 11 | inspector-jay.core)) 12 | 13 | (deftest a-test 14 | (testing "FIXME, I fail." 15 | (is (= 0 1)))) 16 | 17 | (comment 18 | ; Open a ton of inspectors sequentially 19 | (doseq [x (range 1 40)] 20 | (do (inspect x) (println x))) 21 | 22 | ; Simultaneously open many inspectors from different threads 23 | (doseq [x (range 1 80)] 24 | (future (do 25 | (inspect x :pause true) 26 | (println x)))) 27 | 28 | (run-tests 'inspector-jay.core-test)) --------------------------------------------------------------------------------