diff --git a/platform/configuration-store-impl/src/BinaryXmlOutputter.kt b/platform/configuration-store-impl/src/BinaryXmlOutputter.kt new file mode 100644 index 000000000000..3cc4c4cdedfd --- /dev/null +++ b/platform/configuration-store-impl/src/BinaryXmlOutputter.kt @@ -0,0 +1,78 @@ +package com.intellij.configurationStore + +import com.intellij.util.containers.isNullOrEmpty +import org.jdom.* +import java.io.DataOutputStream + +private enum class TypeMarker { + ELEMENT, CDATA, TEXT, ELEMENT_END +} + +fun output(doc: Document, out: DataOutputStream) { + val content = doc.content + val size = content.size + for (i in 0..size - 1) { + val obj = content[i] + when (obj) { + is Element -> printElement(out, doc.rootElement) + } + } + + out.flush() +} + +fun writeElement(element: Element, out: DataOutputStream) { + printElement(out, element) + out.flush() +} + +private fun printElement(out: DataOutputStream, element: Element) { + out.writeByte(TypeMarker.ELEMENT.ordinal) + out.writeUTF(element.name) + + val content = element.content + printAttributes(out, element.attributes) + + for (item in content) { + if (item is Element) { + printElement(out, item) + } + else if (item is Text) { + if (!isAllWhitespace(item)) { + out.writeByte(TypeMarker.TEXT.ordinal) + out.writeUTF(item.text) + } + } + else if (item is CDATA) { + out.writeByte(TypeMarker.CDATA.ordinal) + out.writeUTF(item.text) + } + } + out.writeByte(TypeMarker.ELEMENT_END.ordinal) +} + +private fun printAttributes(out: DataOutputStream, attributes: List?) { + if (attributes.isNullOrEmpty()) { + val size = attributes?.size ?: 0 + if (size > 255) { + throw UnsupportedOperationException("attributes size > 255") + } + out.writeByte(size) + return + } + + for (attribute in attributes!!) { + out.writeUTF(attribute.name) + out.writeUTF(attribute.value) + } +} + +private fun isAllWhitespace(obj: Content): Boolean { + val str = (obj as? Text)?.text ?: return false + for (i in 0..str.length - 1) { + if (!Verifier.isXMLWhitespace(str[i])) { + return false + } + } + return true +} \ No newline at end of file diff --git a/platform/configuration-store-impl/src/StateMap.kt b/platform/configuration-store-impl/src/StateMap.kt index 681cae20ad4c..85354fa4e432 100644 --- a/platform/configuration-store-impl/src/StateMap.kt +++ b/platform/configuration-store-impl/src/StateMap.kt @@ -15,8 +15,10 @@ */ package com.intellij.configurationStore +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.JDOMUtil import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream +import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.vfs.CharsetToolkit import com.intellij.util.ArrayUtil @@ -27,30 +29,63 @@ import org.iq80.snappy.SnappyOutputStream import org.jdom.Element import org.jdom.output.Format import java.io.ByteArrayInputStream +import java.io.DataOutputStream import java.io.OutputStreamWriter import java.util.* import java.util.concurrent.atomic.AtomicReferenceArray private val XML_FORMAT = Format.getRawFormat().setTextMode(Format.TextMode.TRIM).setOmitEncoding(true).setOmitDeclaration(true) -private fun archiveState(state: Element): ByteArray { +// must be mot modified during app life +private val isUseNewSaving by lazy { ApplicationManager.getApplication().isUnitTestMode || Registry.`is`("configuration.saving.v3", false) } + +private fun archiveState(state: Element): BufferExposingByteArrayOutputStream { + if (isUseNewSaving) { + return archiveStateBinary(state) + } + else { + return archiveStateXml(state) + } +} + +fun archiveStateBinary(state: Element): BufferExposingByteArrayOutputStream { + val byteOut = BufferExposingByteArrayOutputStream() + DataOutputStream(SnappyOutputStream(byteOut)).use { + writeElement(state, it) + } + return byteOut +} + +fun archiveStateXml(state: Element): BufferExposingByteArrayOutputStream { val byteOut = BufferExposingByteArrayOutputStream() OutputStreamWriter(SnappyOutputStream(byteOut), CharsetToolkit.UTF8_CHARSET).use { val xmlOutputter = JDOMUtil.MyXMLOutputter() xmlOutputter.format = XML_FORMAT xmlOutputter.output(state, it) } - return ArrayUtil.realloc(byteOut.internalBuffer, byteOut.size()) + return byteOut } private fun unarchiveState(state: ByteArray) = JDOMUtil.load(SnappyInputStream(ByteArrayInputStream(state))) fun getNewByteIfDiffers(key: String, newState: Any, oldState: ByteArray): ByteArray? { - val newBytes = if (newState is Element) archiveState(newState) else newState as ByteArray - if (Arrays.equals(newBytes, oldState)) { - return null + val newBytes: ByteArray + if (newState is Element) { + val byteOut = archiveState(newState) + if (arrayEquals(byteOut.internalBuffer, oldState, byteOut.size())) { + return null + } + + newBytes = ArrayUtil.realloc(byteOut.internalBuffer, byteOut.size()) } - else if (SystemProperties.getBooleanProperty("idea.log.changed.components", false)) { + else { + newBytes = newState as ByteArray + if (Arrays.equals(newBytes, oldState)) { + return null + } + } + + if (SystemProperties.getBooleanProperty("idea.log.changed.components", false)) { fun stateToString(state: Any) = JDOMUtil.writeParent(state as? Element ?: unarchiveState(state as ByteArray), "\n") val before = stateToString(oldState) @@ -75,8 +110,7 @@ fun stateToElement(key: String, state: Any?, newLiveStates: Map } class StateMap private constructor(private val names: Array, private val states: AtomicReferenceArray) { - override fun toString(): String = - if (this == EMPTY) "EMPTY" else states.toString(); + override fun toString() = if (this == EMPTY) "EMPTY" else states.toString() companion object { val EMPTY = StateMap(emptyArray(), AtomicReferenceArray(0)) @@ -225,5 +259,24 @@ internal fun updateState(states: MutableMap, key: String, newState: } states.put(key, newBytes ?: newState) + return true +} + +fun arrayEquals(a: ByteArray, a2: ByteArray, aSize: Int = a.size): Boolean { + if (a == a2) { + return true + } + + val length = aSize + if (a2.size != length) { + return false + } + + for (i in 0..length - 1) { + if (a[i] != a2[i]) { + return false + } + } + return true } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/util/jdom.kt b/platform/platform-impl/src/com/intellij/util/jdom.kt index d376c07b3cc8..8928117dd150 100644 --- a/platform/platform-impl/src/com/intellij/util/jdom.kt +++ b/platform/platform-impl/src/com/intellij/util/jdom.kt @@ -26,6 +26,8 @@ import org.xml.sax.EntityResolver import org.xml.sax.InputSource import java.io.CharArrayReader import java.io.Reader +import java.nio.file.Files +import java.nio.file.Path private val cachedSaxBuilder = ThreadLocal>() @@ -44,6 +46,8 @@ private fun getSaxBuilder(): SAXBuilder { fun loadElement(chars: CharSequence) = loadDocument(CharSequenceReader(chars)).detachRootElement() +fun loadElement(path: Path) = loadDocument(Files.newInputStream(path).bufferedReader()).detachRootElement() + private fun loadDocument(reader: Reader): Document { if (Registry.`is`("jdom.ignoring.whitespace", false) || (ApplicationManager.getApplication()?.isUnitTestMode ?: false)) { try { diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index f140fad86d05..76809e3ff637 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -686,6 +686,7 @@ ruby.remote.debugger.supports.catchpoint.removal=true use.read.action.to.init.service=false use.loaded.state.as.existing=true saving.state.in.new.format.is.allowed=false +configuration.saving.v3=false jdom.ignoring.whitespace=false ide.mac.new.color.picker=false