blob: a62832a6e18b5e6ba22189d79e2e15bb70172149 [file] [log] [blame]
/*
* Copyright 2000-2013 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.Arrays;
import java.util.HashSet;
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 SoftReference[] myUnits = new SoftReference[UNIT_COUNT];
private final HashSet<StatisticsUnit> myModifiedUnits = new HashSet<StatisticsUnit>();
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, new NotNullFunction<String, StatisticsInfo>() {
@NotNull
public StatisticsInfo fun(final String s) {
return 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) {
SoftReference ref = myUnits[unitNumber];
StatisticsUnit unit = (StatisticsUnit)SoftReference.dereference(ref);
if (unit != null) return unit;
unit = loadUnit(unitNumber);
if (unit == null){
unit = new StatisticsUnit(unitNumber);
}
myUnits[unitNumber] = new SoftReference<StatisticsUnit>(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 BufferedInputStream(new FileInputStream(path));
in = new ScrambledInputStream(in);
try{
unit.read(in);
}
finally{
in.close();
}
}
catch(IOException e){
}
catch(WrongFormatException e){
}
}
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) {
Arrays.fill(myUnits, null);
}
myTestingStatistics = false;
}
});
}
}