2 * Copyright 2000-2017 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.stats.personalization.impl
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
26 abstract class UserFactorStorageBase
27 : UserFactorStorage, PersistentStateComponent<Element> {
29 private companion object {
30 val DOUBLE_VALUE_FORMATTER = DecimalFormat().apply {
31 maximumFractionDigits = 6
32 minimumFractionDigits = 1
36 private val state = CollectorState()
38 override fun <U : FactorUpdater> getFactorUpdater(description: UserFactorDescription<U, *>): U =
39 description.updaterFactory.invoke(getAggregateFactor(description.factorId))
41 override fun <R : FactorReader> getFactorReader(description: UserFactorDescription<*, R>): R =
42 description.readerFactory.invoke(getAggregateFactor(description.factorId))
44 override fun getState(): Element {
45 val element = Element("component")
46 state.writeState(element)
50 override fun loadState(newState: Element) {
51 state.applyState(newState)
54 private fun getAggregateFactor(factorId: String): MutableDoubleFactor =
55 state.aggregateFactors.computeIfAbsent(factorId, { DailyAggregateFactor() })
57 private class CollectorState {
58 val aggregateFactors: MutableMap<String, DailyAggregateFactor> = HashMap()
60 fun applyState(element: Element) {
61 aggregateFactors.clear()
62 for (child in element.children) {
63 val factorId = child.getAttributeValue("id")
64 if (child.name == "factor" && factorId != null) {
65 val factor = DailyAggregateFactor.restore(child)
66 if (factor != null) aggregateFactors[factorId] = factor
71 fun writeState(element: Element) {
72 for ((id, factor) in aggregateFactors.asSequence().sortedBy { it.key }) {
73 val factorElement = Element("factor")
74 factorElement.attribute("id", id)
75 factor.writeState(factorElement)
76 element.addContent(factorElement)
81 class DailyAggregateFactor private constructor(private val aggregates: SortedMap<Day, DailyData> = sortedMapOf())
82 : MutableDoubleFactor {
83 constructor() : this(sortedMapOf())
92 fun restore(element: Element): DailyAggregateFactor? {
93 val data = sortedMapOf<Day, DailyData>()
94 for (child in element.children) {
95 val date = child.getAttributeValue("date")
96 val day = DayImpl.fromString(date)
97 if (child.name == "dailyData" && day != null) {
98 val dailyData = DailyData.restore(child)
99 if (dailyData != null) data.put(day, dailyData)
103 if (data.isEmpty()) return null
104 return DailyAggregateFactor(data)
108 fun writeState(element: Element) {
109 for ((day, data) in aggregates) {
110 val dailyDataElement = Element("dailyData")
111 dailyDataElement.attribute("date", day.toString())
112 data.writeState(dailyDataElement)
113 element.addContent(dailyDataElement)
117 override fun availableDays(): List<Day> = aggregates.keys.toList()
119 override fun incrementOnToday(key: String): Boolean {
120 return updateOnDate(DateUtil.today()) {
121 compute(key, { _, oldValue -> if (oldValue == null) 1.0 else oldValue + 1.0 })
125 override fun onDate(date: Day): Map<String, Double>? = aggregates[date]?.data
127 override fun updateOnDate(date: Day, updater: MutableMap<String, Double>.() -> Unit): Boolean {
128 val old = aggregates[date]
130 updater.invoke(old.data)
134 if (aggregates.size < DAYS_LIMIT || aggregates.firstKey() < date) {
135 val data = DailyData()
136 updater.invoke(data.data)
137 aggregates.put(date, data)
145 private fun ensureLimit() {
146 while (aggregates.size > DAYS_LIMIT) {
147 aggregates.remove(aggregates.firstKey())
152 private class DailyData(val data: MutableMap<String, Double> = HashMap()) {
154 fun restore(element: Element): DailyData? {
155 val data = mutableMapOf<String, Double>()
156 for (child in element.children) {
157 if (child.name == "observation") {
158 val dataKey = child.getAttributeValue("name")
159 val dataValue = child.getAttributeValue("value")
161 // skip all if any observation is inconsistent
162 val value = dataValue.toDoubleOrNull() ?: return null
163 data[dataKey] = value
167 if (data.isEmpty()) return null
168 return DailyData(data)
172 fun writeState(element: Element) {
173 for ((key, value) in data.asSequence().sortedBy { it.key }) {
174 val observation = Element("observation")
175 observation.attribute("name", key)
176 observation.attribute("value", DOUBLE_VALUE_FORMATTER.format(value))
177 element.addContent(observation)