| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * 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.android.tools.lint.checks; |
| |
| import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT; |
| import static com.android.tools.lint.client.api.JavaEvaluatorKt.TYPE_BOOLEAN; |
| import static com.android.tools.lint.client.api.JavaEvaluatorKt.TYPE_BOOLEAN_WRAPPER; |
| import static com.android.tools.lint.client.api.JavaEvaluatorKt.TYPE_BYTE_WRAPPER; |
| import static com.android.tools.lint.client.api.JavaEvaluatorKt.TYPE_CHARACTER_WRAPPER; |
| import static com.android.tools.lint.client.api.JavaEvaluatorKt.TYPE_DOUBLE_WRAPPER; |
| import static com.android.tools.lint.client.api.JavaEvaluatorKt.TYPE_FLOAT_WRAPPER; |
| import static com.android.tools.lint.client.api.JavaEvaluatorKt.TYPE_INT; |
| import static com.android.tools.lint.client.api.JavaEvaluatorKt.TYPE_INTEGER_WRAPPER; |
| import static com.android.tools.lint.client.api.JavaEvaluatorKt.TYPE_LONG_WRAPPER; |
| import static com.android.tools.lint.detector.api.Lint.getMethodName; |
| import static com.android.tools.lint.detector.api.Lint.skipParentheses; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.tools.lint.client.api.JavaEvaluator; |
| import com.android.tools.lint.client.api.UElementHandler; |
| import com.android.tools.lint.detector.api.Category; |
| import com.android.tools.lint.detector.api.Detector; |
| import com.android.tools.lint.detector.api.Implementation; |
| import com.android.tools.lint.detector.api.Issue; |
| import com.android.tools.lint.detector.api.JavaContext; |
| import com.android.tools.lint.detector.api.Lint; |
| import com.android.tools.lint.detector.api.LintFix; |
| import com.android.tools.lint.detector.api.Location; |
| import com.android.tools.lint.detector.api.Scope; |
| import com.android.tools.lint.detector.api.Severity; |
| import com.android.tools.lint.detector.api.SourceCodeScanner; |
| import com.google.common.collect.Sets; |
| import com.google.common.collect.Sets.SetView; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiField; |
| import com.intellij.psi.PsiMethod; |
| import com.intellij.psi.PsiType; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import org.jetbrains.uast.UBinaryExpression; |
| import org.jetbrains.uast.UCallExpression; |
| import org.jetbrains.uast.UCallableReferenceExpression; |
| import org.jetbrains.uast.UElement; |
| import org.jetbrains.uast.UExpression; |
| import org.jetbrains.uast.UIfExpression; |
| import org.jetbrains.uast.UMethod; |
| import org.jetbrains.uast.UParenthesizedExpression; |
| import org.jetbrains.uast.UPolyadicExpression; |
| import org.jetbrains.uast.UPrefixExpression; |
| import org.jetbrains.uast.UQualifiedReferenceExpression; |
| import org.jetbrains.uast.UReferenceExpression; |
| import org.jetbrains.uast.USimpleNameReferenceExpression; |
| import org.jetbrains.uast.USuperExpression; |
| import org.jetbrains.uast.UThisExpression; |
| import org.jetbrains.uast.UThrowExpression; |
| import org.jetbrains.uast.UUnaryExpression; |
| import org.jetbrains.uast.UastCallKind; |
| import org.jetbrains.uast.UastUtils; |
| import org.jetbrains.uast.util.UastExpressionUtils; |
| import org.jetbrains.uast.visitor.AbstractUastVisitor; |
| |
| /** |
| * Looks for performance issues in Java files, such as memory allocations during drawing operations |
| * and using HashMap instead of SparseArray. |
| */ |
| public class JavaPerformanceDetector extends Detector implements SourceCodeScanner { |
| |
| private static final Implementation IMPLEMENTATION = |
| new Implementation(JavaPerformanceDetector.class, Scope.JAVA_FILE_SCOPE); |
| |
| /** Allocating objects during a paint method */ |
| public static final Issue PAINT_ALLOC = |
| Issue.create( |
| "DrawAllocation", |
| "Memory allocations within drawing code", |
| "You should avoid allocating objects during a drawing or layout operation. These " |
| + "are called frequently, so a smooth UI can be interrupted by garbage collection " |
| + "pauses caused by the object allocations.\n" |
| + "\n" |
| + "The way this is generally handled is to allocate the needed objects up front " |
| + "and to reuse them for each drawing operation.\n" |
| + "\n" |
| + "Some methods allocate memory on your behalf (such as `Bitmap.create`), and these " |
| + "should be handled in the same way.", |
| Category.PERFORMANCE, |
| 9, |
| Severity.WARNING, |
| IMPLEMENTATION) |
| .setAndroidSpecific(true); |
| |
| /** Using HashMaps where SparseArray would be better */ |
| public static final Issue USE_SPARSE_ARRAY = |
| Issue.create( |
| "UseSparseArrays", |
| "HashMap can be replaced with SparseArray", |
| "For maps where the keys are of type integer, it's typically more efficient to " |
| + "use the Android `SparseArray` API. This check identifies scenarios where you might " |
| + "want to consider using `SparseArray` instead of `HashMap` for better performance.\n" |
| + "\n" |
| + "This is **particularly** useful when the value types are primitives like ints, " |
| + "where you can use `SparseIntArray` and avoid auto-boxing the values from `int` to " |
| + "`Integer`.\n" |
| + "\n" |
| + "If you need to construct a `HashMap` because you need to call an API outside of " |
| + "your control which requires a `Map`, you can suppress this warning using for " |
| + "example the `@SuppressLint` annotation.", |
| Category.PERFORMANCE, |
| 4, |
| Severity.WARNING, |
| IMPLEMENTATION) |
| .setAndroidSpecific(true); |
| |
| /** Using {@code new Integer()} instead of the more efficient {@code Integer.valueOf} */ |
| public static final Issue USE_VALUE_OF = |
| Issue.create( |
| "UseValueOf", |
| "Should use `valueOf` instead of `new`", |
| "You should not call the constructor for wrapper classes directly, such as" |
| + "`new Integer(42)`. Instead, call the `valueOf` factory method, such as " |
| + "`Integer.valueOf(42)`. This will typically use less memory because common integers " |
| + "such as 0 and 1 will share a single instance.", |
| Category.PERFORMANCE, |
| 4, |
| Severity.WARNING, |
| IMPLEMENTATION); |
| |
| static final String ON_MEASURE = "onMeasure"; |
| static final String ON_DRAW = "onDraw"; |
| static final String ON_LAYOUT = "onLayout"; |
| private static final String LAYOUT = "layout"; |
| private static final String HASH_MAP = "java.util.HashMap"; |
| private static final String SPARSE_ARRAY = "android.util.SparseArray"; |
| public static final String CLASS_CANVAS = "android.graphics.Canvas"; |
| |
| /** Constructs a new {@link JavaPerformanceDetector} check */ |
| public JavaPerformanceDetector() {} |
| |
| // ---- implements SourceCodeScanner ---- |
| |
| @Override |
| public List<Class<? extends UElement>> getApplicableUastTypes() { |
| List<Class<? extends UElement>> types = new ArrayList<>(3); |
| types.add(UCallExpression.class); |
| types.add(UMethod.class); |
| return types; |
| } |
| |
| @Override |
| public UElementHandler createUastHandler(@NonNull JavaContext context) { |
| return new PerformanceVisitor(context); |
| } |
| |
| private static class PerformanceVisitor extends UElementHandler { |
| private final JavaContext mContext; |
| private final boolean mCheckMaps; |
| private final boolean mCheckAllocations; |
| private final boolean mCheckValueOf; |
| /** Whether allocations should be "flagged" in the current method */ |
| private boolean mFlagAllocations; |
| |
| public PerformanceVisitor(JavaContext context) { |
| mContext = context; |
| |
| mCheckAllocations = context.isEnabled(PAINT_ALLOC); |
| mCheckMaps = context.isEnabled(USE_SPARSE_ARRAY); |
| mCheckValueOf = context.isEnabled(USE_VALUE_OF); |
| } |
| |
| @Override |
| public void visitMethod(@NonNull UMethod node) { |
| mFlagAllocations = isBlockedAllocationMethod(node); |
| } |
| |
| @Override |
| public void visitCallExpression(@NonNull UCallExpression node) { |
| UastCallKind kind = node.getKind(); |
| if (kind == UastCallKind.CONSTRUCTOR_CALL |
| || kind == UastCallKind.NEW_ARRAY_WITH_DIMENSIONS |
| || kind == UastCallKind.NEW_ARRAY_WITH_INITIALIZER) { |
| visitConstructorCallExpression(node, kind != UastCallKind.CONSTRUCTOR_CALL); |
| } else if (UastExpressionUtils.isMethodCall(node)) { |
| visitMethodCallExpression(node); |
| } |
| } |
| |
| private void visitConstructorCallExpression( |
| @NonNull UCallExpression node, boolean isArray) { |
| String typeName = null; |
| UReferenceExpression classReference = node.getClassReference(); |
| if (mCheckMaps || mCheckValueOf) { |
| if (classReference != null) { |
| typeName = UastUtils.getQualifiedName(classReference); |
| } |
| } |
| |
| if (!isArray && mCheckMaps) { |
| if (SPARSE_ARRAY.equals(typeName)) { |
| checkSparseArray(node); |
| } |
| } |
| |
| if (!isArray && mCheckValueOf) { |
| if (typeName != null |
| && (typeName.equals(TYPE_INTEGER_WRAPPER) |
| || typeName.equals(TYPE_BOOLEAN_WRAPPER) |
| || typeName.equals(TYPE_FLOAT_WRAPPER) |
| || typeName.equals(TYPE_CHARACTER_WRAPPER) |
| || typeName.equals(TYPE_LONG_WRAPPER) |
| || typeName.equals(TYPE_DOUBLE_WRAPPER) |
| || typeName.equals(TYPE_BYTE_WRAPPER)) |
| //&& node.astTypeReference().astParts().size() == 1 |
| && node.getValueArgumentCount() == 1) { |
| String argument = node.getValueArguments().get(0).asSourceString(); |
| |
| String replacedType = typeName.substring(typeName.lastIndexOf('.') + 1); |
| LintFix fix = null; |
| if (!Lint.isKotlin(node.getSourcePsi())) { |
| fix = |
| LintFix.create() |
| .name("Replace with valueOf()", true) |
| .replace() |
| .pattern("(new\\s+" + replacedType + ")") |
| .with(replacedType + ".valueOf") |
| .autoFix() |
| .build(); |
| } |
| mContext.report( |
| USE_VALUE_OF, |
| node, |
| mContext.getLocation(node), |
| getUseValueOfErrorMessage(typeName, argument), |
| fix); |
| } |
| } |
| |
| if (mFlagAllocations |
| && !(skipParentheses(node.getUastParent()) instanceof UThrowExpression) |
| && mCheckAllocations) { |
| // Make sure we're still inside the method declaration that marked |
| // mInDraw as true, in case we've left it and we're in a static |
| // block or something: |
| UMethod method = UastUtils.getParentOfType(node, UMethod.class); |
| if (method != null |
| && isBlockedAllocationMethod(method) |
| && !isLazilyInitialized(node)) { |
| reportAllocation(node); |
| } |
| } |
| } |
| |
| private void reportAllocation(UCallExpression node) { |
| Location location = mContext.getLocation(node); |
| mContext.report( |
| PAINT_ALLOC, |
| node, |
| location, |
| "Avoid object allocations during draw/layout operations (preallocate and " |
| + "reuse instead)"); |
| } |
| |
| private void visitMethodCallExpression(UCallExpression node) { |
| if (!mFlagAllocations) { |
| return; |
| } |
| UExpression receiver = node.getReceiver(); |
| if (receiver == null) { |
| return; |
| } |
| String functionName = getMethodName(node); |
| if (functionName == null) { |
| return; |
| } |
| // Look for forbidden methods |
| if (functionName.equals("createBitmap") || functionName.equals("createScaledBitmap")) { |
| PsiMethod method = node.resolve(); |
| if (method != null |
| && mContext.getEvaluator() |
| .isMemberInClass(method, "android.graphics.Bitmap") |
| && !isLazilyInitialized(node)) { |
| reportAllocation(node); |
| } |
| } else if (functionName.startsWith("decode")) { |
| // decodeFile, decodeByteArray, ... |
| PsiMethod method = node.resolve(); |
| if (method != null |
| && mContext.getEvaluator() |
| .isMemberInClass(method, "android.graphics.BitmapFactory") |
| && !isLazilyInitialized(node)) { |
| reportAllocation(node); |
| } |
| } else if (functionName.equals("getClipBounds") |
| || functionName.equals("clipBounds")) { // Kotlin |
| if (node.getValueArgumentCount() == 0) { |
| Location callLocation = mContext.getLocation(node); |
| mContext.report( |
| PAINT_ALLOC, |
| node, |
| callLocation, |
| "Avoid object allocations during draw operations: Use " |
| + "`Canvas.getClipBounds(Rect)` instead of `Canvas.getClipBounds()` " |
| + "which allocates a temporary `Rect`"); |
| } |
| } |
| } |
| |
| /** |
| * Check whether the given invocation is done as a lazy initialization, e.g. {@code if (foo |
| * == null) foo = new Foo();}. |
| * |
| * <p>This tries to also handle the scenario where the check is on some <b>other</b> |
| * variable - e.g. |
| * |
| * <pre> |
| * if (foo == null) { |
| * foo == init1(); |
| * bar = new Bar(); |
| * } |
| * </pre> |
| * |
| * or |
| * |
| * <pre> |
| * if (!initialized) { |
| * initialized = true; |
| * bar = new Bar(); |
| * } |
| * </pre> |
| */ |
| private static boolean isLazilyInitialized(UElement node) { |
| UElement curr = node.getUastParent(); |
| while (curr != null) { |
| if (curr instanceof UMethod) { |
| return false; |
| } else if (curr instanceof UIfExpression) { |
| UIfExpression ifNode = (UIfExpression) curr; |
| // See if the if block represents a lazy initialization: |
| // compute all variable names seen in the condition |
| // (e.g. for "if (foo == null || bar != foo)" the result is "foo,bar"), |
| // and then compute all variables assigned to in the if body, |
| // and if there is an overlap, we'll consider the whole if block |
| // guarded (so lazily initialized and an allocation we won't complain |
| // about.) |
| List<String> assignments = new ArrayList<>(); |
| AssignmentTracker visitor = new AssignmentTracker(assignments); |
| if (ifNode.getThenExpression() != null) { |
| ifNode.getThenExpression().accept(visitor); |
| } |
| if (!assignments.isEmpty()) { |
| List<String> references = new ArrayList<>(); |
| addReferencedVariables(references, ifNode.getCondition()); |
| if (!references.isEmpty()) { |
| SetView<String> intersection = |
| Sets.intersection( |
| new HashSet<>(assignments), new HashSet<>(references)); |
| return !intersection.isEmpty(); |
| } |
| } |
| return false; |
| } |
| curr = curr.getUastParent(); |
| } |
| |
| return false; |
| } |
| |
| /** Adds any variables referenced in the given expression into the given list */ |
| // TODO: This is old, bogus code. We should switch to a simple PsiVariable visitor! |
| private static void addReferencedVariables( |
| @NonNull Collection<String> variables, @Nullable UExpression expression) { |
| if (expression instanceof UPolyadicExpression) { |
| UPolyadicExpression polyadicExpression = (UPolyadicExpression) expression; |
| for (UExpression operand : polyadicExpression.getOperands()) { |
| addReferencedVariables(variables, operand); |
| } |
| } else if (expression instanceof UPrefixExpression) { |
| UPrefixExpression unary = (UPrefixExpression) expression; |
| addReferencedVariables(variables, unary.getOperand()); |
| } else if (expression instanceof UUnaryExpression) { |
| UUnaryExpression unary = (UUnaryExpression) expression; |
| addReferencedVariables(variables, unary.getOperand()); |
| } else if (expression instanceof UParenthesizedExpression) { |
| UParenthesizedExpression exp = (UParenthesizedExpression) expression; |
| addReferencedVariables(variables, exp.getExpression()); |
| } else if (expression instanceof USimpleNameReferenceExpression) { |
| USimpleNameReferenceExpression reference = |
| (USimpleNameReferenceExpression) expression; |
| variables.add(reference.getIdentifier()); |
| } else if (expression instanceof UQualifiedReferenceExpression) { |
| UQualifiedReferenceExpression ref = (UQualifiedReferenceExpression) expression; |
| UExpression receiver = ref.getReceiver(); |
| UExpression selector = ref.getSelector(); |
| if (receiver instanceof UThisExpression || receiver instanceof USuperExpression) { |
| String identifier = |
| (selector instanceof USimpleNameReferenceExpression) |
| ? ((USimpleNameReferenceExpression) selector).getIdentifier() |
| : null; |
| if (identifier != null) { |
| variables.add(identifier); |
| } |
| } else if (receiver instanceof UCallableReferenceExpression |
| && selector instanceof USimpleNameReferenceExpression |
| && "isInitialized" |
| .equals(((USimpleNameReferenceExpression) selector).getIdentifier()) |
| && (((UCallableReferenceExpression) receiver).getQualifierExpression() |
| == null)) { |
| // If's of the form "if (!::field.isInitialized)" |
| String name = ((UCallableReferenceExpression) receiver).getCallableName(); |
| variables.add(name); |
| } |
| } |
| } |
| |
| /** |
| * Returns whether the given method declaration represents a method where allocating objects |
| * is not allowed for performance reasons |
| */ |
| private boolean isBlockedAllocationMethod(@NonNull PsiMethod node) { |
| JavaEvaluator evaluator = mContext.getEvaluator(); |
| return isOnDrawMethod(evaluator, node) |
| || isOnMeasureMethod(evaluator, node) |
| || isOnLayoutMethod(evaluator, node) |
| || isLayoutMethod(evaluator, node); |
| } |
| |
| /** |
| * Returns true if this method looks like it's overriding android.view.View's {@code |
| * protected void onDraw(Canvas canvas)} |
| */ |
| private static boolean isOnDrawMethod( |
| @NonNull JavaEvaluator evaluator, @NonNull PsiMethod node) { |
| return ON_DRAW.equals(node.getName()) && evaluator.parametersMatch(node, CLASS_CANVAS); |
| } |
| |
| /** |
| * Returns true if this method looks like it's overriding android.view.View's {@code |
| * protected void onLayout(boolean changed, int left, int top, int right, int bottom)} |
| */ |
| private static boolean isOnLayoutMethod( |
| @NonNull JavaEvaluator evaluator, @NonNull PsiMethod node) { |
| return ON_LAYOUT.equals(node.getName()) |
| && evaluator.parametersMatch( |
| node, TYPE_BOOLEAN, TYPE_INT, TYPE_INT, TYPE_INT, TYPE_INT); |
| } |
| |
| /** |
| * Returns true if this method looks like it's overriding android.view.View's {@code |
| * protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)} |
| */ |
| private static boolean isOnMeasureMethod( |
| @NonNull JavaEvaluator evaluator, @NonNull PsiMethod node) { |
| return ON_MEASURE.equals(node.getName()) |
| && evaluator.parametersMatch(node, TYPE_INT, TYPE_INT); |
| } |
| |
| /** |
| * Returns true if this method looks like it's overriding android.view.View's {@code public |
| * void layout(int l, int t, int r, int b)} |
| */ |
| private static boolean isLayoutMethod( |
| @NonNull JavaEvaluator evaluator, @NonNull PsiMethod node) { |
| return LAYOUT.equals(node.getName()) |
| && evaluator.parametersMatch(node, TYPE_INT, TYPE_INT, TYPE_INT, TYPE_INT); |
| } |
| |
| /** |
| * Checks whether the given constructor call and type reference refers to a HashMap |
| * constructor call that is eligible for replacement by a SparseArray call instead |
| * |
| * <p>Deprecated because, while SparseArray is more memory efficient and cache friendly than |
| * HashMap, it is also generally slower than a HashMap because lookups require a binary |
| * search. It is also not intended to be appropriate for maps containing a large number of |
| * items. Thus it does not make sense to unconditionally recommend SparseArray. |
| */ |
| @Deprecated |
| private void checkHashMap(@NonNull UCallExpression node) { |
| if (!mContext.getProject().isAndroidProject()) { |
| return; |
| } |
| List<PsiType> types = node.getTypeArguments(); |
| if (types.size() == 2) { |
| PsiType first = types.get(0); |
| String typeName = first.getCanonicalText(); |
| int minSdk = mContext.getMainProject().getMinSdk(); |
| if (TYPE_INTEGER_WRAPPER.equals(typeName) || TYPE_BYTE_WRAPPER.equals(typeName)) { |
| String valueType = types.get(1).getCanonicalText(); |
| if (valueType.equals(TYPE_INTEGER_WRAPPER)) { |
| mContext.report( |
| USE_SPARSE_ARRAY, |
| node, |
| mContext.getLocation(node), |
| "Use new `SparseIntArray(...)` instead for better performance"); |
| } else if (valueType.equals(TYPE_LONG_WRAPPER) && minSdk >= 18) { |
| mContext.report( |
| USE_SPARSE_ARRAY, |
| node, |
| mContext.getLocation(node), |
| "Use `new SparseLongArray(...)` instead for better performance"); |
| } else if (valueType.equals(TYPE_BOOLEAN_WRAPPER)) { |
| mContext.report( |
| USE_SPARSE_ARRAY, |
| node, |
| mContext.getLocation(node), |
| "Use `new SparseBooleanArray(...)` instead for better performance"); |
| } else { |
| mContext.report( |
| USE_SPARSE_ARRAY, |
| node, |
| mContext.getLocation(node), |
| String.format( |
| "Use `new SparseArray<%1$s>(...)` instead for better performance", |
| valueType.substring(valueType.lastIndexOf('.') + 1))); |
| } |
| } else if (TYPE_LONG_WRAPPER.equals(typeName) |
| && (minSdk >= 16 |
| || Boolean.TRUE |
| == mContext.getMainProject() |
| .dependsOn(SUPPORT_LIB_ARTIFACT))) { |
| boolean useBuiltin = minSdk >= 16; |
| String message = |
| useBuiltin |
| ? "Use `new LongSparseArray(...)` instead for better performance" |
| : "Use `new android.support.v4.util.LongSparseArray(...)` instead for better performance"; |
| mContext.report(USE_SPARSE_ARRAY, node, mContext.getLocation(node), message); |
| } |
| } |
| } |
| |
| private void checkSparseArray(@NonNull UCallExpression node) { |
| List<PsiType> types = node.getTypeArguments(); |
| if (types.size() == 1) { |
| String valueType = types.get(0).getCanonicalText(); |
| if (valueType.equals(TYPE_INTEGER_WRAPPER)) { |
| mContext.report( |
| USE_SPARSE_ARRAY, |
| node, |
| mContext.getLocation(node), |
| "Use `new SparseIntArray(...)` instead for better performance"); |
| } else if (valueType.equals(TYPE_BOOLEAN_WRAPPER)) { |
| mContext.report( |
| USE_SPARSE_ARRAY, |
| node, |
| mContext.getLocation(node), |
| "Use `new SparseBooleanArray(...)` instead for better performance"); |
| } |
| } |
| } |
| } |
| |
| private static String getUseValueOfErrorMessage(String typeName, String argument) { |
| return String.format( |
| "Use `%1$s.valueOf(%2$s)` instead", |
| typeName.substring(typeName.lastIndexOf('.') + 1), argument); |
| } |
| |
| /** Visitor which records variable names assigned into */ |
| private static class AssignmentTracker extends AbstractUastVisitor { |
| private final Collection<String> mVariables; |
| |
| public AssignmentTracker(Collection<String> variables) { |
| mVariables = variables; |
| } |
| |
| @Override |
| public boolean visitBinaryExpression(UBinaryExpression node) { |
| if (UastExpressionUtils.isAssignment(node)) { |
| UExpression left = node.getLeftOperand(); |
| if (left instanceof UQualifiedReferenceExpression) { |
| UQualifiedReferenceExpression ref = (UQualifiedReferenceExpression) left; |
| if (ref.getReceiver() instanceof UThisExpression |
| || ref.getReceiver() instanceof USuperExpression) { |
| PsiElement resolved = ref.resolve(); |
| if (resolved instanceof PsiField) { |
| mVariables.add(((PsiField) resolved).getName()); |
| } |
| } else { |
| PsiElement resolved = ref.resolve(); |
| if (resolved instanceof PsiField) { |
| mVariables.add(((PsiField) resolved).getName()); |
| } |
| } |
| } else if (left instanceof USimpleNameReferenceExpression) { |
| mVariables.add(((USimpleNameReferenceExpression) left).getIdentifier()); |
| } |
| } |
| |
| return super.visitBinaryExpression(node); |
| } |
| } |
| } |