blob: a2d9cabef36e74568102f734eea688b547876fa8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.api.debugger.jpda;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.debugger.Breakpoint;
import org.netbeans.api.debugger.DebuggerEngine;
import org.netbeans.api.debugger.DebuggerManager;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.spi.debugger.jpda.BreakpointStratifier;
import org.openide.filesystems.FileAttributeEvent;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.URLMapper;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.WeakListeners;
/**
* Notifies about line breakpoint events.
*
* <br><br>
* <b>How to use it:</b>
* <pre style="background-color: rgb(255, 255, 153);">
* DebuggerManager.addBreakpoint (LineBreakpoint.create (
* "examples.texteditor.Ted",
* 27
* ));</pre>
* This breakpoint stops in Ted class on 27 line number.
*
* @author Jan Jancura
*/
public class LineBreakpoint extends JPDABreakpoint {
/** Property name constant */
public static final String PROP_LINE_NUMBER = "lineNumber"; // NOI18N
/** Property name constant */
public static final String PROP_URL = "url"; // NOI18N
/** Property name constant. */
public static final String PROP_CONDITION = "condition"; // NOI18N
/** Property name constant. */
public static final String PROP_SOURCE_NAME = "sourceName"; // NOI18N
/** Property name constant. */
public static final String PROP_SOURCE_PATH = "sourcePath"; // NOI18N
/** Property name constant. */
public static final String PROP_STRATUM = "stratum"; // NOI18N
/** Property name constant. */
public static final String PROP_PREFERRED_CLASS_NAME = "classNamePreferred"; // NOI18N
/** Property name constant */
public static final String PROP_INSTANCE_FILTERS = "instanceFilters"; // NOI18N
/** Property name constant */
public static final String PROP_THREAD_FILTERS = "threadFilters"; // NOI18N
private static final Logger LOG = Logger.getLogger(LineBreakpoint.class.getName());
private String url = ""; // NOI18N
private int lineNumber;
private String condition = ""; // NOI18N
private String sourceName = null;
private String sourcePath = null;
private String stratum = "Java"; // NOI18N
private String className = null;
private Map<JPDADebugger,ObjectVariable[]> instanceFilters;
private Map<JPDADebugger,JPDAThread[]> threadFilters;
private LineBreakpoint (String url) {
this.url = url;
}
/**
* Creates a new breakpoint for given parameters.
*
* @param url a string representation of URL of the source file
* @param lineNumber a line number
* @return a new breakpoint for given parameters
*/
public static LineBreakpoint create (
String url,
int lineNumber
) {
LineBreakpoint b = new LineBreakpointImpl (url);
b.setLineNumber (lineNumber);
if (url != null && !url.isEmpty()) {
Collection<? extends BreakpointStratifier> stratifiers = Lookup.getDefault().lookupAll(BreakpointStratifier.class);
for (BreakpointStratifier stratifier : stratifiers) {
stratifier.stratify(b);
}
}
return b;
}
/**
* Gets the string representation of URL of the source file,
* which contains the class to stop on.
*
* @return name of class to stop on
*/
public String getURL () {
return url;
}
/**
* Sets the string representation of URL of the source file,
* which contains the class to stop on.
*
* @param url the URL of class to stop on
*/
public void setURL (String url) {
String old;
synchronized (this) {
if (url == null) {
url = "";
}
if (url.equals(this.url)) {
return;
}
old = this.url;
this.url = url;
}
firePropertyChange (PROP_URL, old, url);
}
/**
* Gets number of line to stop on.
*
* @return line number to stop on
*/
public int getLineNumber () {
return lineNumber;
}
/**
* Sets number of line to stop on.
*
* @param ln a line number to stop on
*/
public void setLineNumber (int ln) {
int old;
synchronized (this) {
if (ln == lineNumber) {
return;
}
old = lineNumber;
lineNumber = ln;
}
firePropertyChange (
PROP_LINE_NUMBER,
Integer.valueOf(old),
Integer.valueOf(ln)
);
}
/**
* Get the instance filter for a specific debugger session.
* @return The instances or <code>null</code> when there is no instance restriction.
*/
public ObjectVariable[] getInstanceFilters(JPDADebugger session) {
if (instanceFilters != null) {
return instanceFilters.get(session);
} else {
return null;
}
}
/**
* Set the instance filter for a specific debugger session. This restricts
* the breakpoint to specific instances in that session.
* @param session the debugger session
* @param instances the object instances or <code>null</code> to unset the filter.
*/
public void setInstanceFilters(JPDADebugger session, ObjectVariable[] instances) {
if (instanceFilters == null) {
instanceFilters = new WeakHashMap<JPDADebugger, ObjectVariable[]>();
}
if (instances != null) {
instanceFilters.put(session, instances);
} else {
instanceFilters.remove(session);
}
firePropertyChange(PROP_INSTANCE_FILTERS, null,
instances != null ?
new Object[] { session, instances } : null);
}
/**
* Get the thread filter for a specific debugger session.
* @return The thread or <code>null</code> when there is no thread restriction.
*/
public JPDAThread[] getThreadFilters(JPDADebugger session) {
if (threadFilters != null) {
return threadFilters.get(session);
} else {
return null;
}
}
/**
* Set the thread filter for a specific debugger session. This restricts
* the breakpoint to specific threads in that session.
* @param session the debugger session
* @param threads the threads or <code>null</code> to unset the filter.
*/
public void setThreadFilters(JPDADebugger session, JPDAThread[] threads) {
if (threadFilters == null) {
threadFilters = new WeakHashMap<JPDADebugger, JPDAThread[]>();
}
if (threads != null) {
threadFilters.put(session, threads);
} else {
threadFilters.remove(session);
}
firePropertyChange(PROP_THREAD_FILTERS, null,
threads != null ?
new Object[] { session, threads } : null);
}
/**
* Returns condition.
*
* @return cond a condition
*/
public String getCondition () {
return condition;
}
/**
* Sets condition.
*
* @param c a new condition
*/
public void setCondition (String c) {
String old;
synchronized (this) {
if (c == null) {
c = "";
}
c = c.trim ();
if (c.equals (condition)) {
return;
}
old = condition;
condition = c;
}
firePropertyChange (PROP_CONDITION, old, c);
}
/**
* Returns stratum.
*
* @return a stratum
*/
public String getStratum () {
return stratum;
}
/**
* Sets stratum.
*
* @param s a new stratum
*/
public void setStratum (String s) {
String old;
synchronized (this) {
if (s == null) {
s = "";
}
s = s.trim ();
if (s.equals (stratum)) {
return;
}
old = stratum;
stratum = s;
}
firePropertyChange (PROP_CONDITION, old, s);
}
/**
* Returns the name of the source file.
*
* @return a source name or <code>null</code> when no source name is defined.
*/
public String getSourceName () {
return sourceName;
}
/**
* Sets the name of the source file.
*
* @param sn a new source name or <code>null</code>.
*/
public void setSourceName (String sn) {
String old;
synchronized (this) {
if (sn != null) {
sn = sn.trim ();
if (sn.equals(sourceName)) {
return ;
}
} else {
if (sourceName == null) {
return ;
}
}
old = sourceName;
sourceName = sn;
}
firePropertyChange (PROP_SOURCE_NAME, old, sn);
}
/**
* Returns source path, relative to the source root.
*
* @return a source path or <code>null</code> when no source path is defined.
*
* @since 1.3
*/
public String getSourcePath() {
return sourcePath;
}
/**
* Sets source path, relative to the source root.
*
* @param sp a new source path or <code>null</code>
*
* @since 1.3
*/
public void setSourcePath (String sp) {
String old;
synchronized (this) {
if (sp != null) {
sp = sp.trim();
if (sp.equals(sourcePath)) {
return ;
}
} else {
if (sourcePath == null) {
return ;
}
}
old = sourcePath;
sourcePath = sp;
}
firePropertyChange (PROP_SOURCE_PATH, old, sp);
}
/**
* Sets the binary class name that is used to submit the breakpoint.
* @param className The binary class name, or <code>null</code> if the class
* name should be retrieved automatically from the URL and line number.
* @since 2.8
*/
public void setPreferredClassName(String className) {
String old;
synchronized (this) {
if (className == null ? this.className == null : className.equals(this.className)) {
return ;
}
old = className;
this.className = className;
}
firePropertyChange (PROP_PREFERRED_CLASS_NAME, old, className);
}
/**
* Gets the binary class name that is used to submit the breakpoint.
* @return The binary class name, if previously set by {@link setPreferedClassName}
* method, or <code>null</code> if the class name should be retrieved
* automatically from the URL and line number.
* @since 2.8
*/
public String getPreferredClassName() {
return className;
}
/**
* Returns a string representation of this object.
*
* @return a string representation of the object
*/
@Override
public String toString () {
String fileName = null;
try {
FileObject fo = URLMapper.findFileObject(new URL(url));
if (fo != null) {
fileName = fo.getNameExt();
}
} catch (MalformedURLException ex) {}
if (fileName == null) {
fileName = url;
}
return "LineBreakpoint " + fileName + " : " + lineNumber;
}
private static class LineBreakpointImpl extends LineBreakpoint
implements Comparable, FileChangeListener,
ChangeListener, PropertyChangeListener {
public static final String PROP_PREFERRED_CLASS_TYPE = "classTypePreferred"; // NOI18N
// We need to hold our FileObject so that it's not GC'ed, because we'd loose our listener.
private FileObject fo;
private ChangeListener registryListener;
private FileChangeListener fileListener;
private JPDAClassType classType;
private boolean wasRegisteredWhenFileDeleted;
public LineBreakpointImpl(String url) {
super(url);
if (url.length() > 0) {
try {
fo = URLMapper.findFileObject(new URL(url));
if (fo != null) {
fileListener = WeakListeners.create(FileChangeListener.class, this, fo);
fo.addFileChangeListener(fileListener);
/*
registryListener = WeakListeners.change(this, DataObject.getRegistry());
DataObject.getRegistry().addChangeListener(registryListener);
*/
}
} catch (MalformedURLException ex) {
LOG.log(Level.WARNING, "URL = '"+url+"'", ex);
} catch (IllegalArgumentException ex) {
LOG.log(Level.WARNING, "URL = '"+url+"'", ex);
}
}
}
@Override
public void setURL(String url) {
if (fo != null) {
fo.removeFileChangeListener(fileListener);
}
super.setURL(url);
if (url.length() > 0) {
try {
fo = URLMapper.findFileObject(new URL(url));
if (fo != null) {
fileListener = WeakListeners.create(FileChangeListener.class, this, fo);
fo.addFileChangeListener(fileListener);
}
} catch (MalformedURLException ex) {
LOG.log(Level.WARNING, "URL = '"+url+"'", ex);
} catch (IllegalArgumentException ex) {
LOG.log(Level.WARNING, "URL = '"+url+"'", ex);
}
}
firePropertyChange(PROP_GROUP_PROPERTIES, null, null);
}
public void setPreferredClassType(JPDAClassType classType) {
JPDAClassType old;
synchronized (this) {
if (classType == null ? this.classType == null : classType.equals(this.classType)) {
return ;
}
old = classType;
this.classType = classType;
}
firePropertyChange (PROP_PREFERRED_CLASS_TYPE, old, classType);
}
public JPDAClassType getPreferredClassType() {
return classType;
}
@Override
public GroupProperties getGroupProperties() {
return new LineGroupProperties();
}
@Override
public int compareTo(Object o) {
if (o instanceof LineBreakpointImpl) {
LineBreakpoint lbthis = this;
LineBreakpoint lb = (LineBreakpoint) o;
int uc = lbthis.url.compareTo(lb.url);
if (uc != 0) {
return uc;
} else {
return lbthis.lineNumber - lb.lineNumber;
}
} else {
return -1;
}
}
@Override
public boolean equals(Object obj) {
// We want each breakpoint to be distinctive
return obj == this;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
@Override
public void fileFolderCreated(FileEvent fe) {
}
@Override
public void fileDataCreated(FileEvent fe) {
}
@Override
public void fileChanged(FileEvent fe) {
}
@Override
public void fileDeleted(FileEvent fe) {
Breakpoint[] breakpoints = DebuggerManager.getDebuggerManager().getBreakpoints();
for (Breakpoint b : breakpoints) {
// Not nice, but the API is limiting
if (b == this) {
wasRegisteredWhenFileDeleted = true;
DebuggerManager.getDebuggerManager().removeBreakpoint(this);
break;
}
}
fo = null;
}
@Override
public void fileRenamed(FileRenameEvent fe) {
this.setURL(((FileObject) fe.getSource()).toURL().toString());
}
@Override
public void fileAttributeChanged(FileAttributeEvent fe) {
}
@Override
public void stateChanged(ChangeEvent chev) {
Object source = chev.getSource();
if (source instanceof Breakpoint.VALIDITY) {
setValidity((Breakpoint.VALIDITY) source, chev.toString());
/*} else if (source instanceof Collection) {
for (Object obj : ((Collection) source)) {
DataObject dobj = (DataObject) obj;
if (registryListener != null) {
FileObject fileObject = this.fo;
if (fileObject == null) {
DataObject.getRegistry().removeChangeListener(registryListener);
registryListener = null;
return ;
}
FileObject primary = dobj.getPrimaryFile();
if (fileObject.equals(primary)) {
dobj.addPropertyChangeListener(WeakListeners.propertyChange(this, dobj));
DataObject.getRegistry().removeChangeListener(registryListener);
registryListener = null;
}
}
}*/
} else {
throw new UnsupportedOperationException(chev.toString());
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
/*if (DataObject.PROP_PRIMARY_FILE.equals(evt.getPropertyName())) {
if (fo != null) {
fo.removeFileChangeListener(fileListener);
}
FileObject newFO = ((DataObject) evt.getSource()).getPrimaryFile();
fileListener = WeakListeners.create(FileChangeListener.class, this, newFO);
newFO.addFileChangeListener(fileListener);
this.setURL(newFO.toURL().toString());
fo = newFO;
if (wasRegisteredWhenFileDeleted) {
// Add back
DebuggerManager.getDebuggerManager().addBreakpoint(this);
wasRegisteredWhenFileDeleted = false;
}
firePropertyChange(PROP_GROUP_PROPERTIES, null, null);
} else*/ if (DebuggerEngine.class.getName().equals(evt.getPropertyName())) {
enginePropertyChange(evt);
}
}
private final class LineGroupProperties extends GroupProperties {
@Override
public String getType() {
return NbBundle.getMessage(LineBreakpoint.class, "LineBrkp_Type");
}
@Override
public String getLanguage() {
return "Java";
}
@Override
public FileObject[] getFiles() {
return new FileObject[] { fo };
}
@Override
public Project[] getProjects() {
FileObject f = fo;
while (f != null) {
f = f.getParent();
if (f != null && ProjectManager.getDefault().isProject(f)) {
break;
}
}
if (f != null) {
try {
return new Project[] { ProjectManager.getDefault().findProject(f) };
} catch (IOException ex) {
} catch (IllegalArgumentException ex) {
}
}
return null;
}
@Override
public DebuggerEngine[] getEngines() {
return LineBreakpointImpl.this.getEngines();
}
@Override
public boolean isHidden() {
return LineBreakpointImpl.this.isHidden();
}
}
}
}