simplify DirectoryBasedStorage — only one component could be stored

add test
This commit is contained in:
Vladimir Krivosheev
2015-09-05 08:22:33 +02:00
parent 6a3e5acad5
commit 9800b7ab97
20 changed files with 337 additions and 341 deletions
@@ -15,7 +15,7 @@
*/
package com.intellij.configurationStore
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.components.StateSplitter
import com.intellij.openapi.components.StateSplitterEx
import com.intellij.openapi.components.StateStorage
@@ -29,7 +29,6 @@ import com.intellij.openapi.util.Pair
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ArrayUtil
import com.intellij.util.LineSeparator
import com.intellij.util.SmartList
import com.intellij.util.SystemProperties
@@ -41,321 +40,199 @@ import java.io.FileNotFoundException
import java.io.IOException
import java.nio.ByteBuffer
open class DirectoryBasedStorage(private val myPathMacroSubstitutor: TrackingPathMacroSubstitutor?, private val myDir: File, private val mySplitter: StateSplitter) : StateStorageBase<Map<String, StateMap>>() {
open class DirectoryBasedStorage(private val dir: File,
private val splitter: StateSplitter,
private val pathMacroSubstitutor: TrackingPathMacroSubstitutor? = null) : StateStorageBase<StateMap>() {
private volatile var virtualFile: VirtualFile? = null
public fun setVirtualDir(dir: VirtualFile?) {
private var componentName: String? = null
fun setVirtualDir(dir: VirtualFile?) {
virtualFile = dir
}
override fun analyzeExternalChangesAndUpdateIfNeed(componentNames: MutableSet<String>) {
// todo reload only changed file, compute diff
val oldData = storageDataRef.get()
val newData = loadData()
storageDataRef.set(newData)
if (oldData == null) {
componentNames.addAll(newData.keySet())
if (componentName != null) {
componentNames.add(componentName!!)
}
}
override fun getSerializedState(storageData: StateMap, component: Any?, componentName: String, archive: Boolean): Element? {
this.componentName = componentName
val state = Element(FileStorageCoreUtil.COMPONENT)
if (storageData.isEmpty()) {
return state
}
if (splitter is StateSplitterEx) {
for (fileName in storageData.keys()) {
val subState = storageData.getState(fileName, archive) ?: return null
splitter.mergeStateInto(state, subState)
}
}
else {
componentNames.addAll(oldData.keySet())
componentNames.addAll(newData.keySet())
val subElements = SmartList<Element>()
for (fileName in storageData.keys()) {
val subState = storageData.getState(fileName, archive) ?: return null
subElements.add(subState)
}
if (!subElements.isEmpty()) {
splitter.mergeStatesInto(state, subElements.toTypedArray())
}
}
return state
}
override fun getSerializedState(storageData: Map<String, StateMap>, component: Any?, componentName: String, archive: Boolean) = getCompositeStateAndArchive(storageData, componentName, mySplitter)
override fun loadData(): MutableMap<String, StateMap> {
return fromMap(DirectoryStorageUtil.loadFrom(getVirtualFile(), myPathMacroSubstitutor))
}
override fun loadData() = StateMap.fromMap(DirectoryStorageUtil.loadFrom(getVirtualFile(), pathMacroSubstitutor))
private fun getVirtualFile(): VirtualFile? {
var virtualFile = virtualFile
if (virtualFile == null) {
virtualFile = LocalFileSystem.getInstance().findFileByIoFile(myDir)
virtualFile = virtualFile
var result = virtualFile
if (result == null) {
result = LocalFileSystem.getInstance().findFileByIoFile(dir)
virtualFile = result
}
return virtualFile
return result
}
override fun startExternalization(): StateStorage.ExternalizationSession? {
return if (checkIsSavingDisabled()) null else MySaveSession(this, getStorageData())
}
override fun startExternalization(): StateStorage.ExternalizationSession? = if (checkIsSavingDisabled()) null else MySaveSession(this, getStorageData())
private class MySaveSession(private val storage: DirectoryBasedStorage, private val originalStates: Map<String, StateMap>) : SaveSessionBase() {
private var copiedStorageData: MutableMap<String, MutableMap<String, Any>>? = null
private class MySaveSession(private val storage: DirectoryBasedStorage, private val originalStates: StateMap) : SaveSessionBase() {
private var copiedStorageData: MutableMap<String, Any>? = null
private val dirtyFileNames = SmartHashSet<String>()
private val removedFileNames = SmartHashSet<String>()
private var someFileRemoved = false
private fun getFileNames(states: Map<String, StateMap>, componentName: String): Array<String> {
val fileToState = states.get(componentName)
return if (fileToState == null || fileToState.isEmpty()) ArrayUtil.EMPTY_STRING_ARRAY else fileToState.keys()
}
override fun setSerializedState(componentName: String, element: Element?) {
storage.componentName = componentName
override fun setSerializedState(component: Any, componentName: String, element: Element?) {
removedFileNames.addAll(getFileNames(originalStates, componentName))
if (JDOMUtil.isEmpty(element)) {
doSetState(componentName, null, null)
if (copiedStorageData != null) {
copiedStorageData!!.clear()
}
else if (!originalStates.isEmpty()) {
copiedStorageData = THashMap<String, Any>()
}
}
else {
for (pair in storage.mySplitter.splitState(element)) {
removedFileNames.remove(pair.second)
doSetState(componentName, pair.second, pair.first)
val stateAndFileNameList = storage.splitter.splitState(element!!)
for (pair in stateAndFileNameList) {
doSetState(pair.second, pair.first)
}
if (!removedFileNames.isEmpty()) {
for (fileName in removedFileNames) {
doSetState(componentName, fileName, null)
outerLoop@
for (key in originalStates.keys()) {
for (pair in stateAndFileNameList) {
if (pair.second == key) {
continue@outerLoop
}
}
if (copiedStorageData == null) {
copiedStorageData = originalStates.toMutableMap()
}
someFileRemoved = true
copiedStorageData!!.remove(key)
}
}
}
private fun doSetState(componentName: String, fileName: String?, subState: Element?) {
private fun doSetState(fileName: String, subState: Element) {
if (copiedStorageData == null) {
copiedStorageData = setStateAndCloneIfNeed(componentName, fileName, subState, originalStates)
if (copiedStorageData != null && fileName != null) {
copiedStorageData = setStateAndCloneIfNeed(fileName, subState, originalStates)
if (copiedStorageData != null) {
dirtyFileNames.add(fileName)
}
}
else if (DirectoryBasedStorage.setState(copiedStorageData!!, componentName, fileName, subState) != null && fileName != null) {
else if (updateState(copiedStorageData!!, fileName, subState)) {
dirtyFileNames.add(fileName)
}
}
override fun createSaveSession(): StateStorage.SaveSession? {
return if (storage.checkIsSavingDisabled() || copiedStorageData == null) null else this
}
override fun createSaveSession() = if (storage.checkIsSavingDisabled() || copiedStorageData == null) null else this
override fun save() {
val stateMap = StateMap.fromMap(copiedStorageData!!)
var dir = storage.getVirtualFile()
if (copiedStorageData!!.isEmpty()) {
if (dir != null && dir.exists()) {
deleteFile(this, dir)
}
storage.setStorageData(copiedStorageData!!)
storage.setStorageData(stateMap)
return
}
if (dir == null || !dir.isValid()) {
dir = createDir(storage.myDir, this)
dir = createDir(storage.dir, this)
storage.virtualFile = dir
}
if (!dirtyFileNames.isEmpty()) {
saveStates(dir)
saveStates(dir, stateMap)
}
if (dir.exists() && !removedFileNames.isEmpty()) {
if (someFileRemoved && dir.exists()) {
deleteFiles(dir)
}
storage.setStorageData(copiedStorageData!!)
storage.setStorageData(stateMap)
}
private fun saveStates(dir: VirtualFile) {
private fun saveStates(dir: VirtualFile, states: StateMap) {
val storeElement = Element(FileStorageCoreUtil.COMPONENT)
for (componentNameToFileNameToStates in copiedStorageData!!.entrySet()) {
for (entry in componentNameToFileNameToStates.getValue().entrySet()) {
val fileName = entry.getKey()
if (!dirtyFileNames.contains(fileName)) {
continue
}
for (fileName in states.keys()) {
if (!dirtyFileNames.contains(fileName)) {
continue
}
var element: Element? = null
try {
element = StateMap.stateToElement(fileName, entry.getValue())
storage.myPathMacroSubstitutor?.collapsePaths(element)
var element: Element? = null
try {
element = states.getElement(fileName, null)
storage.pathMacroSubstitutor?.collapsePaths(element)
storeElement.setAttribute(FileStorageCoreUtil.NAME, componentNameToFileNameToStates.getKey())
storeElement.addContent(element)
storeElement.setAttribute(FileStorageCoreUtil.NAME, storage.componentName!!)
storeElement.addContent(element)
val file = getFile(fileName, dir, this)
// we don't write xml prolog due to historical reasons (and should not in any case)
writeFile(null, this, file, storeElement, LineSeparator.fromString(if (file.exists()) loadFile(file).second else SystemProperties.getLineSeparator()), false)
}
catch (e: IOException) {
LOG.error(e)
}
finally {
if (element != null) {
element.detach()
}
val file = getFile(fileName, dir, this)
// we don't write xml prolog due to historical reasons (and should not in any case)
writeFile(null, this, file, storeElement, LineSeparator.fromString(if (file.exists()) loadFile(file).second else SystemProperties.getLineSeparator()), false)
}
catch (e: IOException) {
LOG.error(e)
}
finally {
if (element != null) {
element.detach()
}
}
}
}
throws(IOException::class)
private fun deleteFiles(dir: VirtualFile) {
val token = WriteAction.start()
try {
runWriteAction {
for (file in dir.getChildren()) {
if (removedFileNames.contains(file.getName())) {
val fileName = file.getName()
if (fileName.endsWith(FileStorageCoreUtil.DEFAULT_EXT) && !copiedStorageData!!.containsKey(fileName)) {
try {
file.delete(this)
}
catch (e: FileNotFoundException) {
throw ReadOnlyModificationException(file, e, null)
}
}
}
}
finally {
token.finish()
}
}
}
private fun setStorageData(newStates: Map<String, Map<String, Any>>) {
storageDataRef.set(fromMap(newStates))
private fun setStorageData(newStates: StateMap) {
storageDataRef.set(newStates)
}
override fun hasState(storageData: Map<String, StateMap>, componentName: String) = storageData.get(componentName)?.hasStates() ?: false
companion object {
fun setState(states: MutableMap<String, MutableMap<String, Any>>, componentName: String, fileName: String?, newState: Element?): Any? {
var fileToState = states.get(componentName)
if (fileName == null || newState == null || JDOMUtil.isEmpty(newState)) {
if (fileToState == null) {
return null
}
else if (fileName == null) {
return states.remove(componentName)
}
else {
val oldState = fileToState.remove(fileName)
if (fileToState.isEmpty()) {
states.remove(componentName)
}
return oldState
}
}
if (fileToState == null) {
fileToState = THashMap<String, Any>()
fileToState.put(fileName, newState)
states.put(componentName, fileToState)
}
else {
val oldState = fileToState.get(fileName)
var newBytes: ByteArray? = null
if (oldState is Element) {
if (JDOMUtil.areElementsEqual(oldState as Element?, newState)) {
return null
}
}
else if (oldState != null) {
newBytes = StateMap.getNewByteIfDiffers(fileName, newState, oldState as ByteArray) ?: return null
}
fileToState.put(fileName, newBytes ?: newState)
}
return newState
}
public fun Map<String, StateMap>.toMutableMap(): MutableMap<String, MutableMap<String, Any>> {
val map = THashMap<String, MutableMap<String, Any>>(size())
for (entry in entrySet()) {
map.put(entry.getKey(), entry.getValue().toMutableMap())
}
return map
}
fun fromMap(map: Map<String, Map<String, Any>>): MutableMap<String, StateMap> {
val states = THashMap<String, StateMap>(map.size())
for (entry in map.entrySet()) {
states.put(entry.getKey(), StateMap.fromMap(entry.getValue()))
}
return states
}
private fun getCompositeStateAndArchive(states: Map<String, StateMap>, componentName: String, SuppressWarnings("deprecation") splitter: StateSplitter): Element? {
val fileToState = states.get(componentName)
val state = Element(FileStorageCoreUtil.COMPONENT)
if (fileToState == null || fileToState.isEmpty()) {
return state
}
if (splitter is StateSplitterEx) {
for (fileName in fileToState.keys()) {
val subState = fileToState.getStateAndArchive(fileName) ?: return null
splitter.mergeStateInto(state, subState)
}
}
else {
val subElements = SmartList<Element>()
for (fileName in fileToState.keys()) {
val subState = fileToState.getStateAndArchive(fileName) ?: return null
subElements.add(subState)
}
if (!subElements.isEmpty()) {
splitter.mergeStatesInto(state, subElements.toTypedArray())
}
}
return state
}
public fun setStateAndCloneIfNeed(componentName: String, fileName: String?, newState: Element?, oldStates: Map<String, StateMap>): MutableMap<String, MutableMap<String, Any>>? {
val fileToState = oldStates.get(componentName)
val oldState: Any? = if (fileToState == null || fileName == null) null else fileToState.get(fileName)
if (fileName == null || newState == null || JDOMUtil.isEmpty(newState)) {
if (fileName == null) {
if (fileToState == null) {
return null
}
}
else if (oldState == null) {
return null
}
val newStorageData = oldStates.toMutableMap()
if (fileName == null) {
newStorageData.remove(componentName)
}
else {
val clonedFileToState = newStorageData.get(componentName)
if (clonedFileToState!!.size() == 1) {
newStorageData.remove(componentName)
}
else {
clonedFileToState.remove(fileName)
if (clonedFileToState.isEmpty()) {
newStorageData.remove(componentName)
}
}
}
return newStorageData
}
var newBytes: ByteArray? = null
if (oldState is Element) {
if (JDOMUtil.areElementsEqual(oldState as Element?, newState)) {
return null
}
}
else if (oldState != null) {
newBytes = StateMap.getNewByteIfDiffers(componentName, newState, oldState as ByteArray)
if (newBytes == null) {
return null
}
}
val newStorageData = oldStates.toMutableMap()
put(newStorageData, componentName, fileName, newBytes ?: newState)
return newStorageData
}
private fun put(states: MutableMap<String, MutableMap<String, Any>>, componentName: String, fileName: String, state: Any) {
var fileToState: MutableMap<String, Any>? = states.get(componentName)
if (fileToState == null) {
fileToState = THashMap<String, Any>()
states.put(componentName, fileToState)
}
fileToState.put(fileName, state)
}
}
override fun hasState(storageData: StateMap, componentName: String) = storageData.hasStates()
}
private val NON_EXISTENT_FILE_DATA = Pair.create<ByteArray, String>(null, SystemProperties.getLineSeparator())
@@ -25,7 +25,7 @@ import com.intellij.util.xmlb.XmlSerializer
import org.jdom.Element
abstract class SaveSessionBase : StateStorage.SaveSession, StateStorage.ExternalizationSession, SafeWriteRequestor {
override final fun setState(component: Any, componentName: String, state: Any) {
override final fun setState(component: Any?, componentName: String, state: Any) {
val element: Element?
try {
element = serializeState(state)
@@ -39,10 +39,10 @@ abstract class SaveSessionBase : StateStorage.SaveSession, StateStorage.External
return
}
setSerializedState(component, componentName, element)
setSerializedState(componentName, element)
}
protected abstract fun setSerializedState(component: Any, componentName: String, element: Element?)
protected abstract fun setSerializedState(componentName: String, element: Element?)
}
private val skipDefaultsSerializationFilter = ThreadLocal<SoftReference<SkipDefaultsSerializationFilter>>()
@@ -23,7 +23,8 @@ import com.intellij.openapi.application.invokeAndWaitIfNeed
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.components.RoamingType
import com.intellij.openapi.components.impl.ServiceManagerImpl
import com.intellij.openapi.components.impl.stores.StorageUtil
import com.intellij.openapi.components.impl.stores.FileStorageCoreUtil
import com.intellij.openapi.components.impl.stores.FileStorageCoreUtil.DEFAULT_EXT
import com.intellij.openapi.components.service
import com.intellij.openapi.extensions.AbstractExtensionPointBean
import com.intellij.openapi.options.*
@@ -84,7 +85,7 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
updateExtension = processor.isUpgradeNeeded()
}
else {
schemeExtension = StorageUtil.DEFAULT_EXT
schemeExtension = FileStorageCoreUtil.DEFAULT_EXT
updateExtension = false
}
@@ -226,8 +227,8 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
return if (StringUtilRt.endsWithIgnoreCase(fileName, schemeExtension)) {
schemeExtension
}
else if (StringUtilRt.endsWithIgnoreCase(fileName, StorageUtil.DEFAULT_EXT)) {
StorageUtil.DEFAULT_EXT
else if (StringUtilRt.endsWithIgnoreCase(fileName, DEFAULT_EXT)) {
DEFAULT_EXT
}
else if (allowAny) {
PathUtil.getFileExtension(fileName.toString())!!
@@ -392,7 +393,7 @@ public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val
private val ExternalizableScheme.fileName: String?
get() = schemeToInfo.get(this)?.fileNameWithoutExtension
private fun canRead(name: CharSequence) = updateExtension && StringUtilRt.endsWithIgnoreCase(name, StorageUtil.DEFAULT_EXT) || StringUtilRt.endsWithIgnoreCase(name, schemeExtension)
private fun canRead(name: CharSequence) = updateExtension && StringUtilRt.endsWithIgnoreCase(name, DEFAULT_EXT) || StringUtilRt.endsWithIgnoreCase(name, schemeExtension)
private fun readSchemeFromFile(file: VirtualFile, duringLoad: Boolean): E? {
val fileName = file.getNameSequence()
@@ -15,7 +15,6 @@
*/
package com.intellij.configurationStore
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream
import com.intellij.openapi.util.text.StringUtil
@@ -35,8 +34,6 @@ import java.util.concurrent.atomic.AtomicReferenceArray
class StateMap private constructor(private val names: Array<String>, private val states: AtomicReferenceArray<Any?>) {
companion object {
private val LOG = Logger.getInstance(javaClass<StateMap>())
private val XML_FORMAT = Format.getRawFormat().setTextMode(Format.TextMode.TRIM).setOmitEncoding(true).setOmitDeclaration(true)
val EMPTY = StateMap(emptyArray(), AtomicReferenceArray(0))
@@ -118,7 +115,7 @@ class StateMap private constructor(private val names: Array<String>, private val
return if (index < 0) null else states.get(index)
}
fun getElement(key: String, newLiveStates: Map<String, Element>) = stateToElement(key, get(key), newLiveStates)
fun getElement(key: String, newLiveStates: Map<String, Element>? = null) = stateToElement(key, get(key), newLiveStates)
fun isEmpty() = names.isEmpty()
@@ -150,8 +147,6 @@ class StateMap private constructor(private val names: Array<String>, private val
}
}
fun getStateAndArchive(key: String) = getState(key, true)
fun getState(key: String, archive: Boolean = false): Element? {
val index = Arrays.binarySearch(names, key)
if (index < 0) {
@@ -175,4 +170,61 @@ class StateMap private constructor(private val names: Array<String>, private val
LOG.assertTrue(currentState is Element)
states.set(index, if (state == null) null else archiveState(state))
}
}
fun setStateAndCloneIfNeed(key: String, newState: Element?, oldStates: StateMap, newLiveStates: MutableMap<String, Element>? = null): MutableMap<String, Any>? {
val oldState = oldStates.get(key)
if (newState == null || JDOMUtil.isEmpty(newState)) {
if (oldState == null) {
return null
}
val newStates = oldStates.toMutableMap()
newStates.remove(key)
return newStates
}
newLiveStates?.put(key, newState)
var newBytes: ByteArray? = null
if (oldState is Element) {
if (JDOMUtil.areElementsEqual(oldState as Element?, newState)) {
return null
}
}
else if (oldState != null) {
newBytes = StateMap.getNewByteIfDiffers(key, newState, oldState as ByteArray)
if (newBytes == null) {
return null
}
}
val newStates = oldStates.toMutableMap()
newStates.put(key, newBytes ?: newState)
return newStates
}
// true if updated (not equals to previous state)
private fun updateState(states: MutableMap<String, Any>, key: String, newState: Element?, newLiveStates: MutableMap<String, Element>? = null): Boolean {
if (newState == null || JDOMUtil.isEmpty(newState)) {
states.remove(key)
return true
}
newLiveStates?.put(key, newState)
val oldState = states.get(key)
var newBytes: ByteArray? = null
if (oldState is Element) {
if (JDOMUtil.areElementsEqual(oldState as Element?, newState)) {
return false
}
}
else if (oldState != null) {
newBytes = StateMap.getNewByteIfDiffers(key, newState, oldState as ByteArray) ?: return false
}
states.put(key, newBytes ?: newState)
return true
}
@@ -213,7 +213,8 @@ open class StateStorageManagerImpl(private val rootTagName: String,
return storage
}
private class MyDirectoryStorage(override val storageManager: StateStorageManagerImpl, file: File, splitter: StateSplitter) : DirectoryBasedStorage(storageManager.pathMacroSubstitutor, file, splitter), StorageVirtualFileTracker.TrackedStorage
private class MyDirectoryStorage(override val storageManager: StateStorageManagerImpl, file: File, splitter: StateSplitter) :
DirectoryBasedStorage(file, splitter, storageManager.pathMacroSubstitutor), StorageVirtualFileTracker.TrackedStorage
private class MyFileStorage(override val storageManager: StateStorageManagerImpl,
file: File,
@@ -1,8 +1,8 @@
package com.intellij.configurationStore
import com.intellij.openapi.components.StateStorage
import com.intellij.openapi.components.impl.stores.FileStorageCoreUtil
import com.intellij.openapi.components.impl.stores.StateStorageManager
import com.intellij.openapi.components.impl.stores.StorageUtil
import com.intellij.openapi.util.text.StringUtilRt
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
@@ -76,7 +76,7 @@ class StorageVirtualFileTracker(private val messageBus: MessageBus) {
// but we should detect deletion - but again, it is not supported case. So, we don't check if some of registered storages located inside changed directory.
// but if we have DirectoryBasedStorage, we check - if file located inside it
if (storage == null && hasDirectoryBasedStorages && StringUtilRt.endsWithIgnoreCase(path, StorageUtil.DEFAULT_EXT)) {
if (storage == null && hasDirectoryBasedStorages && StringUtilRt.endsWithIgnoreCase(path, FileStorageCoreUtil.DEFAULT_EXT)) {
storage = filePathToStorage.get(VfsUtil.getParentDir(path))
}
}
@@ -29,7 +29,7 @@ import java.io.IOException
abstract class XmlElementStorage protected constructor(protected val fileSpec: String,
protected val rootElementName: String,
protected val pathMacroSubstitutor: TrackingPathMacroSubstitutor?,
protected val pathMacroSubstitutor: TrackingPathMacroSubstitutor? = null,
roamingType: RoamingType? = RoamingType.DEFAULT,
provider: StreamProvider? = null) : StorageBaseEx<StateMap>() {
val roamingType: RoamingType = roamingType ?: RoamingType.DEFAULT
@@ -116,7 +116,8 @@ abstract class XmlElementStorage protected constructor(protected val fileSpec: S
override fun createSaveSession() = if (storage.checkIsSavingDisabled() || copiedStates == null) null else this
override fun setSerializedState(component: Any, componentName: String, element: Element?) {
override fun setSerializedState(componentName: String, element: Element?) {
element?.normalizeRootName()
if (copiedStates == null) {
copiedStates = setStateAndCloneIfNeed(componentName, element, originalStates, newLiveStates)
}
@@ -127,7 +128,7 @@ abstract class XmlElementStorage protected constructor(protected val fileSpec: S
override fun save() {
val stateMap = StateMap.fromMap(copiedStates!!)
var element = save(stateMap, newLiveStates, storage.rootElementName)
var element = save(stateMap, storage.rootElementName, newLiveStates)
if (element == null || JDOMUtil.isEmpty(element)) {
element = null
}
@@ -197,7 +198,7 @@ abstract class XmlElementStorage protected constructor(protected val fileSpec: S
}
}
private fun save(states: StateMap, newLiveStates: Map<String, Element>, rootElementName: String): Element? {
fun save(states: StateMap, rootElementName: String, newLiveStates: Map<String, Element>? = null): Element? {
if (states.isEmpty()) {
return null
}
@@ -230,40 +231,6 @@ private fun save(states: StateMap, newLiveStates: Map<String, Element>, rootElem
return rootElement
}
fun setStateAndCloneIfNeed(componentName: String, newState: Element?, oldStates: StateMap, newLiveStates: MutableMap<String, Element>): MutableMap<String, Any>? {
val oldState = oldStates.get(componentName)
if (newState == null || JDOMUtil.isEmpty(newState)) {
if (oldState == null) {
return null
}
val newStates = oldStates.toMutableMap()
newStates.remove(componentName)
return newStates
}
newState.normalizeRootName()
newLiveStates.put(componentName, newState)
var newBytes: ByteArray? = null
if (oldState is Element) {
if (JDOMUtil.areElementsEqual(oldState as Element?, newState)) {
return null
}
}
else if (oldState != null) {
newBytes = StateMap.getNewByteIfDiffers(componentName, newState, oldState as ByteArray)
if (newBytes == null) {
return null
}
}
val newStates = oldStates.toMutableMap()
newStates.put(componentName, newBytes ?: newState)
return newStates
}
fun Element.normalizeRootName(): Element {
if (getParent() != null) {
LOG.warn("State element must not have parent ${JDOMUtil.writeElement(this)}")
@@ -273,31 +240,6 @@ fun Element.normalizeRootName(): Element {
return this
}
private fun updateState(states: MutableMap<String, Any>, componentName: String, newState: Element?, newLiveStates: MutableMap<String, Element>) {
if (newState == null || JDOMUtil.isEmpty(newState)) {
states.remove(componentName)
return
}
newState.normalizeRootName()
newLiveStates.put(componentName, newState)
val oldState = states.get(componentName)
var newBytes: ByteArray? = null
if (oldState is Element) {
if (JDOMUtil.areElementsEqual(oldState as Element?, newState)) {
return
}
}
else if (oldState != null) {
newBytes = StateMap.getNewByteIfDiffers(componentName, newState, oldState as ByteArray) ?: return
}
states.put(componentName, newBytes ?: newState)
}
// newStorageData - myStates contains only live (unarchived) states
private fun StateMap.getChangedComponentNames(newStates: StateMap): Set<String> {
val bothStates = keys().toMutableSet()
@@ -0,0 +1,90 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.configurationStore
import com.intellij.openapi.components.MainConfigurationStateSplitter
import com.intellij.openapi.components.StateStorage
import com.intellij.openapi.components.impl.stores.StateStorageBase
import com.intellij.openapi.util.JDOMUtil
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.RuleChain
import com.intellij.testFramework.TemporaryDirectory
import com.intellij.testFramework.runInEdtAndWait
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.hasChildren
import org.jdom.Element
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
private fun StateStorage.ExternalizationSession.save() {
runInEdtAndWait {
createSaveSession()!!.save()
}
}
private fun StateStorageBase<*>.setStateAndSave(componentName: String, state: String?) {
var externalizationSession = startExternalization()!!
externalizationSession.setState(null, componentName, if (state == null) Element("state") else JDOMUtil.load(state.reader))
externalizationSession.save()
}
class DirectoryBasedStorageTest {
companion object {
@ClassRule val projectRule = ProjectRule()
}
val tempDirManager = TemporaryDirectory()
private val ruleChain = RuleChain(tempDirManager)
@Rule fun getChain() = ruleChain
@Test fun save() {
val dir = tempDirManager.newPath()
val storage = DirectoryBasedStorage(dir.toFile(), object : MainConfigurationStateSplitter() {
override fun getComponentStateFileName() = "main"
override fun getSubStateTagName() = "sub"
override fun getSubStateFileName(element: Element) = element.getAttributeValue("name")
})
val componentName = "test"
storage.setStateAndSave(componentName,"""<component name="$componentName"><sub name="foo" /><sub name="bar" /></component>""")
assertThat(dir).hasChildren("foo.xml", "bar.xml", "main.xml")
assertThat(dir.resolve("foo.xml")).hasContent(generateData("foo"))
assertThat(dir.resolve("bar.xml")).hasContent(generateData("bar"))
assertThat(dir.resolve("main.xml")).hasContent(generateData("test"))
storage.setStateAndSave(componentName, """<component name="$componentName"><sub name="bar" /></component>""")
assertThat(dir).hasChildren("main.xml", "bar.xml")
assertThat(dir.resolve("bar.xml")).hasContent(generateData("bar"))
assertThat(dir.resolve("main.xml")).hasContent(generateData("test"))
storage.setStateAndSave(componentName, null)
assertThat(dir).doesNotExist()
}
private fun generateData(name: String): String {
return """<component name="test">
<${if (name == "test") "component" else "sub"} name="$name" />
</component>"""
}
}
@@ -70,8 +70,7 @@ class ProjectStoreTest {
val tempDirManager = TemporaryDirectory()
private val ruleChain = RuleChain(tempDirManager)
public Rule fun getChain(): RuleChain = ruleChain
@Rule fun getChain() = ruleChain
Language("XML")
private val iprFileContent =
@@ -26,6 +26,7 @@ Suite.SuiteClasses(
ModuleStoreTest::class, ModuleStoreRenameTest::class,
StorageManagerTest::class,
SchemeManagerTest::class,
XmlElementStorageTest::class
XmlElementStorageTest::class,
DirectoryBasedStorageTest::class
)
class StoreTestSuite
@@ -40,14 +40,14 @@ class XmlElementStorageTest {
val storage = MyXmlElementStorage(tag("root", tag("component", attr("name", "test"), tag("foo"))))
val newState = tag("component", attr("name", "test"), tag("bar"))
val externalizationSession = storage.startExternalization()!!
externalizationSession.setState(this, "test", newState)
externalizationSession.setState(null, "test", newState)
externalizationSession.createSaveSession()!!.save()
assertThat(storage.savedElement).isNotNull()
assertThat(storage.savedElement!!.getChild("component").getChild("bar")).isNotNull()
assertThat(storage.savedElement!!.getChild("component").getChild("foo")).isNull()
}
private class MyXmlElementStorage(private val myElement: Element) : XmlElementStorage("", "root", null, null, null) {
private class MyXmlElementStorage(private val myElement: Element) : XmlElementStorage("", "root") {
var savedElement: Element? = null
override fun loadLocalData() = myElement
@@ -48,8 +48,6 @@ import java.io.IOException;
import java.util.*;
public class StorageUtil {
public static final String DEFAULT_EXT = ".xml";
private static final Logger LOG = Logger.getInstance(StorageUtil.class);
@TestOnly
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package com.intellij.openapi.components;
import com.intellij.openapi.util.Pair;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -24,7 +25,7 @@ import java.util.List;
* @deprecated Use {@link StateSplitterEx}
*/
public interface StateSplitter {
List<Pair<Element, String>> splitState(Element e);
List<Pair<Element, String>> splitState(@NotNull Element e);
void mergeStatesInto(Element target, Element[] elements);
}
@@ -41,7 +41,7 @@ public interface StateStorage {
void analyzeExternalChangesAndUpdateIfNeed(@NotNull Set<String> componentNames);
interface ExternalizationSession {
void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state);
void setState(@Nullable Object component, @NotNull String componentName, @NotNull Object state);
/**
* return null if nothing to save
@@ -85,8 +85,8 @@ public class CoreProjectLoader {
VirtualFile libraries = dotIdea.findChild("libraries");
if (libraries != null) {
Map<String, Map<String, Element>> data = DirectoryStorageUtil.loadFrom(libraries, PathMacroManager.getInstance(project).createTrackingSubstitutor());
Element libraryTable = DefaultStateSerializer.deserializeState(DirectoryStorageUtil.getCompositeState(data, "libraryTable", new ProjectLibraryTable.LibraryStateSplitter()), Element.class, null);
Map<String, Element> data = DirectoryStorageUtil.loadFrom(libraries, PathMacroManager.getInstance(project).createTrackingSubstitutor());
Element libraryTable = DefaultStateSerializer.deserializeState(DirectoryStorageUtil.getCompositeState(data, new ProjectLibraryTable.LibraryStateSplitter()), Element.class, null);
((LibraryTableBase) ProjectLibraryTable.getInstance(project)).loadState(libraryTable);
}
@@ -36,13 +36,13 @@ public class DirectoryStorageUtil {
private static final Logger LOG = Logger.getInstance(DirectoryStorageUtil.class);
@NotNull
public static Map<String, Map<String, Element>> loadFrom(@Nullable VirtualFile dir, @Nullable TrackingPathMacroSubstitutor pathMacroSubstitutor) {
public static Map<String, Element> loadFrom(@Nullable VirtualFile dir, @Nullable TrackingPathMacroSubstitutor pathMacroSubstitutor) {
if (dir == null || !dir.exists()) {
return Collections.emptyMap();
}
StringInterner interner = new StringInterner();
Map<String, Map<String, Element>> map = new THashMap<String, Map<String, Element>>();
Map<String, Element> fileToState = new THashMap<String, Element>();
for (VirtualFile file : dir.getChildren()) {
// ignore system files like .DS_Store on Mac
if (!StringUtilRt.endsWithIgnoreCase(file.getNameSequence(), FileStorageCoreUtil.DEFAULT_EXT)) {
@@ -56,8 +56,8 @@ public class DirectoryStorageUtil {
}
Element element = JDOMUtil.load(file.getInputStream());
String name = FileStorageCoreUtil.getComponentNameIfValid(element);
if (name == null) {
String componentName = FileStorageCoreUtil.getComponentNameIfValid(element);
if (componentName == null) {
continue;
}
@@ -79,31 +79,22 @@ public class DirectoryStorageUtil {
JDOMUtil.internElement(state, interner);
if (pathMacroSubstitutor != null) {
pathMacroSubstitutor.expandPaths(state);
pathMacroSubstitutor.addUnknownMacros(name, PathMacrosCollector.getMacroNames(state));
pathMacroSubstitutor.addUnknownMacros(componentName, PathMacrosCollector.getMacroNames(state));
}
Map<String, Element> fileToState = map.get(name);
if (fileToState == null) {
fileToState = new THashMap<String, Element>();
fileToState.put(file.getName(), state);
map.put(name, fileToState);
}
else {
fileToState.put(file.getName(), state);
}
fileToState.put(file.getName(), state);
}
catch (Throwable e) {
LOG.warn("Unable to load state", e);
}
}
return map;
return fileToState;
}
@Nullable
public static Element getCompositeState(@NotNull Map<String, Map<String, Element>> states, @NotNull String componentName, @NotNull StateSplitterEx splitter) {
Map<String, Element> fileToState = states.get(componentName);
public static Element getCompositeState(@NotNull Map<String, Element> fileToState, @NotNull StateSplitterEx splitter) {
Element state = new Element(FileStorageCoreUtil.COMPONENT);
if (fileToState == null || fileToState.isEmpty()) {
if (fileToState.isEmpty()) {
return state;
}
+1
View File
@@ -25,5 +25,6 @@
<orderEntry type="module" module-name="images" exported="" scope="RUNTIME" />
<orderEntry type="module" module-name="RegExpSupport" exported="" scope="RUNTIME" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" scope="TEST" name="assertJ" level="project" />
</component>
</module>
@@ -154,4 +154,4 @@ public fun Path.refreshVfs() {
}
val VirtualFile.path: String
get() = getPath()
get() = getPath()
@@ -0,0 +1,42 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.assertj.core.api
import org.assertj.core.internal.ComparatorBasedComparisonStrategy
import org.assertj.core.internal.Iterables
import java.nio.file.Files
import java.nio.file.Path
import java.util.Comparator
fun AbstractPathAssert<*>.hasChildren(vararg names: String) {
paths.assertIsDirectory(info, actual)
Iterables(ComparatorBasedComparisonStrategy(object : Comparator<Any> {
override fun compare(o1: Any, o2: Any): Int {
if (o1 is Path && o2 is Path) {
return o1.compareTo(o2)
}
else if (o1 is String && o2 is String) {
return o1.compareTo(o2)
}
else {
return if ((o1 as Path).endsWith(o2 as String)) 0 else -1
}
}
}))
.assertContainsOnly(info, Files.newDirectoryStream(actual).toList(), names)
}
@@ -65,7 +65,7 @@ final class ClasspathSaveSession implements StateStorage.ExternalizationSession,
}
@Override
public void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state) {
public void setState(Object component, @NotNull String componentName, @NotNull Object state) {
try {
CachedXmlDocumentSet fileSet = EclipseClasspathStorageProvider.getFileCache(module);