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

org.sonar.java.resolve.BytecodeCompleter Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube Java
 * Copyright (C) 2012 SonarSource
 * [email protected]
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * 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 GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 */
package org.sonar.java.resolve;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Closeables;
import org.apache.commons.lang.StringUtils;
import org.objectweb.asm.ClassReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.java.bytecode.ClassLoaderBuilder;

import javax.annotation.Nullable;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BytecodeCompleter implements JavaSymbol.Completer {

  private static final Logger LOG = LoggerFactory.getLogger(BytecodeCompleter.class);

  private static final int ACCEPTABLE_BYTECODE_FLAGS = Flags.ACCESS_FLAGS |
      Flags.INTERFACE | Flags.ANNOTATION | Flags.ENUM |
      Flags.STATIC | Flags.FINAL | Flags.SYNCHRONIZED | Flags.VOLATILE | Flags.TRANSIENT | Flags.VARARGS | Flags.NATIVE |
      Flags.ABSTRACT | Flags.STRICTFP | Flags.DEPRECATED;

  private Symbols symbols;
  private final List projectClasspath;
  private final ParametrizedTypeCache parametrizedTypeCache;

  /**
   * Indexed by flat name.
   */
  private final Map classes = new HashMap();
  private final Map packages = new HashMap();

  private ClassLoader classLoader;

  public BytecodeCompleter(List projectClasspath, ParametrizedTypeCache parametrizedTypeCache) {
    this.projectClasspath = projectClasspath;
    this.parametrizedTypeCache = parametrizedTypeCache;
  }

  public void init(Symbols symbols) {
    this.symbols = symbols;
  }

  public JavaSymbol.TypeJavaSymbol registerClass(JavaSymbol.TypeJavaSymbol classSymbol) {
    String flatName = formFullName(classSymbol);
    Preconditions.checkState(!classes.containsKey(flatName), "Registering class 2 times : " + flatName);
    classes.put(flatName, classSymbol);
    return classSymbol;
  }

  @Override
  public void complete(JavaSymbol symbol) {
    LOG.debug("Completing symbol : " + symbol.name);
    //complete outer class to set flags for inner class properly.
    if (symbol.owner.isKind(JavaSymbol.TYP)) {
      symbol.owner.complete();
    }
    String bytecodeName = formFullName(symbol);
    JavaSymbol.TypeJavaSymbol classSymbol = getClassSymbol(bytecodeName);
    Preconditions.checkState(classSymbol == symbol);

    InputStream inputStream = null;
    ClassReader classReader = null;
    try {
      inputStream = inputStreamFor(bytecodeName);
      if(inputStream != null) {
        classReader = new ClassReader(inputStream);
      }
    } catch (IOException e) {
      throw Throwables.propagate(e);
    } finally {
      Closeables.closeQuietly(inputStream);
    }
    if (classReader != null) {
      classReader.accept(
          new BytecodeVisitor(this, symbols, (JavaSymbol.TypeJavaSymbol) symbol, parametrizedTypeCache),
          ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
    }
  }

  @Nullable
  private InputStream inputStreamFor(String fullname) {
    return getClassLoader().getResourceAsStream(Convert.bytecodeName(fullname) + ".class");
  }

  private ClassLoader getClassLoader() {
    if (classLoader == null) {
      classLoader = ClassLoaderBuilder.create(projectClasspath);
    }
    return classLoader;
  }

  public String formFullName(JavaSymbol symbol) {
    return formFullName(symbol.name, symbol.owner);
  }

  public String formFullName(String name, JavaSymbol site) {
    String result = name;
    JavaSymbol owner = site;
    while (owner != symbols.defaultPackage) {
      //Handle inner classes, if owner is a type, separate by $
      String separator = ".";
      if (owner.kind == JavaSymbol.TYP) {
        separator = "$";
      }
      result = owner.name + separator + result;
      owner = owner.owner();
    }
    return result;
  }

  @VisibleForTesting
  JavaSymbol.TypeJavaSymbol getClassSymbol(String bytecodeName) {
    return getClassSymbol(bytecodeName, 0);
  }

  // FIXME(Godin): or parameter must be renamed, or should not receive flat name, in a former case - first transformation in this method seems useless
  JavaSymbol.TypeJavaSymbol getClassSymbol(String bytecodeName, int flags) {
    String flatName = Convert.flatName(bytecodeName);
    JavaSymbol.TypeJavaSymbol symbol = classes.get(flatName);
    if (symbol == null) {
      String shortName = Convert.shortName(flatName);
      String packageName = Convert.packagePart(flatName);
      String enclosingClassName = Convert.enclosingClassName(shortName);
      if (StringUtils.isNotEmpty(enclosingClassName)) {
        //handle innerClasses
        symbol = new JavaSymbol.TypeJavaSymbol(filterBytecodeFlags(flags), Convert.innerClassName(shortName), getClassSymbol(Convert.fullName(packageName, enclosingClassName)));
      } else {
        symbol = new JavaSymbol.TypeJavaSymbol(filterBytecodeFlags(flags), shortName, enterPackage(packageName));
      }
      symbol.members = new Scope(symbol);
      symbol.typeParameters = new Scope(symbol);

      // (Godin): IOException will happen without this condition in case of missing class:
      if (getClassLoader().getResource(Convert.bytecodeName(flatName) + ".class") != null) {
        symbol.completer = this;
      } else {
        LOG.error("Class not found: " + bytecodeName);
        ((JavaType.ClassJavaType) symbol.type).interfaces = ImmutableList.of();
        ((JavaType.ClassJavaType) symbol.type).supertype = Symbols.unknownType;
      }

      classes.put(flatName, symbol);
    }
    return symbol;
  }

  public int filterBytecodeFlags(int flags) {
    return flags & ACCEPTABLE_BYTECODE_FLAGS;
  }

  /**
   * Note: Attempt to find something like "java.class" on case-insensitive file system can result in unwanted loading of "JAVA.class".
   * This method performs check of class name within file in order to avoid such situation.
   * This is definitely not the best solution in terms of performance, but acceptable for now.
   *
   * @return symbol for requested class, if corresponding class file exists, and {@link org.sonar.java.resolve.Resolve.JavaSymbolNotFound} otherwise
   */
  // TODO(Godin): Method name is misleading because of lazy loading.
  public JavaSymbol loadClass(String fullname) {
    JavaSymbol.TypeJavaSymbol symbol = classes.get(fullname);
    if (symbol != null) {
      return symbol;
    }

    // TODO(Godin): pull out conversion of name from the next method to avoid unnecessary conversion afterwards:
    InputStream inputStream = inputStreamFor(fullname);
    String bytecodeName = Convert.bytecodeName(fullname);

    if (inputStream == null) {
      return new Resolve.JavaSymbolNotFound();
    }

    try {
      ClassReader classReader = new ClassReader(inputStream);
      String className = classReader.getClassName();
      if (!className.equals(bytecodeName)) {
        return new Resolve.JavaSymbolNotFound();
      }
    } catch (IOException e) {
      throw Throwables.propagate(e);
    } finally {
      Closeables.closeQuietly(inputStream);
    }

    return getClassSymbol(fullname);
  }

  public JavaSymbol.PackageJavaSymbol enterPackage(String fullname) {
    if (StringUtils.isBlank(fullname)) {
      return symbols.defaultPackage;
    }
    JavaSymbol.PackageJavaSymbol result = packages.get(fullname);
    if (result == null) {
      result = new JavaSymbol.PackageJavaSymbol(fullname, symbols.defaultPackage);
      packages.put(fullname, result);
    }
    return result;
  }

  public void done() {
    if (classLoader != null && classLoader instanceof Closeable) {
      Closeables.closeQuietly((Closeable) classLoader);
    }
  }

  /**
   * Compiler marks all artifacts not presented in the source code as {@link Flags#SYNTHETIC}.
   */
  static boolean isSynthetic(int flags) {
    return (flags & Flags.SYNTHETIC) != 0;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy