/* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj package br package analyses package cg import scala.collection.mutable import org.opalj.collection.mutable.ArrayMap import net.ceedubs.ficus.Ficus.* /** * Determines whether a class or interface is '''directly extensible by a (yet unknown) * client application/library'''. A class/interface is directly extensible if a developer can * define a direct - not transitive - subtype that is not part of the given application/library. * * This analysis uses the [[ClosedPackages]] information. * * @author Michael Reif * @author Michael Eichberg */ abstract class ClassExtensibility extends (ClassType => Answer) { /** See [[isClassExtensible]]. */ override final def apply(t: ClassType): Answer = this.isClassExtensible(t) /** * Determines whether the given class can directly be extended by (yet unknown) code. */ def isClassExtensible(t: ClassType): Answer } abstract class AbstractClassExtensibility extends ClassExtensibility { val project: SomeProject /** * Enables subclasses to explicitly specify the (non-)extensible types. This enables * users to use domain knowledge to override the result of the base analysis. * * @note See [[AbstractClassExtensibility#parseSpecifiedClassesList]] for how to use OPAL's * configuration to configure sets of class types. * * @return Those types for which the direct extensibility is explicit configured. */ protected def configuredExtensibleClasses: Iterator[(ClassType, Answer)] = Iterator.empty /** * Get the list of configured types using the configured config key. * * @param simpleKey The simple name of the config key that will be used to get a list of * configured class types. [[ClassExtensibilityKey.ConfigKeyPrefix]]. * @return A list of [[ClassType]]s. The semantic of those types is encoded by the * respective analysis; * [[AbstractClassExtensibility#configuredExtensibleClasses]]. */ protected def parseSpecifiedClassesList(simpleKey: String): List[ClassType] = { val completeKey = ClassExtensibilityKey.ConfigKeyPrefix + simpleKey val fqns = project.config.as[Option[List[String]]](completeKey).getOrElse(List.empty) import project.classHierarchy fqns.flatMap { fqn => // We chose "/." to identify all subtypes, because we can only use a character // (sequence) that contains an invalid character in a JVM identifier. if (fqn.endsWith("/.")) { val ct = ClassType(fqn.substring(0, fqn.length - 2)) classHierarchy.allSubtypes(ct, reflexive = true) } else { List(ClassType(fqn)) } } } private val classExtensibility: ArrayMap[Answer] = { val isClosedPackage = project.get(ClosedPackagesKey) val configuredTypes: mutable.LongMap[Answer] = mutable.LongMap.empty[Answer] ++ configuredExtensibleClasses.map { e => val (ct, answer) = e (ct.id.toLong, answer) } val allClassFiles = project.allClassFiles val entries = ClassType.classTypesCount val extensibility = allClassFiles.foldLeft(ArrayMap[Answer](entries)) { (r, classFile) => val classType = classFile.thisType val isExtensible = { val configured = configuredTypes.get(classType.id.toLong) if (configured.isDefined) configured.get else if (classFile.isEffectivelyFinal || classFile.isEnumDeclaration || classFile.isAnnotationDeclaration ) No else if (classFile.isPublic) Yes else if (isClosedPackage(classType.packageName)) No else // => non-public class in an open package... Yes } r(classType.id) = isExtensible r } extensibility } /** * Determines whether the given class can directly be extended by (yet unknown) code. */ def isClassExtensible(t: ClassType): Answer = classExtensibility.get(t.id).getOrElse(Unknown) } class DefaultClassExtensibility(val project: SomeProject) extends AbstractClassExtensibility /** * Determines whether a type is directly extensible by a (yet unknown) * client application/library using the base analysis [[ClassExtensibility]] * and an explicitly configured list of extensible types where the list overrides the findings of * the analysis. This enables domain specific configurations. * * Additional configuration has to be done using OPAL's configuration file. To specify * extensible classes/interfaces the following key has to be used: * `[[ClassExtensibilityKey.ConfigKeyPrefix]] + extensibleClasses` * * @example The following example configuration would consider `java/util/Math` and * `com/example/Type` as ''extensible''. * {{{ * org.opalj.br.analyses.cg.ClassExtensibilityKey.extensibleClasses = * ["java/util/Math", "com/example/Type"] * }}} */ class ConfiguredExtensibleClasses(val project: SomeProject) extends AbstractClassExtensibility { /** * Returns the types which are extensible. */ override def configuredExtensibleClasses: Iterator[(ClassType, Yes.type)] = { parseSpecifiedClassesList("extensibleClasses").iterator.map(t => (t, Yes)) } } /** * Determines whether a type is directly extensible by a (yet unknown) * client application/library using the base analysis [[ClassExtensibility]] * and an explicitly configured list of final (not extensible) types; the configuration * is always considered first. This enables domain specific configurations. * * Additional configuration has to be done using OPAL's configuration file. To specify * classes/interfaces that are not extensible/that are final the following key has to be used: * `[[ClassExtensibilityKey.ConfigKeyPrefix]] + finalClasses` * * @example The following example configuration would consider `java/util/Math` and * `com/example/Type` as ''not extensible''. * {{{ * org.opalj.br.analyses.cg.ClassExtensibilityKey.finalClasses = * ["java/util/Math", "com/example/Type"] * }}} */ class ConfiguredFinalClasses(val project: SomeProject) extends AbstractClassExtensibility { /** * Returns the types which are not extensible/which are final. */ override def configuredExtensibleClasses: Iterator[(ClassType, No.type)] = { parseSpecifiedClassesList("finalClasses").iterator.map(t => (t, No)) } } class ClassHierarchyIsNotExtensible(val project: SomeProject) extends ClassExtensibility { def isClassExtensible(t: ClassType): Answer = No }