blob: 2477958b704b8bcf19f47039ac66295302edce85 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
* QNX Software Systems - Refactored to use platform implementation
* Marc Khouzam (Ericsson) - Added support for Tracepoints (bug 376116)
* Marc Khouzam (Ericsson) - Added support for Dynamic-Printf (bug 400628)
* Jonah Graham - Set REQUESTED_* fields when creating from dialog (bug 46026)
* Jonah Graham (Kichwa Coders) - Create "Add Line Breakpoint (C/C++)" action (Bug 464917)
*******************************************************************************/
package org.eclipse.cdt.debug.internal.ui.breakpoints;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.debug.core.CDIDebugModel;
import org.eclipse.cdt.debug.core.model.ICBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpoint2;
import org.eclipse.cdt.debug.core.model.ICDynamicPrintf;
import org.eclipse.cdt.debug.core.model.ICLineBreakpoint2;
import org.eclipse.cdt.debug.core.model.ICTracepoint;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.jface.preference.IPersistentPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
/**
* A preference store that presents the state of the properties of a C/C++ breakpoint,
* tracepoint or dynamic-printf.
*/
public class CBreakpointPreferenceStore implements IPersistentPreferenceStore {
// This map is the current properties/values being maintained/manipulated
private HashMap<String, Object> fProperties = new HashMap<String, Object>();
// Original set of values. So we can see what has really changed on the save and
// perform appropriate change operations. We only really want to operate on changed
// values, to avoid generating churn.
private HashMap<String, Object> fOriginalValues = new HashMap<String, Object>();
private boolean fIsDirty = false;
private boolean fIsCanceled = false;
private ListenerList fListeners;
private final CBreakpointContext fContext;
public CBreakpointPreferenceStore() {
this (null, null);
}
public CBreakpointPreferenceStore(CBreakpointContext context, Map<String, Object> attributes) {
fListeners = new ListenerList(org.eclipse.core.runtime.ListenerList.IDENTITY);
fContext = context;
fOriginalValues.clear();
fProperties.clear();
if (context != null) {
IMarker marker = context.getBreakpoint().getMarker();
if (marker != null) {
Map<String, Object> bpAttrs = Collections.emptyMap();
try {
bpAttrs = marker.getAttributes();
fOriginalValues.putAll(bpAttrs);
fProperties.putAll(bpAttrs);
} catch (CoreException e) {
DebugPlugin.log(e);
}
}
}
if (attributes != null) {
fProperties.putAll(attributes);
fIsDirty = true;
}
}
public Map<String, Object> getAttributes() {
return fProperties;
}
public void setCanceled(boolean canceled) {
fIsCanceled = canceled;
}
public void save() throws IOException {
if (!fIsCanceled && fContext != null && fContext.getBreakpoint() != null) {
ICBreakpoint bp = fContext.getBreakpoint();
if (bp.getMarker() != null && fIsDirty) {
saveToExistingMarker(bp, bp.getMarker());
} else {
IResource resolved = getResource(fContext.getResource());
if (resolved != null) {
saveToNewMarker(bp, resolved);
} else {
throw new IOException("Unable to create breakpoint: no resource specified."); //$NON-NLS-1$
}
}
}
}
/**
* Get the resource to apply the marker against. This may not be the same
* resource the dialog was created for if the user has selected a different
* resource.
* <p>
* If the {@link ICBreakpoint#SOURCE_HANDLE} resolves to the same file on
* the filesystem as the preferred resource the preferred resource is used.
*
* @param preferred
* resource to use if it matches the SOURCE_HANDLE
* @return Resource to install marker on, or <code>null</code> for not
* available.
*/
private IResource getResource(IResource preferred) {
IResource resolved = null;
String source = getString(ICBreakpoint.SOURCE_HANDLE);
if (!"".equals(source)) { //$NON-NLS-1$
IPath rawLocation = preferred.getRawLocation();
if (rawLocation != null) {
File file = rawLocation.toFile();
File sourceFile = new File(source);
if (file.getAbsoluteFile().equals(sourceFile.getAbsoluteFile())) {
resolved = preferred;
}
}
if (resolved == null) {
IPath path = Path.fromOSString(source);
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IFile file = root.getFileForLocation(path);
if (file == null) {
resolved = root;
} else {
resolved = file;
}
}
}
if (resolved == null) {
resolved = preferred;
}
return resolved;
}
private void saveToExistingMarker(final ICBreakpoint breakpoint, final IMarker marker) throws IOException {
final List<String> changedProperties = new ArrayList<String>( 5 );
Set<String> valueNames = fProperties.keySet();
for ( String name : valueNames ) {
if ( fProperties.containsKey( name ) ) {
Object originalObject = fOriginalValues.get( name );
Object currentObject = fProperties.get( name );
if ( originalObject == null ) {
changedProperties.add( name );
}
else if ( ! originalObject.equals( currentObject ) ) {
changedProperties.add( name );
}
}
}
if ( ! changedProperties.isEmpty() ) {
IWorkspaceRunnable wr = new IWorkspaceRunnable() {
public void run( IProgressMonitor monitor ) throws CoreException {
Iterator<String> changed = changedProperties.iterator();
while( changed.hasNext() ) {
String property = changed.next();
if ( property.equals( ICBreakpoint.ENABLED ) ) {
breakpoint.setEnabled( getBoolean( ICBreakpoint.ENABLED ) );
}
else if ( property.equals( ICBreakpoint.IGNORE_COUNT ) ) {
breakpoint.setIgnoreCount( getInt( ICBreakpoint.IGNORE_COUNT ) );
}
else if ( breakpoint instanceof ICTracepoint && property.equals( ICTracepoint.PASS_COUNT ) ) {
((ICTracepoint)breakpoint).setPassCount( getInt( ICTracepoint.PASS_COUNT ) );
}
else if ( breakpoint instanceof ICDynamicPrintf && property.equals( ICDynamicPrintf.PRINTF_STRING ) ) {
((ICDynamicPrintf)breakpoint).setPrintfString( getString( ICDynamicPrintf.PRINTF_STRING ) );
}
else if ( property.equals( ICBreakpoint.CONDITION ) ) {
breakpoint.setCondition( getString( ICBreakpoint.CONDITION ) );
}
else if ( property.equals( IMarker.LINE_NUMBER ) ) {
if (breakpoint instanceof ICLineBreakpoint2) {
// refresh message and line number
// Note there are no API methods to set the line number of a Line Breakpoint, so we
// replicate what is done in CDIDebugModel.setLineBreakpointAttributes()
// to set the line number fields properly and then refresh the message if possible
((ICLineBreakpoint2)breakpoint).setRequestedLine(getInt(IMarker.LINE_NUMBER));
breakpoint.getMarker().setAttribute(IMarker.LINE_NUMBER, getInt(IMarker.LINE_NUMBER));
((ICBreakpoint2)breakpoint).refreshMessage();
} else {
// already workspace runnable, setting markers are safe
breakpoint.getMarker().setAttribute(IMarker.LINE_NUMBER, getInt(IMarker.LINE_NUMBER));
breakpoint.getMarker().setAttribute(ICLineBreakpoint2.REQUESTED_LINE, getInt(IMarker.LINE_NUMBER));
}
} else {
// this allow set attributes contributed by other plugins
Object value = fProperties.get(property);
if ( value != null ) {
marker.setAttribute(property, value);
if (breakpoint instanceof ICBreakpoint2) {
// To be safe, refresh the breakpoint message as the property
// change might affect it.
((ICBreakpoint2)breakpoint).refreshMessage();
}
}
}
}
}
};
try {
ResourcesPlugin.getWorkspace().run( wr, null );
}
catch( CoreException ce ) {
throw new IOException("Cannot save properties to breakpoint.", ce); //$NON-NLS-1$
}
}
}
private void saveToNewMarker(final ICBreakpoint breakpoint, final IResource resource) throws IOException {
try {
// On initial creation of BP, make sure that requested values of breakpoint
// match the current values (i.e. make sure it starts as a not-relocated breakpoint)
// See CDIDebugModel.setLineBreakpointAttributes
if (fProperties.containsKey(ICLineBreakpoint2.REQUESTED_SOURCE_HANDLE)) {
fProperties.put(ICLineBreakpoint2.REQUESTED_SOURCE_HANDLE, fProperties.get(ICBreakpoint.SOURCE_HANDLE));
}
if (fProperties.containsKey(ICLineBreakpoint2.REQUESTED_LINE)) {
fProperties.put(ICLineBreakpoint2.REQUESTED_LINE, fProperties.get(IMarker.LINE_NUMBER));
}
if (fProperties.containsKey(ICLineBreakpoint2.REQUESTED_CHAR_START)) {
fProperties.put(ICLineBreakpoint2.REQUESTED_CHAR_START, fProperties.get(IMarker.CHAR_START));
}
if (fProperties.containsKey(ICLineBreakpoint2.REQUESTED_CHAR_END)) {
fProperties.put(ICLineBreakpoint2.REQUESTED_CHAR_END, fProperties.get(IMarker.CHAR_END));
}
CDIDebugModel.createBreakpointMarker(breakpoint, resource, fProperties, true);
}
catch( CoreException ce ) {
throw new IOException("Cannot save properties to new breakpoint.", ce); //$NON-NLS-1$
}
}
///////////////////////////////////////////////////////////////////////
// IPreferenceStore
public boolean needsSaving() {
return fIsDirty && !fIsCanceled;
}
public boolean contains(String name) {
return fProperties.containsKey(name);
}
public void addPropertyChangeListener(IPropertyChangeListener listener) {
fListeners.add(listener);
}
public void removePropertyChangeListener(IPropertyChangeListener listener) {
fListeners.remove(listener);
}
public void firePropertyChangeEvent(String name,
Object oldValue,
Object newValue)
{
Object[] listeners = fListeners.getListeners();
// Do we need to fire an event.
if (listeners.length > 0 && (oldValue == null || !oldValue.equals(newValue))) {
PropertyChangeEvent pe = new PropertyChangeEvent(this, name, oldValue, newValue);
for (int i = 0; i < listeners.length; ++i) {
IPropertyChangeListener l = (IPropertyChangeListener) listeners[i];
l.propertyChange(pe);
}
}
}
public boolean getBoolean(String name) {
boolean retVal = false;
Object o = fProperties.get(name);
if (o instanceof Boolean) {
retVal = ((Boolean)o).booleanValue();
}
return retVal;
}
public int getInt(String name) {
int retVal = 0;
Object o = fProperties.get(name);
if (o instanceof Integer) {
retVal = ((Integer)o).intValue();
}
return retVal;
}
public String getString(String name) {
String retVal = ""; //$NON-NLS-1$
Object o = fProperties.get(name);
if (o instanceof String) {
retVal = (String)o;
}
return retVal;
}
public double getDouble(String name) { return 0; }
public float getFloat(String name) { return 0; }
public long getLong(String name) { return 0; }
public boolean isDefault(String name) { return false; }
public boolean getDefaultBoolean(String name) { return false; }
public double getDefaultDouble(String name) { return 0; }
public float getDefaultFloat(String name) { return 0; }
public int getDefaultInt(String name) { return 0; }
public long getDefaultLong(String name) { return 0; }
public String getDefaultString(String name) { return null; }
public void putValue(String name, String value) {
Object oldValue = fProperties.get(name);
if ( oldValue == null || !oldValue.equals(value) ) {
fProperties.put(name, value);
setDirty(true);
}
}
public void setDefault(String name, double value) {}
public void setDefault(String name, float value) {}
public void setDefault(String name, int value) {}
public void setDefault(String name, long value) {}
public void setDefault(String name, String defaultObject) {}
public void setDefault(String name, boolean value) {}
public void setToDefault(String name) {}
public void setValue(String name, boolean value) {
boolean oldValue = getBoolean(name);
if (oldValue != value) {
fProperties.put( name, Boolean.valueOf(value) );
setDirty(true);
firePropertyChangeEvent(name, Boolean.valueOf(oldValue), Boolean.valueOf(value) );
}
}
public void setValue(String name, int value) {
int oldValue = getInt(name);
if (oldValue != value) {
fProperties.put( name, Integer.valueOf(value) );
setDirty(true);
firePropertyChangeEvent(name, Integer.valueOf(oldValue), Integer.valueOf(value) );
}
}
public void setValue(String name, String value) {
Object oldValue = fProperties.get(name);
if ( (oldValue == null && value != null) ||
(oldValue != null && !oldValue.equals(value)) )
{
fProperties.put(name, value);
setDirty(true);
firePropertyChangeEvent(name, oldValue, value);
}
}
public void setValue(String name, float value) {}
public void setValue(String name, double value) {}
public void setValue(String name, long value) {}
// IPreferenceStore
///////////////////////////////////////////////////////////////////////
private void setDirty(boolean isDirty) {
fIsDirty = isDirty;
}
}