From 91cf194a943086de47e02807a5292e16d5d140f4 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Sun, 24 Jul 2022 12:59:17 +0200 Subject: [PATCH] [LL FIR] incrementally update module caches --- .../KotlinModificationTrackerFactory.kt | 37 ++++- .../KotlinStaticModificationTrackerFactory.kt | 36 ++++- .../trackers/CompositeModificationTracker.kt | 20 +++ .../structure/LLFirLibrarySessionFactory.kt | 14 +- .../LLFirNonUnderContentRootSessionFactory.kt | 4 +- .../api/fir/sessions/LLFirSessionFactory.kt | 6 +- .../api/fir/sessions/LLFirSessionProvider.kt | 15 +- .../sessions/LLFirSessionProviderStorage.kt | 140 ++++++++++++------ 8 files changed, 188 insertions(+), 84 deletions(-) create mode 100644 analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/trackers/CompositeModificationTracker.kt diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinModificationTrackerFactory.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinModificationTrackerFactory.kt index 89aa71a2a1d..e60cbf5e691 100644 --- a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinModificationTrackerFactory.kt +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinModificationTrackerFactory.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.ModificationTracker import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.analysis.project.structure.KtModule import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule @@ -62,10 +63,40 @@ public abstract class KotlinModificationTrackerFactory { * See [KotlinModificationTrackerFactory] for the definition of **OOBM**. * @see ModificationTracker */ - public abstract fun createLibrariesModificationTracker(): ModificationTracker + public abstract fun createLibrariesWideModificationTracker(): ModificationTracker + + /** + * Creates [KtModuleStateTracker] which is incremented every time [module] roots changes. + * + * @see KtModuleStateTracker + */ + public abstract fun createModuleStateTracker(module: KtModule): KtModuleStateTracker + @TestOnly public abstract fun incrementModificationsCount() + + public companion object { + public fun getService(project: Project): KotlinModificationTrackerFactory = + project.getService(KotlinModificationTrackerFactory::class.java) + } +} + +/** + * Represents current state of [KtModule] validity, can be created via [org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory.createModuleStateTracker] + */ +public interface KtModuleStateTracker { + /** + * If the module is still valid (i.e., it was not removed) + */ + public val isValid: Boolean + + /** + * Represents modification tracker of modified roots similar to the [ModificationTracker]. + * + * If the [isValid] == `false`, when behaviour is unspecified + */ + public val rootModificationCount: Long } /** @@ -94,6 +125,6 @@ public fun KtSourceModule.createModuleWithoutDependenciesOutOfBlockModificationT * See [KotlinModificationTrackerFactory] for the definition of **OOBM**. * @see ModificationTracker */ -public fun Project.createLibrariesModificationTracker(): ModificationTracker = +public fun Project.createAllLibrariesModificationTracker(): ModificationTracker = ServiceManager.getService(this, KotlinModificationTrackerFactory::class.java) - .createLibrariesModificationTracker() + .createLibrariesWideModificationTracker() diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticModificationTrackerFactory.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticModificationTrackerFactory.kt index 955a6472a41..854fc36b083 100644 --- a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticModificationTrackerFactory.kt +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticModificationTrackerFactory.kt @@ -8,13 +8,17 @@ package org.jetbrains.kotlin.analysis.providers.impl import com.intellij.openapi.util.ModificationTracker import com.intellij.openapi.util.SimpleModificationTracker import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.analysis.project.structure.KtBinaryModule +import org.jetbrains.kotlin.analysis.project.structure.KtModule import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule import org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory +import org.jetbrains.kotlin.analysis.providers.KtModuleStateTracker public class KotlinStaticModificationTrackerFactory : KotlinModificationTrackerFactory() { private val projectWide = SimpleModificationTracker() - private val library = SimpleModificationTracker() - private val forModule = mutableMapOf() + private val librariesWide = SimpleModificationTracker() + private val moduleOutOfBlock = mutableMapOf() + private val moduleState = mutableMapOf() override fun createProjectWideOutOfBlockModificationTracker(): ModificationTracker { return projectWide @@ -22,17 +26,35 @@ public class KotlinStaticModificationTrackerFactory : KotlinModificationTrackerF override fun createModuleWithoutDependenciesOutOfBlockModificationTracker(module: KtSourceModule): ModificationTracker { - return forModule.getOrPut(module) { SimpleModificationTracker() } + return moduleOutOfBlock.getOrPut(module) { SimpleModificationTracker() } } - override fun createLibrariesModificationTracker(): ModificationTracker { - return library + override fun createLibrariesWideModificationTracker(): ModificationTracker { + return librariesWide + } + + override fun createModuleStateTracker(module: KtModule): KtModuleStateTracker { + return moduleState.getOrPut(module) { KtModuleStateTrackerImpl() } } @TestOnly override fun incrementModificationsCount() { projectWide.incModificationCount() - library.incModificationCount() - forModule.values.forEach { it.incModificationCount() } + librariesWide.incModificationCount() + moduleOutOfBlock.values.forEach { it.incModificationCount() } + moduleState.values.forEach { it.incModificationCount() } + } +} + +private class KtModuleStateTrackerImpl: KtModuleStateTracker { + override val isValid: Boolean get() = true + + private var _rootModificationCount = 0L + + override val rootModificationCount: Long get() = _rootModificationCount + + @TestOnly + fun incModificationCount() { + _rootModificationCount++ } } \ No newline at end of file diff --git a/analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/trackers/CompositeModificationTracker.kt b/analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/trackers/CompositeModificationTracker.kt new file mode 100644 index 00000000000..0665ceebc76 --- /dev/null +++ b/analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/trackers/CompositeModificationTracker.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.analysis.utils.trackers + +import com.intellij.openapi.util.ModificationTracker + +public class CompositeModificationTracker private constructor(private val trackers: List) : ModificationTracker { + override fun getModificationCount(): Long = trackers.sumOf { it.modificationCount } + + public companion object { + public fun create(trackers: List): ModificationTracker = when (trackers.size) { + 0 -> ModificationTracker.NEVER_CHANGED + 1 -> trackers.single() + else -> CompositeModificationTracker(trackers) + } + } +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/project/structure/LLFirLibrarySessionFactory.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/project/structure/LLFirLibrarySessionFactory.kt index e1c8f25cc87..b50b4893569 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/project/structure/LLFirLibrarySessionFactory.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/project/structure/LLFirLibrarySessionFactory.kt @@ -9,9 +9,9 @@ import com.intellij.openapi.project.Project import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirFirClassByPsiClassProvider import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirLibrarySessionProvider import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirLibrarySession +import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSession import org.jetbrains.kotlin.analysis.project.structure.KtBinaryModule -import org.jetbrains.kotlin.analysis.providers.createLibrariesModificationTracker -import org.jetbrains.kotlin.analysis.utils.caches.softCachedValue +import org.jetbrains.kotlin.analysis.project.structure.KtModule import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl import org.jetbrains.kotlin.fir.PrivateSessionConstructor import org.jetbrains.kotlin.fir.SessionConfiguration @@ -28,22 +28,16 @@ import org.jetbrains.kotlin.fir.session.registerJavaSpecificResolveComponents import org.jetbrains.kotlin.fir.session.registerModuleData import org.jetbrains.kotlin.fir.symbols.FirPhaseManager import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver -import java.util.concurrent.ConcurrentHashMap -import org.jetbrains.kotlin.analysis.utils.caches.getValue @OptIn(PrivateSessionConstructor::class, SessionConfiguration::class) internal class LLFirLibrarySessionFactory( private val project: Project, ) { - private val librarySessionByModule by softCachedValue(project, project.createLibrariesModificationTracker()) { - ConcurrentHashMap() - } - fun getLibrarySession(ktBinaryModule: KtBinaryModule): LLFirLibrarySession { - return librarySessionByModule.getOrPut(ktBinaryModule) { createModuleLibrariesSession(ktBinaryModule) } + fun getLibrarySession(ktBinaryModule: KtBinaryModule, sessionsCache: MutableMap): LLFirLibrarySession { + return sessionsCache.getOrPut(ktBinaryModule) { createModuleLibrariesSession(ktBinaryModule) } as LLFirLibrarySession } - private fun createModuleLibrariesSession( ktLibraryModule: KtBinaryModule, ): LLFirLibrarySession { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/project/structure/LLFirNonUnderContentRootSessionFactory.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/project/structure/LLFirNonUnderContentRootSessionFactory.kt index 03516a99be3..17a841c974d 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/project/structure/LLFirNonUnderContentRootSessionFactory.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/project/structure/LLFirNonUnderContentRootSessionFactory.kt @@ -15,7 +15,6 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirProvider import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirNonUnderContentRootSession import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirResolvableModuleSession import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSessionInvalidator -import org.jetbrains.kotlin.analysis.project.structure.KtModule import org.jetbrains.kotlin.analysis.project.structure.KtNotUnderContentRootModule import org.jetbrains.kotlin.analysis.providers.createDeclarationProvider import org.jetbrains.kotlin.analysis.providers.createPackageProvider @@ -41,9 +40,8 @@ internal class LLFirNonUnderContentRootSessionFactory(private val project: Proje fun getNonUnderContentRootSession( module: KtNotUnderContentRootModule, - sessions: MutableMap ): LLFirResolvableModuleSession { - return cache.getOrPut(module) { createSession(module, LLFirSessionInvalidator { cache.clear() }) }.also { sessions[module] = it } + return cache.getOrPut(module) { createSession(module, LLFirSessionInvalidator { cache.clear() }) } } @OptIn(PrivateSessionConstructor::class, SessionConfiguration::class) diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionFactory.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionFactory.kt index db7161d71ae..d18fc87c0db 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionFactory.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionFactory.kt @@ -49,7 +49,7 @@ internal object LLFirSessionFactory { module: KtSourceModule, globalResolveComponents: LLFirGlobalResolveComponents, sessionInvalidator: LLFirSessionInvalidator, - sessionsCache: MutableMap, + sessionsCache: MutableMap, librariesSessionFactory: LLFirLibrarySessionFactory, configureSession: (LLFirSession.() -> Unit)? = null ): LLFirSourcesSession { @@ -114,7 +114,7 @@ internal object LLFirSessionFactory { module.directRegularDependencies.mapNotNullTo(this) { dependency -> when (dependency) { is KtBuiltinsModule -> null // build in is already added - is KtBinaryModule -> LLFirLibrarySessionFactory.getInstance(project).getLibrarySession(dependency) + is KtBinaryModule -> LLFirLibrarySessionFactory.getInstance(project).getLibrarySession(dependency, sessionsCache) is KtSourceModule -> { createSourcesSession( project, @@ -164,7 +164,7 @@ internal object LLFirSessionFactory { globalComponents: LLFirGlobalResolveComponents, sessionInvalidator: LLFirSessionInvalidator, builtinSession: LLFirBuiltinsAndCloneableSession, - sessionsCache: MutableMap, + sessionsCache: MutableMap, languageVersionSettings: LanguageVersionSettings = LanguageVersionSettingsImpl.DEFAULT, configureSession: (LLFirSession.() -> Unit)? = null ): LLFirLibraryOrLibrarySourceResolvableModuleSession { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProvider.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProvider.kt index 6bd065cb8fa..d5ceb1488d2 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProvider.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProvider.kt @@ -19,24 +19,19 @@ import org.jetbrains.kotlin.fir.resolve.providers.dependenciesSymbolProvider class LLFirSessionProvider internal constructor( val project: Project, internal val rootModuleSession: LLFirResolvableModuleSession, - private val moduleToResolvableSession: Map + private val ktModuleToSession: Map ) : FirSessionProvider() { - - private val moduleToSession = moduleToResolvableSession + moduleToResolvableSession.values.flatMap { module -> - (module.dependenciesSymbolProvider as LLFirDependentModuleProviders).dependentSessions - }.associateBy { it.ktModule } - override fun getSession(moduleData: FirModuleData): LLFirSession { requireIsInstance(moduleData) return getResolvableSession(moduleData.ktModule) } fun getSession(module: KtModule): LLFirSession = - moduleToSession.getValue(module) + ktModuleToSession.getValue(module) fun getResolvableSession(module: KtModule): LLFirResolvableModuleSession = - moduleToResolvableSession.getValue(module) + ktModuleToSession.getValue(module) as LLFirResolvableModuleSession - val allSessions: Collection - get() = moduleToResolvableSession.values + val allSessions: Collection + get() = ktModuleToSession.values } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt index b029110f631..c9b4da23e4f 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt @@ -18,10 +18,13 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.llFirMo import org.jetbrains.kotlin.analysis.low.level.api.fir.util.addValueFor import org.jetbrains.kotlin.analysis.low.level.api.fir.util.executeWithoutPCE import org.jetbrains.kotlin.analysis.project.structure.* -import org.jetbrains.kotlin.analysis.providers.createModuleWithoutDependenciesOutOfBlockModificationTracker +import org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory +import org.jetbrains.kotlin.analysis.providers.KtModuleStateTracker +import org.jetbrains.kotlin.analysis.utils.trackers.CompositeModificationTracker class LLFirSessionProviderStorage(val project: Project) { - private val sessionsCache = LLFirSessionsCache() + private val sourceAsUseSiteSessionCache = LLFirSessionsCache() + private val libraryAsUseSiteSessionCache = LLFirSessionsCache() private val librariesSessionFactory = LLFirLibrarySessionFactory.getInstance(project) private val builtInsSessionFactory = LLFirBuiltinsSessionFactory.getInstance(project) @@ -31,46 +34,64 @@ class LLFirSessionProviderStorage(val project: Project) { fun getSessionProvider( useSiteKtModule: KtModule, configureSession: (LLFirSession.() -> Unit)? = null - ): LLFirSessionProvider { - val (sessions, session) = sessionsCache.withMappings(project) { mappings -> - val sessions = mutableMapOf().apply { putAll(mappings) } - val session = executeWithoutPCE { - when (useSiteKtModule) { - is KtSourceModule -> { - LLFirSessionFactory.createSourcesSession( - project, - useSiteKtModule, - globalComponents, - sessionsCache.sessionInvalidator, - sessions, - librariesSessionFactory, - configureSession = configureSession, - ) - } - - is KtLibraryModule, is KtLibrarySourceModule -> { - LLFirSessionFactory.createLibraryOrLibrarySourceResolvableSession( - project, - useSiteKtModule, - globalComponents, - sessionsCache.sessionInvalidator, - builtInsSessionFactory.getBuiltinsSession(useSiteKtModule.platform), - sessions, - configureSession = configureSession, - ) - } - - is KtNotUnderContentRootModule -> - LLFirNonUnderContentRootSessionFactory.getInstance(project) - .getNonUnderContentRootSession(useSiteKtModule, sessions) - - else -> error("Unexpected ${useSiteKtModule::class.simpleName}") - } - + ): LLFirSessionProvider = executeWithoutPCE { + when (useSiteKtModule) { + is KtSourceModule -> { + createSessionProviderForSourceSession(useSiteKtModule, configureSession) } + + is KtLibraryModule, is KtLibrarySourceModule -> { + createSessionProviderForLibraryOrLibrarySource(useSiteKtModule, configureSession) + } + + is KtNotUnderContentRootModule -> { + val session = LLFirNonUnderContentRootSessionFactory.getInstance(project) + .getNonUnderContentRootSession(useSiteKtModule) + LLFirSessionProvider(project, session, mapOf(useSiteKtModule to session)) + } + + else -> error("Unexpected ${useSiteKtModule::class.simpleName}") + } + } + + private fun createSessionProviderForSourceSession( + useSiteKtModule: KtSourceModule, + configureSession: (LLFirSession.() -> Unit)? + ): LLFirSessionProvider { + val (sessions, session) = sourceAsUseSiteSessionCache.withMappings(project) { mappings -> + val sessions = mutableMapOf().apply { putAll(mappings) } + val session = LLFirSessionFactory.createSourcesSession( + project, + useSiteKtModule, + globalComponents, + libraryAsUseSiteSessionCache.sessionInvalidator, + sessions, + librariesSessionFactory, + configureSession = configureSession, + ) sessions to session } + return LLFirSessionProvider(project, session, sessions) + } + + private fun createSessionProviderForLibraryOrLibrarySource( + useSiteKtModule: KtModule, + configureSession: (LLFirSession.() -> Unit)? + ): LLFirSessionProvider { + val (sessions, session) = libraryAsUseSiteSessionCache.withMappings(project) { mappings -> + val sessions = mutableMapOf().apply { putAll(mappings) } + val session = LLFirSessionFactory.createLibraryOrLibrarySourceResolvableSession( + project, + useSiteKtModule, + globalComponents, + libraryAsUseSiteSessionCache.sessionInvalidator, + builtInsSessionFactory.getBuiltinsSession(useSiteKtModule.platform), + sessions, + configureSession = configureSession, + ) + sessions to session + } return LLFirSessionProvider(project, session, sessions) } } @@ -86,20 +107,22 @@ private class LLFirSessionsCache { inline fun withMappings( project: Project, - action: (Map) -> Pair, R> - ): Pair, R> { + action: (Map) -> Pair, R> + ): Pair, R> { val (newMappings, result) = action(getSessions().mapValues { it.value }) mappings = newMappings.mapValues { FirSessionWithModificationTracker(project, it.value) }.toPersistentMap() return newMappings to result } @OptIn(ExperimentalStdlibApi::class) - private fun getSessions(): Map = buildMap { + private fun getSessions(): Map = buildMap { val sessions = mappings.values val wasSessionInvalidated = sessions.associateWithTo(hashMapOf()) { false } val reversedDependencies = sessions.reversedDependencies { session -> - session.firSession.ktModule.directRegularDependencies.mapNotNull { mappings[it] } + if (session.validityTracker.isValid) { + session.firSession.ktModule.directRegularDependencies.mapNotNull { mappings[it] } + } else emptyList() } fun markAsInvalidWithDfs(session: FirSessionWithModificationTracker) { @@ -136,13 +159,31 @@ private class LLFirSessionsCache { private class FirSessionWithModificationTracker( project: Project, - val firSession: LLFirResolvableModuleSession, + val firSession: LLFirSession, ) { - private val modificationTracker = - when (val ktModule = firSession.llFirModuleData.ktModule) { - is KtSourceModule -> ktModule.createModuleWithoutDependenciesOutOfBlockModificationTracker(project) - else -> ModificationTracker.NEVER_CHANGED + + val validityTracker: KtModuleStateTracker + private val modificationTracker: ModificationTracker + + init { + val trackerFactory = KotlinModificationTrackerFactory.getService(project) + val ktModule = firSession.llFirModuleData.ktModule + + validityTracker = trackerFactory.createModuleStateTracker(ktModule) + + val outOfBlockTracker = when (ktModule) { + is KtSourceModule -> trackerFactory.createModuleWithoutDependenciesOutOfBlockModificationTracker(ktModule) + else -> null } + modificationTracker = CompositeModificationTracker.create( + listOfNotNull( + outOfBlockTracker, + object : ModificationTracker { + override fun getModificationCount() = validityTracker.rootModificationCount + } + ) + ) + } private val timeStamp = modificationTracker.modificationCount @@ -154,5 +195,8 @@ private class FirSessionWithModificationTracker( isInvalidated = true } - val isValid: Boolean get() = !isInvalidated && modificationTracker.modificationCount == timeStamp + val isValid: Boolean + get() = validityTracker.isValid + && !isInvalidated + && modificationTracker.modificationCount == timeStamp } \ No newline at end of file