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

org.opalj.br.analyses.cg.ClassExtensibility.scala Maven / Gradle / Ivy

The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package br
package analyses
package cg

import scala.collection.mutable
import net.ceedubs.ficus.Ficus._
import org.opalj.collection.mutable.ArrayMap

/**
 * 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 (ObjectType => Answer) {

    /** See [[isClassExtensible]]. */
    final override def apply(t: ObjectType): Answer = this.isClassExtensible(t)

    /**
     * Determines whether the given class can directly be extended by (yet unknown) code.
     */
    def isClassExtensible(t: ObjectType): 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 object types.
     *
     * @return  Those types for which the direct extensibility is explicit configured.
     */
    protected[this] def configuredExtensibleClasses: Iterator[(ObjectType, 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 object types. [[ClassExtensibilityKey.ConfigKeyPrefix]].
     * @return  A list of [[ObjectType]]s. The semantic of those types is encoded by the
     *          respective analysis;
     *          [[AbstractClassExtensibility#configuredExtensibleClasses]].
     */
    protected[this] def parseSpecifiedClassesList(simpleKey: String): List[ObjectType] = {
        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 ot = ObjectType(fqn.substring(0, fqn.length - 2))
                classHierarchy.allSubtypes(ot, reflexive = true)
            } else {
                List(ObjectType(fqn))
            }
        }
    }

    private[this] val classExtensibility: ArrayMap[Answer] = {

        val isClosedPackage = project.get(ClosedPackagesKey)

        val configuredTypes: mutable.LongMap[Answer] = mutable.LongMap.empty[Answer] ++ configuredExtensibleClasses.map { e =>
            val (ot, answer) = e
            (ot.id.toLong, answer)
        }

        val allClassFiles = project.allClassFiles
        val entries = ObjectType.objectTypesCount
        val extensibility = allClassFiles.foldLeft(ArrayMap[Answer](entries)) { (r, classFile) =>
            val objectType = classFile.thisType
            val isExtensible = {
                val configured = configuredTypes.get(objectType.id.toLong)
                if (configured.isDefined)
                    configured.get
                else if (classFile.isEffectivelyFinal ||
                    classFile.isEnumDeclaration ||
                    classFile.isAnnotationDeclaration)
                    No
                else if (classFile.isPublic)
                    Yes
                else if (isClosedPackage(objectType.packageName))
                    No
                else // => non public class in an open package...
                    Yes
            }
            r(objectType.id) = isExtensible
            r

        }
        extensibility
    }

    /**
     * Determines whether the given class can directly be extended by (yet unknown) code.
     */
    def isClassExtensible(t: ObjectType): 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[(ObjectType, 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/exmaple/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[(ObjectType, No.type)] = {
        parseSpecifiedClassesList("finalClasses").iterator.map(t => (t, No))
    }
}

class ClassHierarchyIsNotExtensible(val project: SomeProject) extends ClassExtensibility {

    def isClassExtensible(t: ObjectType): Answer = No
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy