Do not add commas for double values in the xml
[idea/community.git] / plugins / stats-collector / src / com / intellij / stats / personalization / impl / UserFactorStorageBase.kt
1 /*
2  * Copyright 2000-2017 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.stats.personalization.impl
18
19 import com.intellij.openapi.components.PersistentStateComponent
20 import com.intellij.stats.personalization.*
21 import com.intellij.util.attribute
22 import org.jdom.Element
23 import java.text.DecimalFormat
24 import java.util.*
25
26 abstract class UserFactorStorageBase
27     : UserFactorStorage, PersistentStateComponent<Element> {
28
29     private companion object {
30         val DOUBLE_VALUE_FORMATTER = DecimalFormat().apply {
31             maximumFractionDigits = 6
32             minimumFractionDigits = 1
33             isGroupingUsed = false
34         }
35     }
36
37     private val state = CollectorState()
38
39     override fun <U : FactorUpdater> getFactorUpdater(description: UserFactorDescription<U, *>): U =
40             description.updaterFactory.invoke(getAggregateFactor(description.factorId))
41
42     override fun <R : FactorReader> getFactorReader(description: UserFactorDescription<*, R>): R =
43             description.readerFactory.invoke(getAggregateFactor(description.factorId))
44
45     override fun getState(): Element {
46         val element = Element("component")
47         state.writeState(element)
48         return element
49     }
50
51     override fun loadState(newState: Element) {
52         state.applyState(newState)
53     }
54
55     private fun getAggregateFactor(factorId: String): MutableDoubleFactor =
56             state.aggregateFactors.computeIfAbsent(factorId, { DailyAggregateFactor() })
57
58     private class CollectorState {
59         val aggregateFactors: MutableMap<String, DailyAggregateFactor> = HashMap()
60
61         fun applyState(element: Element) {
62             aggregateFactors.clear()
63             for (child in element.children) {
64                 val factorId = child.getAttributeValue("id")
65                 if (child.name == "factor" && factorId != null) {
66                     val factor = DailyAggregateFactor.restore(child)
67                     if (factor != null) aggregateFactors[factorId] = factor
68                 }
69             }
70         }
71
72         fun writeState(element: Element) {
73             for ((id, factor) in aggregateFactors.asSequence().sortedBy { it.key }) {
74                 val factorElement = Element("factor")
75                 factorElement.attribute("id", id)
76                 factor.writeState(factorElement)
77                 element.addContent(factorElement)
78             }
79         }
80     }
81
82     class DailyAggregateFactor private constructor(private val aggregates: SortedMap<Day, DailyData> = sortedMapOf())
83         : MutableDoubleFactor {
84         constructor() : this(sortedMapOf())
85
86         init {
87             ensureLimit()
88         }
89
90         companion object {
91             val DAYS_LIMIT = 10
92
93             fun restore(element: Element): DailyAggregateFactor? {
94                 val data = sortedMapOf<Day, DailyData>()
95                 for (child in element.children) {
96                     val date = child.getAttributeValue("date")
97                     val day = DayImpl.fromString(date)
98                     if (child.name == "dailyData" && day != null) {
99                         val dailyData = DailyData.restore(child)
100                         if (dailyData != null) data.put(day, dailyData)
101                     }
102                 }
103
104                 if (data.isEmpty()) return null
105                 return DailyAggregateFactor(data)
106             }
107         }
108
109         fun writeState(element: Element) {
110             for ((day, data) in aggregates) {
111                 val dailyDataElement = Element("dailyData")
112                 dailyDataElement.attribute("date", day.toString())
113                 data.writeState(dailyDataElement)
114                 element.addContent(dailyDataElement)
115             }
116         }
117
118         override fun availableDays(): List<Day> = aggregates.keys.toList()
119
120         override fun incrementOnToday(key: String): Boolean {
121             return updateOnDate(DateUtil.today()) {
122                 compute(key, { _, oldValue -> if (oldValue == null) 1.0 else oldValue + 1.0 })
123             }
124         }
125
126         override fun onDate(date: Day): Map<String, Double>? = aggregates[date]?.data
127
128         override fun updateOnDate(date: Day, updater: MutableMap<String, Double>.() -> Unit): Boolean {
129             val old = aggregates[date]
130             if (old != null) {
131                 updater.invoke(old.data)
132                 return true
133             }
134
135             if (aggregates.size < DAYS_LIMIT || aggregates.firstKey() < date) {
136                 val data = DailyData()
137                 updater.invoke(data.data)
138                 aggregates.put(date, data)
139                 ensureLimit()
140                 return true
141             }
142
143             return false
144         }
145
146         private fun ensureLimit() {
147             while (aggregates.size > DAYS_LIMIT) {
148                 aggregates.remove(aggregates.firstKey())
149             }
150         }
151     }
152
153     private class DailyData(val data: MutableMap<String, Double> = HashMap()) {
154         companion object {
155             fun restore(element: Element): DailyData? {
156                 val data = mutableMapOf<String, Double>()
157                 for (child in element.children) {
158                     if (child.name == "observation") {
159                         val dataKey = child.getAttributeValue("name")
160                         val dataValue = child.getAttributeValue("value")
161
162                         // skip all if any observation is inconsistent
163                         val value = dataValue.toDoubleOrNull() ?: return null
164                         data[dataKey] = value
165                     }
166                 }
167
168                 if (data.isEmpty()) return null
169                 return DailyData(data)
170             }
171         }
172
173         fun writeState(element: Element) {
174             for ((key, value) in data.asSequence().sortedBy { it.key }) {
175                 val observation = Element("observation")
176                 observation.attribute("name", key)
177                 observation.attribute("value", DOUBLE_VALUE_FORMATTER.format(value))
178                 element.addContent(observation)
179             }
180         }
181     }
182 }