/**
 * @name Access to unsupported JDK-internal API
 * @description Use of unsupported JDK-internal APIs may cause compatibility issues
 *              when upgrading to newer versions of Java, in particular Java 9.
 * @kind problem
 * @problem.severity recommendation
 * @precision high
 * @id java/jdk-internal-api-access
 * @tags maintainability
 */

import java
import JdkInternals
import JdkInternalsReplacement

predicate importedType(Import i, RefType t) {
  i.(ImportType).getImportedType() = t or
  i.(ImportStaticTypeMember).getTypeHoldingImport() = t or
  i.(ImportStaticOnDemand).getTypeHoldingImport() = t or
  i.(ImportOnDemandFromType).getTypeHoldingImport() = t
}

predicate importedPackage(Import i, Package p) {
  i.(ImportOnDemandFromPackage).getPackageHoldingImport() = p
}

predicate typeReplacement(RefType t, string repl) {
  exists(string old | jdkInternalReplacement(old, repl) | t.getQualifiedName() = old)
}

predicate packageReplacementForType(RefType t, string repl) {
  exists(string old, string pkgName |
    jdkInternalReplacement(old, repl) and t.getPackage().getName() = pkgName
  |
    pkgName = old or
    pkgName.prefix(old.length() + 1) = old + "."
  )
}

predicate packageReplacement(Package p, string repl) {
  exists(string old | jdkInternalReplacement(old, repl) |
    p.getName() = old or
    p.getName().prefix(old.length() + 1) = old + "."
  )
}

predicate replacement(RefType t, string repl) {
  typeReplacement(t, repl)
  or
  not typeReplacement(t, _) and packageReplacementForType(t, repl)
}

abstract class JdkInternalAccess extends Element {
  abstract string getAccessedApi();

  abstract string getReplacement();
}

class JdkInternalTypeAccess extends JdkInternalAccess, TypeAccess {
  JdkInternalTypeAccess() { jdkInternalApi(this.getType().(RefType).getPackage().getName()) }

  override string getAccessedApi() { result = getType().(RefType).getQualifiedName() }

  override string getReplacement() {
    exists(RefType t | this.getType() = t |
      (
        replacement(t, result)
        or
        not replacement(t, _) and result = "unknown"
      )
    )
  }
}

class JdkInternalImport extends JdkInternalAccess, Import {
  JdkInternalImport() {
    exists(RefType t | importedType(this, t) | jdkInternalApi(t.getPackage().getName()))
    or
    exists(Package p | importedPackage(this, p) | jdkInternalApi(p.getName()))
  }

  override string getAccessedApi() {
    exists(RefType t | result = t.getQualifiedName() | importedType(this, t))
    or
    exists(Package p | result = p.getName() | importedPackage(this, p))
  }

  override string getReplacement() {
    exists(RefType t |
      importedType(this, t) and
      (
        replacement(t, result)
        or
        not replacement(t, _) and result = "unknown"
      )
    )
    or
    exists(Package p |
      importedPackage(this, p) and
      (
        packageReplacement(p, result)
        or
        not packageReplacement(p, _) and result = "unknown"
      )
    )
  }
}

predicate jdkPackage(Package p) {
  exists(string pkgName |
    p.getName() = pkgName or
    p.getName().prefix(pkgName.length() + 1) = pkgName + "."
  |
    pkgName = "com.sun" or
    pkgName = "sun" or
    pkgName = "java" or
    pkgName = "javax" or
    pkgName = "com.oracle.net" or
    pkgName = "genstubs" or
    pkgName = "jdk" or
    pkgName = "build.tools" or
    pkgName = "org.omg.CORBA" or
    pkgName = "org.ietf.jgss"
  )
}

from JdkInternalAccess ta, string repl, string msg
where
  repl = ta.getReplacement() and
  (if (repl = "unknown") then msg = "" else msg = " (" + repl + ")") and
  not jdkInternalApi(ta.getCompilationUnit().getPackage().getName()) and
  not jdkPackage(ta.getCompilationUnit().getPackage())
select ta, "Access to unsupported JDK-internal API '" + ta.getAccessedApi() + "'." + msg
