/******************************************************************************* * Copyright (c) 2008, 2016 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.debug.core.breakpoints; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Stack; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.core.model.IBreakpointImportParticipant; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.ArrayType; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.PackageDeclaration; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.debug.core.IJavaWatchpoint; /** * Default implementation covering the import of all platform Java breakpoints * * @since 3.5 */ public class JavaBreakpointImportParticipant implements IBreakpointImportParticipant { class BreakpointVerifier extends ASTVisitor { final int TYPE = 0; final int METHOD = 1; final int FIELD = 2; String fTypename = null; String fName = null; String fSignature = null; IBreakpoint fBreakpoint = null; CompilationUnit fUnit = null; Stack<String> fTypeNameStack = null; /** * Constructor * * @param breakpoint * @param unit */ public BreakpointVerifier(IBreakpoint breakpoint, CompilationUnit unit) { fTypename = getBreakpointTypeName(breakpoint); fName = getMemberName(breakpoint); fSignature = getMemberSignature(breakpoint); fBreakpoint = breakpoint; fUnit = unit; fTypeNameStack = new Stack<>(); } /** * Returns the value of the {@link JavaBreakpoint#TYPE_NAME} attribute * from the breakpoint or <code>null</code> * * @param breakpoint * @return the value of the type name attribute */ String getBreakpointTypeName(IBreakpoint breakpoint) { return breakpoint.getMarker().getAttribute( JavaBreakpoint.TYPE_NAME, null); } /** * Returns the name of the member from the breakpoint attributes. The * name will be one of (1) {@link JavaWatchpoint#FIELD_NAME}, if the * breakpoint is a watchpoint, or (2) * {@link JavaMethodBreakpoint#METHOD_NAME} if the breakpoint is a * method or method entry breakpoint (3) <code>null</code> if there is * no member name * * @param breakpoint * @return the member name or <code>null</code> */ String getMemberName(IBreakpoint breakpoint) { if (breakpoint instanceof IJavaWatchpoint) { return breakpoint.getMarker().getAttribute( JavaWatchpoint.FIELD_NAME, null); } return breakpoint.getMarker().getAttribute( JavaMethodBreakpoint.METHOD_NAME, null); } /** * Returns the signature of the member, defined with the * {@link JavaMethodBreakpoint#METHOD_SIGNATURE} attribute, or * <code>null</code> * * @param breakpoint * @return the signature of the member or <code>null</code> */ String getMemberSignature(IBreakpoint breakpoint) { return breakpoint.getMarker().getAttribute( JavaMethodBreakpoint.METHOD_SIGNATURE, null); } /** * Returns the fully qualified name of the enclosing type for the given * node * * @param node * @return the fully qualified name of the enclosing type */ private String getTypeName(ASTNode node) { return getTypeName(node, new StringBuffer()); } /** * Constructs the qualified name of the enclosing parent type * * @param node * the node to get the parent name for * @param buffer * the buffer to write the name into * @return the fully qualified name of the parent */ private String getTypeName(ASTNode node, StringBuffer buffer) { switch (node.getNodeType()) { case ASTNode.COMPILATION_UNIT: { CompilationUnit unit = (CompilationUnit) node; PackageDeclaration packageDeclaration = unit.getPackage(); if (packageDeclaration != null) { buffer.insert(0, '.'); buffer.insert(0, packageDeclaration.getName() .getFullyQualifiedName()); } return String.valueOf(buffer); } default: { if (node instanceof AbstractTypeDeclaration) { AbstractTypeDeclaration typeDeclaration = (AbstractTypeDeclaration) node; ITypeBinding binding = typeDeclaration.resolveBinding(); if (binding != null) { return binding.getBinaryName(); } if (typeDeclaration.isPackageMemberTypeDeclaration()) { buffer.insert(0, typeDeclaration.getName() .getIdentifier()); } else { buffer.insert(0, typeDeclaration.getName() .getFullyQualifiedName()); buffer.insert(0, '$'); } } } } return getTypeName(node.getParent(), buffer); } /** * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.TypeDeclaration) */ @Override public boolean visit(TypeDeclaration node) { return doTypeVisit(node); } /** * @see org.eclipse.jdt.core.dom.ASTVisitor#endVisit(org.eclipse.jdt.core.dom.TypeDeclaration) */ @Override public void endVisit(TypeDeclaration node) { doEndTypeVisit(); } /** * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.EnumDeclaration) */ @Override public boolean visit(EnumDeclaration node) { return doTypeVisit(node); } /** * @see org.eclipse.jdt.core.dom.ASTVisitor#endVisit(org.eclipse.jdt.core.dom.EnumDeclaration) */ @Override public void endVisit(EnumDeclaration node) { doEndTypeVisit(); } /** * Cleans up after a type visit has ended */ private void doEndTypeVisit() { if (!fTypeNameStack.isEmpty()) { fTypeNameStack.pop(); } } /** * Visits the type node and return if children should be visited * * @param node * @return true if child nodes should be visited false otherwise */ private boolean doTypeVisit(AbstractTypeDeclaration node) { SimpleName name = node.getName(); String typename = getTypeName(node); fTypeNameStack.push(typename); if (!fTypename.startsWith(typename)) { // we are examining the wrong type stop and process other types return false; } if (fBreakpoint instanceof JavaClassPrepareBreakpoint && name != null && typename.equals(fTypename)) { int charstart = name.getStartPosition(); IMarker marker = fBreakpoint.getMarker(); try { marker.setAttribute(IMarker.CHAR_START, charstart); marker.setAttribute(IMarker.CHAR_END, charstart + name.getLength()); } catch (CoreException ce) { } // found the node we were looking for, do not visit children return false; } return fTypename.indexOf('$') > -1 || name != null; } /** * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.FieldDeclaration) */ @Override public boolean visit(FieldDeclaration node) { if (!fTypename.equals(fTypeNameStack.peek())) { return false; } List<VariableDeclarationFragment> fragments = node.fragments(); SimpleName name = null; IMarker marker = fBreakpoint.getMarker(); int currentstart = marker.getAttribute(IMarker.CHAR_START, -1); for (VariableDeclarationFragment fragment : fragments) { name = fragment.getName(); if (name != null && name.getFullyQualifiedName().equals(fName)) { // found field update the charstart / charend int charstart = name.getStartPosition(); if (currentstart != charstart) { try { marker.setAttribute(IMarker.CHAR_START, charstart); marker.setAttribute(IMarker.CHAR_END, charstart + name.getLength()); } catch (CoreException ce) { } } } } return false; } /** * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.MethodDeclaration) */ @Override public boolean visit(MethodDeclaration node) { SimpleName name = node.getName(); String typename = fTypeNameStack.peek(); if (!fTypename.equals(typename) && !fTypename.startsWith(typename)) { return false; } if (name != null && name.getFullyQualifiedName().equals(fName)) { String sig = getMethodSignatureFromNode(node); if (sig != null) { sig = sig.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$ if (sig.equals(fSignature)) { IMarker marker = fBreakpoint.getMarker(); int currentstart = marker.getAttribute( IMarker.CHAR_START, -1); int charstart = name.getStartPosition(); if (currentstart != charstart) { try { marker.setAttribute(IMarker.CHAR_START, charstart); marker.setAttribute(IMarker.CHAR_END, charstart + name.getLength()); } catch (CoreException ce) { } } } } } // visit children in the event we have a class load breakpoint on a // local type return fBreakpoint instanceof JavaClassPrepareBreakpoint; } /** * Creates a method signature from a specified {@link MethodDeclaration} * * @param node * @return the signature for the given method node or <code>null</code> */ private String getMethodSignatureFromNode(MethodDeclaration node) { Assert.isNotNull(node); List<SingleVariableDeclaration> params = node.parameters(); List<String> rparams = getParametersTypeNames(params); if (rparams.size() == params.size()) { if (!node.isConstructor()) { Type returnType = node.getReturnType2(); if (returnType != null) { String rtype = getTypeSignature(returnType); if (rtype != null) { return Signature .createMethodSignature( rparams .toArray(new String[rparams .size()]), rtype); } } } else { StringBuffer buffer = new StringBuffer(); buffer.append("<init>"); //$NON-NLS-1$ collectSyntheticParam(node, rparams); buffer.append(Signature.createMethodSignature( rparams.toArray(new String[rparams .size()]), Signature.SIG_VOID)); return buffer.toString(); } } return null; } /** * Returns the listing of the signatures of the parameters passed in * * @param rawparams * @return a listing of signatures for the specified parameters */ private List<String> getParametersTypeNames(List<SingleVariableDeclaration> rawparams) { List<String> rparams = new ArrayList<>(rawparams.size()); String pname = null; for (SingleVariableDeclaration param : rawparams) { pname = getTypeSignature(param.getType()); if (pname != null) { rparams.add(pname); } } return rparams; } /** * Processes the signature for the given {@link Type} * * @param type * the type to process * @return the signature for the type or <code>null</code> if one could * not be derived */ private String getTypeSignature(Type type) { ITypeBinding binding = type.resolveBinding(); if (binding == null) { return null; } switch (type.getNodeType()) { case ASTNode.PRIMITIVE_TYPE: case ASTNode.QUALIFIED_TYPE: case ASTNode.SIMPLE_TYPE: { return Signature.createTypeSignature( binding.getQualifiedName(), true); } case ASTNode.ARRAY_TYPE: { ArrayType a = (ArrayType) type; return Signature .createArraySignature( getTypeSignature(a.getElementType()), a.getDimensions()); } case ASTNode.PARAMETERIZED_TYPE: { // we don't need to care about the other scoping types only the // base type return getTypeSignature(((ParameterizedType) type).getType()); } } return null; } /** * Collects the synthetic parameter of the fully qualified name of the * enclosing context for a constructor of an inner type * * @param method * the constructor declaration * @param rparams * the listing of parameters to add to */ private void collectSyntheticParam(final MethodDeclaration method, List<String> rparams) { Assert.isNotNull(method); if (isInTopLevelType(method)) { return; } ASTNode parent = method.getParent(); StringBuffer name = new StringBuffer(); while (parent != null) { parent = parent.getParent(); if (parent instanceof AbstractTypeDeclaration) { AbstractTypeDeclaration type = (AbstractTypeDeclaration) parent; name.insert(0, type.getName().getFullyQualifiedName()); if (type.isMemberTypeDeclaration()) { name.insert(0, '$'); } continue; } if (parent instanceof CompilationUnit) { CompilationUnit cunit = (CompilationUnit) parent; PackageDeclaration pdec = cunit.getPackage(); if (pdec != null) { name.insert(0, '.'); name.insert(0, cunit.getPackage().getName() .getFullyQualifiedName()); } } } name.insert(0, "L"); //$NON-NLS-1$ name.append(';'); if (name.length() > 2) { rparams.add(0, name.toString()); } } /** * Determines if the given {@link MethodDeclaration} is present in a top * level type * * @param method * @return */ private boolean isInTopLevelType(final MethodDeclaration method) { TypeDeclaration type = (TypeDeclaration) method.getParent(); return type != null && type.isPackageMemberTypeDeclaration(); } } /* * (non-Javadoc) * * @see * org.eclipse.debug.core.model.IBreakpointImportParticipant#matches(java * .util.Map, org.eclipse.debug.core.model.IBreakpoint) */ @Override public boolean matches(Map<String, Object> attributes, IBreakpoint breakpoint) throws CoreException { if (attributes == null || breakpoint == null) { return false; } String type = (String) attributes.get("type"); //$NON-NLS-1$ if (type == null) { return false; } if (!breakpoint.getMarker().getType().equals(type)) { return false; } if (breakpoint instanceof JavaClassPrepareBreakpoint) { return matchesClassBreakpoint(attributes, (JavaClassPrepareBreakpoint) breakpoint); } if (breakpoint instanceof JavaExceptionBreakpoint) { return matchesExceptionBreakpoint(attributes, (JavaExceptionBreakpoint) breakpoint); } if (breakpoint instanceof JavaMethodBreakpoint) { return matchesMethodBreakpoint(attributes, (JavaMethodBreakpoint) breakpoint); } if (breakpoint instanceof JavaMethodEntryBreakpoint) { return matchesMethodEntryBreakpoint(attributes, (JavaMethodEntryBreakpoint) breakpoint); } if (breakpoint instanceof JavaWatchpoint) { return matchesWatchpoint(attributes, (JavaWatchpoint) breakpoint); } if (breakpoint instanceof JavaStratumLineBreakpoint) { return matchesStratumLineBreakpoint(attributes, (JavaStratumLineBreakpoint) breakpoint); } if (breakpoint instanceof JavaPatternBreakpoint) { return matchesPatternBreakpoint(attributes, (JavaPatternBreakpoint) breakpoint); } if (breakpoint instanceof JavaTargetPatternBreakpoint) { return matchesTargetPatternBreakpoint(attributes, (JavaTargetPatternBreakpoint) breakpoint); } if (breakpoint instanceof JavaLineBreakpoint) { return matchesLineBreakpoint(attributes, (JavaLineBreakpoint) breakpoint); } return false; } /* * (non-Javadoc) * * @see * org.eclipse.debug.core.model.IBreakpointImportParticipant#verify(org. * eclipse.debug.core.model.IBreakpoint) */ @Override public void verify(IBreakpoint breakpoint) throws CoreException { IResource resource = breakpoint.getMarker().getResource(); CompilationUnit unit = null; if (resource != null && resource.getType() == IResource.FILE) { ICompilationUnit cunit = JavaCore .createCompilationUnitFrom((IFile) resource); if (cunit != null) { ASTParser parser = ASTParser.newParser(AST.JLS4); parser.setSource(cunit); parser.setResolveBindings(true); unit = (CompilationUnit) parser .createAST(new NullProgressMonitor()); } } if (unit != null) { if (breakpoint instanceof JavaClassPrepareBreakpoint || breakpoint instanceof JavaWatchpoint || breakpoint instanceof JavaMethodEntryBreakpoint || breakpoint instanceof JavaMethodBreakpoint) { unit.accept(new BreakpointVerifier(breakpoint, unit)); } else if (breakpoint instanceof JavaLineBreakpoint) { JavaLineBreakpoint bp = (JavaLineBreakpoint) breakpoint; // line breakpoint use the ValidBreakpointLocationLocator to // (re)place it int currentline = bp.getLineNumber(); ValidBreakpointLocationLocator locator = new ValidBreakpointLocationLocator( unit, currentline, true, true); unit.accept(locator); int newline = locator.getLineLocation(); if (locator.getLocationType() == ValidBreakpointLocationLocator.LOCATION_LINE) { if (currentline != newline) { if (locator.getFullyQualifiedTypeName() == null) throw new CoreException(Status.CANCEL_STATUS); bp.getMarker().setAttribute(JavaBreakpoint.TYPE_NAME, locator.getFullyQualifiedTypeName()); bp.getMarker().setAttribute(IMarker.LINE_NUMBER, newline); int length = bp.getCharEnd() - bp.getCharStart(); int pos = unit.getPosition(newline, 1); bp.getMarker().setAttribute(IMarker.CHAR_START, pos); bp.getMarker().setAttribute(IMarker.CHAR_END, pos + length); } } else { // the line breakpoint will not be a line breakpoint anymore // get rid of it throw new CoreException(Status.CANCEL_STATUS); } } } } /** * Compares two attributes in a <code>null</code> safe way * * @param attr1 * the first attribute * @param attr2 * the second attribute * @return true if the attributes are equal, false otherwise. If both * attributes are <code>null</code> they are considered to be equal */ private boolean attributesEqual(Object attr1, Object attr2) { if (attr1 == null) { return attr2 == null; } if (attr2 == null) { return false; } return attr1.equals(attr2); } /** * Returns if the given map of attributes matches the given line breakpoint * * @param attributes * @param breakpoint * @return true if the attributes match the breakpoints' attributes, false * otherwise * @throws CoreException */ private boolean matchesLineBreakpoint(Map<String, Object> attributes, JavaLineBreakpoint breakpoint) throws CoreException { Integer line = (Integer) attributes.get(IMarker.LINE_NUMBER); return breakpoint.getLineNumber() == (line == null ? -1 : line.intValue()) && attributesEqual(breakpoint.getTypeName(), attributes.get(JavaBreakpoint.TYPE_NAME)); } /** * Returns if the given map of attributes matches the given class prepare * breakpoint * * @param attributes * @param breakpoint * @return true if the attributes match the breakpoints' attributes, false * otherwise * @throws CoreException */ private boolean matchesClassBreakpoint(Map<String, Object> attributes, JavaClassPrepareBreakpoint breakpoint) throws CoreException { Integer type = (Integer) attributes .get(JavaClassPrepareBreakpoint.MEMBER_TYPE); return attributesEqual(breakpoint.getTypeName(), attributes.get(JavaBreakpoint.TYPE_NAME)) && breakpoint.getMemberType() == (type == null ? -1 : type .intValue()); } /** * Returns if the given map of attributes matches the given exception * breakpoint * * @param attributes * @param breakpoint * @return true if the attributes match the breakpoints' attributes, false * otherwise * @throws CoreException */ private boolean matchesExceptionBreakpoint(Map<String, Object> attributes, JavaExceptionBreakpoint breakpoint) throws CoreException { return attributesEqual(breakpoint.getTypeName(), attributes.get(JavaBreakpoint.TYPE_NAME)); } /** * Returns if the given map of attributes matches the given method * breakpoint * * @param attributes * @param breakpoint * @return true if the attributes match the breakpoints' attributes, false * otherwise * @throws CoreException */ private boolean matchesMethodBreakpoint(Map<String, Object> attributes, JavaMethodBreakpoint breakpoint) throws CoreException { return attributesEqual(breakpoint.getTypeName(), attributes.get(JavaBreakpoint.TYPE_NAME)) && attributesEqual(breakpoint.getMethodName(), attributes.get(JavaMethodBreakpoint.METHOD_NAME)) && attributesEqual(breakpoint.getMethodSignature(), attributes.get(JavaMethodBreakpoint.METHOD_SIGNATURE)); } /** * Returns if the given map of attributes matches the given method entry * breakpoint * * @param attributes * @param breakpoint * @return true if the attributes match the breakpoints' attributes, false * otherwise * @throws CoreException */ private boolean matchesMethodEntryBreakpoint(Map<String, Object> attributes, JavaMethodEntryBreakpoint breakpoint) throws CoreException { return attributesEqual(breakpoint.getTypeName(), attributes.get(JavaBreakpoint.TYPE_NAME)) && attributesEqual(breakpoint.getMethodName(), attributes.get(JavaMethodBreakpoint.METHOD_NAME)) && attributesEqual(breakpoint.getMethodSignature(), attributes.get(JavaMethodBreakpoint.METHOD_SIGNATURE)); } /** * Returns if the given map of attributes matches the given watchpoint * * @param attributes * @param breakpoint * @return true if the attributes match the watchpoints' attributes, false * otherwise * @throws CoreException */ private boolean matchesWatchpoint(Map<String, Object> attributes, JavaWatchpoint watchpoint) throws CoreException { return watchpoint.getFieldName().equals( attributes.get(JavaWatchpoint.FIELD_NAME)) && attributesEqual(watchpoint.getTypeName(), attributes.get(JavaBreakpoint.TYPE_NAME)); } /** * Returns if the given map of attributes matches the given stratum line * breakpoint * * @param attributes * @param breakpoint * @return true if the attributes match the breakpoints' attributes, false * otherwise * @throws CoreException */ private boolean matchesStratumLineBreakpoint(Map<String, Object> attributes, JavaStratumLineBreakpoint breakpoint) throws CoreException { Integer line = (Integer) attributes.get(IMarker.LINE_NUMBER); return breakpoint.getLineNumber() == (line == null ? -1 : line .intValue()) && attributesEqual(breakpoint.getSourceName(), attributes.get(JavaLineBreakpoint.SOURCE_NAME)) && attributesEqual(breakpoint.getStratum(), attributes.get(JavaStratumLineBreakpoint.STRATUM)) && attributesEqual(breakpoint.getSourcePath(), attributes.get(JavaStratumLineBreakpoint.SOURCE_PATH)); } /** * Returns if the given map of attributes matches the given pattern * breakpoint * * @param attributes * @param breakpoint * @return true if the attributes match the breakpoints' attributes, false * otherwise * @throws CoreException */ private boolean matchesPatternBreakpoint(Map<String, Object> attributes, JavaPatternBreakpoint breakpoint) throws CoreException { Integer line = (Integer) attributes.get(IMarker.LINE_NUMBER); return breakpoint.getLineNumber() == (line == null ? -1 : line .intValue()) && attributesEqual(breakpoint.getSourceName(), attributes.get(JavaLineBreakpoint.SOURCE_NAME)) && // TDOD comparing pattern too restrictive?? breakpoint.getPattern().equals( attributes.get(JavaPatternBreakpoint.PATTERN)); } /** * Returns if the given map of attributes matches the given target pattern * breakpoint * * @param attributes * @param breakpoint * @return true if the attributes match the breakpoints' attributes, false * otherwise * @throws CoreException */ private boolean matchesTargetPatternBreakpoint(Map<String, Object> attributes, JavaTargetPatternBreakpoint breakpoint) throws CoreException { Integer line = (Integer) attributes.get(IMarker.LINE_NUMBER); return breakpoint.getLineNumber() == (line == null ? -1 : line .intValue()) && attributesEqual(breakpoint.getTypeName(), attributes.get(JavaBreakpoint.TYPE_NAME)) && attributesEqual(breakpoint.getSourceName(), attributes.get(JavaLineBreakpoint.SOURCE_NAME)); } }