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:
042: /*
043: * Created on Apr 27, 2004
044: */
045: package org.netbeans.modules.mobility.project.ant;
046:
047: import java.io.BufferedReader;
048: import java.io.File;
049: import java.io.IOException;
050: import java.io.StringReader;
051: import java.util.Arrays;
052: import java.util.HashMap;
053: import java.util.HashSet;
054: import java.util.Map;
055: import org.apache.tools.ant.BuildException;
056: import org.apache.tools.ant.Project;
057: import org.apache.tools.ant.Task;
058: import org.netbeans.api.debugger.DebuggerManager;
059: import org.netbeans.api.debugger.jpda.DebuggerStartException;
060: import org.netbeans.api.debugger.jpda.JPDADebugger;
061: import org.netbeans.api.debugger.jpda.MethodBreakpoint;
062: import org.netbeans.api.debugger.jpda.event.JPDABreakpointEvent;
063: import org.netbeans.api.debugger.jpda.event.JPDABreakpointListener;
064: import org.netbeans.api.java.classpath.ClassPath;
065: import org.netbeans.api.java.classpath.ClassPath;
066: import org.netbeans.api.java.platform.JavaPlatform;
067: import org.netbeans.api.java.platform.JavaPlatform;
068: import org.netbeans.api.java.platform.JavaPlatformManager;
069: import org.openide.ErrorManager;
070: import org.openide.filesystems.FileObject;
071: import org.openide.filesystems.FileUtil;
072: import org.openide.util.NbBundle;
073: import java.beans.PropertyChangeEvent;
074: import java.net.URL;
075: import java.util.Iterator;
076: import java.util.Set;
077: import org.netbeans.api.debugger.DebuggerEngine;
078: import org.netbeans.api.debugger.DebuggerManagerAdapter;
079: import org.netbeans.api.java.queries.SourceForBinaryQuery;
080: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
081: import org.openide.filesystems.FileStateInvalidException;
082: import org.openide.util.RequestProcessor;
083:
084: /**
085: *
086: * Ant task to try connecting the JPDADebugger to the KDP
087: * for a period of time. This behaviour is required because the KDP takes some time to start up
088: * and emulators mostly support debugging in listening (server) mode.
089: *
090: * <p>Attributes:<ol>
091: * <li>delay - Optional. Amount of time in ms for which the task delay before trying to connect the debugger to the KDP for the first time. Default value: 3000ms
092: * <li>timeout - Optional. Amount of time in ms for which the task will keep trying to connect the debugger to the KDP. Default value: 20000ms
093: * <li>period - Optional. Amount of time in ms to wait between connection attempts. Default value: 1000ms
094: * </ol></p>
095: */
096: public class KdpDebugTask extends Task {
097:
098: private static final ErrorManager err = ErrorManager
099: .getDefault()
100: .getInstance(
101: "org.netbeans.modules.mobility.project.ant.KdpDebugTask"); //NOI18N
102:
103: private long startTime;
104: private long delay = 5000;
105: private long timeout = 45000;
106: private long period = 2000;
107: private String host = "localhost"; //NOI18N
108: private String address;
109:
110: /**
111: * Name which will represent this debugging session in debugger UI.
112: * If known in advance it should be name of the app which will be debugged.
113: */
114: private String name;
115:
116: /**
117: * Default transport is socket
118: */
119: private String transport = "dt_socket"; //NOI18N
120:
121: /**
122: * @param periodMS The periodMS to set.
123: */
124: public void setPeriod(long periodMS) {
125: this .period = periodMS;
126: }
127:
128: /**
129: * @param timeoutMS The timeoutMS to set.
130: */
131: public void setTimeout(long timeoutMS) {
132: this .timeout = timeoutMS;
133: }
134:
135: /**
136: * @param delay The delay to set.
137: */
138: public void setDelay(long delay) {
139: this .delay = delay;
140: }
141:
142: /**
143: * Host to connect to.
144: * By default, localhost.
145: */
146: public void setHost(String h) {
147: host = h;
148: }
149:
150: public void setAddress(String address) {
151: this .address = address;
152: }
153:
154: public void setTransport(String transport) {
155: this .transport = transport;
156: }
157:
158: public void setName(String name) {
159: this .name = name;
160: }
161:
162: public void execute() throws BuildException {
163:
164: Project project = getProject();
165: if (name == null)
166: name = project.getProperty("app.codename"); //NOI18N
167: if (name == null)
168: throw new BuildException(
169: NbBundle.getMessage(KdpDebugTask.class,
170: "ERR_ANT_Session_name_missing"),
171: getLocation()); //NOI18N
172: if (address == null)
173: throw new BuildException(NbBundle.getMessage(
174: KdpDebugTask.class, "ERR_ANT_Address_missing"),
175: getLocation()); //NOI18N
176: int intAddr = 0;
177: if (transport.equals("dt_socket"))
178: try {
179: intAddr = Integer.parseInt(address);
180: } catch (NumberFormatException nfe) {
181: throw new BuildException(NbBundle.getMessage(
182: KdpDebugTask.class, "ERR_ANT_Address_missing"),
183: getLocation()); //NOI18N
184: }
185:
186: //locate source root
187: String src = project.getProperty("src.dir"); //NOI18N
188: if (src == null)
189: throw new BuildException(NbBundle.getMessage(
190: KdpDebugTask.class, "ERR_ANT_source_root_missing"),
191: getLocation()); //NOI18N
192: File srcFile = new File(project.getBaseDir(), src);
193: if (!srcFile.isDirectory())
194: srcFile = new File(src);
195: final FileObject srcRoot = FileUtil.toFileObject(srcFile);
196: if (!srcFile.isDirectory() || srcRoot == null)
197: throw new BuildException(NbBundle.getMessage(
198: KdpDebugTask.class, "ERR_ANT_source_root_missing"),
199: getLocation()); //NOI18N
200:
201: //adjust sleep delay when too big jar to deploy
202: String dist = project.getProperty("dist.dir"); //NOI18N
203: String jar = project.getProperty("dist.jar"); //NOI18N
204: if (dist != null && jar != null) {
205: File jarFile = new File(project.getBaseDir(), dist + '/'
206: + jar);
207: if (jarFile.isFile() && jarFile.length() > 50000) {
208: long newTimeoutAdd = jarFile.length() - 50000;
209: log(NbBundle.getMessage(KdpDebugTask.class,
210: "ERR_ANT_Debugger_Add_time_out", Long
211: .toString(newTimeoutAdd / 1000)));
212: timeout = newTimeoutAdd + timeout;
213: }
214: }
215:
216: //get platform source path
217: ClassPath jdkSourcePath = JavaPlatformManager.getDefault()
218: .getDefaultPlatform().getSourceFolders();
219: String platform = project.getProperty("platform.active"); //NOI18N
220: if (platform != null)
221: for (JavaPlatform p : JavaPlatformManager.getDefault()
222: .getInstalledPlatforms()) {
223: if (platform.equals(p.getProperties().get(
224: "platform.ant.name"))) { //NOI18N
225: jdkSourcePath = p.getSourceFolders();
226: }
227: }
228:
229: //get source path
230: ClassPath sourcePath = ClassPath.getClassPath(srcRoot,
231: ClassPath.SOURCE);
232: ClassPath libPath = ClassPath.getClassPath(srcRoot,
233: ClassPath.COMPILE);
234: if (libPath != null) {
235: Set exist = new HashSet();
236: HashSet<FileObject> resources = new HashSet();
237: for (FileObject root : libPath.getRoots())
238: try {
239: URL url = root.getURL();
240: for (FileObject fos : SourceForBinaryQuery
241: .findSourceRoots(url).getRoots()) {
242: if (FileUtil.isArchiveFile(fos))
243: fos = FileUtil.getArchiveRoot(fos);
244: resources.add(fos);
245: }
246: } catch (IllegalArgumentException ex) {
247: ErrorManager.getDefault().notify(
248: ErrorManager.EXCEPTION, ex);
249: } catch (FileStateInvalidException fsie) {
250: ErrorManager.getDefault().notify(
251: ErrorManager.EXCEPTION, fsie);
252: }
253: if (!resources.isEmpty()) {
254: if (sourcePath != null)
255: resources.addAll(Arrays.asList(sourcePath
256: .getRoots()));
257: sourcePath = ClassPathSupport.createClassPath(resources
258: .toArray(new FileObject[resources.size()]));
259: }
260: }
261:
262: final Map<String, Object> properties = new HashMap<String, Object>();
263: properties.put("sourcepath", sourcePath); //NOI18N
264: properties.put("name", name); //NOI18N
265: properties.put("jdksources", jdkSourcePath); //NOI18N
266: //J2ME specific - disables STEP-INTO on smart-stepping, instead if no source is found does a STEP_OUT
267: properties.put("SS_ACTION_STEPOUT", Boolean.TRUE); //NOI18N
268: properties.put("J2ME_DEBUGGER", Boolean.TRUE); //NOI18N
269:
270: //sleep for defined delay
271: try {
272: Thread.sleep(this .delay);
273: } catch (InterruptedException e1) {
274: e1.printStackTrace();
275: }
276:
277: //start debugger
278: this .startTime = System.currentTimeMillis();
279: int attemptCount = 0;
280: boolean debuggerConnected = false;
281: do {
282: try {
283: attemptCount++;
284: log(NbBundle.getMessage(KdpDebugTask.class,
285: "LBL_ANT_DebuggerConnecting", Integer
286: .toString(attemptCount)));
287:
288: if (transport.equals("dt_socket"))
289: JPDADebugger.attach(host, intAddr,
290: new Object[] { properties }); //NOI18N
291: else
292: JPDADebugger.attach(address,
293: new Object[] { properties });
294:
295: if (host == null)
296: log(NbBundle.getMessage(KdpDebugTask.class,
297: "LBL_ANT_Debugger_attached", address)); //NOI18N
298: else
299: log(NbBundle.getMessage(KdpDebugTask.class,
300: "LBL_ANT_Debugger_attached_with_host",
301: host, address)); //NOI18N
302:
303: debuggerConnected = true;
304: } catch (DebuggerStartException e) {
305: try {
306: Thread.sleep(this .period += 1000);
307: } catch (InterruptedException ie) {
308: ie.printStackTrace();
309: }
310: }
311: } while (!debuggerConnected
312: && (System.currentTimeMillis() < this .startTime
313: + this .timeout));
314:
315: if (!debuggerConnected) {
316: int attemptTime = (int) ((System.currentTimeMillis() - this .startTime) / 1000);
317: log(NbBundle.getMessage(KdpDebugTask.class,
318: "ERR_ANT_Debugger_timed_out", Integer
319: .toString(attemptCount), Integer
320: .toString(attemptTime)));
321: throw new BuildException(NbBundle.getMessage(
322: KdpDebugTask.class, "ERR_ANT_Debugger_timed_out", //NOI18N
323: Integer.toString(attemptCount), Integer
324: .toString(attemptTime))); //NOI18N
325: }
326: }
327:
328: private static class BreakManager extends DebuggerManagerAdapter
329: implements JPDABreakpointListener {
330:
331: private Set<MethodBreakpoint> breakpoints;
332: private Set debuggers = new HashSet();
333:
334: BreakManager(Set<MethodBreakpoint> breakpoints) {
335: this .breakpoints = breakpoints;
336: DebuggerManager dm = DebuggerManager.getDebuggerManager();
337: dm.addDebuggerListener(
338: DebuggerManager.PROP_DEBUGGER_ENGINES, this );
339: for (MethodBreakpoint breakpoint : breakpoints) {
340: breakpoint.addJPDABreakpointListener(this );
341: dm.addBreakpoint(breakpoint);
342: }
343: }
344:
345: public void propertyChange(PropertyChangeEvent e) {
346: if (e.getPropertyName() == JPDADebugger.PROP_STATE) {
347: int state = ((Integer) e.getNewValue()).intValue();
348: if ((state == JPDADebugger.STATE_DISCONNECTED)
349: || (state == JPDADebugger.STATE_STOPPED))
350: breakpointReached(null);
351: }
352: }
353:
354: public void engineAdded(DebuggerEngine engine) {
355: JPDADebugger debugger = (JPDADebugger) engine.lookupFirst(
356: null, JPDADebugger.class);
357: if (debugger == null)
358: return;
359: debugger.addPropertyChangeListener(JPDADebugger.PROP_STATE,
360: this );
361: debuggers.add(debugger);
362: }
363:
364: public void engineRemoved(DebuggerEngine engine) {
365: JPDADebugger debugger = (JPDADebugger) engine.lookupFirst(
366: null, JPDADebugger.class);
367: if (debugger == null)
368: return;
369: debugger.removePropertyChangeListener(
370: JPDADebugger.PROP_STATE, this );
371: debuggers.remove(debugger);
372: }
373:
374: public void breakpointReached(JPDABreakpointEvent event) {
375: RequestProcessor.getDefault().post(new Runnable() {
376: public void run() {
377: DebuggerManager dm = DebuggerManager
378: .getDebuggerManager();
379: synchronized (breakpoints) {
380: for (MethodBreakpoint breakpoint : breakpoints)
381: dm.removeBreakpoint(breakpoint);
382: breakpoints.clear();
383: }
384: }
385: });
386: DebuggerManager
387: .getDebuggerManager()
388: .removeDebuggerListener(
389: DebuggerManager.PROP_DEBUGGER_ENGINES, this );
390: Iterator it = debuggers.iterator();
391: while (it.hasNext()) {
392: JPDADebugger d = (JPDADebugger) it.next();
393: d.removePropertyChangeListener(JPDADebugger.PROP_STATE,
394: this);
395: }
396: }
397: }
398: }
|