5c5b26b2637d6d81da564abe6fb70ccf04009bc7
[phpeclipse.git] / net.sourceforge.phpeclipse.debug.core / src / net / sourceforge / phpdt / internal / debug / core / PHPDBGProxy.java
1 /***********************************************************************************************************************************
2  * Copyright (c) 2000, 2002 IBM Corp. and others. All rights reserved. This program and the accompanying materials are made
3  * available under the terms of the Common Public License v1.0 which accompanies this distribution, and is available at
4  * http://www.eclipse.org/legal/cpl-v10.html
5  *
6  * Contributors: IBM Corporation - Initial implementation Vicente Fernando - www.alfersoft.com.ar Christian Perkonig - remote debug
7  **********************************************************************************************************************************/
8 package net.sourceforge.phpdt.internal.debug.core;
9
10 import java.io.BufferedReader;
11 import java.io.IOException;
12 import java.io.InputStreamReader;
13 import java.io.OutputStream;
14 import java.net.ServerSocket;
15 import java.net.Socket;
16 import java.net.SocketTimeoutException;
17 import java.util.Map;
18 import java.util.Vector;
19
20 import net.sourceforge.phpdt.internal.debug.core.breakpoints.PHPLineBreakpoint;
21 import net.sourceforge.phpdt.internal.debug.core.model.PHPDebugTarget;
22 import net.sourceforge.phpdt.internal.debug.core.model.PHPStackFrame;
23 import net.sourceforge.phpdt.internal.debug.core.model.PHPThread;
24 import net.sourceforge.phpdt.internal.debug.core.model.PHPVariable;
25 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
26
27 import org.eclipse.core.runtime.CoreException;
28 import org.eclipse.core.runtime.IPath;
29 import org.eclipse.core.runtime.Path;
30 import org.eclipse.debug.core.DebugException;
31 import org.eclipse.debug.core.DebugPlugin;
32 import org.eclipse.debug.core.model.IBreakpoint;
33
34 public class PHPDBGProxy {
35
36         private ServerSocket            server          = null;
37         private BufferedReader          reader          = null;
38         private PHPDBGInterface         DBGInt          = null;         // The DBG interface which is linked with the proxy
39         private PHPDebugTarget          debugTarget = null;
40         private PHPDBGProxy             thisProxy       = null;
41         private PHPLoop                         phpLoop;
42         private PHPThread                       PHPMainThread;
43         private Socket                          socket;
44         private int                             port;
45         private boolean                         remote;
46         private boolean                         pathtranslation;
47         private Map                             pathmap;
48         private IPath                           remoteSourcePath;
49
50         /**
51          */
52         public PHPDBGProxy () {
53           thisProxy = this;
54         }
55
56         /**
57          * @param remote
58          * @param remoteSourcePath
59          * @param pathTranslate
60          * @param paths
61          */
62         public PHPDBGProxy (boolean remote, String remoteSourcePath, boolean pathTranslate, Map paths) {
63                 thisProxy             = this;
64                 this.remote               = remote;
65                 this.remoteSourcePath = new Path (remoteSourcePath);
66                 this.pathmap          = paths;
67                 this.pathtranslation  = pathTranslate;
68         }
69
70         /**
71          *
72          */
73         public void start () {
74                 createServerSocket ();                                      // Create a server socket for communicatio with DBG
75
76                 this.startPHPLoop ();                                                                           //
77         }
78
79         /**
80          *
81          */
82         public void stop () {
83                 phpLoop.setShouldStop ();                                   // Notify the thread's 'run loop' to stop
84
85                 if (DBGInt != null) {                                       // If we have a DBG interface linked with this proxy
86                 DBGInt.setShouldStop ();                                //  Notify the DBG interface to stop the waiting for response
87                 }
88
89                 if (!remote) {                                              // If it's not a remote proxy session
90                 try {
91                         getDebugTarget ().getProcess ().terminate ();       //
92                 } catch (DebugException e) {
93                         e.printStackTrace ();
94                 }
95                 }
96
97                 phpLoop.notifyWait ();
98         }
99         
100         public void setTerminated () {
101                 try {
102                         PHPMainThread.terminate ();
103                 }
104                 catch (DebugException e) {
105                 }
106         }
107
108         /**
109          * TODO Is this method called from anywhere?
110          *
111          * Returns a already created server socket, or
112          * creates a server socket if none exists, and
113          * returns the newly created one.
114          *
115          * @return A server socket
116          */
117         protected ServerSocket getServerSocket () throws IOException {
118                 if (server == null) {                                                                           // Do we have already a server socket
119                 createServerSocket ();                                                                  //  No, then create one
120                 }
121
122                 return server;                                                                                          // Return the server socket
123         }
124
125         /**
126          * Find a free unused port between 10001 and 10101 if the current debug session
127          * is for remote debugging, and a unused port 7869 if it is used as non remote debugging.
128          * 
129          * For remote debugging the used port is submitted with the URL.
130          * E.g. http://localhost/index.php?DBGSESSID=1@localhost:10001
131          * For non remote debugging (if PHPeclipse used e.g. php cli directly) no port
132          * can be submitted by parameter, and only the default port (7869) can be used.
133          * 
134          * @note: The free dbg version doesn't allow to set the appropriate port within php.ini! 
135          * 
136          * 
137          */
138         protected void createServerSocket () {
139                 if (this.remote) {
140                         port = SocketUtil.findUnusedLocalPort ("localhost", 10001, 10101);      // Get the first free port in the range from 10001 to 10101
141                 }
142                 else {
143                         port = SocketUtil.findUnusedLocalPort ("localhost", 7869, 7869);        // Get the first free port in the range from 7869 to 7869
144                 }
145                 
146                 if (port == -1) {                                                   // Did we get a free port?
147                 PHPDebugCorePlugin.log (5, "Cannot find free port!!!!");        //  No, output a error message
148
149                 return;                                                         //  And return
150                 }
151                 try {
152                 if (server == null) {                                           // If there is no server socket yet
153                         server = new ServerSocket (port);                           //  create a server socket for the free port
154                         //System.out.println("ServerSocket on port: " + port);
155                 }
156                 } catch (IOException e) {
157                 PHPDebugCorePlugin.log (e);
158                 stop ();
159                 }
160         }
161
162         /**
163          *
164          */
165         public Socket getSocket () throws IOException {
166                 return socket;                                                          // Return the socket
167         }
168
169         /**
170          * Set the DBG interface which is linked to this proxy
171          *
172          * @paran DBGInt The DGB interface which is linked with this proxy
173          */
174         protected void setDBGInterface (PHPDBGInterface DBGInt) {
175                 this.DBGInt = DBGInt;
176         }
177
178         /**
179          * Get the DBG interface which is linked to this proxy
180          *
181          * @paran DBGInt The DGB interface which is linked with this proxy
182          */
183         public PHPDBGInterface getDBGInterface () {
184                 return DBGInt;
185         }
186         
187         /**
188          * Give back a buffered input stream for the socket which is
189          * linked with this proxy
190          */
191         public BufferedReader getReader () throws IOException {
192                 if (reader == null) {                                               // Do we already have a buffered input stream
193                 reader = new BufferedReader (new InputStreamReader (this.getSocket ().getInputStream (),
194                                                                         "ISO8859_1"));
195                 }
196
197           return reader;                                                      // Return the buffered input stream
198         }
199
200         /**
201          *
202          */
203         public BufferedReader getReader (Socket socket) throws IOException {
204                 if (socket != null) {                                                                                           // Is a socket provided
205                 return new BufferedReader (new InputStreamReader (socket.getInputStream (),
206                                                                                                   "ISO8859_1"));  // Then create a buffered input stream
207                 }
208                 else {
209                 return null;                                                      // Without a socket we can't create a input stream
210                 }
211         }
212
213         /**
214          *
215          * @return The output stream for this proxy's socket
216          */
217         public OutputStream getOutputStream () throws IOException {
218                 return this.getSocket ().getOutputStream ();
219         }
220
221         /**
222          *
223          */
224         protected void setBreakPoints () throws IOException, CoreException {
225                 IBreakpoint[] breakpoints = DebugPlugin.getDefault ().getBreakpointManager ().getBreakpoints ();
226
227                 for (int i = 0; i < breakpoints.length; i++) {
228                         if (breakpoints[i].isEnabled ()) {
229                                 addBreakpoint (breakpoints[i]);
230                         }
231                 }
232         }
233
234         /**
235          *
236          */
237         private String MapPath (PHPLineBreakpoint phpLBP) {
238                 IPath    filename;
239                 IPath    remotePath;
240                 IPath    newpath;
241                 IPath    localPath;
242                 String   local;
243
244                 if (remote) {
245                         filename = phpLBP.getMarker().getResource().getProjectRelativePath();
246                         filename = remoteSourcePath.append (filename);
247                 } else {
248                         filename = phpLBP.getMarker().getResource().getLocation();
249                 }
250
251                 String path = filename.toOSString();
252
253                 if ((pathmap != null) && remote) {
254                         java.util.Iterator i = pathmap.keySet().iterator();
255
256                         while (i.hasNext()) {
257                                 String k = (String) i.next();
258                                 if (path.startsWith(k)) {
259                                         path = pathmap.get(k) + path.substring(k.length());
260                                         break;
261                                 }
262                         }
263                 }
264
265                 if (remoteSourcePath.isEmpty ()) {
266                         if ((pathmap != null) && remote) {
267                                 java.util.Iterator iterator = pathmap.keySet().iterator();
268
269                                 while (iterator.hasNext ()) {
270                                         local      = (String) iterator.next ();                 // Get the local/client side path of the mapping
271                                         remotePath = new Path ((String) pathmap.get (local));   // Get the remote/server side path of the mapping
272                                         localPath  = new Path (local);                          // Get the remote/server side path of the mapping
273
274                                         if (localPath.isPrefixOf (filename)) {                  // Starts the remote/server side file path with the remote/server side mapping path
275                                                                                                                                                         // dann prefix abhängen und den remote path davorhägen
276                                                 newpath = filename.removeFirstSegments (localPath.matchingFirstSegments (filename));
277                                                 newpath = remotePath.append (newpath);
278                                                 path    = newpath.toString ();
279
280                                                 if (path.substring (0, 1).equals ("/")) {
281                                                         path = path.replace ('\\', '/');
282                                                 }
283                                                 else {
284                                                         path = path.replace ('/', '\\');
285                                                 }
286
287                                                 return path;
288                                         }
289                                 }
290                         }
291                 }
292                 else {
293                         if (pathtranslation && remote) {
294                                 if (remoteSourcePath.toString ().substring (0, 1).equals ("/")) {
295                                         path = path.replace ('\\', '/');
296                                 }
297                                 else {
298                                         path = path.replace ('/', '\\');
299                                 }
300                         }
301                 }
302
303                 return path;
304         }
305
306         /**
307          *
308          */
309         public void addBreakpoint (IBreakpoint breakpoint) {
310                 if (DBGInt == null) {
311                 return;
312                 }
313
314                 int bpNo = 0;
315
316                 try {
317                 PHPLineBreakpoint phpLBP;
318
319                 if (breakpoint.getModelIdentifier() == PHPDebugCorePlugin.getUniqueIdentifier()) {
320                         phpLBP = (PHPLineBreakpoint) breakpoint;
321
322                         //      bpNo= DBGInt.addBreakpoint(phpLBP.getMarker().getResource().getLocation().toOSString(), phpLBP.getLineNumber());
323                         if (phpLBP.isConditionEnabled ()) {
324                                 bpNo = DBGInt.addBreakpoint (MapPath(phpLBP),
325                                                                                          phpLBP.getLineNumber(), 
326                                                                  phpLBP.getHitCount(),
327                                                                  phpLBP.getCondition ());
328                         }
329                         else {
330                                 bpNo = DBGInt.addBreakpoint (MapPath(phpLBP),
331                                                                                          phpLBP.getLineNumber(), 
332                                                                  phpLBP.getHitCount(),
333                                                                  "");
334                         }
335                         
336                         phpLBP.setDBGBpNo(bpNo);
337                 }
338                 } catch (IOException e) {
339                     PHPDebugCorePlugin.log(e);
340                     stop();
341                 } catch (CoreException e) {
342                 PHPDebugCorePlugin.log(e);
343                 stop();
344                 }
345         }
346
347         /**
348          *
349          */
350         public void removeBreakpoint (IBreakpoint breakpoint) {
351                 if (DBGInt == null) {
352                 return;
353                 }
354
355                 try {
356                 PHPLineBreakpoint phpLBP;
357
358                 if (breakpoint.getModelIdentifier() == PHPDebugCorePlugin.getUniqueIdentifier ()) {
359                         phpLBP = (PHPLineBreakpoint) breakpoint;
360
361                         //      bpNo= DBGInt.addBreakpoint(filename.toOSString(), phpLBP.getLineNumber());
362
363                         DBGInt.removeBreakpoint(MapPath(phpLBP), phpLBP.getLineNumber(), phpLBP.getDBGBpNo());
364                 }
365                 } catch (IOException e) {
366                 PHPDebugCorePlugin.log (e);
367                 stop ();
368                 } catch (CoreException e) {
369                 PHPDebugCorePlugin.log (e);
370                 stop ();
371                 }
372         }
373
374         /**
375          *
376          */
377         public void phpLoopNotify () {
378                 phpLoop.notifyWait ();
379         }
380
381         /**
382          *
383          */
384         public void startPHPLoop () {
385                 phpLoop = new PHPLoop ();                                                                       // Create a DBG communication loop object
386
387                 phpLoop.start ();                                                                                       // And start the communication loop
388         }
389
390         /**
391          *
392          */
393         public void resume () {
394                 try {
395                 DBGInt.continueExecution();
396                 phpLoop.notifyWait();
397                 } catch (IOException e) {
398                 PHPeclipsePlugin.log("Debugging session ended.", e);
399                 stop();
400                 }
401         }
402
403         /**
404          *
405          */
406         public void pause () {
407                 try {
408                 if (null != DBGInt) {
409                                 DBGInt.pauseExecution();
410                         }
411                 else {
412                         // TODO Make sure the Suspend action is grayed out
413                         // when DBGInt is null
414                 }
415                 } catch (IOException e) {
416                 PHPDebugCorePlugin.log (e);
417                 stop ();
418                 }
419         }
420
421         /**
422          *
423          */
424         protected PHPDebugTarget getDebugTarget() {
425                 return debugTarget;
426         }
427
428         /**
429          * Is called by the DebuggerRunner
430          * 
431          * @param debugTarget
432          */
433         public void setDebugTarget (PHPDebugTarget debugTarget) {
434                 this.debugTarget = debugTarget;
435                 debugTarget.setPHPDBGProxy(this);
436         }
437
438         /**
439          * This method is called by a stackframe.
440          * It reads the variables from PHP via DBG
441          *
442          * @param frame The stackframe which wants the variables.
443          * @return      The list of variables for this stackframe.
444          */
445         public Vector readVariables (PHPStackFrame frame) {
446                 try {
447                 return DBGInt.getVariables (frame);                                             // Get the variables from DBG interface
448                 } catch (IOException ioex) {
449                 ioex.printStackTrace ();
450                 throw new RuntimeException (ioex.getMessage ());
451                 } catch (DebugException ex) {
452                     ex.printStackTrace ();
453                     throw new RuntimeException (ex.getMessage ());
454                 }
455         }
456
457         /**
458          *
459          * @param frame
460          * @param evalString
461          * @return
462          */
463         public PHPVariable[] eval (PHPStackFrame frame, String evalString) {
464                 try {
465                 return DBGInt.evalBlock (frame, evalString);
466                 //return DBGInt.getVariables(frame);
467                 } catch (IOException ioex) {
468                 ioex.printStackTrace();
469                 throw new RuntimeException(ioex.getMessage());
470                 } catch (DebugException ex) {
471                 ex.printStackTrace();
472                 throw new RuntimeException(ex.getMessage());
473                 }
474         }
475
476         public void readStepOverEnd (PHPStackFrame stackFrame) {
477                 try {
478                 DBGInt.stepOver();
479                 phpLoop.notifyWait();
480                 } catch (Exception e) {
481                 PHPDebugCorePlugin.log(e);
482                 }
483         }
484
485         public void readStepReturnEnd (PHPStackFrame stackFrame) {
486                 try {
487                 DBGInt.stepOut();
488                     phpLoop.notifyWait();
489                 } catch (Exception e) {
490                 PHPDebugCorePlugin.log(e);
491                 }
492         }
493
494         public void readStepIntoEnd (PHPStackFrame stackFrame) {
495                 try {
496                 DBGInt.stepInto();
497                 phpLoop.notifyWait();
498                 } catch (Exception e) {
499                 PHPDebugCorePlugin.log(e);
500                 }
501         }
502
503         /*
504          * public PHPStackFrame[] readFrames(PHPThread thread) { //try { //this.println("th " + thread.getId() + " ; f "); //return new
505          * FramesReader(getMultiReaderStrategy()).readFrames(thread); return null; //} catch (IOException e) { //
506          * PHPDebugCorePlugin.log(e); // return null; //}
507          *  }
508          */
509
510         public void closeSocket() throws IOException {
511                 if (socket != null) {
512                 socket.close();
513                 }
514         }
515
516         public void closeServerSocket() throws IOException {
517                 if (server != null) {
518                 server.close();
519                 }
520         }
521
522         public int getPort() {
523                 return port;
524         }
525
526         /**
527          *
528          *
529          */
530         class PHPLoop extends Thread {
531                 private boolean shouldStop;
532
533                 public PHPLoop () {
534                 shouldStop = false;
535                 this.setName ("PHPDebuggerLoop");
536                 }
537
538                 /**
539                  *
540                  */
541                 public synchronized void setShouldStop () {
542                         shouldStop = true;                                                                                      // The run loop should stop
543
544                         try {
545                                 // If the loop thread is blocked on the server socket,
546                                 // forcibly unblock it to avoid leaking the thread,
547                                 // the socket and the port
548                                 closeServerSocket ();
549                         } catch (IOException x) {
550                                 // Log this as a warning?
551                                 PHPDebugCorePlugin.log (x);
552                         }
553                 }
554
555                 /**
556                  *
557                  */
558                 public synchronized void notifyWait () {
559                 notify ();
560                 }
561
562                 /**
563                  *
564                  *
565                  */
566                 public void run () {
567                 try {
568                         int                     i;
569                                 int                     timeout;
570                         long                    interval        = 200;                                  // Wait 200 ms maximum for a DBG response
571                         boolean                 newconnect      = false;                                //
572                         Socket                  newSocket       = null;
573                         PHPStackFrame[] StackList;
574                         PHPDBGInterface newDBGInt;
575
576                         //                              synchronized (this) {
577                         //                                      wait();
578                         //                              }
579
580                         PHPMainThread = new PHPThread (getDebugTarget (), getPort ());
581                         PHPMainThread.setName ("Thread [main]");
582                         timeout       = 0;
583
584                         //                              while ((getDebugTarget() == null) && (timeout < 100)) {
585                         //                                      sleep(100);
586                         //                                      timeout++;
587                         //                              }
588                         // Be sure debug target is set
589                         //                              PHPMainThread.setDebugTarget(getDebugTarget());
590
591                                 getDebugTarget ().addThread (PHPMainThread);
592
593                         //System.out.println("Waiting for breakpoints.");
594
595                         while (!shouldStop) {                                                           // As long as nobody will stop us
596                                 newconnect = true;                              // The first time
597
598                                 try {
599                                         newSocket = server.accept();                            // Waits until DBG want to connect
600                                         //System.out.println("Accepted! : " + socket.toString());
601                                 } catch (SocketTimeoutException e) {
602                                         newconnect = false;                                                     // No one wants to connect (connection already done)
603                                 } catch (IOException e) {
604                                         PHPDebugCorePlugin.log(e);
605                                         return;
606                                 }
607
608                                 if (newconnect) {                                                               // Is it just after a new connection
609                                         if (DBGInt == null) {                                           // Do we have a DBG interface?
610                                         server.setSoTimeout(1);                                 // ???
611                                                 }
612
613                                         newDBGInt = new PHPDBGInterface (getReader (newSocket),                 // Create a new interface
614                                                                                                                  newSocket.getOutputStream (),
615                                                                                                                  thisProxy);
616                                         newDBGInt.waitResponse (1000);                          // Wait for the initial DBG response
617                                         newDBGInt.flushAllPackets ();               // Read and process the DBG response
618
619                                         // Check version and session ID
620                                         if ((DBGInt == null) ||                                 // If we have no interface
621                                                 (DBGInt.getSID () == newDBGInt.getSID ())) {// or the new session ID is different to the old one
622                                         DBGInt = newDBGInt;                                                     // Set the new interface as current one
623
624                                         try {
625                                                 closeSocket ();
626                                         }
627                                                         catch (IOException e) {
628                                                 PHPDebugCorePlugin.log (e);
629                                                 shouldStop = true;
630                                         }
631
632                                         socket = newSocket;
633                                         setBreakPoints ();
634                                         DBGInt.continueExecution ();                    // Notify DBG that PHP should continue
635                                         }
636                                                 else {
637                                         newDBGInt.continueExecution ();                         // Notify DBG that PHP should continue
638                                         newSocket.close ();
639                                         }
640                                 }
641
642                                 if (DBGInt.waitResponse (interval)) {                                           // Wait for a DBG response (200 ms)
643                                         DBGInt.flushAllPackets ();                                                              // If we got something, read and process it
644
645                                         if (DBGInt.BPUnderHit != 0) {                                                   // ???
646                                         StackList = DBGInt.getStackList ();                 // Get the stack list from DBGInterface
647
648                                             if (StackList.length > 0) {                         // If there is something in stack list
649                                                 for (i = 0; i < StackList.length; i++) {        // For all stack list
650                                                         StackList[i].setThread (PHPMainThread);     // Set the PHPTread for all PHPStackFrames
651
652                                                         if (DBGInt.getModByNo (StackList[i].getModNo ()).equals ("")) {
653                                                                 DBGInt.getSourceTree ();
654                                                         }
655
656                                                         StackList[i].setFile (DBGInt.getModByNo (StackList[i].getModNo ()));
657                                                 }
658
659                                                 PHPMainThread.setStackFrames (StackList);
660                                         }
661
662                                         PHPMainThread.suspend ();                             // Fire debug event
663
664                                             synchronized (this) {
665                                                 wait ();
666                                         }
667                                         }
668                                 }
669
670                                 if (remote) {
671                                         if (PHPMainThread.isTerminated ()) {
672                                         shouldStop = true;
673
674                                             break;                                                // Go for terminating the thread
675                                         }
676                                 } else {
677                                         if (PHPMainThread.isTerminated () ||
678                                                 getDebugTarget ().getProcess ().isTerminated ()) {
679                                         shouldStop = true;
680
681                                         break;                                                // Go for terminating the thread
682                                         }
683                                 }
684                         }
685                 } catch (Exception ex) {
686                         PHPDebugCorePlugin.log (ex);
687                         System.out.println (ex);
688                 } finally {
689                         try {
690                                 getDebugTarget ().terminate ();
691                                 closeSocket();
692                                 closeServerSocket ();
693                         } catch (IOException e) {
694                                 PHPDebugCorePlugin.log (e);
695
696                                 return;
697                         }
698
699                         //System.out.println("Socket loop finished.");
700                 }
701                 }
702         }
703 }