├── .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 | 
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))
--------------------------------------------------------------------------------