/* * 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.codeInsight.daemon; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.UserDataHolderEx; import com.intellij.openapi.util.text.StringHash; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import java.util.concurrent.atomic.AtomicInteger; public class UsedColors { private static final Key<Object/*UsedColor or UsedColor[]*/> USED_COLOR = Key.create("USED_COLOR"); public static final AtomicInteger counter = new AtomicInteger(); private static class UsedColor { @NotNull final String name; final int index; UsedColor(@NotNull String name, int index) { this.name = name; this.index = index; counter.incrementAndGet(); } } public static int getOrAddColorIndex(@NotNull final UserDataHolderEx context, @NotNull final String name, int colorsCount) { int colorIndex; while (true) { Object data = context.getUserData(USED_COLOR); Object newColors; if (data == null) { colorIndex = hashColor(name, colorsCount); newColors = new UsedColor(name, colorIndex); // put an object instead of array to save space } else if (data instanceof UsedColor) { UsedColor usedColor = (UsedColor)data; if (usedColor.name.equals(name)) { colorIndex = usedColor.index; newColors = null; // found, no need to create new } else { int hashedIndex = hashColor(name, colorsCount); if (hashedIndex == usedColor.index) hashedIndex = (hashedIndex + 1) % colorsCount; colorIndex = hashedIndex; UsedColor newColor = new UsedColor(name, colorIndex); newColors = new UsedColor[]{usedColor, newColor}; } } else { colorIndex = -1; int hashedIndex = hashColor(name, colorsCount); int[] index2usage = new int[colorsCount]; UsedColor[] usedColors = (UsedColor[])data; for (UsedColor usedColor : usedColors) { int index = usedColor.index; index2usage[index]++; if (usedColor.name.equals(name)) { colorIndex = index; break; } } if (colorIndex == -1) { int minIndex1 = indexOfMin(index2usage, hashedIndex, colorsCount); int minIndex2 = indexOfMin(index2usage, 0, hashedIndex); colorIndex = index2usage[minIndex1] <= index2usage[minIndex2] ? minIndex1 : minIndex2; UsedColor newColor = new UsedColor(name, colorIndex); newColors = ArrayUtil.append(usedColors, newColor); } else { newColors = null; } } if (newColors == null || context.replace(USED_COLOR, data, newColors)) { break; } } return colorIndex; } private static int hashColor(@NotNull String name, int colorsCount) { return Math.abs(StringHash.murmur(name, 0x55AA) % colorsCount); } @Contract(pure = true) private static int indexOfMin(@NotNull int[] values, int start, int end) { int min = Integer.MAX_VALUE; int minIndex = start; for (int i = start; i < end; i++) { int value = values[i]; if (value < min) { min = value; minIndex = i; } } return minIndex; } }