/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.netbeans.modules.scala.debugger.projects; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.netbeans.api.debugger.Breakpoint; import org.netbeans.api.debugger.Breakpoint.VALIDITY; import org.netbeans.api.debugger.DebuggerEngine; import org.netbeans.api.debugger.DebuggerManager; import org.netbeans.api.debugger.DebuggerManagerListener; import org.netbeans.api.debugger.Session; import org.netbeans.api.debugger.Watch; import org.netbeans.api.debugger.jpda.FieldBreakpoint; import org.netbeans.api.debugger.jpda.JPDABreakpoint; import org.netbeans.api.debugger.jpda.LineBreakpoint; import org.netbeans.api.debugger.jpda.MethodBreakpoint; import org.netbeans.spi.debugger.jpda.EditorContext; import org.openide.cookies.LineCookie; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileStateInvalidException; import org.openide.loaders.DataObject; import org.openide.loaders.DataObjectNotFoundException; import org.openide.text.Annotation; import org.openide.text.AnnotationProvider; import org.openide.text.Line; import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.RequestProcessor; import org.openide.util.WeakListeners; import org.openide.util.WeakSet; /** * @Note by Caoyuan: I just keep this file here for reference only. * ===================================================== * To enabale this provider, needs to add a META-INFO.services as: * org.openide.text.AnnotationProvider with content: * org.netbeans.modules.scala.debugger.projects.BreakpointAnnotationProvider * But, since there has been another BreakpointAnnotationProvider from: * org.netbeans.modules.debugger.jpda.projects, which also process add/remove * annotation when breakpoint is added/removed, we should not enable this provider * again, otherwise, the annotation will be added twice for each breakpoint, and * showing "Multiple breakpoints". * ===================================================== * * This class is called when some file in editor is openend. It changes if * some LineBreakpoints with annotations should be readed. * * @author Jan Jancura, Martin Entlicher */ public class BreakpointAnnotationProvider implements AnnotationProvider, DebuggerManagerListener { private Map<JPDABreakpoint, Annotation[]> breakpointToAnnotations; private Set<FileObject> annotatedFiles; private Set<PropertyChangeListener> dataObjectListeners; public void annotate (Line.Set set, Lookup lookup) { final FileObject fo = lookup.lookup(FileObject.class); if (fo != null) { final DataObject dobj = lookup.lookup(DataObject.class); if (dobj != null) { PropertyChangeListener pchl = new PropertyChangeListener() { /** annotate renamed files. */ public void propertyChange(PropertyChangeEvent evt) { if (DataObject.PROP_PRIMARY_FILE.equals(evt.getPropertyName())) { FileObject newFO = dobj.getPrimaryFile(); annotate(newFO); } } }; dobj.addPropertyChangeListener(WeakListeners.propertyChange(pchl, dobj)); synchronized (this) { if (dataObjectListeners == null) { dataObjectListeners = new HashSet<PropertyChangeListener>(); } // Prevent from GC. dataObjectListeners.add(pchl); } } annotate(fo); } } public void annotate (final FileObject fo) { boolean attachManagerListener = false; synchronized (this) { if (breakpointToAnnotations == null) { breakpointToAnnotations = new HashMap<JPDABreakpoint, Annotation[]>(); annotatedFiles = new WeakSet<FileObject>(); attachManagerListener = true; } } synchronized (breakpointToAnnotations) { if (annotatedFiles.contains(fo)) { // Already annotated return ; } Set<JPDABreakpoint> annotatedBreakpoints = breakpointToAnnotations.keySet(); for (Breakpoint breakpoint : DebuggerManager.getDebuggerManager().getBreakpoints()) { if (isAnnotatable(breakpoint)) { JPDABreakpoint b = (JPDABreakpoint) breakpoint; if (!annotatedBreakpoints.contains(b)) { b.addPropertyChangeListener (this); breakpointToAnnotations.put(b, new Annotation[] {}); if (b instanceof LineBreakpoint) { LineBreakpoint lb = (LineBreakpoint) b; LineTranslations.getTranslations().registerForLineUpdates(lb); } } addAnnotationTo(b, fo); } } annotatedFiles.add(fo); } if (attachManagerListener) { DebuggerManager.getDebuggerManager().addDebuggerListener( WeakListeners.create(DebuggerManagerListener.class, this, DebuggerManager.getDebuggerManager())); } } public void breakpointAdded(Breakpoint breakpoint) { if (isAnnotatable(breakpoint)) { JPDABreakpoint b = (JPDABreakpoint) breakpoint; b.addPropertyChangeListener (this); RequestProcessor.getDefault().post(new AnnotationRefresh(b, false, true)); if (b instanceof LineBreakpoint) { LineBreakpoint lb = (LineBreakpoint) b; LineTranslations.getTranslations().registerForLineUpdates(lb); } } } public void breakpointRemoved(Breakpoint breakpoint) { if (isAnnotatable(breakpoint)) { JPDABreakpoint b = (JPDABreakpoint) breakpoint; b.removePropertyChangeListener (this); RequestProcessor.getDefault().post(new AnnotationRefresh(b, true, false)); if (b instanceof LineBreakpoint) { LineBreakpoint lb = (LineBreakpoint) b; LineTranslations.getTranslations().unregisterFromLineUpdates(lb); } } } public void propertyChange(PropertyChangeEvent evt) { String propertyName = evt.getPropertyName (); if (propertyName == null) return; if ( (!JPDABreakpoint.PROP_ENABLED.equals (propertyName)) && (!JPDABreakpoint.PROP_VALIDITY.equals (propertyName)) && (!LineBreakpoint.PROP_CONDITION.equals (propertyName)) && (!LineBreakpoint.PROP_URL.equals (propertyName)) && (!LineBreakpoint.PROP_LINE_NUMBER.equals (propertyName)) && (!FieldBreakpoint.PROP_CLASS_NAME.equals (propertyName)) && (!FieldBreakpoint.PROP_FIELD_NAME.equals (propertyName)) && (!MethodBreakpoint.PROP_CLASS_FILTERS.equals (propertyName)) && (!MethodBreakpoint.PROP_CLASS_EXCLUSION_FILTERS.equals (propertyName)) && (!MethodBreakpoint.PROP_METHOD_NAME.equals (propertyName)) && (!MethodBreakpoint.PROP_METHOD_SIGNATURE.equals (propertyName)) ) return; JPDABreakpoint b = (JPDABreakpoint) evt.getSource (); RequestProcessor.getDefault().post(new AnnotationRefresh(b, true, true)); } private final class AnnotationRefresh implements Runnable { private JPDABreakpoint b; private boolean remove, add; public AnnotationRefresh(JPDABreakpoint b, boolean remove, boolean add) { this.b = b; this.remove = remove; this.add = add; } public void run() { synchronized (breakpointToAnnotations) { if (remove) { removeAnnotations(b); if (!add) breakpointToAnnotations.remove(b); } if (add) { breakpointToAnnotations.put(b, new Annotation[] {}); for (FileObject fo : annotatedFiles) { addAnnotationTo(b, fo); } } } } } private static boolean isAnnotatable(Breakpoint b) { return (b instanceof LineBreakpoint || b instanceof FieldBreakpoint || b instanceof MethodBreakpoint) && !((JPDABreakpoint) b).isHidden(); } private static String getAnnotationType(JPDABreakpoint b, boolean isConditional) { boolean isInvalid = b.getValidity() == VALIDITY.INVALID; String annotationType; if (b instanceof LineBreakpoint) { annotationType = b.isEnabled () ? (isConditional ? EditorContext.CONDITIONAL_BREAKPOINT_ANNOTATION_TYPE : EditorContext.BREAKPOINT_ANNOTATION_TYPE) : (isConditional ? EditorContext.DISABLED_CONDITIONAL_BREAKPOINT_ANNOTATION_TYPE : EditorContext.DISABLED_BREAKPOINT_ANNOTATION_TYPE); } else if (b instanceof FieldBreakpoint) { annotationType = b.isEnabled () ? EditorContext.FIELD_BREAKPOINT_ANNOTATION_TYPE : EditorContext.DISABLED_FIELD_BREAKPOINT_ANNOTATION_TYPE; } else if (b instanceof MethodBreakpoint) { annotationType = b.isEnabled () ? EditorContext.METHOD_BREAKPOINT_ANNOTATION_TYPE : EditorContext.DISABLED_METHOD_BREAKPOINT_ANNOTATION_TYPE; } else { throw new IllegalStateException(b.toString()); } if (isInvalid && b.isEnabled ()) annotationType += "_broken"; return annotationType; } /** @return The annotation lines or <code>null</code>. */ private static int[] getAnnotationLines(JPDABreakpoint b, FileObject fo) { if (b instanceof LineBreakpoint) { LineBreakpoint lb = (LineBreakpoint) b; try { if (fo.getURL().equals(new URL(lb.getURL()))) { return new int[] { lb.getLineNumber() }; } } catch (MalformedURLException ex) { Exceptions.printStackTrace(ex); } catch (FileStateInvalidException ex) { Exceptions.printStackTrace(ex); } return null; } else if (b instanceof FieldBreakpoint) { FieldBreakpoint fb = (FieldBreakpoint) b; String className = fb.getClassName(); String fieldName = fb.getFieldName(); int line = EditorContextImpl.getFieldLineNumber(fo, className, fieldName); return new int[] { line }; } else if (b instanceof MethodBreakpoint) { MethodBreakpoint mb = (MethodBreakpoint) b; String[] filters = mb.getClassFilters(); int[] lns = new int[] {}; for (int i = 0; i < filters.length; i++) { // TODO: annotate also other matched classes if (!filters[i].startsWith("*") && !filters[i].endsWith("*")) { int[] newlns = EditorContextImpl.getMethodLineNumbers( fo, filters[i], mb.getClassExclusionFilters(), mb.getMethodName(), mb.getMethodSignature()); if (lns.length == 0) { lns = newlns; } else { int[] ln = new int[lns.length + newlns.length]; System.arraycopy(lns, 0, ln, 0, lns.length); System.arraycopy(newlns, 0, ln, lns.length, newlns.length); lns = ln; } } } return lns; } else { throw new IllegalStateException(b.toString()); } } // Is called under synchronized (breakpointToAnnotations) private void addAnnotationTo(JPDABreakpoint b, FileObject fo) { int[] lines = getAnnotationLines(b, fo); if (lines == null || lines.length == 0) { return ; } String condition; if (b instanceof LineBreakpoint) { condition = ((LineBreakpoint) b).getCondition(); } else if (b instanceof FieldBreakpoint) { condition = ((FieldBreakpoint) b).getCondition(); } else if (b instanceof MethodBreakpoint) { condition = ((MethodBreakpoint) b).getCondition(); } else { throw new IllegalStateException(b.toString()); } boolean isConditional = (condition != null) && condition.trim().length() > 0; String annotationType = getAnnotationType(b, isConditional); DataObject dataObject; try { dataObject = DataObject.find(fo); } catch (DataObjectNotFoundException donfex) { donfex.printStackTrace(); return ; } LineCookie lc = dataObject.getCookie(LineCookie.class); if (lc == null) return; List<DebuggerBreakpointAnnotation> annotations = new ArrayList<DebuggerBreakpointAnnotation>(); for (int l : lines) { try { Line line = lc.getLineSet().getCurrent(l - 1); DebuggerBreakpointAnnotation annotation = new DebuggerBreakpointAnnotation (annotationType, line, b); annotations.add(annotation); } catch (IndexOutOfBoundsException e) { } catch (IllegalArgumentException e) { } } if (annotations.size() == 0) { return ; } Object[] oldAnnotations = breakpointToAnnotations.get(b); if (oldAnnotations == null || oldAnnotations.length == 0) { breakpointToAnnotations.put(b, annotations.toArray(new Annotation[0])); } else { Annotation[] newAnnotations = new Annotation[oldAnnotations.length + annotations.size()]; System.arraycopy(oldAnnotations, 0, newAnnotations, 0, oldAnnotations.length); for (int i = 0; i < annotations.size(); i++) { newAnnotations[i + oldAnnotations.length] = annotations.get(i); } breakpointToAnnotations.put(b, newAnnotations); } } // Is called under synchronized (breakpointToAnnotations) private void removeAnnotations(JPDABreakpoint b) { Annotation[] annotations = breakpointToAnnotations.remove(b); if (annotations == null) return ; for (Annotation a : annotations) { a.detach(); } } // Not used public Breakpoint[] initBreakpoints() { return new Breakpoint[] {}; } // Not used public void initWatches() {} // Not used public void watchAdded(Watch watch) {} // Not used public void watchRemoved(Watch watch) {} // Not used public void sessionAdded(Session session) {} // Not used public void sessionRemoved(Session session) {} // Not used public void engineAdded(DebuggerEngine engine) {} // Not used public void engineRemoved(DebuggerEngine engine) {} }