<commit_before>/*
 * Copyright 2013-2020 consulo.io
 *
 * 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 consulo.ui.impl.image;

import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.util.io.URLUtil;
import consulo.logging.Logger;
import consulo.ui.image.IconLibraryManager;
import consulo.ui.image.Image;
import consulo.util.lang.StringUtil;
import consulo.util.lang.ThreeState;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @author VISTALL
 * @since 2020-09-26
 */
public abstract class BaseIconLibraryManager implements IconLibraryManager {
  private static class JarIcon {
    public ThreeState svgState = ThreeState.UNSURE;

    public byte[] _1x;
    public byte[] _2x;
  }

  private static final Logger LOG = Logger.getInstance(BaseIconLibraryManager.class);

  public static final String ICON_LIBRARY_MARKER = "icon/id.txt";

  private final AtomicLong myModificationCount = new AtomicLong();
  private final AtomicBoolean myInitialized = new AtomicBoolean();

  private Map<String, IconLibrary> myLibraries = new HashMap<>();
  private String myActiveLibraryName = LIGHT_LIBRARY_ID;
  private IconLibrary myActiveLibrary;

  @Nonnull
  @Override
  public Set<String> getLibrariesId() {
    return myLibraries.keySet();
  }

  @Nonnull
  @Override
  public String getActiveLibraryId() {
    return myActiveLibraryName;
  }

  @Override
  public long getModificationCount() {
    return myModificationCount.get();
  }

  @Override
  public void setActiveLibrary(@Nonnull String id) {
    IconLibrary iconLibrary = myLibraries.get(id);
    if(iconLibrary != null) {
      myActiveLibraryName = id;
      myActiveLibrary = iconLibrary;
      myModificationCount.incrementAndGet();
    }
    else {
      LOG.error("Can't find icon library with id {0}", id);
    }
  }

  @Nullable
  public IconLibrary getLibrary(@Nonnull String id) {
    return myLibraries.get(id);
  }

  @Nonnull
  public IconLibrary getActiveLibrary() {
    if (myActiveLibrary == null) {
      myActiveLibrary = myLibraries.get(myActiveLibraryName);
    }

    if (myActiveLibrary == null) {
      throw new Error("There no default active library. Distribution broken");
    }
    return myActiveLibrary;
  }

  @Nonnull
  protected abstract IconLibrary createLibrary(@Nonnull IconLibraryId id);

  public void initialize(@Nullable List<String> files) {
    if (myInitialized.compareAndSet(false, true)) {
      if (files == null) {
        return;
      }

      for (String file : files) {
        try {
          analyzeLibraryJar(file);
        }
        catch (IOException e) {
          LOG.error("Fail to analyze library from url: " + file, e);
        }
      }
      myModificationCount.incrementAndGet();
    }
  }

  private void analyzeLibraryJar(String filePath) throws IOException {
    String libraryText = null;
    File jarFile = new File(filePath);

    Map<String, JarIcon> iconUrls = new HashMap<>();

    try (ZipFile zipFile = new ZipFile(jarFile)) {
      Enumeration<? extends ZipEntry> entries = zipFile.entries();

      while (entries.hasMoreElements()) {
        ZipEntry zipEntry = entries.nextElement();

        final String name = zipEntry.getName();

        if (ICON_LIBRARY_MARKER.equals(name)) {
          try (InputStream inputStream = zipFile.getInputStream(zipEntry)) {
            byte[] bytes = StreamUtil.loadFromStream(inputStream);

            libraryText = new String(bytes, StandardCharsets.UTF_8);
          }
        }
        else {
          if (!processMaybeIconFile(name, jarFile, zipFile, zipEntry, iconUrls, "svg", true)) {
            processMaybeIconFile(name, jarFile, zipFile, zipEntry, iconUrls, "png", false);
          }
        }
      }
    }

    if (StringUtil.isEmptyOrSpaces(libraryText)) {
      LOG.error("Icon library identificator not set " + libraryText);
      return;
    }

    String[] split = libraryText.split(":");
    IconLibraryId iconLibraryId = extractName(split[0]);
    String groupId = split[1];

    String prefix = "icon/" + groupId.replace(".", "/") + "/";

    IconLibrary lib = myLibraries.computeIfAbsent(iconLibraryId.getId(), it -> createLibrary(iconLibraryId));

    for (Map.Entry<String, JarIcon> entry : iconUrls.entrySet()) {
      String iconPath = entry.getKey();
      JarIcon value = entry.getValue();

      if (iconPath.startsWith(prefix)) {
        if (value._1x == null) {
          LOG.error("There no 1.0 scale icon for path " + iconPath + ". Skipping");
          continue;
        }<commit_msg>do not allow redefine icon groups<commit_after>/*
 * Copyright 2013-2020 consulo.io
 *
 * 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 consulo.ui.impl.image;

import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.util.io.URLUtil;
import consulo.logging.Logger;
import consulo.ui.image.IconLibraryManager;
import consulo.ui.image.Image;
import consulo.util.lang.StringUtil;
import consulo.util.lang.ThreeState;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @author VISTALL
 * @since 2020-09-26
 */
public abstract class BaseIconLibraryManager implements IconLibraryManager {
  private static class JarIcon {
    public ThreeState svgState = ThreeState.UNSURE;

    public byte[] _1x;
    public byte[] _2x;
  }

  private static final Logger LOG = Logger.getInstance(BaseIconLibraryManager.class);

  public static final String ICON_LIBRARY_MARKER = "icon/id.txt";

  private final AtomicLong myModificationCount = new AtomicLong();
  private final AtomicBoolean myInitialized = new AtomicBoolean();

  private Map<String, IconLibrary> myLibraries = new HashMap<>();
  private String myActiveLibraryName = LIGHT_LIBRARY_ID;
  private IconLibrary myActiveLibrary;

  @Nonnull
  @Override
  public Set<String> getLibrariesId() {
    return myLibraries.keySet();
  }

  @Nonnull
  @Override
  public String getActiveLibraryId() {
    return myActiveLibraryName;
  }

  @Override
  public long getModificationCount() {
    return myModificationCount.get();
  }

  @Override
  public void setActiveLibrary(@Nonnull String id) {
    IconLibrary iconLibrary = myLibraries.get(id);
    if(iconLibrary != null) {
      myActiveLibraryName = id;
      myActiveLibrary = iconLibrary;
      myModificationCount.incrementAndGet();
    }
    else {
      LOG.error("Can't find icon library with id {0}", id);
    }
  }

  @Nullable
  public IconLibrary getLibrary(@Nonnull String id) {
    return myLibraries.get(id);
  }

  @Nonnull
  public IconLibrary getActiveLibrary() {
    if (myActiveLibrary == null) {
      myActiveLibrary = myLibraries.get(myActiveLibraryName);
    }

    if (myActiveLibrary == null) {
      throw new Error("There no default active library. Distribution broken");
    }
    return myActiveLibrary;
  }

  @Nonnull
  protected abstract IconLibrary createLibrary(@Nonnull IconLibraryId id);

  public void initialize(@Nullable List<String> files) {
    if (myInitialized.compareAndSet(false, true)) {
      if (files == null) {
        return;
      }

      Set<String> analyzedGroups = new HashSet<>();
      
      for (String file : files) {
        try {
          analyzeLibraryJar(file, analyzedGroups);
        }
        catch (IOException e) {
          LOG.error("Fail to analyze library from url: " + file, e);
        }
      }
      myModificationCount.incrementAndGet();
    }
  }

  private void analyzeLibraryJar(@Nonnull String filePath, @Nonnull Set<String> analyzedGroups) throws IOException {
    String libraryText = null;
    File jarFile = new File(filePath);

    Map<String, JarIcon> iconUrls = new HashMap<>();

    try (ZipFile zipFile = new ZipFile(jarFile)) {
      Enumeration<? extends ZipEntry> entries = zipFile.entries();

      while (entries.hasMoreElements()) {
        ZipEntry zipEntry = entries.nextElement();

        final String name = zipEntry.getName();

        if (ICON_LIBRARY_MARKER.equals(name)) {
          try (InputStream inputStream = zipFile.getInputStream(zipEntry)) {
            byte[] bytes = StreamUtil.loadFromStream(inputStream);

            libraryText = new String(bytes, StandardCharsets.UTF_8);
          }
        }
        else {
          if (!processMaybeIconFile(name, jarFile, zipFile, zipEntry, iconUrls, "svg", true)) {
            processMaybeIconFile(name, jarFile, zipFile, zipEntry, iconUrls, "png", false);
          }
        }
      }
    }

    if (StringUtil.isEmptyOrSpaces(libraryText)) {
      LOG.error("Icon library identificator not set " + libraryText);
      return;
    }

    String[] split = libraryText.split(":");
    IconLibraryId iconLibraryId = extractName(split[0]);
    String groupId = split[1];

    String prefix = "icon/" + groupId.replace(".", "/") + "/";

    if (!analyzedGroups.add(iconLibraryId.getId() + ":" + groupId)) {
      LOG.error("Can't redefine icon group " + groupId + " in library " + iconLibraryId.getId() + ". Path: " + filePath);
      return;
    }

    IconLibrary lib = myLibraries.computeIfAbsent(iconLibraryId.getId(), it -> createLibrary(iconLibraryId));

    for (Map.Entry<String, JarIcon> entry : iconUrls.entrySet()) {
      String iconPath = entry.getKey();
      JarIcon value = entry.getValue();

      if (iconPath.startsWith(prefix)) {
        if (value._1x == null) {
          LOG.error("There no 1.0 scale icon for path " + iconPath + ". Skipping");
          continue;
        }
{'fraction_non_alphanumeric': 0.10555655457651501, 'fraction_numerical': 0.005214889408379788, 'mean_word_length': 3.3517214397496087, 'pattern_counts': {'":': 3, '<': 22, '<?xml version=': 0, '>': 24, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5016061', 'n_tokens_mistral': 3277, 'n_tokens_neox': 3171, 'n_words': 858}