diff --git a/OPAL/ProjectDependencies.mmd b/OPAL/ProjectDependencies.mmd
index 9f8ea40a7d..5fb11b0b22 100644
--- a/OPAL/ProjectDependencies.mmd
+++ b/OPAL/ProjectDependencies.mmd
@@ -1,4 +1,4 @@
-%%{ init: { 'flowchart': { 'defaultRenderer': 'elk', 'curve': 'linear' } } }%%
+%%{ init: { 'flowchart': { 'defaultRenderer': 'elk', 'curve': 'linear', 'padding': 10, 'wrappingWidth': 205 } } }%%
flowchart BT
Framework[Framework framework]
ThreeAddressCode[Three Address Code tac]
@@ -8,6 +8,7 @@ flowchart BT
BytecodeInfrastructure[Bytecode Infrastructure bi]
AbstractInterpretationFramework[Abstract Interpretation Framework ai]
Hermes[Hermes hermes]
+ ConfigurationExplorer[Configuration Explorer ce]
Common[Common common]
BytecodeDisassembler[Bytecode Disassembler da]
StaticAnalysisInfrastructure[Static Analysis Infrastructure si]
@@ -34,6 +35,7 @@ flowchart BT
BytecodeInfrastructure --> Common
AbstractInterpretationFramework --> BytecodeRepresentation
Hermes --> Framework
+ ConfigurationExplorer --> BytecodeRepresentation
BytecodeDisassembler --> BytecodeInfrastructure
StaticAnalysisInfrastructure --> Common
IFDS --> IDE
diff --git a/OPAL/ProjectDependencies.pdf b/OPAL/ProjectDependencies.pdf
index 2252a4ac31..d8cc210f68 100644
Binary files a/OPAL/ProjectDependencies.pdf and b/OPAL/ProjectDependencies.pdf differ
diff --git a/OPAL/ProjectDependencies.svg b/OPAL/ProjectDependencies.svg
index 8e998de5d0..0244c94178 100644
--- a/OPAL/ProjectDependencies.svg
+++ b/OPAL/ProjectDependencies.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/OPAL/ai/src/main/resources/reference.conf b/OPAL/ai/src/main/resources/reference.conf
index db2275d277..bea820b17c 100644
--- a/OPAL/ai/src/main/resources/reference.conf
+++ b/OPAL/ai/src/main/resources/reference.conf
@@ -1,7 +1,9 @@
org.opalj {
+
+ # Configuration for the Abstract Interpretation subproject which provides highly configurable abstract interpretation of Java bytecode
ai {
common {
- // we currently support the strategies: cheapest and best
+ # We currently support the strategies: cheapest and best
DomainRegistry.defaultStrategy = "cheapest"
}
}
diff --git a/OPAL/ai/src/main/scala/org/opalj/ai/package.scala b/OPAL/ai/src/main/scala/org/opalj/ai/package.scala
index 9565c4dc0a..0b4a61d9f6 100644
--- a/OPAL/ai/src/main/scala/org/opalj/ai/package.scala
+++ b/OPAL/ai/src/main/scala/org/opalj/ai/package.scala
@@ -19,11 +19,11 @@ import org.opalj.log.OPALLogger
import org.opalj.util.AnyToAnyThis
/**
- * Implementation of an abstract interpretation (ai) framework – also referred to as OPAL.
+ * Implementation of an abstract interpretation (ai) framework.
*
- * Please note that OPAL/the abstract interpreter just refers to the classes and traits
+ * Please note that the abstract interpreter just refers to the classes and traits
* defined in this package (`ai`). The classes and traits defined in the sub-packages
- * (in particular in `domain`) are not considered to be part of the core of OPAL/the
+ * (in particular in `domain`) are not considered to be part of the core of the
* abstract interpreter.
*
* @note This framework assumes that the analyzed bytecode is valid; i.e., the JVM's
diff --git a/OPAL/apk/src/main/resources/reference.conf b/OPAL/apk/src/main/resources/reference.conf
index 1268bdcd82..8a2794796e 100644
--- a/OPAL/apk/src/main/resources/reference.conf
+++ b/OPAL/apk/src/main/resources/reference.conf
@@ -1,5 +1,6 @@
org.opalj {
- apk {
+ // Configuration for the APK subproject which parses Android APK packages
+ apk {
# Set this to true if you want stdout and stderr of commands (retdec, enjarify, dex2jar) being logged.
logOutput = false
diff --git a/OPAL/ba/src/main/resources/reference.conf b/OPAL/ba/src/main/resources/reference.conf
index 932ad26d4e..dfa21dfcf9 100644
--- a/OPAL/ba/src/main/resources/reference.conf
+++ b/OPAL/ba/src/main/resources/reference.conf
@@ -1,4 +1,5 @@
org.opalj {
+ // Configuration for the Bytecode Assembler subproject which provides a DSL for assembling Java Bytecode
ba {
CODE {
logDeadCodeRemoval = true, // if true, a short message is printed if compile time dead code is found and removed
diff --git a/OPAL/bc/Readme.md b/OPAL/bc/Readme.md
index dbcbf8623f..46d9664a13 100644
--- a/OPAL/bc/Readme.md
+++ b/OPAL/bc/Readme.md
@@ -1,5 +1,5 @@
# Overview
-The ***Bytecode Creator*** (BC) is a library for assembling Java bytecode given a the naive
+The ***Bytecode Creator*** (BC) is a library for assembling Java bytecode given the naive
representation provided by the **Bytecode Disassembler** (DA) project. Functionality to
convert class files using the higher-level representation provided by the
**Bytecode Representation** (BR) project is provided as part of OPAL's **Bytecode Assembler** (BA).
diff --git a/OPAL/br/src/main/resources/reference.conf b/OPAL/br/src/main/resources/reference.conf
index a2f93083cc..b03e839312 100644
--- a/OPAL/br/src/main/resources/reference.conf
+++ b/OPAL/br/src/main/resources/reference.conf
@@ -1,5 +1,6 @@
org.opalj {
+ # Configuration for the Bytecode Representation subproject which provides handling for Java Bytecode
br {
# The CFG's structure is validated; primarily of interest when the algorithms related to
# computing the CFG are maintained, extended, or changed.
@@ -32,6 +33,7 @@ org.opalj {
}
}
+ // Configuration for Java analyses
analyses {
cg {
@@ -57,11 +59,28 @@ org.opalj {
finalClasses = [] # used by org.opalj.br.analyses.cg.ConfiguredFinalClasses
}
+ # Configuration for which methods should be considered entrypoints for the program under analysis
InitialEntryPointsKey {
- #analysis = "org.opalj.br.analyses.cg.ApplicationEntryPointsFinder"
+ # @brief Specifies the implementation that computes the entry points
+ # @type subclass
+ # @constraint org.opalj.br.analyses.cg.EntryPointFinder
analysis = "org.opalj.br.analyses.cg.ApplicationWithoutJREEntryPointsFinder"
- #analysis = "org.opalj.br.analyses.cg.LibraryEntryPointsFinder"
- #analysis = "org.opalj.br.analyses.cg.MetaEntryPointsFinder"
+
+ # Additional preconfigured entry points
+ # Default values are entry points of the JDK
+ # additional entry points can be specified by adding a respective tuple that must consist of
+ # a class name and a method name and can be refined by also defining a method descriptor.
+ # In addition, the specified class name can be suffixed with a "+" which implies that all methods that match
+ # the specified name -- and if definied descriptor -- from subtypes are considered too.
+ # eg.:
+ # entryPoints = [
+ # {declaringClass = "java/util/List+", name = "add"},
+ # {declaringClass = "java/util/List", name = "remove", descriptor = "(I)Z"}
+ # ]
+ # Please note that the first entry point, by adding the "+" to the declaring class' name, considers all
+ # "add" methods from all subtypes. In constrast, the second entry does specify a descriptor and does not
+ # consider list subtypes (by not suffixing a plus to the declaringClass) which implies that only the remove
+ # method with this descriptor is considered as entry point
entryPoints = [
{declaringClass = "java/lang/System", name = "initializeSystemClass", descriptor = "()V"},
{declaringClass = "java/lang/Thread", name = "", descriptor = "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;)V"},
@@ -77,25 +96,16 @@ org.opalj {
{declaringClass = "java/lang/ClassLoader", name = "findNative", descriptor = "(Ljava/lang/ClassLoader;Ljava/lang/String;)J"},
{declaringClass = "java/security/PrivilegedActionException", name = "", descriptor = "(Ljava/lang/Exception;)V"}
]
- # additional entry points can be specified by adding a respective tuple that must consist of
- # a class name and a method name and can be refined by also defining a method descriptor.
- # In addition, the specified class name can be suffixed with a "+" which implies that all methods that match
- # the specified name -- and if definied descriptor -- from subtypes are considered too.
- # eg.:
- # entryPoints = [
- # {declaringClass = "java/util/List+", name = "add"},
- # {declaringClass = "java/util/List", name = "remove", descriptor = "(I)Z"}
- # ]
- # Please note that the first entry point, by adding the "+" to the declaring class' name, considers all
- # "add" methods from all subtypes. In constrast, the second entry does specify a descriptor and does not
- # consider list subtypes (by not suffixing a plus to the declaringClass) which implies that only the remove
- # method with this descriptor is considered as entry point.
}
+ # Configuration for which types should be considered existing at the start of the program under analysis
InitialInstantiatedTypesKey {
+ # @brief Specifies the implementation that computes the initially instantiated types
+ # @type subclass
+ # @constraint org.opalj.br.analyses.cg.InstantiatedTypesFinder
analysis = "org.opalj.br.analyses.cg.ApplicationInstantiatedTypesFinder"
- #analysis = "org.opalj.br.analyses.cg.LibraryInstantiatedTypesFinder"
+ # Additional preconfigured initially instantiated types
instantiatedTypes = [
]
diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedTypesFinder.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedTypesFinder.scala
index 0cb49d789b..6dd0e93b84 100644
--- a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedTypesFinder.scala
+++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedTypesFinder.scala
@@ -22,8 +22,8 @@ sealed trait InstantiatedTypesFinder {
}
/**
- * This trait considers only the type java.lang.String as instantiated that is the type that is
- * be instantiated for command line applications for their command line parameters.
+ * This trait considers only the types java.lang.String and java.lang.Class as instantiated, which can be introduced by
+ * constants.
*
* @author Florian Kuebler
*/
diff --git a/OPAL/br/src/main/scala/org/opalj/br/package.scala b/OPAL/br/src/main/scala/org/opalj/br/package.scala
index 6cabd352f4..a1b409c481 100644
--- a/OPAL/br/src/main/scala/org/opalj/br/package.scala
+++ b/OPAL/br/src/main/scala/org/opalj/br/package.scala
@@ -28,7 +28,7 @@ import org.opalj.log.OPALLogger.info
* representation is called the resolved representation.
*
* This representation of Java bytecode is considered as OPAL's standard representation
- * for writing Scala based analyses. This representation is engineered such
+ * for writing simple Scala based analyses. This representation is engineered such
* that it facilitates writing analyses that use pattern matching.
*
* @author Michael Eichberg
diff --git a/OPAL/common/src/main/resources/reference.conf b/OPAL/common/src/main/resources/reference.conf
index 87892f8f86..c91177e149 100644
--- a/OPAL/common/src/main/resources/reference.conf
+++ b/OPAL/common/src/main/resources/reference.conf
@@ -1,3 +1,4 @@
+// Configuration for the Common subproject that provides basic necessities for OPAL
org.opalj {
// currently no settings
}
diff --git a/OPAL/common/src/main/scala/org/opalj/package.scala b/OPAL/common/src/main/scala/org/opalj/package.scala
index f28cddf164..5dfc3b7d37 100644
--- a/OPAL/common/src/main/scala/org/opalj/package.scala
+++ b/OPAL/common/src/main/scala/org/opalj/package.scala
@@ -18,17 +18,24 @@ import org.opalj.log.OPALLogger
* - a library (`Common`) which provides generally useful data-structures and algorithms
* for static analyses.
* - a framework for implementing lattice based static analyses (`Static Analysis Infrastructure`)
- * - a framework for parsing Java bytecode (Bytecode Infrastructure`) that can be used to
+ * - a framework for parsing Java bytecode (`Bytecode Infrastructure` - [[org.opalj.bi]]) that can be used to
* create arbitrary representations.
* - a library to create a one-to-one in-memory representation of Java bytecode
- * (`Bytecode Disassembler`).
+ * (`Bytecode Disassembler` - [[org.opalj.da]]).
+ * - a library to convert this representation to Java class files (`Bytecode Creator` - [[org.opalj.bc]]).
* - a library to create a representation of Java bytecode that facilitates writing
* simple static analyses (`Bytecode Representation` - [[org.opalj.br]]).
+ * - a library to create a stackless, three-address code representation of Java bytecode that facilitates writing
+ * complex static analyses (`Three Address Code` - [[org.opalj.tac]]).
* - a scalable, easily customizable framework for the abstract interpretation of
* Java bytecode (`Abstract Interpretation Framework` - [[org.opalj.ai]]).
- * - a library to extract dependencies between code elements and to facilitate checking
- * architecture definitions.
- * - a library for the lightweight manipulation and creation of Java bytecode (Bytecode Assembler).
+ * - a library to extract dependencies between code elements (`Dependencies Extraction` - [[org.opalj.de]]) and to
+ * facilitate checking architecture definitions (`Architecture Validation` - [[org.opalj.av]]).
+ * - a library for the lightweight manipulation and creation of Java bytecode
+ * (`Bytecode Assembler` - [[org.opalj.ba]]).
+ * - a library for parsing Android packages (`APK` - [[org.opalj.apk]]).
+ * - libraries for writing static analyses using the interprocedural finite distributive subset
+ * (`IFDS` - [[org.opalj.ifds]]) and interprocedural distributive environment (`IDE` - [[org.opal.ide]]) algorithms.
*
* ==General Design Decisions==
*
diff --git a/OPAL/framework/src/main/resources/CommandLineProject.conf b/OPAL/framework/src/main/resources/CommandLineProject.conf
index 93c837d068..5df0cd1eb2 100644
--- a/OPAL/framework/src/main/resources/CommandLineProject.conf
+++ b/OPAL/framework/src/main/resources/CommandLineProject.conf
@@ -1,3 +1,7 @@
+// Standard configuration for typical (command line) applications with a main method:
+// - The class hierarchy is considered complete and non-extensible
+// - main methods of the application are assumed as entry points
+// - only types String and Class are considered available before the main method
org.opalj.br.analyses {
cg {
ClosedPackagesKey {
diff --git a/OPAL/framework/src/main/resources/LibraryProject.conf b/OPAL/framework/src/main/resources/LibraryProject.conf
index 2780e0e50f..b75abf5e91 100644
--- a/OPAL/framework/src/main/resources/LibraryProject.conf
+++ b/OPAL/framework/src/main/resources/LibraryProject.conf
@@ -1,3 +1,7 @@
+// Standard configuration for library projects:
+// - The class hierarchy is considered incomplete and extensible
+// - all accessible methods of accessible classes are considered as entry points
+// - all classes that can be instantiated from a client application are considered instantiated
org.opalj.br.analyses {
cg {
ClosedPackagesKey {
diff --git a/OPAL/framework/src/main/resources/NoTransformations.conf b/OPAL/framework/src/main/resources/NoTransformations.conf
index 85588fde31..4f49699b6d 100644
--- a/OPAL/framework/src/main/resources/NoTransformations.conf
+++ b/OPAL/framework/src/main/resources/NoTransformations.conf
@@ -1,4 +1,4 @@
-// Tuns of all transformations that are done while loding class files.
+// Turns of all transformations that are done while loading class files, particularly rewriting of invokedynamic and dynamic constants.
org.opalj.br.reader {
ClassFileReader {
BytecodeOptimizer {
diff --git a/OPAL/ifds/src/main/resources/reference.conf b/OPAL/ifds/src/main/resources/reference.conf
index d1459a04b7..2556481a2c 100644
--- a/OPAL/ifds/src/main/resources/reference.conf
+++ b/OPAL/ifds/src/main/resources/reference.conf
@@ -1,4 +1,5 @@
org.opalj {
+ // Configuration for the IFDS subproject which provides a solver framework for IFDS (interprocedural, finite, distributive, subset) problems
ifds {
debug = false,
}
diff --git a/OPAL/si/src/main/resources/reference.conf b/OPAL/si/src/main/resources/reference.conf
index 55fab37324..a8ceca8c16 100644
--- a/OPAL/si/src/main/resources/reference.conf
+++ b/OPAL/si/src/main/resources/reference.conf
@@ -1,5 +1,6 @@
org.opalj {
+ // Configuration for the fixed-point computation framework, OPAL's main framework for analyses
fpcf {
PropertyStore {
// Turns on the debugging support of the property store which is primarily meant
@@ -23,11 +24,17 @@ org.opalj {
AnalysisScenario {
AnalysisAutoConfig = false
+
+ # @brief Specifies the implementation that computes the FPCF schedule
+ # @type subclass
+ # @constraint org.opalj.fpcf.scheduling.SchedulingStrategy
SchedulingStrategy = "org.opalj.fpcf.scheduling.MaximumPhaseScheduling"
+
ScheduleLazyInMultiplePhases = true
}
- }
+ }
+ // Configuration for the Static Analysis Infrastructure subproject which provides generic infrastructure for analyses
si.flowanalysis.StructuralAnalysis {
maxIterations = 1000
}
diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/IndependentPhaseMergeScheduling.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/IndependentPhaseMergeScheduling.scala
index e420612b3b..c05a363bcc 100644
--- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/IndependentPhaseMergeScheduling.scala
+++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/IndependentPhaseMergeScheduling.scala
@@ -77,7 +77,6 @@ abstract class PhaseMergeScheduling extends MultiplePhaseScheduling {
}
/**
- * Independent Phase Merge Scheduling (IPMS) Strategy.
* Merges independent batches to optimize parallelism.
*/
object IndependentPhaseMergeScheduling extends PhaseMergeScheduling {
diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala
index 246c39e490..d494219a41 100644
--- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala
+++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala
@@ -157,7 +157,6 @@ object MultiplePhaseScheduling {
}
/**
- * Maximum Phase Scheduling (MPS) Strategy.
* Breaks down computations into as many phases as possible based on dependencies and computation types.
*/
object MaximumPhaseScheduling extends MultiplePhaseScheduling {
diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/SinglePhaseScheduling.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/SinglePhaseScheduling.scala
index c3645fd8dc..ba40103ec5 100644
--- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/SinglePhaseScheduling.scala
+++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/SinglePhaseScheduling.scala
@@ -8,9 +8,10 @@ import com.typesafe.config.Config
import org.opalj.log.LogContext
/**
- * Single Phase Scheduling (SPS) Strategy.
* Schedules all computations in a single batch without considering dependencies.
*/
+object SinglePhaseScheduling extends SinglePhaseScheduling
+
abstract class SinglePhaseScheduling extends SchedulingStrategy {
override def schedule[A](ps: PropertyStore, allCS: Set[ComputationSpecification[A]])(implicit
@@ -21,5 +22,3 @@ abstract class SinglePhaseScheduling extends SchedulingStrategy {
List(computePhase(ps, allCS, Set.empty))
}
}
-
-object SinglePhaseScheduling extends SinglePhaseScheduling
diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/SmallestPhaseMergeScheduling.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/SmallestPhaseMergeScheduling.scala
index cb1a9ce6b0..1c24cc151a 100644
--- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/SmallestPhaseMergeScheduling.scala
+++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/SmallestPhaseMergeScheduling.scala
@@ -4,7 +4,6 @@ package fpcf
package scheduling
/**
- * Smallest Phase Merge Scheduling (SPS) Strategy.
* Merging batches based on the number of analyses to keep merged batches of similar sizes.
*/
object SmallestPhaseMergeScheduling extends PhaseMergeScheduling {
diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf
index de5bd44c72..42a8645f98 100644
--- a/OPAL/tac/src/main/resources/reference.conf
+++ b/OPAL/tac/src/main/resources/reference.conf
@@ -4,6 +4,7 @@ org.opalj {
},
fpcf {
registry {
+ # Mapping of analysis names to implementations to facilitate instantiation at runtime.
analyses {
"L0TACAIAnalysis" {
description = "Performs an abstract interpretation using the configured domain and then computes the 3-address code.",
@@ -143,6 +144,7 @@ org.opalj {
}
}
},
+ # Configuration options for various TAC-based analyses
analyses {
ConfigurationBasedConstructorEscapeAnalysis {
constructors = [
@@ -186,8 +188,7 @@ org.opalj {
ConfiguredPurity {
purities = [
# Native methods
- # All methods of the following types have been analyzed, methods not listed are
- # considered impure
+ # All methods of the following types have been analyzed, methods not listed are considered impure
{cf = "java/lang/Class", m = "desiredAssertionStatus0", desc = "(Ljava/lang/Class;)Z", p = "SideEffectFree"},
{cf = "java/lang/Class", m = "getPrimitiveClass", desc = "(Ljava/lang/String;)Ljava/lang/Class;", p = "SideEffectFree"},
{cf = "java/lang/Class", m = "getComponentType", desc = "()Ljava/lang/Class;", p = "Pure"},
@@ -328,6 +329,7 @@ org.opalj {
{cf = "java/lang/invoke/MethodHandle", "m" = "invokeWithArguments", "desc" = "([Ljava/lang/Object;)Ljava/lang/Object;", p = "SideEffectFree"}
]
},
+ # Models for the behavior of native or hard-to-analyze methods w.r.t. instantiated types and pointer assignments
ConfiguredNativeMethodsAnalysis {
nativeMethods = [
//////////////////////////////////////////////////////////////////////////////////////////
@@ -1566,6 +1568,7 @@ org.opalj {
},
tac.cg {
CallGraphKey {
+ # Modules (analyses for different functionalities) to be used when constructing call graphs
modules = [
"org.opalj.tac.fpcf.analyses.cg.FinalizerAnalysisScheduler",
"org.opalj.tac.fpcf.analyses.cg.LoadedClassesAnalysisScheduler",
@@ -1579,12 +1582,13 @@ org.opalj {
]
},
PointsTo {
+ # Modules (analyses for different functionalities) to be used when performing points-to analysis
modules = [
- "ConfiguredMethodsPointsTo",
- "ArraycopyPointsTo",
- "UnsafePointsTo",
- "SerializationAllocations",
- "NewInstance",
+ "org.opalj.tac.fpcf.analyses.pointsto.ConfiguredMethodsPointsTo",
+ "org.opalj.tac.fpcf.analyses.pointsto.ArraycopyPointsTo",
+ "org.opalj.tac.fpcf.analyses.pointsto.UnsafePointsTo",
+ "org.opalj.tac.fpcf.analyses.pointsto.SerializationAllocations",
+ "org.opalj.tac.fpcf.analyses.pointsto.NewInstance",
"org.opalj.tac.fpcf.analyses.pointsto.ReflectionAllocationsAnalysisScheduler",
"org.opalj.tac.fpcf.analyses.fieldaccess.TriggeredFieldAccessInformationAnalysis",
"org.opalj.tac.fpcf.analyses.fieldaccess.reflection.ReflectionRelatedFieldAccessesAnalysisScheduler",
diff --git a/README.markdown b/README.markdown
index b89de0befa..1fbaa544c0 100644
--- a/README.markdown
+++ b/README.markdown
@@ -19,6 +19,8 @@ OPAL consists of several projects:
* **Bytecode Creator** (OPAL/bc): Most basic infrastructure to engineer Java bytecode.
+* **Bytecode Assembler** (OPAL/ba): DSL for assembling Java bytecode.
+
* **Bytecode Representation** (OPAL/br): OPAL's base representation of Java bytecode. Implements all functionality to do basic analyses of Java class files.
* **Abstract Interpretation Framework** (OPAL/ai): Implementation of an abstract interpretation based framework that can be used to easily implement analyses at different levels of precision.
diff --git a/TOOLS/bp/src/main/resources/application.conf b/TOOLS/bp/src/main/resources/application.conf
index 78aced631d..88166e260c 100644
--- a/TOOLS/bp/src/main/resources/application.conf
+++ b/TOOLS/bp/src/main/resources/application.conf
@@ -1,3 +1,5 @@
+// Configuration for the BugPicker tool
+
org.opalj {
bugpicker.analysisParameter {
diff --git a/TOOLS/ce/build.sbt b/TOOLS/ce/build.sbt
new file mode 100644
index 0000000000..b511e98651
--- /dev/null
+++ b/TOOLS/ce/build.sbt
@@ -0,0 +1 @@
+// build settings reside in the opal root build.sbt file
diff --git a/TOOLS/ce/readme.md b/TOOLS/ce/readme.md
new file mode 100644
index 0000000000..5d090c3b6d
--- /dev/null
+++ b/TOOLS/ce/readme.md
@@ -0,0 +1,131 @@
+# Configuration explorer documentation
+## How to document your configs
+The configuration explorer uses a custom parser to parse flags into a browsable documentation
+This makes every element of your config documentable.
+This guide is about to teach you how to utilize these flags to create a comprehensible documentation
+
+### How do I create a browsable configuration?
+1. Start up your sbt shell
+2. run the doc command
+3. Open the generated commentedconfigs.html in your source directory
+
+### Where can I add my documentation?
+You can add your documentation in the lines before an element, or directly behind the element within the same line.
+
+```
+ // You can either add your comment to the element here
+ Value = "one" //Or you can also add your comment here
+
+ // However, you cannot add your comment here
+```
+
+Also keep in mind that HOCON allows for multiple values within one line.
+In this case, the comment will be associated with its closest neighbor that fullfills the criteria:
+
+```
+ Object = {Key = "Value", AnotherKey = "SecondValue"} // This comment will be associated with Object, since its closing bracket is closest
+```
+
+Sub-values need to be placed in its own lines in order to be documented.
+
+### Usage of flags
+
+The configuration explorer allows for different flags to be utilized for different elements of the documentation.
+Each flag will be interpreted differently and be represented in a different way after exporting.
+
+The flags can be grouped in two groups, by the point in time where they are visible when the documentation is exported.
+
+#### @label
+@label documents the label of the object.
+It will be shown in the documentation as the name of the object and will be visible even when the object is collapsed.
+If the configuration element is part of an object, the label will be automatically set as its identifier within the object.
+This is overridable by manually setting the @label flag within its documentation.
+
+```
+ {
+ key = "value" // The label property will be set to "key" automatically.
+
+ //@label Custom label
+ another_key = "value" // The label property is now overridden to "Custom label"
+ }
+```
+
+#### @brief
+@brief shows a brief description of the element for easier comprehension without the need for expanding the element.
+For optimal formatting, try to keep the length of the text behind this flag below 50 characters.
+
+#### @description
+@description will set the description of the configuration element when the element is expanded.
+Usage of the flag is optional, as unflagged content will be added to the description area too.
+
+```
+ {
+ // @description You can use this flag to add this text to the elements description.
+ // However, without any flags, the text will be added to the description too.
+ key = "value"
+ }
+```
+
+#### @type
+@type can be used to indicate the type of a value that will be used.
+
+##### Subclass type
+The subclass type is one of two special types currently implemented into Configuration Explorer
+When tagged with a subclass type, configuration explorer will search for all subclasses of a given root class.
+
+##### Enum type
+The enum type is the second of the special types in Configuration Explorer. Use it if you have a finite amount of allowed values that you all want to list in the constraints.
+
+##### Other types
+You can pick a type that you want to indicate, which logical restraints are, but they will be treated as-is and not be refined further.
+
+#### @constraint
+Use @constraint to define which values are allowed and which are not.
+If there are multiple constraints, use a new line for each constraint using the flag "@constraint" at the beginnning of each line.
+
+There are currently two types implemented where you use a special style to list constraints:
+##### Subclass type
+If your type is "subclass", then list exactly one constraint. The constraint must be the class where all allowed classes inherit from.
+Configuration Explorer will fetch all valid Subclasses of the listed class and list these in the documentation. You may specify a different value in the value field when generating the documentation.
+
+Example:
+```
+ {
+ // @description Configuration Explorer will list all subclasses of ConfigNode as allowed values. (Which are ConfigObject, ConfigList, ConfigEntry)
+ // @type subclass
+ // @constraint ConfigNode
+ value = ConfigObject
+ }
+```
+
+##### Enum type
+Create one line with the constraint flag for every allowed value that you want to list.
+
+Example:
+
+```
+ {
+ // @description Add one row for each allowed value
+ // @type enum
+ // @constraint two
+ // @constraint three
+ // @constraint five
+ // @constraint seven
+ primeNumberBelowTen = three
+ }
+```
+
+#### Other types
+The constraints for other types will be passed as-is and can be in a free text.
+
+Example:
+
+```
+ {
+ // @type int
+ // @constraint Values must be within 0 and 100
+ // @constraint Values must be even
+ key = 2
+ }
+```
+
diff --git a/TOOLS/ce/src/main/resources/ce.conf b/TOOLS/ce/src/main/resources/ce.conf
new file mode 100644
index 0000000000..71651c443f
--- /dev/null
+++ b/TOOLS/ce/src/main/resources/ce.conf
@@ -0,0 +1,65 @@
+// @brief Configuration for the config explorer
+// @description Settings under this hierarchy control the configuration explorer options
+
+{
+ org.opalj.ce {
+ // @brief Used filenames for configurations
+ // @description This setting contains a list of filenames that are in use for configuration files in this project
+ // reference.conf, application.conf are default filenames defined by the maker of typesafe configuration
+ // @type String
+ configurationFilenames = ["ce.conf","reference.conf","application.conf", "hermes.conf","CommandLineProject.conf","LibraryProject.conf","NoTransformations.conf"]
+
+ // @brief Toggle for replacing classes in the configuration documentation
+ // @description You can use this option to activate / deactivate the replacement of subclass type configuration entries
+ // When activated, ce will list all implemented subclasses able to replace a class type value
+ // @type Boolean
+ replaceSubclasses = true
+
+ // @label HTML Settings
+ // @brief These settings store the information for the HTML export
+ html = {
+ // @brief Path to the HTMLTemplate
+ // @description This setting contains a relative path from the projects root to the file that stores the HTML Stylesheet and the JavaScript components
+ // @type String
+ // @constraint must be a path to an existing html file in Linux syntax (use / instead of \)
+ template = "TOOLS/ce/src/main/resources/template.html"
+
+ // @brief Defines the headline of a config Node
+ // @description This sets the syntax for the headline of a config node.
+ // $label will be replaced with the label of the ConfigNode
+ // $brief will be replaced with the brief description of the ConfigNode
+ // This does not render correctly on the documentation due to it being an HTML expression
+ // @type String
+ // @constraint Must be a valid closed HTML expression
+ headline = "
$label $brief$details
"
+
+ brief = "- $brief "
+
+ details = "Show"
+
+ // @brief defines syntax of the content bracket
+ // @description This setting sets the syntax of the content brackets
+ // $content is a placeholder that will be replaced with the further content of the config node
+ // This does not render correctly on the documentation due to it being an HTML expression
+ // @type String
+ // @constraint Must be a valid closed HTML expression
+ // @constraint Must contain the placeholder $content
+ content = "
$content
"
+
+ // @brief Defines the export path of the commented commentedconfigs
+ // @type String
+ // @constraint must be a file name
+ export = "target/scala-$scalaVersion/unidoc/config.html"
+
+ // @brief Defines if the keys in the export should be sorted alphabetically
+ // @type boolean
+ sortAlphabetically = false
+
+ // @brief Defines how long the fallback preview of the brief description can be
+ // @description This value defines, how long the preview in the brief window can be, if there is not explicit brief description available.
+ // The number is the amount of characters displayed.
+ // @type Integer
+ maximumHeadlinePreviewLength = 95
+ }
+ }
+}
\ No newline at end of file
diff --git a/TOOLS/ce/src/main/resources/template.html b/TOOLS/ce/src/main/resources/template.html
new file mode 100644
index 0000000000..38ca9980ee
--- /dev/null
+++ b/TOOLS/ce/src/main/resources/template.html
@@ -0,0 +1,165 @@
+
+
+
+
+
+ The OPAL Framework $version
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The OPAL Framework5.0.1-SNAPSHOT< Back
+
+
+
+
+
+
+
+ $body
+
+
+
+
+
+
+
+
diff --git a/TOOLS/ce/src/main/scala/org/opalj/ce/CommentParser.scala b/TOOLS/ce/src/main/scala/org/opalj/ce/CommentParser.scala
new file mode 100644
index 0000000000..701d386ddc
--- /dev/null
+++ b/TOOLS/ce/src/main/scala/org/opalj/ce/CommentParser.scala
@@ -0,0 +1,348 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj
+package ce
+
+import java.nio.file.Path
+import scala.collection.mutable
+import scala.collection.mutable.ListBuffer
+import scala.io.Source
+import scala.util.Using
+import scala.util.control.Breaks.break
+import scala.util.control.Breaks.breakable
+
+import com.typesafe.config.ConfigFactory
+
+import org.opalj.log.GlobalLogContext
+import org.opalj.log.OPALLogger
+
+/**
+ * Parses commented config files for the Configuration Explorer.
+ */
+object CommentParser {
+ /**
+ * Made to parse multiple Configuration Files in bulk.
+ * Used in combination with the file Locator to locate and parse all config files of a project.
+ * @param filepaths accepts a list of full paths to the HOCON config files that shall be parsed.
+ * @param rootDirectory the project root directory, to allow for relative paths.
+ * @return is a Seq of the parsed configuration files, paired with the path they originate from.
+ */
+ def iterateConfigs(filepaths: Iterable[Path], rootDirectory: Path): Seq[ConfigObject] = {
+ val commentedConfigs = filepaths.map(filepath => parseComments(filepath, rootDirectory)).toList
+
+ // Merge all config files named "reference.conf"
+ val (mergingConfigs, otherConfigs) = commentedConfigs.partition(_.comment.label.endsWith("reference.conf"))
+
+ val mergedReferenceConfOpt = if (mergingConfigs.nonEmpty) {
+ val mergedConfig =
+ mergingConfigs.foldLeft(ConfigObject(
+ mutable.Map[String, ConfigNode](),
+ new DocumentationComment(
+ "reference.conf",
+ "Aggregated standard configuration of merged reference.conf files",
+ Seq(),
+ "",
+ Seq()
+ )
+ )) {
+ (accumulatedConfig, mergingConfig) => accumulatedConfig.merge(mergingConfig); accumulatedConfig
+ }
+ Some(mergedConfig)
+ } else {
+ None
+ }
+
+ val finalConfigs = otherConfigs ++ mergedReferenceConfOpt
+ finalConfigs.foreach(config => config.collapse())
+
+ finalConfigs
+ }
+
+ /**
+ * Handles the frame around parsing the configuration file.
+ * Also checks if the config files are in an allowed HOCON formats to prevent endless loops.
+ * @param filePath accepts the full path to a valid HOCON config file.
+ * @param rootDirectory the project root directory, to allow for relative paths.
+ * @return returns the parsed config as a ConfigNode.
+ */
+ def parseComments(filePath: Path, rootDirectory: Path): ConfigObject = {
+ // This prevents the Parser from parsing a file without valid syntax
+ ConfigFactory.load(filePath.toString)
+
+ OPALLogger.info("Configuration Explorer", s"Parsing: $filePath")(GlobalLogContext)
+
+ Using.resource(Source.fromFile(filePath.toFile)) { source =>
+ new HOCONParser(source.getLines()).parseFile(filePath, rootDirectory)
+ }
+ }
+
+ /**
+ * This class handles the parsing process itself
+ */
+ private class HOCONParser(var configLines: Iterator[String]) {
+ private var line = ""
+
+ /**
+ * parseComments initiates the parsing process.
+ * A ConfigNode can consist out of 3 possible types that are parsed differently: Objects, Lists and Entries.
+ * The source node of a config file always is an object.
+ * During parsing, the Parser will iterate the file and sort it into the ConfigNode structure.
+ * Since the Nodes can be nested and there can be multiple Nodes in one line, the Parser needs to examine most control structures like a stream and not linewise.
+ * @param filePath accepts the path to a valid HOCON file.
+ * @param rootDirectory the project root directory, to allow for relative paths.
+ * @return returns the fully parsed file as a configObject.
+ */
+ def parseFile(filePath: Path, rootDirectory: Path): ConfigObject = {
+ // Parse initial Comments
+ val initialComment = ListBuffer[String]()
+ initialComment += ("@label " + rootDirectory.relativize(filePath))
+ parseComments(initialComment)
+ parseObject(initialComment)
+ }
+
+ val objectKeyTerminatingChars = Set(':', '=', '{', '[')
+
+ /**
+ * Method responsible to parse Object-Type Nodes.
+ * @param currentComment assigns previously parsed comment to this Node. This is necessary as most comments appear before the opening bracket of an object (Which identifies it as an object).
+ * @return returns the fully parsed object.
+ */
+ private def parseObject(currentComment: ListBuffer[String]): ConfigObject = {
+
+ line = line.trim.stripPrefix("{")
+ // Creating necessary components
+ val entries = mutable.Map[String, ConfigNode]()
+ var nextComment = parseComments()
+ var currentKey = ""
+ var currentvalue: ConfigNode = null
+
+ // Using a breakable while loop to interrupt as soon as the object ends
+ breakable {
+ while (configLines.hasNext || line.nonEmpty) {
+ parseComments(nextComment)
+ if (line.startsWith("}")) {
+ // Found the closing bracket of the object. Remove the closing bracket and stop parsing the object
+ line = line.stripPrefix("}")
+ break();
+ } else if (line.nonEmpty) {
+ // What follows now is part of the content of the object
+ // Objects are Key Value pairs, so parsing them is a two stage job:
+ // Separating Key and value and then parsing the value
+
+ // 1. Separating Key and value
+ // In JSON, Keys and values are separated with ':'. HOCON allows substituting ':' with '=' and
+ // also allows ommitting these symbols when using a '{' or '[' to open an object/list afterward
+ // Finding first instance of these symbols
+ // TerminatingIndex is the index of the symbol that terminates the key.
+ val terminatingIndex = line.indexWhere(objectKeyTerminatingChars.contains)
+
+ // Splitting the key from the string (while splitting of the ':' or '=' as they are not needed anymore
+ currentKey = line.substring(0, terminatingIndex - 1).trim.stripPrefix("\"").stripSuffix("\"")
+ line = line.substring(terminatingIndex).stripPrefix(":").stripPrefix("=").trim
+
+ // Evaluating the type of value
+ if (line.startsWith("{")) {
+ // Case: Value is an object
+ currentvalue = parseObject(nextComment)
+ } else if (line.startsWith("[")) {
+ // Case: Value is a list
+ currentvalue = parseList(nextComment)
+ } else {
+ // Case: Value is an entry
+ currentvalue = parseEntry(nextComment)
+ }
+
+ // Json Keys are split using a ",". This is not necessary, but tolerated in HOCON syntax
+ line = line.stripPrefix(",").trim
+
+ // If there is a comment directly behind the comma, add it to comments too.
+ getSingleLineComment.foreach { comment =>
+ currentvalue.comment =
+ currentvalue.comment.mergeComment(DocumentationComment.fromString(Seq(comment)))
+ }
+
+ // Reset next comment
+ nextComment = ListBuffer[String]()
+
+ // Adding the new Key, Value pair to the Map
+ entries += ((currentKey, currentvalue))
+ }
+
+ // Proceed with the next line if the current one was fully parsed
+ while (line.isEmpty && configLines.hasNext) {
+ line = configLines.next().trim
+ }
+ }
+ }
+
+ // If there is a comment directly behind the closing bracket of the object, add it to comments too.
+ currentComment ++= getSingleLineComment
+
+ // Return the finished ConfigObject
+ ConfigObject(entries, DocumentationComment.fromString(currentComment))
+ }
+
+ val stringTerminatingChars = Set(',', ']', '}', ' ')
+
+ /**
+ * Method responsible to parse Entry-Type Nodes.
+ * @param currentComment assings previously parsed comment to this Node. This is necessary as most comments appear before the opening bracket of an object (Which identifies it as an object).
+ * @return returns the fully parsed entry.
+ */
+ private def parseEntry(currentComment: ListBuffer[String]): ConfigEntry = {
+ // Creation of necessary values
+ var value = ""
+
+ parseComments(currentComment)
+
+ if (line.startsWith("\"\"\"")) {
+ // Case: line starts with a triple quoted string (These allow for multi-line values, so the line end does not necessarily terminate the value
+ line = line.stripPrefix("\"\"\"")
+ val valueBuilder = new StringBuilder
+ var index = line.indexOf("\"\"\"")
+ if (index >= 0) {
+ // The value is a single line value
+ valueBuilder ++= line.substring(0, index)
+ line = line.substring(index).stripPrefix("\"\"\"").trim
+ } else {
+ // The value is a multi line value
+ while (index == -1 && configLines.hasNext) {
+ valueBuilder ++= s"$line\n"
+ line = configLines.next()
+ index = line.indexOf("\"\"\"")
+ }
+ if (index != -1) {
+ valueBuilder ++= s"${line.substring(0, index)}"
+ line = line.substring(index).stripPrefix("\"\"\"").trim
+ } else {
+ valueBuilder ++= s"$line\n"
+ }
+ }
+ value = valueBuilder.toString
+ } else if (line.startsWith("\"")) {
+ // Case: line starts with a double-quoted string
+ line = line.stripPrefix("\"").trim
+ // A '\' can escape a quote. Thus, we need to exclude that from the terminating Index
+ var index = line.indexOf('"')
+ while (index > 0 && line(index - 1) == '\\') {
+ index = line.indexOf('"', index + 1)
+ }
+
+ value = line.substring(0, index).replace("\\\"", "\"")
+ line = line.substring(index + 1).trim
+ } else if (line.startsWith("\'")) {
+ // Case: line starts with a single quoted string
+ line = line.stripPrefix("\'").trim
+ // A '\' can escape a quote. Thus, we need to exclude that from the terminating Index
+ var index = line.indexOf('\'')
+ while (index > 0 && line(index - 1) == '\\') {
+ index = line.indexOf('\'', index + 1)
+ }
+
+ value = line.substring(0, index).replace("\\'", "'")
+ line = line.substring(index + 1).trim
+ } else {
+ // Case: Line starts with an unquoted string
+ // There are two ways of terminating an unquoted string
+ // Option 1: The value is inside a pattern that has other control structures
+ val terminatingIndex = line.indexWhere(stringTerminatingChars.contains)
+
+ if (terminatingIndex > 0) {
+ value = line.substring(0, terminatingIndex).trim
+ line = line.stripPrefix(value).trim
+ } else {
+ // Option 2: The end of the line
+ value = line
+ line = ""
+ }
+ }
+
+ currentComment ++= getSingleLineComment
+ ConfigEntry(value, DocumentationComment.fromString(currentComment))
+ }
+
+ /**
+ * Method responsible to parse List-Type Nodes.
+ * @param currentComment assigns previously parsed comment to this Node. This is necessary as most comments appear before the opening bracket of an object (Which identifies it as an object).
+ * @return returns the fully parsed entry.
+ */
+ private def parseList(currentComment: ListBuffer[String]): ConfigList = {
+ line = line.stripPrefix("[").trim
+ // Creating necessary variables
+ val value = new ListBuffer[ConfigNode]
+ var nextComment = ListBuffer[String]()
+
+ breakable {
+ while (configLines.hasNext || line.nonEmpty) {
+ nextComment = parseComments()
+ if (line.startsWith("]") || (line.startsWith(",") && line.stripPrefix(",").trim.startsWith("]"))) {
+ // Case: The following symbol closes the list
+ line = line.stripPrefix(",").trim.stripPrefix("]").trim
+ break();
+ } else if (line.startsWith("{")) {
+ // Case: The following symbol opens an object
+ value += parseObject(nextComment)
+ } else if (line.startsWith("[")) {
+ // Case: The following symbol opens a list
+ value += parseList(nextComment)
+ } else if (line.nonEmpty) {
+ // Case: The following symbol is an entry
+ value += parseEntry(nextComment)
+ }
+ line = line.stripPrefix(",").trim
+
+ // If there is a comment directly behind the comma, add it to comments too.
+ getSingleLineComment.foreach { comment =>
+ value.last.comment =
+ value.last.comment.mergeComment(DocumentationComment.fromString(Seq(comment)))
+ }
+
+ while (line.isEmpty && configLines.hasNext) {
+ // Load next line when done
+ line = configLines.next().trim
+ }
+ }
+ }
+ currentComment ++= getSingleLineComment
+
+ ConfigList(value, DocumentationComment.fromString(currentComment))
+ }
+
+ /**
+ * Gets all comments until the next non-comment entry and writes them into a new ListBuffer.
+ * @return Returns a new ListBuffer with the content of the comment.
+ */
+ private def parseComments(): ListBuffer[String] = {
+ val comment = new ListBuffer[String]
+ parseComments(comment)
+ }
+
+ /**
+ * Gets all comments until the next non-comment entry and writes them into an existing ListBuffer.
+ * @param comment Accepts the ListBuffer that the following comment should be added to.
+ * @return Returns the existing ListBuffer, but with the content of the comment added to it.
+ */
+ private def parseComments(comment: ListBuffer[String]): ListBuffer[String] = {
+
+ while (line.startsWith("#") || line.startsWith("//") || line.isEmpty) {
+ comment ++= getSingleLineComment
+ line = configLines.next().trim
+ }
+ comment
+ }
+
+ /**
+ * Adds a single line of Comment to the raw Comment string if the line has the comment flags
+ * @return Returns the Comment text if the line is a comment. Else, returns an empty string.
+ */
+ private def getSingleLineComment: Option[String] = {
+ if (line.startsWith("#") || line.startsWith("//")) {
+ // Add the comment in the same line of the list as well
+ val currentComment = line.stripPrefix("#").stripPrefix("//").trim
+ line = ""
+ Some(currentComment)
+ } else {
+ None
+ }
+ }
+ }
+}
diff --git a/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigEntry.scala b/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigEntry.scala
new file mode 100644
index 0000000000..c039993059
--- /dev/null
+++ b/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigEntry.scala
@@ -0,0 +1,71 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj
+package ce
+
+import org.opalj.br.ClassType
+import org.opalj.br.analyses.SomeProject
+
+import org.apache.commons.text.StringEscapeUtils
+
+/**
+ * Stores a value inside the structure of the configNode.
+ *
+ * @param value is the value stored in the entry.
+ * @param comment are all the comments associated with the value.
+ */
+case class ConfigEntry(value: String, var comment: DocumentationComment) extends ConfigNode {
+
+ override def valueToHTML(exporter: HTMLExporter, pageHTML: StringBuilder)(implicit project: SomeProject): Unit = {
+ pageHTML ++= "Value: "
+ val escapedVal = StringEscapeUtils.escapeHtml4(value)
+ if (value.startsWith("org.opalj.")) {
+ if (project.classFile(ClassType(value.replace('.', '/') + "$")).isDefined) {
+ pageHTML ++= s"$escapedVal"
+ } else if (project.classFile(ClassType(value.replace('.', '/'))).isDefined) {
+ pageHTML ++= s"$escapedVal"
+ } else {
+ pageHTML ++= escapedVal
+ }
+ } else {
+ pageHTML ++= escapedVal
+ }
+ pageHTML ++= ""
+ }
+
+ /**
+ * Produces the HTML for the individual entries.
+ * @param pageHTML accepts a StringBuilder. The method adds the HTML String to this StringBuilder.
+ */
+ override protected def entriesToHTML(
+ exporter: HTMLExporter,
+ pageHTML: StringBuilder
+ )(implicit project: SomeProject): Unit = {
+ valueToHTML(exporter, pageHTML)
+ }
+
+ /**
+ * Checks if the value object is empty.
+ * @return true if both the value and the comment are empty.
+ */
+ override def isEmpty: Boolean = {
+ comment.isEmpty && value.isEmpty
+ }
+
+ /**
+ * Collapse is not needed in config Entry, due to it not having any sub-objects.
+ */
+ override def collapse(): Unit = {}
+
+ /**
+ * Expand is not needed in config Entry, due to it not having any sub-objects.
+ */
+ override def expand(): Unit = {}
+
+ /**
+ * Method for replacing a potential subclass type in the comment of the entry.
+ * @param se Accepts an initialized SubclassExtractor containing the ClassHierarchy required for a successful replacement.
+ */
+ override def replaceClasses(se: SubclassExtractor): Unit = {
+ comment = comment.replaceClasses(se)
+ }
+}
diff --git a/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigList.scala b/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigList.scala
new file mode 100644
index 0000000000..66e6af1445
--- /dev/null
+++ b/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigList.scala
@@ -0,0 +1,75 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj
+package ce
+
+import scala.collection.mutable.ListBuffer
+
+import org.opalj.br.analyses.SomeProject
+
+import org.apache.commons.text.StringEscapeUtils
+
+/**
+ * Stores a List structure inside the ConfigNode structure.
+ * @param entries contains a List of ConfigNodes.
+ * @param comment are all the comments associated with the List.
+ */
+case class ConfigList(entries: ListBuffer[ConfigNode], var comment: DocumentationComment) extends ConfigNode {
+
+ /**
+ * Produces the HTML for the individual entries.
+ * @param pageHTML accepts a StringBuilder. The method adds the HTML String to this StringBuilder.
+ */
+ protected def entriesToHTML(
+ exporter: HTMLExporter,
+ pageHTML: StringBuilder
+ )(implicit project: SomeProject): Unit = {
+ for (entry <- entries) {
+ entry.toHTML(exporter, "", pageHTML)
+ pageHTML ++= "\n"
+ }
+ }
+
+ override def valueToHTML(exporter: HTMLExporter, pageHTML: StringBuilder)(implicit project: SomeProject): Unit = {
+ pageHTML ++= "Value: [ "
+ val contentHTML = entries.map {
+ case e: ConfigEntry => StringEscapeUtils.escapeHtml4(e.value)
+ case _: ConfigObject => "{...}"
+ case _: ConfigList => "[...]"
+
+ }.mkString(", ")
+ pageHTML ++= exporter.restrictLength(contentHTML)
+ pageHTML ++= " ]"
+ }
+
+ /**
+ * Checks if the list is empty.
+ * @return true if both the List and the comment are empty.
+ */
+ override def isEmpty: Boolean = {
+ comment.isEmpty && entries.forall(_.isEmpty)
+ }
+
+ /**
+ * This method collapses the object structure by joining inheriting objects containing only one value.
+ * Inverse function of expand.
+ */
+ override def collapse(): Unit = {
+ entries.foreach(_.collapse())
+ }
+
+ /**
+ * This method expands the current object to represent all ob-objects within the structure.
+ * Inverse function of collapse (except for comments, which are not unmerged).
+ */
+ override def expand(): Unit = {
+ entries.foreach(_.expand())
+ }
+
+ /**
+ * Iterator for replacing subclass types of all members of the List.
+ * @param se Accepts an initialized SubclassExtractor containing the ClassHierarchy required for a successful replacement.
+ */
+ override def replaceClasses(se: SubclassExtractor): Unit = {
+ entries.foreach(_.replaceClasses(se))
+ }
+}
diff --git a/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigNode.scala b/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigNode.scala
new file mode 100644
index 0000000000..11f747e316
--- /dev/null
+++ b/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigNode.scala
@@ -0,0 +1,108 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj
+package ce
+
+import org.opalj.br.analyses.SomeProject
+
+import org.apache.commons.text.StringEscapeUtils
+
+/**
+ * Trait for representing the config structure
+ */
+trait ConfigNode {
+ var comment: DocumentationComment
+
+ /**
+ * Method for handling the export of the configuration structure into an HTML file.
+ * @param label required if the Object is part of another object (Writes the key of the K,V Map there instead). Overrides the label property of the Comment object. Supply an empty string if not needed.
+ * @param pageHTML accepts a StringBuilder. The method adds the HTML String to this StringBuilder.
+ */
+ def toHTML(
+ exporter: HTMLExporter,
+ label: String,
+ pageHTML: StringBuilder
+ )(implicit project: SomeProject): Unit = {
+ val labelText = StringEscapeUtils.escapeHtml4(getHeadlineText(label))
+ val brief = getBrief(exporter)
+ val labelHTML = exporter.headline.replace("$label", if (labelText.isEmpty) brief else labelText)
+
+ val headlineHTML =
+ if (labelText.isEmpty) {
+ labelHTML.replace("$brief", "")
+ } else {
+ labelHTML.replace("$brief", exporter.brief.replace("$brief", brief))
+ }
+
+ if (this.isInstanceOf[ConfigEntry] && comment.isEmpty) {
+ pageHTML ++= headlineHTML.replace("$details", "")
+ pageHTML ++= "\n"
+ } else {
+ pageHTML ++= headlineHTML.replace("$details", exporter.details)
+ pageHTML ++= "\n"
+
+ // Write value into HTML code
+ val splitContent = exporter.content.split("\\$content")
+ pageHTML ++= splitContent(0)
+ comment.toHTML(pageHTML)
+ entriesToHTML(exporter, pageHTML)
+ pageHTML ++= "\n"
+ pageHTML ++= splitContent(1)
+ }
+ }
+
+ /**
+ * Returns a text for the HTML headline entry.
+ */
+ protected def getHeadlineText(label: String): String = {
+ if (comment.label.nonEmpty) comment.label
+ else label
+ }
+
+ /**
+ * Returns an HMTL-escaped text for the brief description.
+ */
+ protected def getBrief(exporter: HTMLExporter)(implicit project: SomeProject): String = {
+ if (comment.brief.nonEmpty || comment.description.nonEmpty)
+ StringEscapeUtils.escapeHtml4(comment.getBrief(exporter))
+ else {
+ val brief = new StringBuilder()
+ valueToHTML(exporter, brief)
+ brief.toString()
+ }
+ }
+
+ def valueToHTML(exporter: HTMLExporter, pageHTML: StringBuilder)(implicit project: SomeProject): Unit
+
+ /**
+ * Produces the HTML for the individual entries.
+ * @param pageHTML accepts a StringBuilder. The method adds the HTML String to this StringBuilder.
+ */
+ protected def entriesToHTML(
+ exporter: HTMLExporter,
+ pageHTML: StringBuilder
+ )(implicit project: SomeProject): Unit
+
+ /**
+ * Checks if the configNode (and its potential child objects are empty.
+ * @return Returns true, if the ConfigNode, its comment and its childObjects are all empty. Returns false otherwise.
+ */
+ def isEmpty: Boolean
+
+ /**
+ * This method expands the current object to represent all objects within the structure.
+ * Inverse function of collapse.
+ */
+ def expand(): Unit
+
+ /**
+ * This method collapses the object structure by joining inheriting objects containing only one value.
+ * Inverse function of expand (except for comments, which are not unmerged).
+ */
+ def collapse(): Unit
+
+ /**
+ * Method for replacing a potential subclass type in the comment of the Node.
+ * @param se Accepts an initialized SubclassExtractor containing the ClassHierarchy required for a successful replacement.
+ */
+ def replaceClasses(se: SubclassExtractor): Unit
+}
diff --git a/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigObject.scala b/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigObject.scala
new file mode 100644
index 0000000000..afa9d12b11
--- /dev/null
+++ b/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigObject.scala
@@ -0,0 +1,174 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj
+package ce
+
+import scala.collection.mutable
+
+import org.opalj.br.analyses.SomeProject
+import org.opalj.log.GlobalLogContext
+import org.opalj.log.LogContext
+import org.opalj.log.OPALLogger
+
+import org.apache.commons.text.StringEscapeUtils
+
+/**
+ * Stores a List structure inside the ConfigNode structure.
+ * @param entries contains a K,V Map of ConfigNodes.
+ * @param comment are all the comments associated with the Object.
+ */
+case class ConfigObject(var entries: mutable.Map[String, ConfigNode], var comment: DocumentationComment)
+ extends ConfigNode {
+ implicit val logContext: LogContext = GlobalLogContext
+
+ /**
+ * Produces the HTML for the individual entries.
+ * @param pageHTML accepts a StringBuilder. The method adds the HTML String to this StringBuilder.
+ */
+ protected def entriesToHTML(
+ exporter: HTMLExporter,
+ pageHTML: StringBuilder
+ )(implicit project: SomeProject): Unit = {
+ def entryToHTML(key: String, entry: ConfigNode): Unit = {
+ entry.toHTML(exporter, key, pageHTML)
+ pageHTML ++= "\n"
+ }
+
+ if (exporter.sortAlphabetically) {
+ val sortedKeys = entries.keys.toSeq.sorted
+ for (key <- sortedKeys) {
+ entryToHTML(key, entries(key))
+ }
+ } else {
+ for ((key, node) <- entries) {
+ entryToHTML(key, node)
+ }
+ }
+ }
+
+ override def valueToHTML(exporter: HTMLExporter, pageHTML: StringBuilder)(implicit project: SomeProject): Unit = {
+ pageHTML ++= "Value: { "
+ val contentHTML = entries.map { case (key, node) =>
+ s"\"${StringEscapeUtils.escapeHtml4(key)}\" = " +
+ (node match {
+ case e: ConfigEntry => StringEscapeUtils.escapeHtml4(e.value)
+ case _: ConfigObject => "{...}"
+ case _: ConfigList => "[...]"
+ })
+ }.mkString(", ")
+ pageHTML ++= exporter.restrictLength(contentHTML)
+ pageHTML ++= " }"
+ }
+
+ /**
+ * Checks if the object is empty.
+ * @return true if both the Object and the comment are empty.
+ */
+ override def isEmpty: Boolean = {
+ comment.isEmpty && entries.valuesIterator.forall(_.isEmpty)
+ }
+
+ /**
+ * Merges two type compatible objects.
+ * This means that the objects are free of conflicting values and lists. Objects are allowed to overlap as long as there are no conflicts down the tree.
+ * @param insertingObject Is the object that is supposed to be merged into the executing one.
+ */
+ def merge(insertingObject: ConfigObject): Unit = {
+
+ // Expanding both objects guarantees compatible key naming syntax
+ expand()
+ insertingObject.expand()
+
+ // Insert object
+ for (kvpair @ (key, value) <- insertingObject.entries) {
+ if (entries.contains(key)) {
+ val conflicting_entry = entries.getOrElse(key, null)
+ if (conflicting_entry.isInstanceOf[ConfigObject] && value.isInstanceOf[ConfigObject]) {
+ val conflicting_child_object = conflicting_entry.asInstanceOf[ConfigObject]
+ conflicting_child_object.merge(value.asInstanceOf[ConfigObject])
+ } else {
+ OPALLogger.error("Configuration Explorer", s"Info on incompatible keys: ${key.trim}")
+ throw new IllegalArgumentException(
+ s"Unable to merge incompatible types: ${value.getClass} & ${conflicting_entry.getClass}"
+ )
+ }
+ } else {
+ OPALLogger.info("Configuration Explorer", s"No conflict detected. Inserting ${key.trim}")
+ entries += kvpair
+ }
+ }
+
+ collapse()
+ }
+
+ /**
+ * This method collapses the object structure by joining inheriting objects containing only one value.
+ * Inverse function of expand.
+ */
+ def collapse(): Unit = {
+ for ((key, value) <- entries) {
+ value.collapse()
+
+ // If the entry is a config object with exactly one child -> merge
+ value match {
+ case valueObject: ConfigObject if valueObject.entries.size == 1 =>
+ // Merge Keys
+ val (childkey, childvalue) = valueObject.entries.head
+ val newkey = key.trim + "." + childkey.trim
+
+ // Merge comments
+ childvalue.comment = value.comment.mergeComment(childvalue.comment)
+
+ // Add new object
+ entries += (newkey -> childvalue)
+
+ // Remove old object
+ entries -= key
+ case _ =>
+ }
+ }
+ }
+
+ /**
+ * This method expands the current object to represent all objects within the structure.
+ * Inverse function of collapse (except for comments, which are not unmerged).
+ */
+ def expand(): Unit = {
+ for (entry <- entries) {
+ // Expand substructure of monitored object
+ val (key, value) = entry
+ value.expand()
+
+ if (key.contains(".")) {
+ // Create expanded object
+ val Array(firstKey, remainingKey) = key.split("\\.", 2).map(_.trim)
+ val newEntry = mutable.Map[String, ConfigNode](remainingKey -> value)
+ val newObject = ConfigObject(newEntry, new DocumentationComment("", "", Seq(), "", Seq()))
+ newObject.expand()
+ if (entries.contains(firstKey)) {
+ entries(firstKey) match {
+ case configObject: ConfigObject =>
+ configObject.merge(newObject)
+ case other =>
+ // If the child object already exists and is NOT a config object, the config structure has a label conflict (Problem!)
+ throw new IllegalArgumentException(
+ s"Unable to Merge ${firstKey} due to incompatible types: ${other.getClass}"
+ )
+ }
+ } else {
+ entries += (firstKey -> newObject)
+ }
+
+ // Delete old entry from the map to avoid duplicates
+ entries -= key
+ }
+ }
+ }
+
+ /**
+ * Replaces subclass types of all members of the Object.
+ * @param se Accepts an initialized SubclassExtractor containing the ClassHierarchy required for a successful replacement.
+ */
+ override def replaceClasses(se: SubclassExtractor): Unit = {
+ entries.valuesIterator.foreach(_.replaceClasses(se))
+ }
+}
diff --git a/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigurationExplorer.scala b/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigurationExplorer.scala
new file mode 100644
index 0000000000..5017112bb7
--- /dev/null
+++ b/TOOLS/ce/src/main/scala/org/opalj/ce/ConfigurationExplorer.scala
@@ -0,0 +1,65 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj
+package ce
+
+import java.net.URL
+
+import com.typesafe.config.Config
+import com.typesafe.config.ConfigFactory
+
+import org.opalj.br.analyses.Project
+import org.opalj.log.GlobalLogContext
+import org.opalj.log.LogContext
+import org.opalj.log.OPALLogger
+
+/*
+ * Standalone configuration explorer.
+ * This is the main method that runs the configuration explorer.
+ * It creates a browsable HTML File out of the configuration files present in the entire OPAL project.
+ */
+object ConfigurationExplorer extends App {
+ implicit val logContext: LogContext = GlobalLogContext
+
+ OPALLogger.info("Configuration Explorer", "Configuration Explorer Started")
+ val buildVersion = System.getProperty("build.version", "unknown")
+
+ // Load config with default filename for this application
+ val conf = loadConfig()
+
+ val locator = new FileLocator(conf)
+ val filepaths = locator.getConfigurationPaths
+
+ // Bulk Imports all the configs
+ val configs = CommentParser.iterateConfigs(filepaths, locator.projectRoot)
+
+ implicit val project: Project[URL] =
+ Project.apply(locator.findJarArchives(buildVersion).toArray, Array(org.opalj.bytecode.JavaBase))
+
+ // Replace class type values
+ if (conf.getBoolean("org.opalj.ce.replaceSubclasses")) {
+ val se = new SubclassExtractor(project)
+ for (config <- configs) {
+ config.replaceClasses(se)
+ }
+ }
+
+ // Export
+ new HTMLExporter(conf).exportHTML(
+ configs.reverse,
+ locator.projectRoot.resolve(conf.getString("org.opalj.ce.html.template")),
+ locator.projectRoot.resolve(
+ conf.getString("org.opalj.ce.html.export").replace("$scalaVersion", util.ScalaMajorVersion)
+ ).toFile
+ )
+
+ /**
+ * Loads default configuration of the configuration explorer
+ * @return returns the configuration of the configuration explorer
+ */
+ def loadConfig(): Config = {
+ OPALLogger.info("Configuration Explorer", "Loading configuration")
+ val conf = ConfigFactory.load("ce")
+
+ conf
+ }
+}
diff --git a/TOOLS/ce/src/main/scala/org/opalj/ce/DocumentationComment.scala b/TOOLS/ce/src/main/scala/org/opalj/ce/DocumentationComment.scala
new file mode 100644
index 0000000000..59e92a0d78
--- /dev/null
+++ b/TOOLS/ce/src/main/scala/org/opalj/ce/DocumentationComment.scala
@@ -0,0 +1,153 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj
+package ce
+
+import scala.collection.mutable.ListBuffer
+
+import org.apache.commons.text.StringEscapeUtils
+
+/**
+ * Container for the comments of a config node.
+ */
+case class DocumentationComment(
+ label: String,
+ brief: String,
+ description: Seq[String],
+ datatype: String,
+ constraints: Seq[String]
+) {
+
+ def needsDetails(maxLength: Int): Boolean = {
+ datatype.nonEmpty || constraints.nonEmpty ||
+ description.nonEmpty && (brief.nonEmpty || description.size > 1 || description.head.length > maxLength)
+ }
+
+ /**
+ * Converts the Comment object into HTML syntax.
+ * @param pageHTML The method will add the export to this StringBuilder.
+ */
+ def toHTML(pageHTML: StringBuilder): Unit = {
+ if (!isEmpty) {
+ if (description.mkString("").trim.nonEmpty) {
+ pageHTML ++= "
\n"
+ }
+ }
+ }
+
+ /**
+ * Merges another comment into this comment.
+ * The datatype flag will not be merged. Reason for this is that datatypes are only used to describe entries, which cannot be merged anyways.
+ * @param comment accepts the comment that should be merged into this comment.
+ * @return returns a merged DocumentationComment.
+ */
+ def mergeComment(comment: DocumentationComment): DocumentationComment = {
+ val mergedLabel = if (label != "" && comment.label != "") {
+ s"${comment.label}.$label"
+ } else {
+ s"${comment.label}$label"
+ }
+ val mergedBrief = s"${comment.brief} $brief".trim
+ val mergedDescription = description ++ comment.description
+ val mergedConstraints = constraints ++ comment.constraints
+
+ new DocumentationComment(mergedLabel, mergedBrief, mergedDescription, "", mergedConstraints)
+ }
+
+ /**
+ * Checks if the comment is empty.
+ * @return returns true if the comment is empty but the label property (the label property is set automatically for config files.).
+ */
+ def isEmpty: Boolean = {
+ description.isEmpty && constraints.isEmpty && datatype.isEmpty
+ }
+
+ /**
+ * Method used for fetching information of the brief field.
+ * @return Returns the brief field of the DocumentationComment if it exists. If it does not exist, it returns a preview of the description.
+ */
+ def getBrief(exporter: HTMLExporter): String = {
+ if (brief.isEmpty && description.nonEmpty) {
+ val brief = exporter.restrictLength(description.head)
+ if (description.size > 1 && !brief.endsWith("...")) brief + " ..."
+ else brief
+ } else brief
+ }
+
+ /**
+ * This method is responsible for finding all subclasses to a subclass type and adds them to the constraints.
+ * @param se Accepts an initialized subclass extractor. It accesses the ClassHierarchy that was extracted by the subclass extractor and finds its subclasses within the structure.
+ * @return Returns an Updated DocumentationComment if there were classes to replace. Returns itself otherwise.
+ */
+ def replaceClasses(se: SubclassExtractor): DocumentationComment = {
+ if (datatype.equals("subclass")) {
+ // Get a Set of all subclasses
+ val root = constraints.head
+
+ // Replace Types
+ val updatedConstraints = root +: se.extractSubclasses(root)
+
+ new DocumentationComment(label, brief, description, "subclass", updatedConstraints)
+ } else {
+ this
+ }
+ }
+}
+
+/**
+ * Factory method for creating a Comment.
+ */
+object DocumentationComment {
+ /**
+ * Factory method for creating a comment.
+ * @param commentLines the raw content of the comment in the form of trimmed lines
+ * @return the Comment
+ */
+ def fromString(commentLines: scala.collection.Seq[String]): DocumentationComment = {
+ var label = ""
+ var brief = ""
+ val description = ListBuffer[String]()
+ var datatype = ""
+ val constraints = ListBuffer[String]()
+ for (line <- commentLines) {
+ if (line.startsWith("@label")) {
+ label = line.stripPrefix("@label").trim
+ } else if (line.startsWith("@brief")) {
+ brief = line.stripPrefix("@brief").trim
+ } else if (line.startsWith("@constraint")) {
+ constraints += line.stripPrefix("@constraint").trim
+ } else if (line.startsWith("@type")) {
+ datatype = line.stripPrefix("@type").trim
+ } else {
+ description += line.stripPrefix("@description").trim
+ }
+ }
+ new DocumentationComment(label, brief, description.toSeq, datatype, constraints.toSeq)
+ }
+}
diff --git a/TOOLS/ce/src/main/scala/org/opalj/ce/FileLocator.scala b/TOOLS/ce/src/main/scala/org/opalj/ce/FileLocator.scala
new file mode 100644
index 0000000000..9194ec2faf
--- /dev/null
+++ b/TOOLS/ce/src/main/scala/org/opalj/ce/FileLocator.scala
@@ -0,0 +1,109 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj
+package ce
+
+import java.io.File
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.nio.file.attribute.BasicFileAttributes
+import scala.collection.mutable.ListBuffer
+import scala.jdk.CollectionConverters._
+
+import com.typesafe.config.Config
+
+import org.opalj.log.GlobalLogContext
+import org.opalj.log.LogContext
+import org.opalj.log.OPALLogger
+
+/**
+ * File Locator aids locating the Files that the configuration Explorer needs to parse.
+ * It can locate a range of files, but its internal usage by Configuration Explorer is locating configuration Files and Jar Archives containing the current build number.
+ * @param config accepts the config of the Configuration Explorer.
+ */
+class FileLocator(config: Config) {
+ implicit val logContext: LogContext = GlobalLogContext
+
+ /**
+ * The root directory of the opal project.
+ */
+ lazy val projectRoot: Path = {
+ val subprojectDirectory = config.getString("user.dir")
+ val projectRoot = Paths.get(subprojectDirectory).getParent.getParent
+ OPALLogger.info("Configuration Explorer", s"Searching in the following directory: $projectRoot")
+ projectRoot
+ }
+
+ /**
+ * Loads the filenames of the configuration files that shall be parsed.
+ * @return is a List of the filenames that shall be parsed by the Configuration Explorer.
+ */
+ def getConfigurationFilenames: Seq[String] = {
+ val projectNames = config.getStringList("org.opalj.ce.configurationFilenames").asScala
+
+ OPALLogger.info("Configuration Explorer", "Loaded the following Filenames: ")
+ for (filename <- projectNames) {
+ OPALLogger.info("Configuration Explorer", filename)
+ }
+ projectNames.toSeq
+ }
+
+ /**
+ * Finds all files that are named after one of the configuration filenames and are NOT within the target folder structure.
+ * @return returns a List of full FilePaths to all found config files.
+ */
+ def getConfigurationPaths: Seq[Path] = {
+ val projectNames = getConfigurationFilenames
+ searchFiles(projectNames)
+ }
+
+ /**
+ * Finds all files that match the filename within the given Seq.
+ * @param filenames Accepts a List of all filenames that should be included in the result.
+ * @return returns a List of full FilePaths to all found files.
+ */
+ def searchFiles(filenames: Seq[String]): Seq[Path] = {
+ val foundFiles = ListBuffer[Path]()
+
+ Files.walkFileTree(
+ projectRoot,
+ new java.nio.file.SimpleFileVisitor[Path]() {
+ override def visitFile(file: Path, attrs: BasicFileAttributes): java.nio.file.FileVisitResult = {
+ if (filenames.contains(file.getFileName.toString) &&
+ !file.toAbsolutePath.toString.contains(s"target${File.separatorChar}scala")
+ ) {
+ foundFiles += file
+ OPALLogger.info("Configuration Explorer", s"Found file: ${file.toString}")
+ }
+ java.nio.file.FileVisitResult.CONTINUE
+ }
+ }
+ )
+ foundFiles.toSeq
+ }
+
+ /**
+ * Finds all jar archives in the project, where the file name contains the pathWildcard.
+ * @param pathWildcard accepts a String to filter the filenames of the jar archives. Will only return jar archives that contain the parameter in their file name.
+ * @return Will only return jar archives that contain the parameter in their file name and that are not in the bg-jobs folder.
+ */
+ def findJarArchives(pathWildcard: String): Seq[File] = {
+ val foundFiles = ListBuffer[File]()
+ Files.walkFileTree(
+ projectRoot,
+ new java.nio.file.SimpleFileVisitor[Path]() {
+ override def visitFile(file: Path, attrs: BasicFileAttributes): java.nio.file.FileVisitResult = {
+ if (file.getFileName.toString.endsWith(".jar") &&
+ file.getFileName.toString.contains(pathWildcard) &&
+ !file.toString.contains("bg-jobs")
+ ) {
+ foundFiles += file.toFile
+ OPALLogger.info("Configuration Explorer", s"Found file: ${file.toString}")
+ }
+ java.nio.file.FileVisitResult.CONTINUE
+ }
+ }
+ )
+ foundFiles.toSeq
+ }
+}
diff --git a/TOOLS/ce/src/main/scala/org/opalj/ce/HTMLExporter.scala b/TOOLS/ce/src/main/scala/org/opalj/ce/HTMLExporter.scala
new file mode 100644
index 0000000000..e275ad7a6c
--- /dev/null
+++ b/TOOLS/ce/src/main/scala/org/opalj/ce/HTMLExporter.scala
@@ -0,0 +1,63 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj
+package ce
+
+import java.io.File
+import java.io.PrintWriter
+import java.nio.file.Files
+import java.nio.file.Path
+import scala.io.Source
+import scala.util.Using
+
+import com.typesafe.config.Config
+
+import org.opalj.br.analyses.SomeProject
+
+/**
+ * Exports the Config structure into an HTML file.
+ */
+class HTMLExporter(config: Config) {
+ val headline = config.getString("org.opalj.ce.html.headline")
+ val brief = config.getString("org.opalj.ce.html.brief")
+ val details = config.getString("org.opalj.ce.html.details")
+ val content = config.getString("org.opalj.ce.html.content")
+ val sortAlphabetically = config.getBoolean("org.opalj.ce.html.sortAlphabetically")
+ val maximumHeadlinePreviewLength = config.getInt("org.opalj.ce.html.maximumHeadlinePreviewLength")
+
+ /**
+ * Exports the ConfigList into an HTML file.
+ * The following parameters are all read from the Configuration Explorer config, however, the CE config was not handed over due to namespace conflicts with the internally used ConfigNode.
+ * @param configList A list of parsed Configuration Files.
+ * @param templatePath A path to the HTML Template that should be used.
+ * @param config The config of the ConfigurationExplorer in order to read necessary values from it directly.
+ * @param exportFile A path to the file that the Config shall be written to.
+ */
+ def exportHTML(configList: Iterable[ConfigNode], templatePath: Path, exportFile: File)(implicit
+ project: SomeProject
+ ): Unit = {
+ val pageHTML = new StringBuilder()
+
+ // Generate HTML
+ var fileContent = ""
+ val template = Using(Source.fromFile(templatePath.toFile)) { _.mkString }.getOrElse("")
+ for (config <- configList) {
+ if (!config.isEmpty) {
+ config.toHTML(this, "", pageHTML)
+ pageHTML ++= "\n"
+ }
+ }
+ fileContent =
+ template.replaceAll("\\$version", ConfigurationExplorer.buildVersion).replace("$body", pageHTML.toString())
+
+ // Write to file
+ Files.createDirectories(exportFile.toPath.getParent)
+ Using(new PrintWriter(exportFile)) { _.write(fileContent) }
+ }
+
+ def restrictLength(text: String): String = {
+ val length = text.length
+ text.substring(0, length.min(maximumHeadlinePreviewLength)) +
+ (if (length > maximumHeadlinePreviewLength && text.charAt(maximumHeadlinePreviewLength - 1) != ' ') "..."
+ else "")
+ }
+}
diff --git a/TOOLS/ce/src/main/scala/org/opalj/ce/SubclassExtractor.scala b/TOOLS/ce/src/main/scala/org/opalj/ce/SubclassExtractor.scala
new file mode 100644
index 0000000000..527cd8eeb6
--- /dev/null
+++ b/TOOLS/ce/src/main/scala/org/opalj/ce/SubclassExtractor.scala
@@ -0,0 +1,40 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj
+package ce
+
+import scala.collection.mutable
+
+import org.opalj.bi.ACC_ABSTRACT
+import org.opalj.br.ClassHierarchy
+import org.opalj.br.ClassType
+import org.opalj.br.analyses.SomeProject
+
+/**
+ * The class subclassExtractor is a wrapper class around the bytecode representation project.
+ * It will fetch all subclasses from the opal project and prepares the bytecode hierarchies for querying.
+ * @param files accepts an array of files of the jar archives that should be included in the ClassHierarchies
+ */
+class SubclassExtractor(project: SomeProject) {
+ val classHierarchy: ClassHierarchy = project.classHierarchy
+
+ /**
+ * This method queries the extracted class hierarchies for a class.
+ * @param root accepts a string class name in dot notation. E.g. "org.opalj.ce.ConfigNode".
+ * @return returns a set of the names of all subclasses of the root subclass.
+ */
+ def extractSubclasses(root: String): Seq[String] = {
+ val results = mutable.Set[String]()
+ val rootClassType = ClassType(root.replace(".", "/"))
+ val compatibleTypes = classHierarchy.allSubclassTypes(
+ rootClassType,
+ reflexive = classHierarchy.isInterface(ClassType(root.replace(".", "/"))).isNoOrUnknown
+ )
+ for {
+ entry <- compatibleTypes
+ if project.classFile(entry).forall(cf => !ACC_ABSTRACT.isSet(cf.accessFlags))
+ } {
+ results += entry.fqn
+ }
+ results.toSeq
+ }
+}
diff --git a/TOOLS/hermes/src/main/resources/hermes.conf b/TOOLS/hermes/src/main/resources/hermes.conf
index 5913b23e4f..71677f813f 100644
--- a/TOOLS/hermes/src/main/resources/hermes.conf
+++ b/TOOLS/hermes/src/main/resources/hermes.conf
@@ -1,8 +1,4 @@
-org.opalj.br.reader.ClassFileReader {
- // Disable rewriting so queries about those features aren't mislead
- Invokedynamic.rewrite = false
- DynamicConstants.rewrite = false
-}
+// Configuration for the Hermes code query framework
org.opalj.hermes {
@@ -82,4 +78,10 @@ org.opalj.hermes {
ratio.categorySize = 1
}
}
+}
+
+org.opalj.br.reader.ClassFileReader {
+ // Disable rewriting so queries about those features aren't mislead
+ Invokedynamic.rewrite = false
+ DynamicConstants.rewrite = false
}
\ No newline at end of file
diff --git a/build.sbt b/build.sbt
index 07e8754565..3482810dad 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,5 +1,6 @@
import java.io.FileWriter
+import sbt.Keys.javaOptions
import sbt.Test
import sbtassembly.AssemblyPlugin.autoImport._
import sbtunidoc.ScalaUnidocPlugin
@@ -497,6 +498,33 @@ lazy val `Demos` = (project in file("DEVELOPING_OPAL/demos"))
.dependsOn(framework)
.configs(IntegrationTest)
+lazy val ce = `ConfigurationExplorer`
+
+lazy val `ConfigurationExplorer` = (project in file("TOOLS/ce"))
+ .settings(buildSettings: _*)
+ .settings(
+ fork := true,
+ javaOptions += s"-Dbuild.version=${version.value}",
+ name := "Configuration Explorer",
+ libraryDependencies ++= Dependencies.ce,
+ Compile / doc := {
+ // Overrides doc method to include config documentation at doc
+ val originalDoc = (Compile / doc).value
+ (Compile / compile).value
+ (Compile / run).toTask("").value
+ originalDoc
+ },
+ Compile / doc / scalacOptions ++= Opts.doc.title("OPAL - Configuration Explorer")
+ )
+ .dependsOn(
+ br % "compile->compile",
+ apk % "runtime->compile",
+ demos % "runtime->compile",
+ // bp % "runtime->compile",
+ hermes % "runtime->compile"
+ )
+ .configs(IntegrationTest)
+
/* ***************************************************************************
*
* TASKS, etc
@@ -544,8 +572,8 @@ runProjectDependencyGeneration := {
s"-u $uid:$gid"
}.getOrElse("")
- val mmd = new StringBuilder();
- mmd.append("%%{ init: { 'flowchart': { 'defaultRenderer': 'elk', 'curve': 'linear' } } }%%\n")
+ val mmd = new StringBuilder()
+ mmd.append("%%{ init: { 'flowchart': { 'defaultRenderer': 'elk', 'curve': 'linear', 'padding': 10, 'wrappingWidth': 205 } } }%%\n")
mmd.append("flowchart BT\n")
val excludedProjects = Seq("OPAL", "Validate", "Tools")
@@ -576,9 +604,10 @@ runProjectDependencyGeneration := {
for {
(subproject, ref) <- allProjects
if !excludedProjects.contains(subproject.id)
- dependency <- subproject.referenced
+ dependency <- subproject.dependencies
+ if dependency.configuration.forall(_.contains("compile->compile"))
} {
- val project = allProjects.find { case (p, r) => r == dependency }.get._1
+ val project = allProjects.find { case (p, r) => r == dependency.project }.get._1
mmd.append(s" ${subproject.id} --> ${project.id}\n")
}
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 5e9387728b..f280896d1d 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -87,4 +87,5 @@ object Dependencies {
val tools = Seq(txtmark, jacksonDF)
val hermes = Seq(txtmark, jacksonDF, javafxBase)
val apk = Seq(apkparser, scalaxml)
+ val ce = Seq(commonstext)
}