001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.php.dbgp;
042:
043: import java.io.IOException;
044: import java.net.ConnectException;
045: import java.net.ServerSocket;
046: import java.net.Socket;
047: import java.net.SocketException;
048: import java.net.SocketTimeoutException;
049: import java.text.MessageFormat;
050: import java.util.ArrayList;
051: import java.util.Collection;
052: import java.util.HashSet;
053: import java.util.LinkedList;
054: import java.util.List;
055: import java.util.Map;
056: import java.util.Set;
057: import java.util.WeakHashMap;
058: import java.util.concurrent.atomic.AtomicBoolean;
059: import java.util.logging.Level;
060: import java.util.logging.Logger;
061:
062: import org.netbeans.api.debugger.DebuggerEngine;
063: import org.netbeans.api.debugger.DebuggerManager;
064: import org.netbeans.api.debugger.Session;
065: import org.netbeans.modules.php.dbgp.api.SessionId;
066: import org.netbeans.modules.php.dbgp.api.StartActionProvider;
067: import org.netbeans.modules.php.dbgp.packets.StatusCommand;
068: import org.netbeans.spi.debugger.DebuggerEngineProvider;
069: import org.openide.DialogDisplayer;
070: import org.openide.NotifyDescriptor;
071: import org.openide.util.NbBundle;
072: import org.openide.util.RequestProcessor;
073:
074: /**
075: * @author ads
076: *
077: */
078: public class StartActionProviderImpl implements StartActionProvider {
079:
080: private static final String LOCALHOST = "localhost"; // NOI18N
081:
082: private static final int DEFAULT_PORT = 9000;
083:
084: private static final int PORT_RANGE = 100;
085:
086: private static final int TIMEOUT = 60000;
087:
088: private static final String PORT_OCCUPIED = "MSG_PortOccupied"; // NOI18N
089:
090: private StartActionProviderImpl() {
091: mySessions = new HashSet<DebugSession>();
092: myCurrentSessions = new WeakHashMap<Session, DebugSession>();
093: }
094:
095: public static StartActionProviderImpl getInstance() {
096: return INSTANCE;
097: }
098:
099: /* (non-Javadoc)
100: * @see org.netbeans.modules.php.dbgp.api.StartActionProvider#start()
101: */
102: public synchronized void start() {
103: if (myThread == null) {
104: /*
105: * TODO : port may be red from options, found free port via
106: * #findFreePort(), suggest to user via option about free port.
107: */
108: int port = DEFAULT_PORT;
109: myThread = new ServerThread(port);
110: RequestProcessor.getDefault().post(myThread);
111: } else {
112: /*
113: * Case stopping thread ( situation when debug session was
114: * started right after previous stopping ).
115: */
116: if (myThread.isStop()) {
117: /*
118: * Not accurate stop accepting from other thread.
119: * But otherwise one need to wait TIMEOUT seconds
120: * for stopping listening thread.
121: */
122: myThread.closeSocket();
123: myThread = null;
124: start();
125: }
126: }
127: }
128:
129: public synchronized DebugSession getSessionById(String id) {
130: for (DebugSession session : mySessions) {
131: SessionId sessId = session.getSessionId();
132: if (sessId == null) {
133: continue;
134: }
135: String curId = sessId.getId();
136: if (id.equals(curId)) {
137: return session;
138: }
139: }
140: return null;
141: }
142:
143: public synchronized DebugSession getCurrentSession(SessionId id) {
144: if (id == null) {
145: return null;
146: }
147: Session[] sessions = DebuggerManager.getDebuggerManager()
148: .getSessions();
149: for (Session session : sessions) {
150: SessionId sessId = (SessionId) session.lookupFirst(null,
151: SessionId.class);
152: if (id.equals(sessId)) {
153: return myCurrentSessions.get(session);
154: }
155: }
156: return null;
157: }
158:
159: public synchronized Collection<DebugSession> getSessions(
160: SessionId id) {
161: List<DebugSession> result = new LinkedList<DebugSession>();
162: for (DebugSession session : mySessions) {
163: if (id.equals(session.getSessionId())) {
164: result.add(session);
165: }
166: }
167: return result;
168: }
169:
170: public synchronized void stop(Session session) {
171: SessionId id = (SessionId) session.lookupFirst(null,
172: SessionId.class);
173: List<DebugSession> list = new ArrayList<DebugSession>(
174: mySessions);
175: for (DebugSession debSess : list) {
176: if (debSess.getSessionId() == id) {
177: debSess.setStop();
178: mySessions.remove(debSess);
179: }
180: }
181: Session[] sessions = DebuggerManager.getDebuggerManager()
182: .getSessions();
183: boolean last = true;
184: for (Session sess : sessions) {
185: if (sess.equals(session)) {
186: continue;
187: }
188: if (sess.lookupFirst(null, SessionId.class) != null) {
189: last = false;
190: }
191: }
192: if (last) {
193: myThread.setStop();
194: }
195:
196: stopEngines(session);
197: }
198:
199: public synchronized void setCurrentSession(Session session,
200: DebugSession debugSession) {
201: myCurrentSessions.put(session, debugSession);
202: }
203:
204: synchronized void attachDebugSession(Session session,
205: DebugSession debugSession) {
206: myCurrentSessions.put(session, debugSession);
207: debugSession.getBridge().hideAnnotations();
208: debugSession.getBridge().setSuspended(false);
209: debugSession.getBridge().getThreadsModel().update();
210: }
211:
212: synchronized void removeSession(DebugSession session) {
213: Session sess = (Session) session.getBridge().getEngine()
214: .lookupFirst(null, Session.class);
215: SessionId id = session.getSessionId();
216: mySessions.remove(session);
217: if (id != null) {
218: Collection<DebugSession> collection = getSessions(id);
219: if (collection.size() > 0) {
220: DebugSession debugSession = collection.iterator()
221: .next();
222: setCurrentSession(sess, debugSession);
223: StatusCommand command = new StatusCommand(debugSession
224: .getTransactionId());
225: debugSession.sendCommandLater(command);
226: }
227: }
228: }
229:
230: private synchronized void setupCurrentSession(DebugSession session) {
231: mySessions.add(session);
232: }
233:
234: private void stopEngines(Session session) {
235: String[] languages = session.getSupportedLanguages();
236: for (String language : languages) {
237: DebuggerEngine engine = session
238: .getEngineForLanguage(language);
239: ((DbgpEngineProvider) engine.lookupFirst(null,
240: DebuggerEngineProvider.class)).getDestructor()
241: .killEngine();
242: }
243:
244: }
245:
246: private int findFreePort() {
247: for (int port = DEFAULT_PORT; port < DEFAULT_PORT + PORT_RANGE; port++) {
248: Socket testClient = null;
249:
250: try {
251: /*
252: * Try to connect at localhost with spcified port and check if
253: * it is possible. If it is possible then port is listened
254: * by some server
255: */
256: testClient = new Socket(LOCALHOST, port);
257: } catch (ConnectException ce) {
258: // connection failed , so port is not listened by anyone, return it.
259: return port;
260: } catch (IOException e) {
261: // just ignore
262: } finally {
263: closeTestSocket(testClient);
264: }
265: }
266:
267: return -1;
268: }
269:
270: private void closeTestSocket(Socket testClient) {
271: if (testClient != null) {
272: // something listened on that socket. It's not useful for us.
273: try {
274: testClient.close();
275: } catch (IOException ioe) {
276: // We don't care here.
277: }
278: }
279: }
280:
281: private ServerThread myThread;
282:
283: private Set<DebugSession> mySessions;
284:
285: private Map<Session, DebugSession> myCurrentSessions;
286:
287: private static final StartActionProviderImpl INSTANCE = new StartActionProviderImpl();
288:
289: private class ServerThread implements Runnable {
290:
291: ServerThread(int port) {
292: myPort = port;
293: isStop = new AtomicBoolean(false);
294: }
295:
296: public void run() {
297: if (!createServer()) {
298: return;
299: }
300: while (!isStop.get()) {
301: Socket sessionSocket = null;
302:
303: try {
304: sessionSocket = myServer.accept();
305: } catch (SocketException e) {
306: /*
307: * This can be result of inaccurate closing socket from
308: * other thread. Just log with inforamtion severity.
309: */
310: logInforamtion(e);
311: } catch (SocketTimeoutException e) {
312: // skip this exception, it's normal
313: } catch (IOException e) {
314: log(e);
315: }
316: if (sessionSocket != null) {
317: DebugSession session = new DebugSession(
318: sessionSocket);
319: RequestProcessor.getDefault().post(session);
320: setupCurrentSession(session);
321: }
322: }
323:
324: closeSocket();
325: }
326:
327: private void log(Exception exception) {
328: Logger.getLogger(StartActionProviderImpl.class.getName())
329: .log(Level.FINE, null, exception);
330: }
331:
332: private void logInforamtion(SocketException e) {
333: Logger.getLogger(StartActionProviderImpl.class.getName())
334: .log(Level.FINE, null, e);
335: }
336:
337: private synchronized boolean createServer() {
338: try {
339: myServer = new ServerSocket(myPort);
340: myServer.setSoTimeout(TIMEOUT);
341: } catch (IOException e) {
342: String mesg = NbBundle.getMessage(
343: StartActionProviderImpl.class, PORT_OCCUPIED);
344: mesg = MessageFormat.format(mesg, myPort);
345: NotifyDescriptor descriptor = new NotifyDescriptor.Message(
346: mesg, NotifyDescriptor.INFORMATION_MESSAGE);
347: DialogDisplayer.getDefault().notify(descriptor);
348: log(e);
349: return false;
350: }
351: return true;
352: }
353:
354: private synchronized void closeSocket() {
355: if (myServer == null) {
356: return;
357: }
358: try {
359: if (!myServer.isClosed()) {
360: myServer.close();
361: }
362: } catch (IOException e) {
363: log(e);
364: }
365: }
366:
367: private void setStop() {
368: isStop.set(true);
369: }
370:
371: private boolean isStop() {
372: return isStop.get();
373: }
374:
375: private int myPort;
376:
377: private ServerSocket myServer;
378:
379: private AtomicBoolean isStop;
380:
381: }
382:
383: }
|