Initial Commit
[packages] / xemacs-packages / jde / java / src / jde / debugger / Debugger.java
1 package jde.debugger;
2
3 import java.io.IOException;
4 import java.util.HashMap;
5 import java.util.Iterator;
6 import java.util.List;
7 import java.util.Map;
8
9 import com.sun.jdi.ThreadReference;
10 import com.sun.jdi.VirtualMachine;
11 import com.sun.jdi.connect.AttachingConnector;
12 import com.sun.jdi.connect.Connector;
13 import com.sun.jdi.connect.IllegalConnectorArgumentsException;
14 import com.sun.jdi.connect.LaunchingConnector;
15 import com.sun.jdi.connect.ListeningConnector;
16 import com.sun.jdi.connect.VMStartException;
17 import com.sun.jdi.request.ClassPrepareRequest;
18 import com.sun.jdi.request.EventRequest;
19 import com.sun.jdi.request.EventRequestManager;
20 import jde.debugger.command.CommandHandler;
21 import jde.debugger.command.ProcessCommandHandler;
22 import jde.debugger.gui.GUI;
23 import jde.debugger.spec.EventRequestSpecList;
24
25
26 /**
27  * The main class for debugging a specific process. A Debugger instance
28  * handles the following tasks:
29  * <ul>
30  *  <li>Executing commands sent from Emacs (through the
31  * ProcessCommandHandler in {@link #m_handler m_handler})</li>
32  *  <li>Handling events from the VM (through the EventHandler in
33  * {@link #m_eventHandler m_eventHandler})</li>
34  *  <li>Keeping track of requested event specifications that haven't
35  * yet been resolved ({@link #m_eventRequestSpecList m_eventRequestSpecList})</li>
36  *  <li>Keeping track of objects and the ID that is used as a
37  * reference in the Emacs/Java communication. ({@link #m_objectStore m_objectStore})</li>
38  *  <li>Connecting the standard input/output/error streams of an
39  * application that was launched through the debugger to Emacs. This
40  * is done through {@link #m_sio m_sio}</li>
41  * </ul>
42  *
43  * Created: Tue Jan 08 12:24:36 2002
44  *
45  * @author Petter Måhlén
46  * @version $Revision: 1.3 $
47  */
48
49 public class Debugger implements Protocol {
50   private ProcessCommandHandler m_handler;
51   private VirtualMachine        m_vm;
52   private boolean               m_vmAlive;
53   private EventRequestSpecList  m_eventRequestSpecList;
54   private Integer               m_procID;
55   private DebuggeeSIO           m_sio;
56   private EventHandler          m_eventHandler;
57   private ObjectStore           m_objectStore;
58   private boolean               m_useGUI;
59   private GUI                   m_gui;
60
61   /**
62    * This map stores the event requests that are NOT specs. storing
63    * it here allows the user to cancel them easily: they just specify the
64    * id, that gets reverse-looked up here, uniquely identifying the actual
65    * request.
66    * <p>
67    * Of course, the id is sent back to the user when the actual command is
68    * responded to, so that the handle is available to jde in the first
69    * place
70    */
71   protected Map                 m_identifiableEventRequests;
72
73   /**
74    * Creates a new <code>Debugger</code> instance. Before the
75    * instance can be used, the following things must happen:
76    * <ul>
77    *  <li>The VM connection must be set up. This is done through
78    * either {@link #launchVM launchVM()}, {@link #attachVMShmem
79    * attachVMShmem()}, {@link #attachVMSocket attachVMSocket()},
80    * {@link #listenShmem listenShmem()}, or {@link
81    * #listenSocket listenSocket()}.</li>
82    *  <li>The Debugger instance must have been started. This is done
83    * through the {@link #start start} method.</li>
84    * </ul>
85    *
86    * @param procID an <code>Integer</code> value identifying this
87    * process in the communication with Emacs.
88    */
89   public Debugger(Integer procID, boolean useGUI) {
90     JDE.debug(EVENTS, "creating debugger with id: " + procID);
91
92     m_procID                    = procID;
93     m_identifiableEventRequests = new HashMap();
94     m_handler                   = new ProcessCommandHandler(this);
95     m_eventRequestSpecList      = new EventRequestSpecList(this);
96     m_sio                       = new DebuggeeSIO(this);
97     m_eventHandler              = new EventHandler(this);
98     m_objectStore               = new ObjectStore(this);
99     m_vmAlive                   = false;
100     m_useGUI                    = useGUI;
101     if (m_useGUI)
102     m_gui                       = new GUI(this);
103     else
104       m_gui                     = null;
105   }
106
107   /**
108    * Starts up the threads that make the debugger go. Also makes sure that
109    * a ClassPrepareRequest is sent to the VM, so that it's possible to resolve
110    * breakpoints, etc.
111    *
112    * @exception JDEException if an error occurs
113    */
114   public void start() throws JDEException {
115     JDE.debug(EVENTS, "starting debugger: " + m_procID);
116
117     if (!m_vmAlive) {
118       throw new JDEException("INTERNAL ERROR: attempted to start debugger " + m_procID + " without a VM");
119     }
120
121     m_handler.start();
122     m_eventHandler.start();
123
124
125     // we need to raise all class prepare events to
126     // make sure we resolve the corresponding specs.
127     ClassPrepareRequest cprequest =
128       m_vm.eventRequestManager().createClassPrepareRequest();
129
130     // this (hack?) is used to identify if the user itself specified
131     // a class prepare request, or the event was raised because of
132     // this request.
133     cprequest.putProperty("default", "default");
134     cprequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
135
136     // XXX we don't want to get ClassPrepareEvents for the standard
137     // java classes, since there's no point in debugging those. At least
138     // I don't think there is. / Petter
139     cprequest.addClassExclusionFilter("java.*");
140     cprequest.addClassExclusionFilter("javax.*");
141     cprequest.addClassExclusionFilter("sun.*");
142
143     cprequest.enable();
144   }
145
146   /**
147    * Tells the debugger to stop executing, meaning that the VM is shut down.
148    * The actual execution of the threads is not stopped until the shutdown()
149    * method is called, which is only done when a VM disconnect event is sent
150    * from the VM.
151    *
152    * @see EventHandler#vmDisconnectEvent
153    */
154   public void stopExecution() {
155     VMUtil.shutdown(m_vm);
156     m_vmAlive = false;
157   }
158
159   /**
160    * Shuts the debugger down and deregisters it from the SessionManager.
161    *
162    * @exception JDEException if an error occurs
163    * @see SessionManager#deregisterDebugger
164    */
165   public void shutdown() throws JDEException {
166     JDE.debug(EVENTS, "debugger " + m_procID + " shutting down");
167
168     if (m_vmAlive) {
169       // the VM will not have been properly shut down if the application runs
170       // to the end and the VM disconnects because of that. On the other hand,
171       // if the Emacs side issues a Finish command, the VM will be shut down,
172       // which is the reason for the m_vmAlive variable.
173       VMUtil.shutdown(m_vm);
174       m_vmAlive = false;
175     }
176
177     if (m_handler != null) {
178       m_handler.requestStop();
179     }
180
181     if (m_eventHandler != null) {
182       m_eventHandler.shutdown();
183     }
184
185     if (m_sio != null) {
186       m_sio.shutdown();
187     }
188
189     if (null != m_gui) {
190       m_gui.shutdown();
191     }
192     // XXX - stop the rest of the stuff as well, when that has been added.
193
194     // Invalidate this object
195     m_handler      = null;
196     m_eventHandler = null;
197     m_vm           = null;
198     m_sio          = null;
199     m_gui          = null;
200
201     // this debugger should no longer be available to the session manager
202     SessionManager.deregisterDebugger(this);
203   }
204
205
206
207   /**
208    * Launches a virtual machine for the process to be debugged, and
209    * sets up the standard in/out/err streams for the process.
210    *
211    * @param cmdID an <code>Integer</code> value used for setting up
212    * SIO streams.
213    * @param args a <code>List</code> value
214    * @exception JDEException if an error occurs
215    */
216   public void launchVM(Integer cmdID, List args) throws JDEException {
217
218     // this is the connector that launches a debuggee vm
219     String connectSpec = "com.sun.jdi.CommandLineLaunch";
220
221     // check if this kind of connector is, indeed,
222     // available. if not, throw an exception.
223     LaunchingConnector connector = (LaunchingConnector) VMUtil.getConnector(connectSpec);
224     if (connector == null)
225       throw new JDEException("No such connector is available: "+connectSpec);
226
227
228     // first set up the argument map. a table that describes the
229     // different keys should be in the public jpda documentation.
230     Map argumentMap = connector.defaultArguments();
231
232     Connector.Argument mainArg =
233       (Connector.Argument)argumentMap.get("main");
234
235     // compose the command line
236     String commandLine = "";
237     String quote =((Connector.Argument)argumentMap.get("quote")).value();
238
239     // check if there are special launch options we need to process
240     if (args.size() == 0)
241       throw new JDEException("Insufficient arguments");
242
243     // XXX - not sure if it's a brilliant idea to hard-code the
244     // java executable name here, but that's the way it was
245     // done. Anyway, if it becomes a problem, it is now flagged. / Petter
246     String executable = "java";
247
248     // be careful with the loop here....
249     while ((args.size() >0)
250            && args.get(0).toString().startsWith("-")) {
251       String origArg = args.remove(0).toString();
252       String arg = origArg.toLowerCase();
253       if (arg.equals("-vmexec")) {
254         if (args.size() == 0)
255           throw new JDEException("Missing argument to 'use_executable'");
256         executable = args.remove(0).toString();
257         Connector.Argument vmexecArg =
258           (Connector.Argument)argumentMap.get("vmexec");
259         vmexecArg.setValue(executable);
260       }
261       else if (arg.equals("-home")) {
262         if (args.size() == 0)
263           throw new JDEException("Missing argument to 'home'");
264         String home = args.remove(0).toString();
265         Connector.Argument homeArg = (Connector.Argument) argumentMap.get("home");
266         homeArg.setValue(home);
267         continue;
268       }
269       else {
270         args.add(0, origArg);
271         break;
272       }
273     }
274
275     if (args.size() == 0)
276       throw new JDEException("Missing arguments: no class specified?");
277
278     // take care of spaces too! so quote everything.
279     Iterator iterator = args.iterator();
280     while(iterator.hasNext()) {
281       // commandLine += quote + iterator.next() + quote + " ";
282       String arg = (String)iterator.next();
283       if (arg.equalsIgnoreCase("-classic")) {
284         Connector.Argument optionsArg =
285           (Connector.Argument)argumentMap.get("options");
286         String options = optionsArg.value();
287         options = "-classic" + " " + options;
288         optionsArg.setValue(options);
289         JDE.signal(m_procID, MESSAGE, "VM options: '" + options + "'", QUOTE);
290       }
291       else
292         commandLine += quote + arg + quote + " ";
293     }
294     mainArg.setValue(commandLine);
295
296     m_vm = null;
297
298     try {
299       m_vm = connector.launch(argumentMap);
300       JDE.signal(m_procID, MESSAGE, "Launched VM " + m_vm.description(), QUOTE);
301       m_vmAlive = true;
302
303       // If we're launching the application, the standard in/out/err needs to be connected
304       // to Emacs.
305       m_sio.initConnect(cmdID);
306     } catch (IOException ex) {
307       JDE.debug(EXCEPTION, "Exception launching VM: " + ex.toString());
308       throw new JDEException("Unable to launch: " + ex.toString().replace('\\','/'));
309     } catch (IllegalConnectorArgumentsException ex) {
310       throw new JDEException("Invalid or inconsistent connector arguments for connector '"+connector+"'");
311     } catch (VMStartException ex) {
312       throw new JDEException(ex.getMessage().toString().replace('\\','/'));
313     }
314   }
315
316
317   /**
318    * Attaches to a currently running VM through shared memory. The
319    * JPDA framework currently only supports that on Windows systems.
320    *
321    * @param args a <code>List</code> value
322    * @exception JDEException if an error occurs
323    */
324   public void attachVMShmem(List args) throws JDEException {
325
326     if (args.size() < 1)
327       throw new JDEException("Missing name");
328
329     // the attaching connector...
330     String connectSpec = null;
331     connectSpec = "com.sun.jdi.SharedMemoryAttach";
332
333     AttachingConnector connector = (AttachingConnector) VMUtil.getConnector(connectSpec);
334     if (connector  == null)
335       throw new JDEException("No such connector is available: "+connectSpec);
336
337     try {
338       Map argumentMap = connector.defaultArguments();
339
340       Connector.Argument nameArg =
341         (Connector.Argument) argumentMap.get("name");
342       nameArg.setValue(args.remove(0).toString());
343
344       m_vm      = connector.attach(argumentMap);
345       m_vmAlive = true;
346
347       JDE.signal(m_procID, MESSAGE, "Attached VM (shmem) " + m_vm.description(), QUOTE);
348
349     } catch (IOException ex) {
350       JDE.debug(EXCEPTION, ex.toString());
351       throw new JDEException("Error attempting to attach to process via shared memory.");
352     } catch (IllegalConnectorArgumentsException ex) {
353       throw new JDEException("Illegal connector arguments for connector '"+connector);
354     }
355   }
356
357   /**
358    * Attaches to a currently running VM through socket
359    * communication. Works for all platforms, but is slower than
360    * shared memory.
361    *
362    * @param args a <code>List</code> value
363    * @exception JDEException if an error occurs
364    */
365   public void attachVMSocket(List args) throws JDEException {
366
367     if (args.size() < 1)
368       throw new JDEException("Missing arguments: specify at least the port");
369
370     // the attaching connector...
371     String connectSpec = null;
372     connectSpec = "com.sun.jdi.SocketAttach";
373
374     AttachingConnector connector = (AttachingConnector) VMUtil.getConnector(connectSpec);
375     if (connector == null)
376       throw new JDEException("No such connector is available: " + connectSpec);
377
378     try {
379       Map argumentMap = connector.defaultArguments();
380
381       while ((args.size() > 0) && args.get(0).toString().startsWith("-")) {
382         String arg = args.remove(0).toString().toLowerCase();
383         if (arg.equals("-host")) {
384           if (args.size() == 0)
385             throw new JDEException("Missing argument to 'host'");
386           String host = args.remove(0).toString();
387           Connector.Argument hostArg =
388             (Connector.Argument)argumentMap.get("hostname");
389           hostArg.setValue(host);
390         } else if (arg.equals("-port")) {
391           if (args.size() == 0)
392             throw new JDEException("Missing argument to 'port'");
393           String port = args.remove(0).toString();
394           Connector.Argument portArg =
395             (Connector.Argument)argumentMap.get("port");
396           portArg.setValue(port);
397         } else {
398           args.add(0, arg);
399           break;
400         }
401       }
402
403       m_vm      = connector.attach(argumentMap);
404       m_vmAlive = true;
405
406       JDE.signal(m_procID, MESSAGE, "Attached VM (socket) " + m_vm.description(), QUOTE);
407
408     } catch (IOException ex) {
409       JDE.debug(EXCEPTION, ex.toString());
410       throw new JDEException("I/O error occurred while attempting to attach process.");
411     } catch (IllegalConnectorArgumentsException ex) {
412       throw new JDEException("Illegal connector arguments for connector '"+connector);
413     }
414
415   }
416
417   /**
418    * Starts a thread that waits for a VM to be launched and connect
419    * to a given address using shared memory. Executing this in a
420    * separate thread means that the command handler can go on
421    * waiting for new commands, without freezing up. The new thread
422    * dies as soon as the VM connects.
423    *
424    * @param address a <code>String</code> value
425    * @exception JDEException if an error occurs
426    */
427   public void listenShmem(final String address) throws JDEException {
428     String connectSpec = "com.sun.jdi.SharedMemoryListen";
429
430     final ListeningConnector connector   = (ListeningConnector) VMUtil.getConnector(connectSpec);
431     final Debugger           thisAsLocal = this;
432
433     if (connector == null)
434       throw new JDEException("No such connector is available: "+connectSpec);
435
436     Thread thread = new Thread("Listen on shared memory channel.") {
437
438         public void run()  {
439
440           try {
441             Map argumentMap = connector.defaultArguments();
442
443             Connector.Argument nameArg =
444               (Connector.Argument)argumentMap.get("name");
445             nameArg.setValue(address);
446
447             connector.startListening(argumentMap);
448             m_vm = connector.accept(argumentMap);
449             connector.stopListening(argumentMap);
450
451             JDE.signal(m_procID, MESSAGE, "Attached VM (shmem) " + m_vm.description(), QUOTE);
452             m_vmAlive = true;
453             thisAsLocal.start();
454           } catch (IOException ex) {
455             JDE.debug(EXCEPTION, ex.toString());
456             JDE.signal(m_procID, MESSAGE,
457                        "I/O error occurred while listening at shared memory address:"
458                        + address,
459                        QUOTE);
460             try {
461               SessionManager.deregisterDebugger(thisAsLocal);
462             }
463             catch (JDEException e) { /* FALLTHROUGH */ }
464           } catch (IllegalConnectorArgumentsException ex) {
465             JDE.debug(EXCEPTION, ex.toString());
466             JDE.signal(m_procID, MESSAGE,
467                        "Illegal argument error occurred while listening " +
468                        "at shared memory address: " + address,
469                        QUOTE);
470             try {
471               SessionManager.deregisterDebugger(thisAsLocal);
472             }
473             catch (JDEException e) { /* FALLTHROUGH */ }
474           } catch (JDEException ex) {
475             JDE.debug(EXCEPTION, ex.toString());
476             JDE.signal(m_procID, MESSAGE,
477                        "Error starting up debugger: " + ex,
478                        QUOTE);
479             try {
480               SessionManager.deregisterDebugger(thisAsLocal);
481             }
482             catch (JDEException e) { /* FALLTHROUGH */ }
483           }
484         }
485       };
486
487     JDE.signal(m_procID, MESSAGE,
488                "Listening at shared memory address: " + address,
489                QUOTE);
490     thread.start();
491
492   }
493
494   /**
495    * Starts a thread that waits for a VM to be launched and connect
496    * to a given address using socket communication. Executing this in a
497    * separate thread means that the command handler can go on
498    * waiting for new commands, without freezing up. The new thread
499    * dies as soon as the VM connects.
500    *
501    * @param address a <code>String</code> value
502    * @exception JDEException if an error occurs
503    */
504   public void listenSocket(final String address) throws JDEException {
505     String connectSpec = "com.sun.jdi.SocketListen";
506
507     final ListeningConnector connector   = (ListeningConnector) VMUtil.getConnector(connectSpec);
508     final Debugger           thisAsLocal = this;
509
510     if (connector == null)
511       throw new JDEException("No such connector is available: "+connectSpec);
512
513     Thread thread = new Thread("Listen on socket.") {
514
515         public void run()  {
516           try {
517             Map argumentMap = connector.defaultArguments();
518
519             Connector.Argument portArg =
520               (Connector.Argument)argumentMap.get("port");
521             portArg.setValue(address);
522
523             connector.startListening(argumentMap);
524             m_vm = connector.accept(argumentMap);
525             connector.stopListening(argumentMap);
526
527             JDE.signal(m_procID, MESSAGE,
528                        "Attached VM (socket) " + m_vm.description(),
529                        QUOTE);
530             m_vmAlive = true;
531             thisAsLocal.start();
532           } catch (IOException ex) {
533             JDE.debug(EXCEPTION, ex.toString());
534             JDE.signal(m_procID, MESSAGE,
535                        "Error occurred when listening on socket: " + ex,
536                        QUOTE);
537             try {
538               SessionManager.deregisterDebugger(thisAsLocal);
539             }
540             catch (JDEException e) { /* FALLTHROUGH */ }
541           } catch(IllegalConnectorArgumentsException ex) {
542             JDE.signal(m_procID, MESSAGE,
543                        "Illegal connector arguments for connector '"+connector + " " + ex,
544                        QUOTE);
545             try {
546               SessionManager.deregisterDebugger(thisAsLocal);
547             }
548             catch (JDEException e) { /* FALLTHROUGH */ }
549           } catch(JDEException ex) {
550             JDE.signal(m_procID, MESSAGE,
551                        "Error starting up debugger: " + ex,
552                        QUOTE);
553             try {
554               SessionManager.deregisterDebugger(thisAsLocal);
555             }
556             catch (JDEException e) { /* FALLTHROUGH */ }
557           }
558         }
559       };
560
561     JDE.signal(m_procID, MESSAGE,
562                "Listening at socket address: " + address, QUOTE);
563     thread.start();
564   }
565
566
567   public EventRequestSpecList getEventRequestSpecList() {
568     return m_eventRequestSpecList;
569   }
570
571   public CommandHandler getCommandHandler() {
572     return m_handler;
573   }
574
575   public Integer getProcID() {
576     return m_procID;
577   }
578
579   public ObjectStore getStore() {
580     return m_objectStore;
581   }
582
583   public void signalCommandResult(Integer cmdID, String message, boolean success) {
584     JDE.commandResult(cmdID, message, success, NOQUOTE);
585   }
586
587   public void signalCommandResult(Integer cmdID, String message, boolean success, boolean quote) {
588     JDE.commandResult(cmdID, message, success, quote);
589   }
590
591   public VirtualMachine getVM() {
592     return m_vm;
593   }
594
595   public GUI getGUI() {
596     return m_gui;
597   }
598
599   /**
600    * Returns true if this is a valid debugger. A debugger is valid if
601    * the start() method has been called, but not the shutdown()
602    * method. XXX - actually not correct at the moment, but it
603    * doesn't matter. The method returns true from the moment the
604    * Debugger instance has been created until the shutdown() method
605    * is called.
606    *
607    * @return a <code>boolean</code> value
608    */
609   public boolean isValid() {
610     // Am using the command handler to indicate whether this is a live
611     // debugger or not.
612     return m_handler != null;
613   }
614
615   /**
616    * Returns the thread corresponding to a given name, or null if
617    * there is no such thread.
618    *
619    * @param name
620    */
621   public ThreadReference getThreadReference(String name) {
622
623     List     list = m_vm.allThreads();
624     Iterator it   = list.iterator();
625
626     ThreadReference thread;
627     while (it.hasNext()) {
628       thread = (ThreadReference)it.next();
629       if (thread.name().equals(name)) return thread;
630     }
631
632     return null;
633   }
634
635
636   /**
637    * Adds an event request to the identifiable events, for future
638    * reference. Also enables the event.
639    *
640    * @return an identifier for the request
641    */
642   public Long addIdentifiableRequest(EventRequest e) {
643     Long id = SessionManager.generateObjectID();
644     synchronized (m_identifiableEventRequests) {
645       m_identifiableEventRequests.put(id, e);
646     }
647
648     e.enable();
649
650     return id;
651   }
652
653   /**
654    * Removes an event request. Also disables/deletes from the vm.
655    */
656   public void deleteIdentifiableRequest(Long id) throws JDEException {
657
658     EventRequestManager erm = getVM().eventRequestManager();
659
660     synchronized (m_identifiableEventRequests) {
661       if (!m_identifiableEventRequests.containsKey(id)) {
662         throw new JDEException("Invalid request ID");
663       } else {
664         Object e = m_identifiableEventRequests.remove(id);
665         if (e == null) {
666           throw new JDEException("No such event request");
667         } else if (e instanceof EventRequest) {
668           ((EventRequest)e).disable();
669           erm.deleteEventRequest((EventRequest)e);
670         } else {
671           throw new JDEException("INTERNAL ERROR: Not an event request : " + e.toString());
672         }
673       }
674     }
675   }
676
677     /** Add an EventSetListener.  If the listener is already in the
678      * list, nothing is done.<p>
679      *
680      * This is handled by the eventHandler, but there is no public
681      * access to that
682      */
683   public void addEventSetListener(EventSetListener listener) {
684       m_eventHandler.addEventSetListener(listener);
685     }
686
687     /** Remove an EventSetListener.  If the listener is already in the
688      * list, nothing is done */
689   public void removeEventSetListener(EventSetListener listener) {
690       m_eventHandler.removeEventSetListener(listener);
691     }
692
693     /** Add an CommandListener.  If the listener is already in the
694      * list, nothing is done.<p>
695      *
696      */
697   public void addCommandListener(CommandListener listener) {
698       m_handler.addCommandListener(listener);
699     }
700
701     /** Remove an CommandListener.  If the listener is already in the
702      * list, nothing is done */
703   public void removeCommandListener(CommandListener listener) {
704       m_handler.removeCommandListener(listener);
705     }
706
707 }// Debugger
708
709
710 /*
711  * $Log: Debugger.java,v $
712  * Revision 1.3  2003/04/29 16:51:56  troy
713  * Initial version of GUI.  Includes display of local variables.
714  *
715  * Revision 1.2  2003/01/15 05:50:51  paulk
716  * Remove CRs.
717  *
718  * Revision 1.1  2003/01/08 07:16:45  paulk
719  * Initial revision.
720  *
721  */
722
723 // End of Debugger.java