/*
* Copyright 2000-2016 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.psi.statistics.impl;
import com.intellij.CommonBundle;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Disposer;
import com.intellij.psi.statistics.StatisticsInfo;
import com.intellij.psi.statistics.StatisticsManager;
import com.intellij.reference.SoftReference;
import com.intellij.util.NotNullFunction;
import com.intellij.util.ScrambledInputStream;
import com.intellij.util.ScrambledOutputStream;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import java.io.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
public class StatisticsManagerImpl extends StatisticsManager {
private static final int UNIT_COUNT = 997;
private static final Object LOCK = new Object();
@NonNls private static final String STORE_PATH = PathManager.getSystemPath() + File.separator + "stat";
private final List<SoftReference<StatisticsUnit>> myUnits = ContainerUtil.newArrayList(Collections.nCopies(UNIT_COUNT, null));
private final HashSet<StatisticsUnit> myModifiedUnits = new HashSet<>();
private boolean myTestingStatistics;
public int getUseCount(@NotNull final StatisticsInfo info) {
if (info == StatisticsInfo.EMPTY) return 0;
int useCount = 0;
for (StatisticsInfo conjunct : info.getConjuncts()) {
useCount = Math.max(doGetUseCount(conjunct), useCount);
}
return useCount;
}
private int doGetUseCount(StatisticsInfo info) {
String key1 = info.getContext();
int unitNumber = getUnitNumber(key1);
synchronized (LOCK) {
StatisticsUnit unit = getUnit(unitNumber);
return unit.getData(key1, info.getValue());
}
}
@Override
public int getLastUseRecency(@NotNull StatisticsInfo info) {
if (info == StatisticsInfo.EMPTY) return 0;
int recency = Integer.MAX_VALUE;
for (StatisticsInfo conjunct : info.getConjuncts()) {
recency = Math.min(doGetRecency(conjunct), recency);
}
return recency;
}
private int doGetRecency(StatisticsInfo info) {
String key1 = info.getContext();
int unitNumber = getUnitNumber(key1);
synchronized (LOCK) {
StatisticsUnit unit = getUnit(unitNumber);
return unit.getRecency(key1, info.getValue());
}
}
public void incUseCount(@NotNull final StatisticsInfo info) {
if (info == StatisticsInfo.EMPTY) return;
if (ApplicationManager.getApplication().isUnitTestMode() && !myTestingStatistics) {
return;
}
ApplicationManager.getApplication().assertIsDispatchThread();
for (StatisticsInfo conjunct : info.getConjuncts()) {
doIncUseCount(conjunct);
}
}
private void doIncUseCount(StatisticsInfo info) {
final String key1 = info.getContext();
int unitNumber = getUnitNumber(key1);
synchronized (LOCK) {
StatisticsUnit unit = getUnit(unitNumber);
unit.incData(key1, info.getValue());
myModifiedUnits.add(unit);
}
}
public StatisticsInfo[] getAllValues(final String context) {
final String[] strings;
synchronized (LOCK) {
strings = getUnit(getUnitNumber(context)).getKeys2(context);
}
return ContainerUtil.map2Array(strings, StatisticsInfo.class, (NotNullFunction<String, StatisticsInfo>)s -> new StatisticsInfo(context, s));
}
public void save() {
synchronized (LOCK) {
if (!ApplicationManager.getApplication().isUnitTestMode()){
ApplicationManager.getApplication().assertIsDispatchThread();
for (StatisticsUnit unit : myModifiedUnits) {
saveUnit(unit.getNumber());
}
}
myModifiedUnits.clear();
}
}
private StatisticsUnit getUnit(int unitNumber) {
StatisticsUnit unit = SoftReference.dereference(myUnits.get(unitNumber));
if (unit != null) return unit;
unit = loadUnit(unitNumber);
if (unit == null){
unit = new StatisticsUnit(unitNumber);
}
myUnits.set(unitNumber, new SoftReference<>(unit));
return unit;
}
private static StatisticsUnit loadUnit(int unitNumber) {
StatisticsUnit unit = new StatisticsUnit(unitNumber);
if (!ApplicationManager.getApplication().isUnitTestMode()){
String path = getPathToUnit(unitNumber);
try (InputStream in = new ScrambledInputStream(new BufferedInputStream(new FileInputStream(path)))) {
unit.read(in);
}
catch(IOException | WrongFormatException ignored){
}
}
return unit;
}
private void saveUnit(int unitNumber){
if (!createStoreFolder()) return;
StatisticsUnit unit = getUnit(unitNumber);
String path = getPathToUnit(unitNumber);
try{
OutputStream out = new BufferedOutputStream(new FileOutputStream(path));
out = new ScrambledOutputStream(out);
try {
unit.write(out);
}
finally{
out.close();
}
}
catch(IOException e){
Messages.showMessageDialog(
IdeBundle.message("error.saving.statistics", e.getLocalizedMessage()),
CommonBundle.getErrorTitle(),
Messages.getErrorIcon()
);
}
}
private static int getUnitNumber(String key1) {
return Math.abs(key1.hashCode() % UNIT_COUNT);
}
private static boolean createStoreFolder(){
File homeFile = new File(STORE_PATH);
if (!homeFile.exists()){
if (!homeFile.mkdirs()){
Messages.showMessageDialog(
IdeBundle.message("error.saving.statistic.failed.to.create.folder", STORE_PATH),
CommonBundle.getErrorTitle(),
Messages.getErrorIcon()
);
return false;
}
}
return true;
}
@SuppressWarnings({"HardCodedStringLiteral"})
private static String getPathToUnit(int unitNumber) {
return STORE_PATH + File.separator + "unit." + unitNumber;
}
@TestOnly
public void enableStatistics(@NotNull Disposable parentDisposable) {
myTestingStatistics = true;
Disposer.register(parentDisposable, new Disposable() {
@Override
public void dispose() {
synchronized (LOCK) {
Collections.fill(myUnits, null);
}
myTestingStatistics = false;
}
});
}
}