All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.model.JSymbol Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube Java
 * Copyright (C) 2012-2024 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the Sonar Source-Available License for more details.
 *
 * You should have received a copy of the Sonar Source-Available License
 * along with this program; if not, see https://sonarsource.com/license/ssal/
 */
package org.sonar.java.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nullable;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Modifier;
import org.sonar.java.model.declaration.ClassTreeImpl;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.SymbolMetadata;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.Tree;

abstract class JSymbol implements Symbol {

  protected final JSema sema;
  protected final IBinding binding;

  /**
   * Cache for {@link #hashCode()}.
   */
  private int hashCode;

  /**
   * Cache for {@link #owner()}.
   */
  private Symbol owner;

  /**
   * Cache for {@link #metadata()}.
   */
  private SymbolMetadata metadata;

  JSymbol(JSema sema, IBinding binding) {
    this.sema = Objects.requireNonNull(sema);
    this.binding = Objects.requireNonNull(binding);
  }

  @Override
  public final boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (!(obj instanceof JSymbol)) {
      return false;
    }
    JSymbol other = (JSymbol) obj;
    if (this.binding.getKind() != other.binding.getKind()) {
      return false;
    }
    switch (this.binding.getKind()) {
      case IBinding.TYPE:
        return JType.areEqual(
          (ITypeBinding) this.binding,
          (ITypeBinding) other.binding
        );
      case IBinding.VARIABLE:
        return areEqualVariables(
          this,
          other
        );
      case IBinding.METHOD:
        return areEqualMethods(
          this,
          other
        );
      default:
        return super.equals(obj);
    }
  }

  private static boolean areEqualVariables(JSymbol thisVariableSymbol, JSymbol otherVariableSymbol) {
    IVariableBinding thisBinding = (IVariableBinding) thisVariableSymbol.binding;
    IVariableBinding otherBinding = (IVariableBinding) otherVariableSymbol.binding;
    return thisBinding.getVariableId() == otherBinding.getVariableId()
      && thisVariableSymbol.owner().equals(otherVariableSymbol.owner());
  }

  private static boolean areEqualMethods(JSymbol thisMethodSymbol, JSymbol otherMethodSymbol) {
    IMethodBinding thisBinding = (IMethodBinding) thisMethodSymbol.binding;
    IMethodBinding otherBinding = (IMethodBinding) otherMethodSymbol.binding;
    return thisMethodSymbol.name().equals(otherMethodSymbol.name())
      && thisMethodSymbol.owner().equals(otherMethodSymbol.owner())
      && Arrays.equals(thisBinding.getParameterTypes(), otherBinding.getParameterTypes())
      && Arrays.equals(thisBinding.getTypeParameters(), otherBinding.getTypeParameters())
      && Arrays.equals(thisBinding.getTypeArguments(), otherBinding.getTypeArguments());
  }

  @Override
  public final int hashCode() {
    if (hashCode == 0) {
      Symbol resolvedOwner = owner();
      hashCode = resolvedOwner == null ? 0 : (resolvedOwner.hashCode() * 31);
      hashCode += name().hashCode();
    }
    return hashCode;
  }

  /**
   * @see JType#name()
   */
  @Override
  public final String name() {
    if (binding.getKind() == IBinding.METHOD && ((IMethodBinding) binding).isConstructor()) {
      return "";
    }
    if (binding.getKind() == IBinding.TYPE && ((ITypeBinding) binding).isParameterizedType()) {
      // without names of parameters
      return ((ITypeBinding) binding).getErasure().getName();
    }
    return binding.getName();
  }

  /**
   * @see #enclosingClass()
   */
  @Override
  public final Symbol owner() {
    if (isUnknown()) {
      return Symbols.unknownSymbol;
    }
    if (owner == null) {
      owner = convertOwner();
    }
    return owner;
  }

  private Symbol convertOwner() {
    switch (binding.getKind()) {
      case IBinding.PACKAGE:
        return Symbols.rootPackage;
      case IBinding.TYPE:
        return typeOwner((ITypeBinding) binding);
      case IBinding.METHOD:
        return methodOwner((IMethodBinding) binding);
      case IBinding.VARIABLE:
        return variableOwner((IVariableBinding) binding);
      default:
        throw new IllegalStateException(unexpectedBinding());
    }
  }

  private String unexpectedBinding() {
    return "Unexpected binding Kind: " + binding.getKind();
  }

  private Symbol typeOwner(ITypeBinding typeBinding) {
    if (typeBinding.isPrimitive()) {
      return Symbols.rootPackage;
    }
    IMethodBinding declaringMethod = typeBinding.getDeclaringMethod();
    if (declaringMethod != null) {
      // local type
      return sema.methodSymbol(declaringMethod);
    }
    ITypeBinding declaringClass = typeBinding.getDeclaringClass();
    if (declaringClass != null) {
      // member type
      return sema.typeSymbol(declaringClass);
    }
    // top-level type
    return sema.packageSymbol(typeBinding.getPackage());
  }

  private Symbol methodOwner(IMethodBinding methodBinding) {
    return sema.typeSymbol(methodBinding.getDeclaringClass());
  }

  private Symbol variableOwner(IVariableBinding variableBinding) {
    if (!variableBinding.isRecordComponent()) {
      IMethodBinding declaringMethod = variableBinding.getDeclaringMethod();
      if (declaringMethod != null) {
        // local variable
        return sema.methodSymbol(declaringMethod);
      }
      ITypeBinding declaringClass = variableBinding.getDeclaringClass();
      if (declaringClass != null) {
        // field
        return sema.typeSymbol(declaringClass);
      }
    }
    Tree node = sema.declarations.get(variableBinding);
    if (node == null) {
      // array.length
      return Symbols.unknownSymbol;
    }
    boolean initializerBlock = false;
    boolean staticInitializerBlock = false;
    while (true) {
      node = node.parent();
      switch (node.kind()) {
        case CLASS,
          RECORD,
          ENUM:
          JTypeSymbol typeSymbol = sema.typeSymbol(((ClassTreeImpl) node).typeBinding);
          if (initializerBlock) {
            return sema.initializerBlockSymbol(typeSymbol);
          }
          if (staticInitializerBlock) {
            return sema.staticInitializerBlockSymbol(typeSymbol);
          }
          // Record parameters
          return typeSymbol;
        case INITIALIZER:
          initializerBlock = true;
          break;
        case STATIC_INITIALIZER:
          staticInitializerBlock = true;
          break;
        case METHOD,
          CONSTRUCTOR:
          // local variable declaration in recovered method
          // and recovered methods do not have bindings
          return Symbols.unknownMethodSymbol;
        default:
          // continue exploring parent
          break;
      }
    }
  }

  @Override
  public final Type type() {
    switch (binding.getKind()) {
      case IBinding.TYPE:
        return sema.type((ITypeBinding) binding);
      case IBinding.VARIABLE:
        ITypeBinding variableType = ((IVariableBinding) binding).getType();
        return variableType != null ? sema.type(variableType) : Symbols.unknownType;
      case IBinding.PACKAGE,
        IBinding.METHOD:
        return Symbols.unknownType;
      default:
        throw new IllegalStateException(unexpectedBinding());
    }
  }

  @Override
  public final boolean isVariableSymbol() {
    return binding.getKind() == IBinding.VARIABLE;
  }

  @Override
  public final boolean isTypeSymbol() {
    return !isUnknown() && binding.getKind() == IBinding.TYPE;
  }

  @Override
  public final boolean isMethodSymbol() {
    return binding.getKind() == IBinding.METHOD;
  }

  @Override
  public final boolean isPackageSymbol() {
    return binding.getKind() == IBinding.PACKAGE;
  }

  @Override
  public final boolean isStatic() {
    return Modifier.isStatic(binding.getModifiers());
  }

  @Override
  public final boolean isFinal() {
    return Modifier.isFinal(binding.getModifiers());
  }

  @Override
  public final boolean isEnum() {
    switch (binding.getKind()) {
      case IBinding.TYPE:
        return ((ITypeBinding) binding).isEnum();
      case IBinding.VARIABLE:
        return ((IVariableBinding) binding).isEnumConstant();
      default:
        return false;
    }
  }

  @Override
  public final boolean isInterface() {
    return binding.getKind() == IBinding.TYPE
      && ((ITypeBinding) binding).isInterface();
  }

  @Override
  public final boolean isAbstract() {
    return Modifier.isAbstract(binding.getModifiers());
  }

  @Override
  public final boolean isPublic() {
    return Modifier.isPublic(binding.getModifiers());
  }

  @Override
  public final boolean isPrivate() {
    return Modifier.isPrivate(binding.getModifiers());
  }

  @Override
  public final boolean isProtected() {
    return Modifier.isProtected(binding.getModifiers());
  }

  @Override
  public final boolean isPackageVisibility() {
    return !isPublic() && !isPrivate() && !isProtected();
  }

  @Override
  public final boolean isDeprecated() {
    return binding.isDeprecated();
  }

  @Override
  public final boolean isVolatile() {
    return Modifier.isVolatile(binding.getModifiers());
  }

  @Override
  public final boolean isUnknown() {
    return binding.isRecovered();
  }

  @Override
  public final SymbolMetadata metadata() {
    if (metadata == null) {
      try {
        metadata = convertMetadata();
      } catch (RuntimeException e) {
        // ECJ raises exception in rare occasions, when it is the case, we don't want to prevent the whole analysis of the file
        metadata = Symbols.EMPTY_METADATA;
      }
    }
    return metadata;
  }

  private SymbolMetadata convertMetadata() {
    switch (binding.getKind()) {
      case IBinding.PACKAGE:
        return new JSymbolMetadata(sema, this, sema.resolvePackageAnnotations(binding.getName()));
      case IBinding.VARIABLE:
        ITypeBinding type = ((IVariableBinding) binding).getType();
        return new JSymbolMetadata(
          sema,
          this,
          type == null ? new IAnnotationBinding[0] : getAnnotations(type),
          binding.getAnnotations());
      case IBinding.METHOD:
        ITypeBinding returnType = ((IMethodBinding) binding).getReturnType();
        // In rare circumstances, when the semantic information is incomplete, returnType can be null.
        if (returnType == null) {
          return Symbols.EMPTY_METADATA;
        }
        return new JSymbolMetadata(sema, this, getAnnotations(returnType), binding.getAnnotations());
      default:
        return new JSymbolMetadata(sema, this, binding.getAnnotations());
    }
  }

  private static IAnnotationBinding[] getAnnotations(ITypeBinding type) {
    List iAnnotationBindings = new ArrayList<>();
    for (ITypeBinding typeArgument : type.getTypeArguments()) {
      Collections.addAll(iAnnotationBindings, typeArgument.getTypeAnnotations());
    }
    Collections.addAll(iAnnotationBindings, type.getTypeAnnotations());
    return iAnnotationBindings.toArray(new IAnnotationBinding[0]);
  }

  /**
   * @see #owner()
   */
  @Nullable
  @Override
  public final TypeSymbol enclosingClass() {
    switch (binding.getKind()) {
      case IBinding.PACKAGE:
        return null;
      case IBinding.TYPE:
        return typeEnclosingClass((ITypeBinding) binding);
      case IBinding.METHOD:
        return methodEnclosingClass((IMethodBinding) binding);
      case IBinding.VARIABLE:
        return variableEnclosingClass((IVariableBinding) binding);
      default:
        throw new IllegalStateException(unexpectedBinding());
    }
  }

  private TypeSymbol typeEnclosingClass(ITypeBinding typeBinding) {
    ITypeBinding declaringClass = typeBinding.getDeclaringClass();
    if (declaringClass != null) {
      // nested (member or local) type
      return sema.typeSymbol(declaringClass);
    }
    // top-level type
    return (TypeSymbol) this;
  }

  private TypeSymbol methodEnclosingClass(IMethodBinding methodBinding) {
    ITypeBinding declaringClass = methodBinding.getDeclaringClass();
    return sema.typeSymbol(declaringClass);
  }

  private TypeSymbol variableEnclosingClass(IVariableBinding variableBinding) {
    ITypeBinding declaringClass = variableBinding.getDeclaringClass();
    if (declaringClass != null) {
      // field
      return sema.typeSymbol(declaringClass);
    }
    IMethodBinding declaringMethod = variableBinding.getDeclaringMethod();
    if (declaringMethod != null) {
      // local variable
      return sema.typeSymbol(declaringMethod.getDeclaringClass());
    }
    Tree node = sema.declarations.get(variableBinding);
    if (node == null) {
      // array.length
      return Symbols.unknownTypeSymbol;
    }
    do {
      node = node.parent();
      switch (node.kind()) {
        case CLASS,
          RECORD,
          ENUM:
          // variable declaration in a static or instance initializer
          // or local variable declaration in recovered method
          return sema.typeSymbol(((ClassTreeImpl) node).typeBinding);
        default:
          // continue exploring parent
          break;
      }
    } while (true);
  }

  @Override
  public final List usages() {
    List usages = sema.usages.get(JSema.declarationBinding(binding));
    return usages != null ? usages : Collections.emptyList();
  }

  @Nullable
  @Override
  public Tree declaration() {
    return sema.declarations.get(JSema.declarationBinding(binding));
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy