| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed 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 com.intellij.debugger.ui.impl.watch; |
| |
| import com.intellij.debugger.DebuggerBundle; |
| import com.intellij.debugger.DebuggerContext; |
| import com.intellij.debugger.SourcePosition; |
| import com.intellij.debugger.engine.DebugProcessImpl; |
| import com.intellij.debugger.engine.DebuggerManagerThreadImpl; |
| import com.intellij.debugger.engine.DebuggerUtils; |
| import com.intellij.debugger.engine.JVMNameUtil; |
| import com.intellij.debugger.engine.evaluation.EvaluateException; |
| import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil; |
| import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; |
| import com.intellij.debugger.impl.DebuggerContextImpl; |
| import com.intellij.debugger.impl.DebuggerContextUtil; |
| import com.intellij.debugger.impl.DebuggerSession; |
| import com.intellij.debugger.impl.PositionUtil; |
| import com.intellij.debugger.settings.NodeRendererSettings; |
| import com.intellij.debugger.ui.tree.FieldDescriptor; |
| import com.intellij.debugger.ui.tree.NodeDescriptor; |
| import com.intellij.debugger.ui.tree.render.ClassRenderer; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.StringBuilderSpinAllocator; |
| import com.sun.jdi.*; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.List; |
| |
| public class FieldDescriptorImpl extends ValueDescriptorImpl implements FieldDescriptor{ |
| public static final String OUTER_LOCAL_VAR_FIELD_PREFIX = "val$"; |
| private final Field myField; |
| private final ObjectReference myObject; |
| private Boolean myIsPrimitive = null; |
| private final boolean myIsStatic; |
| |
| public FieldDescriptorImpl(Project project, ObjectReference objRef, @NotNull Field field) { |
| super(project); |
| myObject = objRef; |
| myField = field; |
| myIsStatic = field.isStatic(); |
| setLvalue(!field.isFinal()); |
| } |
| |
| @Override |
| public Field getField() { |
| return myField; |
| } |
| |
| @Override |
| public ObjectReference getObject() { |
| return myObject; |
| } |
| |
| @SuppressWarnings({"HardCodedStringLiteral"}) |
| @Nullable |
| public SourcePosition getSourcePosition(final Project project, final DebuggerContextImpl context) { |
| return getSourcePosition(project, context, false); |
| } |
| |
| @SuppressWarnings({"HardCodedStringLiteral"}) |
| @Nullable |
| public SourcePosition getSourcePosition(final Project project, final DebuggerContextImpl context, boolean nearest) { |
| if (context.getFrameProxy() == null) { |
| return null; |
| } |
| final ReferenceType type = myField.declaringType(); |
| final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); |
| final String fieldName = myField.name(); |
| if (fieldName.startsWith(OUTER_LOCAL_VAR_FIELD_PREFIX)) { |
| // this field actually mirrors a local variable in the outer class |
| String varName = fieldName.substring(fieldName.lastIndexOf('$') + 1); |
| PsiElement element = PositionUtil.getContextElement(context); |
| if (element == null) { |
| return null; |
| } |
| PsiClass aClass = PsiTreeUtil.getParentOfType(element, PsiClass.class, false); |
| if (aClass == null) { |
| return null; |
| } |
| aClass = (PsiClass) aClass.getNavigationElement(); |
| PsiVariable psiVariable = facade.getResolveHelper().resolveReferencedVariable(varName, aClass); |
| if (psiVariable == null) { |
| return null; |
| } |
| if (nearest) { |
| return DebuggerContextUtil.findNearest(context, psiVariable, aClass.getContainingFile()); |
| } |
| return SourcePosition.createFromOffset(psiVariable.getContainingFile(), psiVariable.getTextOffset()); |
| } |
| else { |
| final DebuggerSession session = context.getDebuggerSession(); |
| final GlobalSearchScope scope = session != null? session.getSearchScope() : GlobalSearchScope.allScope(myProject); |
| PsiClass aClass = facade.findClass(type.name().replace('$', '.'), scope); |
| if (aClass == null) { |
| // trying to search, assuming declaring class is an anonymous class |
| final DebugProcessImpl debugProcess = context.getDebugProcess(); |
| if (debugProcess != null) { |
| try { |
| final List<Location> locations = type.allLineLocations(); |
| if (!locations.isEmpty()) { |
| // important: use the last location to be sure the position will be within the anonymous class |
| final Location lastLocation = locations.get(locations.size() - 1); |
| final SourcePosition position = debugProcess.getPositionManager().getSourcePosition(lastLocation); |
| aClass = JVMNameUtil.getClassAt(position); |
| } |
| } |
| catch (AbsentInformationException ignored) { |
| } |
| catch (ClassNotPreparedException ignored) { |
| } |
| } |
| } |
| |
| if (aClass != null) { |
| aClass = (PsiClass) aClass.getNavigationElement(); |
| for (PsiField field : aClass.getFields()) { |
| if (fieldName.equals(field.getName())) { |
| if (nearest) { |
| return DebuggerContextUtil.findNearest(context, field, aClass.getContainingFile()); |
| } |
| return SourcePosition.createFromOffset(field.getContainingFile(), field.getTextOffset()); |
| } |
| } |
| } |
| return null; |
| } |
| } |
| |
| @Override |
| public void setAncestor(NodeDescriptor oldDescriptor) { |
| super.setAncestor(oldDescriptor); |
| final Boolean isPrimitive = ((FieldDescriptorImpl)oldDescriptor).myIsPrimitive; |
| if (isPrimitive != null) { // was cached |
| // do not loose cached info |
| myIsPrimitive = isPrimitive; |
| } |
| } |
| |
| |
| @Override |
| public boolean isPrimitive() { |
| if (myIsPrimitive == null) { |
| final Value value = getValue(); |
| if (value != null) { |
| myIsPrimitive = super.isPrimitive(); |
| } |
| else { |
| myIsPrimitive = DebuggerUtils.isPrimitiveType(myField.typeName()) ? Boolean.TRUE : Boolean.FALSE; |
| } |
| } |
| return myIsPrimitive.booleanValue(); |
| } |
| |
| @Override |
| public Value calcValue(EvaluationContextImpl evaluationContext) throws EvaluateException { |
| DebuggerManagerThreadImpl.assertIsManagerThread(); |
| try { |
| return (myObject != null) ? myObject.getValue(myField) : myField.declaringType().getValue(myField); |
| } |
| catch (ObjectCollectedException ignored) { |
| throw EvaluateExceptionUtil.OBJECT_WAS_COLLECTED; |
| } |
| } |
| |
| public boolean isStatic() { |
| return myIsStatic; |
| } |
| |
| @Override |
| public String getName() { |
| final String fieldName = myField.name(); |
| if (isOuterLocalVariableValue() && NodeRendererSettings.getInstance().getClassRenderer().SHOW_VAL_FIELDS_AS_LOCAL_VARIABLES) { |
| return StringUtil.trimStart(fieldName, OUTER_LOCAL_VAR_FIELD_PREFIX); |
| } |
| return fieldName; |
| } |
| |
| public boolean isOuterLocalVariableValue() { |
| try { |
| return DebuggerUtils.isSynthetic(myField) && myField.name().startsWith(OUTER_LOCAL_VAR_FIELD_PREFIX); |
| } |
| catch (UnsupportedOperationException ignored) { |
| return false; |
| } |
| } |
| |
| @Override |
| public String calcValueName() { |
| final ClassRenderer classRenderer = NodeRendererSettings.getInstance().getClassRenderer(); |
| StringBuilder buf = StringBuilderSpinAllocator.alloc(); |
| try { |
| buf.append(getName()); |
| if (classRenderer.SHOW_DECLARED_TYPE) { |
| buf.append(": "); |
| buf.append(classRenderer.renderTypeName(myField.typeName())); |
| } |
| return buf.toString(); |
| } |
| finally { |
| StringBuilderSpinAllocator.dispose(buf); |
| } |
| } |
| |
| @Override |
| public PsiExpression getDescriptorEvaluation(DebuggerContext context) throws EvaluateException { |
| PsiElementFactory elementFactory = JavaPsiFacade.getInstance(context.getProject()).getElementFactory(); |
| String fieldName; |
| if(isStatic()) { |
| String typeName = myField.declaringType().name().replace('$', '.'); |
| typeName = DebuggerTreeNodeExpression.normalize(typeName, PositionUtil.getContextElement(context), context.getProject()); |
| fieldName = typeName + "." + getName(); |
| } |
| else { |
| //noinspection HardCodedStringLiteral |
| fieldName = isOuterLocalVariableValue()? StringUtil.trimStart(getName(), OUTER_LOCAL_VAR_FIELD_PREFIX) : "this." + getName(); |
| } |
| try { |
| return elementFactory.createExpressionFromText(fieldName, null); |
| } |
| catch (IncorrectOperationException e) { |
| throw new EvaluateException(DebuggerBundle.message("error.invalid.field.name", getName()), e); |
| } |
| } |
| } |