diff --git a/.run/Run Plugin.run.xml b/.run/Run Plugin.run.xml new file mode 100644 index 00000000..00a760e5 --- /dev/null +++ b/.run/Run Plugin.run.xml @@ -0,0 +1,25 @@ + + + + + + + + true + true + false + false + + + diff --git a/build.gradle.kts b/build.gradle.kts index dad1e207..67b79f0f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,7 +53,7 @@ tasks { // } withType { - dependsOn(copyClassesToSandbox, copyCheckstyleArtifactsToSandbox) + dependsOn(copyClassesToSandbox) } withType { diff --git a/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/CheckstyleVersions.java b/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/CheckstyleVersions.java index f72a03d7..301f5bb3 100644 --- a/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/CheckstyleVersions.java +++ b/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/CheckstyleVersions.java @@ -21,13 +21,11 @@ public class CheckstyleVersions { private static final String PROP_FILE = "src/main/resources/checkstyle-idea.properties"; private static final String PROP_VERSIONS_SUPPORTED = "checkstyle.versions.supported"; - private static final String PROP_DEPENDENCY_MAP = "checkstyle.dependencies.map"; private static final String PROP_NAME_BASEVERSION = "baseVersion"; private final File propertyFile; private final SortedSet versions; - private final Map dependencyMappings; private final String baseVersion; @@ -37,7 +35,6 @@ public CheckstyleVersions(final Project project) { final Properties properties = readProperties(); versions = buildVersionSet(properties); baseVersion = readBaseVersion(properties); - dependencyMappings = readDependencyMap(properties); } private SortedSet buildVersionSet(final Properties properties) { @@ -89,22 +86,6 @@ private String readBaseVersion(final Properties properties) { return baseVersionValue; } - private Map readDependencyMap(final Properties properties) { - final String propertyValue = properties.getProperty(PROP_DEPENDENCY_MAP); - if (propertyValue == null || propertyValue.trim().isEmpty()) { - return Collections.emptyMap(); - } - - final Map mappings = new HashMap<>(); - for (final String mapping : propertyValue.trim().split("\\s*,\\s*")) { - if (!mapping.isEmpty()) { - final String[] oldDependencyToNewDependency = parseKeyValueMapping(mapping); - mappings.put(oldDependencyToNewDependency[0], oldDependencyToNewDependency[1]); - } - } - return Collections.unmodifiableMap(mappings); - } - public File getPropertyFile() { return propertyFile; } @@ -130,17 +111,4 @@ public static Dependency createCheckstyleDependency(final Project project, final csDep.exclude(ex); return csDep; } - - private String[] parseKeyValueMapping(final String mapping) { - final String[] kv = mapping.split("\\s*->\\s*"); - if (kv.length != 2) { - throw new GradleException("Internal error: Property '" + CheckstyleVersions.PROP_DEPENDENCY_MAP - + "' contains invalid mapping '" + mapping + "'"); - } - return kv; - } - - public Map getDependencyMappings() { - return dependencyMappings; - } } diff --git a/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/GatherCheckstyleArtifactsTask.java b/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/GatherCheckstyleArtifactsTask.java deleted file mode 100644 index 4448f80d..00000000 --- a/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/GatherCheckstyleArtifactsTask.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.infernus.idea.checkstyle.build; - -import org.apache.commons.io.FileUtils; -import org.gradle.api.DefaultTask; -import org.gradle.api.GradleException; -import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.TaskAction; -import org.gradle.language.base.plugins.LifecycleBasePlugin; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.*; - -import static java.util.stream.Collectors.toSet; - - -/** - * Download all supported versions of Checkstyle along with their transitive dependencies, for bundling with the - * plugin. - */ -public class GatherCheckstyleArtifactsTask - extends DefaultTask { - public static final String NAME = "gatherCheckstyleArtifacts"; - - private final Map> rawVersionsToDependencies = new HashMap<>(); - private final CheckstyleVersions csVersions; - - @OutputDirectory - private final File bundledJarsDir; - - @OutputFile - private final File classPathsInfoFile; - - public GatherCheckstyleArtifactsTask() { - super(); - setGroup(LifecycleBasePlugin.BUILD_GROUP); - setDescription("Gathers Checkstyle libraries and their dependencies for bundling"); - final Project project = getProject(); - - // Task Inputs: the property file with the list of supported Checkstyle versions - csVersions = new CheckstyleVersions(project); - getInputs().file(csVersions.getPropertyFile()); - - // Task Outputs: the directory full of JARs, and the classpath info file - bundledJarsDir = getTemporaryDir(); - classPathsInfoFile = new File(project.getLayout().getBuildDirectory().getAsFile().get(), "resources-generated/checkstyle-classpaths.properties"); - - for (final String csVersion : csVersions.getVersions()) { - final Set dependencies = resolveDependencies(project, csVersion); - rawVersionsToDependencies.put(csVersion, dependencies); - } - } - - @TaskAction - public void runTask() { - final Set bundledFiles = new TreeSet<>(); - final Properties classPaths = new SortedProperties(); - final Set availableFileNames = new HashSet<>(); - - for (final String csVersion : csVersions.getVersions()) { - Set dependencies = rawVersionsToDependencies.get(csVersion); - availableFileNames.addAll(dependencies.stream().map(File::getName).collect(toSet())); - } - - final Map dependencyMappings = csVersions.getDependencyMappings(); - for (final String csVersion : csVersions.getVersions()) { - Set processedDependencies = rawVersionsToDependencies.get(csVersion).stream() - .map(dependencyFile -> { - if (csVersions.getDependencyMappings().containsKey(dependencyFile.getName()) - && availableFileNames.contains(dependencyMappings.get(dependencyFile.getName()))) { - return dependencyMappings.get(dependencyFile.getName()); - } else { - bundledFiles.add(dependencyFile); - return dependencyFile.getName(); - } - }) - .collect(toSet()); - - classPaths.setProperty(csVersion, convertToClassPath(processedDependencies)); - } - - copyFiles(bundledFiles); - createClassPathsFile(classPaths); - } - - private Set resolveDependencies(final Project project, final String checkstyleVersion) { - final Dependency csDep = CheckstyleVersions.createCheckstyleDependency(project, checkstyleVersion); - final Configuration csConf = project.getConfigurations().detachedConfiguration(csDep); - // workaround for Checkstyle#14123 - csConf.getResolutionStrategy() - .getCapabilitiesResolution() - .withCapability("com.google.collections", "google-collections", resolutionDetails -> resolutionDetails.select("com.google.guava:guava:0")); - return csConf.resolve(); - } - - private String convertToClassPath(final Collection resolvedDependencies) { - final StringBuilder sb = new StringBuilder(); - for (final String fileName : resolvedDependencies) { - sb.append(GradlePluginMain.CSLIB_TARGET_SUBFOLDER); - sb.append('/'); - sb.append(fileName); - sb.append(';'); - } - sb.deleteCharAt(sb.length() - 1); - return sb.toString(); - } - - private void copyFiles(final Set bundledJars) { - for (final File bundledJar : bundledJars) { - try { - FileUtils.copyFileToDirectory(bundledJar, bundledJarsDir, true); - } catch (IOException e) { - throw new GradleException("Unable to copy file: " + bundledJar.getAbsolutePath(), e); - } - } - } - - private void createClassPathsFile(final Properties classPaths) { - //noinspection ResultOfMethodCallIgnored - classPathsInfoFile.getParentFile().mkdir(); - - try (OutputStream os = new FileOutputStream(classPathsInfoFile)) { - classPaths.store(os, " Class path information for Checkstyle artifacts bundled with Checkstyle_IDEA"); - } catch (IOException e) { - throw new GradleException("Unable to write classpath info file: " + classPathsInfoFile.getAbsolutePath(), - e); - } - } - - public File getBundledJarsDir() { - return bundledJarsDir; - } - - public File getClassPathsInfoFile() { - return classPathsInfoFile; - } -} diff --git a/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/GradlePluginMain.java b/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/GradlePluginMain.java index b480be82..9b088c3a 100644 --- a/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/GradlePluginMain.java +++ b/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/GradlePluginMain.java @@ -11,7 +11,6 @@ import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.Copy; import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.testing.Test; @@ -25,7 +24,6 @@ * The main plugin class. The action starts here. */ public class GradlePluginMain implements Plugin { - public static final String CSLIB_TARGET_SUBFOLDER = "checkstyle/lib"; private static final String CSCLASSES_TARGET_SUBFOLDER = "checkstyle/classes"; private CheckstyleVersions supportedCsVersions = null; @@ -99,49 +97,10 @@ private void createCrossCheckTasks(final Project project) { } private void createCheckstyleArtifactTasks(final Project project) { - TaskProvider taskProvider = - project.getTasks().register(GatherCheckstyleArtifactsTask.NAME, GatherCheckstyleArtifactsTask.class); - taskProvider.configure((GatherCheckstyleArtifactsTask task) -> { - project.getTasks().getByName(JavaPlugin.PROCESS_RESOURCES_TASK_NAME).dependsOn(task); - - // Add generated classpath info file to resources - SourceSetContainer sourceSets = (SourceSetContainer) project.getProperties().get("sourceSets"); - SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); - mainSourceSet.getResources().srcDir(task.getClassPathsInfoFile().getParentFile()); - }); - - createCopyCheckstyleArtifactsToSandboxTask(project, false); - createCopyCheckstyleArtifactsToSandboxTask(project, true); - createCopyClassesToSandboxTask(project, false); createCopyClassesToSandboxTask(project, true); } - private void createCopyCheckstyleArtifactsToSandboxTask(final Project project, final boolean test) { - final TaskContainer tasks = project.getTasks(); - final String taskName = test ? "copyCheckstyleArtifactsToTestSandbox" : "copyCheckstyleArtifactsToSandbox"; - final TaskProvider taskProvider = tasks.register(taskName, Copy.class); - taskProvider.configure((Copy copyTask) -> { - copyTask.setGroup("intellij"); - copyTask.setDescription("Adds the gathered Checkstyle artifacts to the prepared " - + (test ? "test " : "") + "sandbox"); - - final GatherCheckstyleArtifactsTask gatherTask = - (GatherCheckstyleArtifactsTask) tasks.getByName(GatherCheckstyleArtifactsTask.NAME); - copyTask.dependsOn(gatherTask, "prepareTestSandbox"); - if (test) { - tasks.getByName(JavaPlugin.TEST_TASK_NAME).dependsOn(copyTask); - tasks.getByName(CsaccessTestTask.NAME).dependsOn(copyTask); - forEachXTest(tasks, xTask -> xTask.dependsOn(copyTask)); - } else { - tasks.getByName("buildSearchableOptions").dependsOn(copyTask); - } - - copyTask.from(gatherTask.getBundledJarsDir()); - copyTask.into(new File(project.getLayout().getBuildDirectory().getAsFile().get(), pluginSandboxDir(test, CSLIB_TARGET_SUBFOLDER))); - }); - } - private void forEachXTest(final TaskContainer tasks, final Consumer taskConsumer) { supportedCsVersions.getVersions().forEach((final String csVersion) -> { if (!supportedCsVersions.getBaseVersion().equals(csVersion)) { @@ -174,10 +133,8 @@ private void createCopyClassesToSandboxTask(final Project project, final boolean tasks.getByName(JavaPlugin.TEST_TASK_NAME).dependsOn(copyTask); tasks.getByName(CsaccessTestTask.NAME).dependsOn(copyTask); forEachXTest(tasks, xTask -> xTask.dependsOn(copyTask)); - copyTask.mustRunAfter(tasks.getByName("copyCheckstyleArtifactsToTestSandbox")); } else { tasks.getByName("buildSearchableOptions").dependsOn(copyTask); - copyTask.mustRunAfter(tasks.getByName("copyCheckstyleArtifactsToSandbox")); } copyTask.from(csaccessSourceSet.getOutput()); @@ -201,13 +158,10 @@ private void wireIntellijPluginTasks(final Project project) { final TaskContainer tasks = project.getTasks(); tasks.all((Task task) -> { if ("buildPlugin".equals(task.getName()) || "runIdea".equals(task.getName()) || "runIde".equals(task.getName())) { - task.dependsOn(tasks.getByName("copyCheckstyleArtifactsToSandbox")); task.dependsOn(tasks.getByName("copyClassesToSandbox")); } else if ("prepareSandbox".equals(task.getName())) { - tasks.getByName("copyCheckstyleArtifactsToSandbox").dependsOn(task); tasks.getByName("copyClassesToSandbox").dependsOn(task); } else if ("prepareTestsSandbox".equals(task.getName())) { - tasks.getByName("copyCheckstyleArtifactsToTestSandbox").dependsOn(task); tasks.getByName("copyClassesToTestSandbox").dependsOn(task); } }); diff --git a/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/SortedProperties.java b/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/SortedProperties.java deleted file mode 100644 index 7d230689..00000000 --- a/buildSrc/src/main/java/org/infernus/idea/checkstyle/build/SortedProperties.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.infernus.idea.checkstyle.build; - -import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.List; -import java.util.Properties; - - -/** - * Just so we generate a sorted property file with the classpath information. - */ -class SortedProperties extends Properties { - - @Override - public synchronized Enumeration keys() { - List keyList = Collections.list(super.keys()); - final Comparator versionComparator = new VersionComparator(); - keyList.sort((a, b) -> versionComparator.compare(a.toString(), b.toString())); - return Collections.enumeration(keyList); - } -} diff --git a/src/main/java/org/infernus/idea/checkstyle/CheckstyleClassLoaderContainer.java b/src/main/java/org/infernus/idea/checkstyle/CheckstyleClassLoaderContainer.java index 3f1ce23e..0f51f5cb 100644 --- a/src/main/java/org/infernus/idea/checkstyle/CheckstyleClassLoaderContainer.java +++ b/src/main/java/org/infernus/idea/checkstyle/CheckstyleClassLoaderContainer.java @@ -11,24 +11,21 @@ import org.jetbrains.annotations.Nullable; import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.lang.reflect.Constructor; import java.net.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.infernus.idea.checkstyle.CheckStyleBundle.message; -import static org.infernus.idea.checkstyle.util.Strings.isBlank; /** * Loads Checkstyle classes from a given Checkstyle version. */ public class CheckstyleClassLoaderContainer { - private static final String PROP_FILE = "checkstyle-classpaths.properties"; private static final String CSACTIONS_CLASS = "org.infernus.idea.checkstyle.service.CheckstyleActionsImpl"; /** @@ -60,17 +57,12 @@ public class CheckstyleClassLoaderContainer { public CheckstyleClassLoaderContainer(@NotNull final Project project, @NotNull final CheckstyleProjectService checkstyleProjectService, - @NotNull final String checkstyleVersion, + @NotNull final Path jarPath, @Nullable final List thirdPartyClassPath) { this.project = project; this.checkstyleProjectService = checkstyleProjectService; - final Properties classPathInfos = loadClassPathInfos(); - final String cpProp = classPathInfos.getProperty(checkstyleVersion); - if (isBlank(cpProp)) { - throw new CheckStylePluginException("Unsupported Checkstyle version: " + checkstyleVersion); - } - classLoader = buildClassLoader(cpProp, emptyListIfNull(thirdPartyClassPath)); + classLoader = buildClassLoader(jarPath, emptyListIfNull(thirdPartyClassPath)); } @NotNull @@ -79,26 +71,15 @@ private List emptyListIfNull(@Nullable final List potentiallyNullList) } @NotNull - private static Properties loadClassPathInfos() { - final Properties result = new Properties(); - try (InputStream is = CheckstyleClassLoaderContainer.class.getClassLoader().getResourceAsStream(PROP_FILE)) { - result.load(is); - } catch (IOException e) { - throw new CheckStylePluginException("Could not read plugin-internal file: " + PROP_FILE, e); - } - return result; - } - - @NotNull - private ClassLoader buildClassLoader(@NotNull final String classPathFromProps, + private ClassLoader buildClassLoader(@NotNull final Path jarPath, @NotNull final List thirdPartyClasspath) { final String basePluginPath = getBasePluginPath(); List urls; if (basePluginPath != null) { - urls = baseClasspathUrlsForPackagedPlugin(classPathFromProps, basePluginPath); + urls = baseClasspathUrlsForPackagedPlugin(jarPath, basePluginPath); } else { - urls = baseClasspathUrlsForIDEAUnitTests(classPathFromProps); + urls = baseClasspathUrlsForIDEAUnitTests(jarPath); } urls.addAll(thirdPartyClasspath); @@ -114,19 +95,12 @@ private ClassLoader buildClassLoader(@NotNull final String classPathFromProps, return newClassLoader; } - private static List baseClasspathUrlsForPackagedPlugin(@NotNull final String classPathFromProps, + private static List baseClasspathUrlsForPackagedPlugin(@NotNull final Path jarPath, @NotNull final String basePath) { try { final List urls = new ArrayList<>(); urls.add(getClassesDirectory(basePath).toURI().toURL()); - - for (String jar : splitClassPathFromProperties(classPathFromProps)) { - File jarLocation = new File(basePath, jar); - if (!jarLocation.exists()) { - throw new CheckStylePluginException("Cannot find packaged artefact: " + jarLocation.getAbsolutePath()); - } - urls.add(jarLocation.toURI().toURL()); - } + urls.add(jarPath.toUri().toURL()); return urls; @@ -137,7 +111,7 @@ private static List baseClasspathUrlsForPackagedPlugin(@NotNull final Strin private static @NotNull File getClassesDirectory(@NotNull final String basePath) { final File basePathFile = new File(basePath); - if (!new File(basePath).exists()) { + if (!basePathFile.exists()) { throw new CheckStylePluginException("Cannot find plugin directory: " + basePathFile.getAbsolutePath()); } @@ -149,22 +123,14 @@ private static List baseClasspathUrlsForPackagedPlugin(@NotNull final Strin return classesDirectory; } - private List baseClasspathUrlsForIDEAUnitTests(@NotNull final String classPathFromProps) { + private List baseClasspathUrlsForIDEAUnitTests(@NotNull final Path jarPath) { try { final List urls = new ArrayList<>(); final String buildPath = guessBuildPathFromClasspath(); URL unitTestingClassPath = getUnitTestingClassPath(buildPath); urls.add(unitTestingClassPath); - - for (String jar : splitClassPathFromProperties(classPathFromProps)) { - String testJarLocation = "tmp/gatherCheckstyleArtifacts" + jar.substring(jar.lastIndexOf('/')); - File jarLocation = new File(buildPath, testJarLocation); - if (!jarLocation.exists()) { - throw new CheckStylePluginException("Cannot find collected artefact: " + jarLocation.getAbsolutePath()); - } - urls.add(jarLocation.toURI().toURL()); - } + urls.add(jarPath.toUri().toURL()); return urls; @@ -189,11 +155,6 @@ private static URL getUnitTestingClassPath(final String buildPath) throws Malfor return unitTestingClassPath; } - @NotNull - private static String[] splitClassPathFromProperties(@NotNull final String classPathFromProps) { - return classPathFromProps.trim().split("\\s*;\\s*"); - } - private boolean weAreDebuggingADifferentVersionOfIdea(final ClassLoader classLoaderToTest) { try { return Project.class != classLoaderToTest.loadClass("com.intellij.openapi.project.Project"); diff --git a/src/main/java/org/infernus/idea/checkstyle/CheckstyleDownloader.java b/src/main/java/org/infernus/idea/checkstyle/CheckstyleDownloader.java new file mode 100644 index 00000000..ee639e53 --- /dev/null +++ b/src/main/java/org/infernus/idea/checkstyle/CheckstyleDownloader.java @@ -0,0 +1,71 @@ +package org.infernus.idea.checkstyle; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.util.io.HttpRequests; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.infernus.idea.checkstyle.config.ApplicationConfigurationState; +import org.jetbrains.annotations.NotNull; + +public class CheckstyleDownloader { + + public void deleteVersion(@NotNull final String version) throws IOException { + Files.delete(getArtifactPath(version)); + } + + public void downloadVersion(@NotNull final String version) throws IOException { + final var outputPath = getArtifactPath(version); + if (!Files.exists(outputPath.getParent())) { + Files.createDirectories(outputPath.getParent()); + } + + try { + var currentDownloadAttempt = 0; + IOException exception = null; + while (currentDownloadAttempt < 3) { + currentDownloadAttempt++; + + try { + tryDownload(version, outputPath); + exception = null; + } catch (final IOException ioException) { + exception = ioException; + } + } + + if (exception != null) { + throw exception; + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public Path getArtifactPath(final String version) { + return ApplicationManager.getApplication().getService(ApplicationConfigurationState.class) + .getState().getCachePath().resolve(getArtifactName(version)); + } + + @NotNull + private String getArtifactName(@NotNull final String version) { + return "checkstyle-" + version + "-all.jar"; + } + + @NotNull + private String getBaseDownloadUrl() { + return ApplicationManager.getApplication().getService(ApplicationConfigurationState.class) + .getState().getBaseDownloadUrl(); + } + + private void tryDownload(final String version, final Path outputPath) throws IOException { + var baseUrl = getBaseDownloadUrl(); + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + + HttpRequests.request(baseUrl + "/checkstyle-" + version + "/" + getArtifactName(version)) + .saveToFile(outputPath.toFile(), null); + } +} diff --git a/src/main/java/org/infernus/idea/checkstyle/CheckstyleDownloadsConfigurable.java b/src/main/java/org/infernus/idea/checkstyle/CheckstyleDownloadsConfigurable.java new file mode 100644 index 00000000..81318572 --- /dev/null +++ b/src/main/java/org/infernus/idea/checkstyle/CheckstyleDownloadsConfigurable.java @@ -0,0 +1,268 @@ +package org.infernus.idea.checkstyle; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.options.BoundSearchableConfigurable; +import com.intellij.openapi.ui.DialogPanel; +import com.intellij.platform.ide.progress.ModalTaskOwner; +import com.intellij.platform.ide.progress.TaskCancellation; +import com.intellij.platform.ide.progress.TasksKt; +import com.intellij.ui.dsl.builder.Align; +import com.intellij.ui.dsl.builder.AlignX; +import com.intellij.ui.dsl.builder.BuilderKt; +import com.intellij.ui.dsl.builder.TextFieldKt; +import com.intellij.ui.table.TableView; +import com.intellij.util.ui.ColumnInfo; +import com.intellij.util.ui.ListTableModel; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import javax.swing.DefaultCellEditor; +import javax.swing.JCheckBox; +import javax.swing.SwingConstants; +import javax.swing.table.TableCellEditor; +import org.infernus.idea.checkstyle.config.ApplicationConfigurationState; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CheckstyleDownloadsConfigurable extends BoundSearchableConfigurable { + + private static final VersionListReader VERSION_LIST_READER = new VersionListReader(); + + private final Set initialVersions = new HashSet<>(); + + private final List versions = new ArrayList<>(); + + + public CheckstyleDownloadsConfigurable() { + super("Checkstyle Downloader", "reference.settings.checkstyleDownloader", + "org.infernus.idea.checkstyle.CheckstyleDownloadsConfigurable"); + } + + @Override + public @NotNull DialogPanel createPanel() { + final var applicationConfigurationState = ApplicationManager.getApplication() + .getService(ApplicationConfigurationState.class); + + return BuilderKt.panel(panel -> { + panel.row("Base Download URL: ", row -> { + TextFieldKt.bindText(row.textField(), + () -> applicationConfigurationState.getState().getBaseDownloadUrl(), value -> { + applicationConfigurationState.getState().setBaseDownloadUrl(value); + return null; + }).align(AlignX.FILL); + + return null; + }); + + panel.row("Cache Path: ", row -> { + TextFieldKt.bindText(row.textField(), + () -> applicationConfigurationState.getState().getCachePath().toString(), + value -> { + applicationConfigurationState.getState().setCachePath(value); + return null; + }).align(AlignX.FILL); + + return null; + }); + + panel.row("", row -> { + final var table = new TableView( + new ListTableModel<>(createDownloadedColumn(), createVersionColumn())); + table.setFillsViewportHeight(true); + table.setStriped(true); + + try { + final var downloadedVersions = getDownloadedVersions( + ApplicationManager.getApplication() + .getService(ApplicationConfigurationState.class).getState() + .getCachePath()); + initialVersions.addAll(downloadedVersions); + versions.addAll(VERSION_LIST_READER.getSupportedVersions().stream().map( + version -> new CheckstyleVersionDownload( + downloadedVersions.contains(version), version)).toList()); + Collections.sort(versions); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + table.getListTableModel().setItems(versions); + + row.scrollCell(table).align(Align.FILL).onApply(() -> { + // Must come after the base URL and cache path changes are persisted to the + // application state to pick up any changes made there first. + final Set downloadedVersions; + try { + downloadedVersions = getDownloadedVersions( + applicationConfigurationState.getState().getCachePath()); + } catch (IOException exception) { + throw new UncheckedIOException(exception); + } + + final Set versionsToDownload = new HashSet<>(); + final Set versionsToRemove = new HashSet<>(); + for (final CheckstyleVersionDownload version : versions) { + // Literally exists on the file system already. + final boolean isAlreadyDownloaded = downloadedVersions.contains( + version.version); + // Selected in the UI as a desired version. + final boolean userIndicatedShouldBeDownloaded = version.isDownloaded; + + if (!isAlreadyDownloaded && userIndicatedShouldBeDownloaded) { + versionsToDownload.add(version.version); + } else if (isAlreadyDownloaded && !userIndicatedShouldBeDownloaded) { + versionsToRemove.add(version.version); + } + } + + final var checkstyleDownloader = ApplicationManager.getApplication() + .getService(CheckstyleDownloader.class); + TasksKt.runWithModalProgressBlocking(ModalTaskOwner.component(table), + "Downloading Checkstyle Versions", TaskCancellation.nonCancellable(), + (scope, continuation) -> { + versionsToDownload.forEach(version -> { + try { + checkstyleDownloader.downloadVersion(version); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + + return null; + }); + + TasksKt.runWithModalProgressBlocking(ModalTaskOwner.component(table), + "Deleting Checkstyle Versions", TaskCancellation.nonCancellable(), + (scope, continuation) -> { + versionsToRemove.forEach(version -> { + try { + checkstyleDownloader.deleteVersion(version); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + + return null; + }); + + return null; + }).onIsModified(() -> { + final Set modifiedVersions = versions.stream() + .filter(version -> version.isDownloaded).map(version -> version.version) + .collect(Collectors.toSet()); + if (!Objects.equals(initialVersions, modifiedVersions)) { + return true; + } + + return false; + }).onReset(() -> { + + return null; + }); + + return null; + }); + + return null; + }); + } + + private Set getDownloadedVersions(final Path cachePath) throws IOException { + if (!Files.exists(cachePath)) { + return Collections.emptySet(); + } + try (final var cachedFiles = Files.list(cachePath)) { + return cachedFiles.map(path -> { + final String fileName = path.getFileName().toString(); + if (!fileName.startsWith("checkstyle-") && !fileName.endsWith("-all.jar")) { + return null; + } + return fileName.substring("checkstyle-".length(), + fileName.length() - "-all.jar".length()); + }).filter(Objects::nonNull).collect(Collectors.toSet()); + } + } + + private static ColumnInfo createDownloadedColumn() { + return new ColumnInfo<>( + CheckStyleBundle.message("config-downloader.downloads.table.downloaded")) { + + @Override + public Class getColumnClass() { + return Boolean.class; + } + + @Override + public TableCellEditor getEditor(CheckstyleVersionDownload checkstyleVersionDownload) { + final var checkbox = new JCheckBox(); + checkbox.setHorizontalAlignment(SwingConstants.CENTER); + return new DefaultCellEditor(checkbox); + } + + @Override + public boolean isCellEditable(CheckstyleVersionDownload checkstyleVersionDownload) { + return true; + } + + @Override + public void setValue(CheckstyleVersionDownload checkstyleVersionDownload, + Boolean value) { + if (checkstyleVersionDownload != null && value != null) { + checkstyleVersionDownload.isDownloaded = value; + } + } + + @Override + public @Nullable Boolean valueOf(CheckstyleVersionDownload checkstyleVersionDownload) { + if (checkstyleVersionDownload == null) { + return null; + } + return checkstyleVersionDownload.isDownloaded; + } + }; + } + + private static ColumnInfo createVersionColumn() { + return new ColumnInfo<>( + CheckStyleBundle.message("config-downloader.downloads.table.version")) { + @Override + public @Nullable String valueOf(CheckstyleVersionDownload checkstyleVersionDownload) { + if (checkstyleVersionDownload == null) { + return null; + } + return checkstyleVersionDownload.version; + } + }; + } + + private static class CheckstyleVersionDownload implements + Comparable { + + private boolean isDownloaded; + private String version; + + CheckstyleVersionDownload(final boolean isDownloaded, final String version) { + this.isDownloaded = isDownloaded; + this.version = version; + } + + private static final Comparator VERSION_COMPARATOR = new VersionComparator().reversed(); + + @Override + public int compareTo(@NotNull final CheckstyleVersionDownload o) { + final var versionCompare = Objects.compare(version, o.version, VERSION_COMPARATOR); + if (versionCompare != 0) { + return versionCompare; + } + return Objects.compare(isDownloaded, o.isDownloaded, Boolean::compareTo); + } + } +} diff --git a/src/main/java/org/infernus/idea/checkstyle/CheckstyleProjectService.java b/src/main/java/org/infernus/idea/checkstyle/CheckstyleProjectService.java index 0b169c26..75f60117 100644 --- a/src/main/java/org/infernus/idea/checkstyle/CheckstyleProjectService.java +++ b/src/main/java/org/infernus/idea/checkstyle/CheckstyleProjectService.java @@ -1,25 +1,28 @@ package org.infernus.idea.checkstyle; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import org.infernus.idea.checkstyle.config.PluginConfigurationManager; -import org.infernus.idea.checkstyle.csapi.CheckstyleActions; -import org.infernus.idea.checkstyle.exception.CheckStylePluginException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - +import com.intellij.platform.ide.progress.TasksKt; import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.SortedSet; import java.util.concurrent.Callable; - +import org.infernus.idea.checkstyle.config.PluginConfigurationManager; +import org.infernus.idea.checkstyle.csapi.CheckstyleActions; +import org.infernus.idea.checkstyle.exception.CheckStylePluginException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** - * Makes the Checkstyle tool available to the plugin in the correct version. Registered in {@code plugin.xml}. - * This must be a project-level service because the Checkstyle version is chosen per project. + * Makes the Checkstyle tool available to the plugin in the correct version. Registered in + * {@code plugin.xml}. This must be a project-level service because the Checkstyle version is chosen + * per project. */ public class CheckstyleProjectService { @@ -34,12 +37,12 @@ public class CheckstyleProjectService { public CheckstyleProjectService(@NotNull final Project project) { this(project, pluginConfigurationManager(project).getCurrent().getCheckstyleVersion(), - pluginConfigurationManager(project).getCurrent().getThirdPartyClasspath()); + pluginConfigurationManager(project).getCurrent().getThirdPartyClasspath()); } private CheckstyleProjectService(@NotNull final Project project, - @Nullable final String requestedVersion, - @Nullable final List thirdPartyJars) { + @Nullable final String requestedVersion, + @Nullable final List thirdPartyJars) { this.project = project; supportedVersions = new VersionListReader().getSupportedVersions(); @@ -53,7 +56,8 @@ private static void ensureAValidatingParsingIsSetIfPiccoloIsInClasspath() { // is available if Piccolo is on the project classpath try { Class.forName("org.apache.xerces.jaxp.SAXParserFactoryImpl"); - System.setProperty("com.bluecast.xml.ValidatingSAXParserFactory", "org.apache.xerces.jaxp.SAXParserFactoryImpl"); + System.setProperty("com.bluecast.xml.ValidatingSAXParserFactory", + "org.apache.xerces.jaxp.SAXParserFactoryImpl"); } catch (ClassNotFoundException ignored) { // ignored } @@ -61,8 +65,8 @@ private static void ensureAValidatingParsingIsSetIfPiccoloIsInClasspath() { @NotNull public static CheckstyleProjectService forVersion(@NotNull final Project project, - @Nullable final String requestedVersion, - @Nullable final List thirdPartyJars) { + @Nullable final String requestedVersion, + @Nullable final List thirdPartyJars) { return new CheckstyleProjectService(project, requestedVersion, thirdPartyJars); } @@ -77,18 +81,37 @@ private String getDefaultVersion() { } public void activateCheckstyleVersion(@Nullable final String requestedVersion, - @Nullable final List thirdPartyJars) { + @Nullable final List thirdPartyJars) { String checkstyleVersionToLoad = versionToLoad(requestedVersion); + // TODO: Need to offer an option for users to pre-download library versions. + // TODO: Need to offer an option for users to download the library through an external tool, + // and place it into the cache directory for later use. + final var checkstyleDownloader = ApplicationManager.getApplication() + .getService(CheckstyleDownloader.class); synchronized (project) { + + TasksKt.runWithModalProgressBlocking(project, + "Downloading Checkstyle Versions", + (scope, continuation) -> { + try { + checkstyleDownloader.downloadVersion(checkstyleVersionToLoad); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + return null; + }); + + final var jarPath = checkstyleDownloader.getArtifactPath(checkstyleVersionToLoad); checkstyleClassLoaderContainer = null; checkstyleClassLoaderFactory = new Callable<>() { @Override public CheckstyleClassLoaderContainer call() { return new CheckstyleClassLoaderContainer( - project, - CheckstyleProjectService.this, - checkstyleVersionToLoad, - toListOfUrls(thirdPartyJars)); + project, + CheckstyleProjectService.this, + jarPath, + toListOfUrls(thirdPartyJars)); } @NotNull @@ -99,7 +122,8 @@ private List toListOfUrls(@Nullable final List jarFilePaths) { try { result.add(new File(absolutePath).toURI().toURL()); } catch (MalformedURLException e) { - LOG.warn("Skipping malformed third party classpath entry: " + absolutePath, e); + LOG.warn("Skipping malformed third party classpath entry: " + + absolutePath, e); } } } diff --git a/src/main/java/org/infernus/idea/checkstyle/VersionListReader.java b/src/main/java/org/infernus/idea/checkstyle/VersionListReader.java index 897b8091..5c483710 100644 --- a/src/main/java/org/infernus/idea/checkstyle/VersionListReader.java +++ b/src/main/java/org/infernus/idea/checkstyle/VersionListReader.java @@ -6,12 +6,9 @@ import java.util.HashSet; import java.util.Properties; import java.util.Set; -import java.util.SortedMap; import java.util.SortedSet; -import java.util.TreeMap; import java.util.TreeSet; -import com.intellij.openapi.util.Pair; import org.infernus.idea.checkstyle.exception.CheckStylePluginException; import org.infernus.idea.checkstyle.util.Strings; import org.jetbrains.annotations.NotNull; @@ -25,10 +22,8 @@ public class VersionListReader { private static final String PROP_FILE = "checkstyle-idea.properties"; private static final String PROP_SUPPORTED_VERSIONS = "checkstyle.versions.supported"; - private static final String PROP_VERSION_MAP = "checkstyle.versions.map"; private final SortedSet supportedVersions; - private final SortedMap replacementMap; public VersionListReader() { this(PROP_FILE); @@ -37,7 +32,6 @@ public VersionListReader() { VersionListReader(@NotNull final String propertyFile) { final Properties props = readProperties(propertyFile); supportedVersions = readSupportedVersions(propertyFile, props); - replacementMap = readVersionMap(propertyFile, props, supportedVersions); } @@ -102,65 +96,6 @@ private Set readVersions(@NotNull final String propertyFile, return result; } - - @NotNull - private SortedMap readVersionMap(@NotNull final String propertyFile, - @NotNull final Properties props, - @NotNull final SortedSet pSupportedVersions) { - final String propertyValue = props.getProperty(PROP_VERSION_MAP); - if (Strings.isBlank(propertyValue)) { - throw new CheckStylePluginException("Internal error: Property '" + PROP_VERSION_MAP + "' missing from " - + "configuration file '" + propertyFile + "'"); - } - - final String[] mappings = propertyValue.trim().split("\\s*,\\s*"); - final SortedMap result = new TreeMap<>(new VersionComparator()); - for (final String mapping : mappings) { - if (!mapping.isEmpty()) { - final Pair validMapping = readValidMapping(propertyFile, mapping, pSupportedVersions); - if (result.containsKey(validMapping.getFirst())) { - throw new CheckStylePluginException("Internal error: Property '" + PROP_VERSION_MAP + "' " - + "contains duplicate mapping \"" + mapping + "\" in configuration file '" + propertyFile - + "'"); - } - result.put(validMapping.getFirst(), validMapping.getSecond()); - } - } - return Collections.unmodifiableSortedMap(result); - } - - - private Pair readValidMapping(@NotNull final String propertyFile, - @NotNull final String mapping, - @NotNull final SortedSet pSupportedVersions) { - - final String[] kv = mapping.split("\\s*->\\s*"); - if (kv.length != 2) { - throw new CheckStylePluginException("Internal error: Property '" + PROP_VERSION_MAP + "' contains " - + "invalid mapping '" + mapping + "' in configuration file '" + propertyFile + "'"); - } - - final String unsupportedVersion = kv[0]; - final String goodVersion = kv[1]; - if (unsupportedVersion.isEmpty() || goodVersion.isEmpty()) { - throw new CheckStylePluginException("Internal error: Property '" + PROP_VERSION_MAP + "' contains " - + "invalid mapping '" + mapping + "' in configuration file '" + propertyFile + "'"); - } - - if (!pSupportedVersions.contains(goodVersion)) { - throw new CheckStylePluginException("Internal error: Property '" + PROP_VERSION_MAP + "' contains " - + "invalid mapping '" + mapping + "'. Target version " + goodVersion + " is not a supported " - + "version in configuration file '" + propertyFile + "'"); - } - if (pSupportedVersions.contains(unsupportedVersion)) { - throw new CheckStylePluginException("Internal error: Property '" + PROP_VERSION_MAP + "' contains " - + "invalid mapping '" + mapping + "'. Checkstyle version " + unsupportedVersion + " is in " - + "fact supported in configuration file '" + propertyFile + "'"); - } - return new Pair<>(unsupportedVersion, goodVersion); - } - - @NotNull public SortedSet getSupportedVersions() { return supportedVersions; @@ -175,9 +110,4 @@ public String getDefaultVersion() { public static String getDefaultVersion(@NotNull final SortedSet pSupportedVersions) { return pSupportedVersions.last(); } - - @NotNull - public SortedMap getReplacementMap() { - return replacementMap; - } } diff --git a/src/main/java/org/infernus/idea/checkstyle/config/ApplicationConfigurationState.java b/src/main/java/org/infernus/idea/checkstyle/config/ApplicationConfigurationState.java index 4656222b..9821c32d 100644 --- a/src/main/java/org/infernus/idea/checkstyle/config/ApplicationConfigurationState.java +++ b/src/main/java/org/infernus/idea/checkstyle/config/ApplicationConfigurationState.java @@ -4,17 +4,19 @@ import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.util.xmlb.annotations.MapAnnotation; -import org.infernus.idea.checkstyle.CheckStylePlugin; -import org.jetbrains.annotations.NotNull; - +import java.nio.file.Path; import java.util.Map; import java.util.Objects; import java.util.TreeMap; +import org.infernus.idea.checkstyle.CheckStylePlugin; +import org.jetbrains.annotations.NotNull; @State(name = CheckStylePlugin.ID_PLUGIN + "-app", storages = {@Storage("checkstyle-idea.xml")}) -public class ApplicationConfigurationState - implements PersistentStateComponent { +public class ApplicationConfigurationState implements + PersistentStateComponent { + private static final String BASE_DOWNLOAD_URL = "base-download-url"; + private static final String CACHE_PATH = "cache-path"; private static final String LAST_ACTIVE_PLUGIN_VERSION = "last-active-plugin-version"; private ApplicationSettings applicationSettings = defaultApplicationSettings(); @@ -24,6 +26,7 @@ private ApplicationSettings defaultApplicationSettings() { return ApplicationSettings.create(CheckStylePlugin.version()); } + @NotNull public ApplicationSettings getState() { return applicationSettings; } @@ -32,22 +35,50 @@ public void loadState(@NotNull final ApplicationSettings sourceApplicationSettin applicationSettings = sourceApplicationSettings; } - @NotNull - PluginConfigurationBuilder populate(@NotNull final PluginConfigurationBuilder builder) { + public void setBaseDownloadUrl(@NotNull final String baseDownloadUrl) { + final var updatedApplicationSettings = new ApplicationSettings(applicationSettings); + updatedApplicationSettings.setBaseDownloadUrl(baseDownloadUrl); + applicationSettings = updatedApplicationSettings; + } + + public void setCachePath(@NotNull final Path cachePath) { + final var updatedApplicationSettings = new ApplicationSettings(applicationSettings); + updatedApplicationSettings.setCachePath(cachePath.toString()); + applicationSettings = updatedApplicationSettings; + } + + @NotNull PluginConfigurationBuilder populate( + @NotNull final PluginConfigurationBuilder builder) { Map settingsMap = applicationSettings.configuration(); - return builder - .withLastActivePluginVersion(settingsMap.get(LAST_ACTIVE_PLUGIN_VERSION)); + return builder.withLastActivePluginVersion(settingsMap.get(LAST_ACTIVE_PLUGIN_VERSION)); } void setCurrentConfig(@NotNull final PluginConfiguration currentPluginConfig) { - applicationSettings = ApplicationSettings.create(currentPluginConfig.getLastActivePluginVersion()); + applicationSettings = ApplicationSettings.create( + currentPluginConfig.getLastActivePluginVersion()); } - static class ApplicationSettings { + public static class ApplicationSettings { + @MapAnnotation private Map configuration; - static ApplicationSettings create(final String lastActivePluginVersion) { + public ApplicationSettings() { + + } + + public ApplicationSettings(final ApplicationSettings applicationSettings) { + this.configuration = new TreeMap<>(); + this.configuration.putAll(applicationSettings.configuration()); + } + + public static ApplicationSettings create() { + final ApplicationSettings applicationSettings = new ApplicationSettings(); + applicationSettings.configuration = new TreeMap<>(); + return applicationSettings; + } + + public static ApplicationSettings create(final String lastActivePluginVersion) { final Map mapForSerialization = new TreeMap<>(); mapForSerialization.put(LAST_ACTIVE_PLUGIN_VERSION, lastActivePluginVersion); @@ -59,7 +90,44 @@ static ApplicationSettings create(final String lastActivePluginVersion) { @NotNull public Map configuration() { - return Objects.requireNonNullElseGet(configuration, TreeMap::new); + configuration = Objects.requireNonNullElse(configuration, new TreeMap<>()); + return configuration; + } + + @NotNull + public String getBaseDownloadUrl() { + final var config = configuration(); + return Objects.requireNonNullElse(config.get(BASE_DOWNLOAD_URL), + "https://github.com/checkstyle/checkstyle/releases/download"); + } + + public void setBaseDownloadUrl(@NotNull final String baseDownloadUrl) { + configuration().put(BASE_DOWNLOAD_URL, baseDownloadUrl); + } + + @NotNull + public Path getCachePath() { + final var config = configuration(); + final String cachePath = config.getOrDefault(CACHE_PATH, "").strip(); + if (cachePath.isBlank()) { + return Path.of(System.getProperty("java.io.tmpdir"), "checkstyle-idea-cache"); + } + + return Path.of(cachePath); } + + public void setCachePath(@NotNull final String cachePath) { + configuration().put(CACHE_PATH, cachePath); + } + + public String getLastActivePluginVersion() { + return Objects.requireNonNullElse(configuration().get(LAST_ACTIVE_PLUGIN_VERSION), + CheckStylePlugin.version()); + } + + public void setLastActivePluginVersion(final String lastActivePluginVersion) { + configuration().put(LAST_ACTIVE_PLUGIN_VERSION, lastActivePluginVersion); + } + } } diff --git a/src/main/java/org/infernus/idea/checkstyle/config/LegacyProjectConfigurationStateDeserialiser.java b/src/main/java/org/infernus/idea/checkstyle/config/LegacyProjectConfigurationStateDeserialiser.java index 5db7ce08..767b455d 100644 --- a/src/main/java/org/infernus/idea/checkstyle/config/LegacyProjectConfigurationStateDeserialiser.java +++ b/src/main/java/org/infernus/idea/checkstyle/config/LegacyProjectConfigurationStateDeserialiser.java @@ -93,7 +93,7 @@ private String readCheckstyleVersion(@NotNull final Map configur if (result == null) { return vlr.getDefaultVersion(); } - return vlr.getReplacementMap().getOrDefault(result.toString(), result.toString()); + return result.toString(); } @NotNull diff --git a/src/main/java/org/infernus/idea/checkstyle/config/PluginConfiguration.java b/src/main/java/org/infernus/idea/checkstyle/config/PluginConfiguration.java index c664598d..9a1cacd0 100644 --- a/src/main/java/org/infernus/idea/checkstyle/config/PluginConfiguration.java +++ b/src/main/java/org/infernus/idea/checkstyle/config/PluginConfiguration.java @@ -25,6 +25,7 @@ public class PluginConfiguration { private final SortedSet activeLocationIds; private final boolean scanBeforeCheckin; private final String lastActivePluginVersion; + private final String baseDownloadUrl; PluginConfiguration(@NotNull final String checkstyleVersion, @NotNull final ScanScope scanScope, @@ -34,7 +35,8 @@ public class PluginConfiguration { @NotNull final List thirdPartyClasspath, @NotNull final SortedSet activeLocationIds, final boolean scanBeforeCheckin, - @Nullable final String lastActivePluginVersion) { + @Nullable final String lastActivePluginVersion, + @NotNull final String baseDownloadUrl) { this.checkstyleVersion = checkstyleVersion; this.scanScope = scanScope; this.suppressErrors = suppressErrors; @@ -46,6 +48,7 @@ public class PluginConfiguration { .collect(Collectors.toCollection(TreeSet::new)); this.scanBeforeCheckin = scanBeforeCheckin; this.lastActivePluginVersion = lastActivePluginVersion; + this.baseDownloadUrl = baseDownloadUrl; } @NotNull @@ -88,7 +91,12 @@ public String getLastActivePluginVersion() { return lastActivePluginVersion; } - public SortedSet getActiveLocationIds() { + @NotNull + public String getBaseDownloadUrl() { + return baseDownloadUrl; + } + + public SortedSet getActiveLocationIds() { return this.activeLocationIds; } diff --git a/src/main/java/org/infernus/idea/checkstyle/config/PluginConfigurationBuilder.java b/src/main/java/org/infernus/idea/checkstyle/config/PluginConfigurationBuilder.java index 8e4ebdcd..2f0a4542 100644 --- a/src/main/java/org/infernus/idea/checkstyle/config/PluginConfigurationBuilder.java +++ b/src/main/java/org/infernus/idea/checkstyle/config/PluginConfigurationBuilder.java @@ -23,6 +23,7 @@ public final class PluginConfigurationBuilder { private SortedSet activeLocationIds; private boolean scanBeforeCheckin; private String lastActivePluginVersion; + private String baseDownloadUrl; private PluginConfigurationBuilder(@NotNull final String checkstyleVersion, @NotNull final ScanScope scanScope, @@ -32,7 +33,8 @@ private PluginConfigurationBuilder(@NotNull final String checkstyleVersion, @NotNull final List thirdPartyClasspath, @NotNull final SortedSet activeLocationIds, final boolean scanBeforeCheckin, - @Nullable final String lastActivePluginVersion) { + @Nullable final String lastActivePluginVersion, + @NotNull final String baseDownloadUrl) { this.checkstyleVersion = checkstyleVersion; this.scanScope = scanScope; this.suppressErrors = suppressErrors; @@ -42,6 +44,7 @@ private PluginConfigurationBuilder(@NotNull final String checkstyleVersion, this.activeLocationIds = activeLocationIds; this.scanBeforeCheckin = scanBeforeCheckin; this.lastActivePluginVersion = lastActivePluginVersion; + this.baseDownloadUrl = baseDownloadUrl; } public static PluginConfigurationBuilder defaultConfiguration(@NotNull final Project project) { @@ -62,7 +65,8 @@ public static PluginConfigurationBuilder defaultConfiguration(@NotNull final Pro Collections.emptyList(), Collections.emptySortedSet(), false, - CheckStylePlugin.version()); + CheckStylePlugin.version(), + "https://github.com/checkstyle/checkstyle/releases/download"); } public static PluginConfigurationBuilder testInstance(@NotNull final String checkstyleVersion) { @@ -75,7 +79,8 @@ public static PluginConfigurationBuilder testInstance(@NotNull final String chec Collections.emptyList(), Collections.emptySortedSet(), false, - "aVersion"); + "aVersion", + ""); } public static PluginConfigurationBuilder from(@NotNull final PluginConfiguration source) { @@ -87,7 +92,8 @@ public static PluginConfigurationBuilder from(@NotNull final PluginConfiguration source.getThirdPartyClasspath(), source.getActiveLocationIds(), source.isScanBeforeCheckin(), - source.getLastActivePluginVersion()); + source.getLastActivePluginVersion(), + source.getBaseDownloadUrl()); } public PluginConfigurationBuilder withCheckstyleVersion(@NotNull final String newCheckstyleVersion) { @@ -135,6 +141,11 @@ public PluginConfigurationBuilder withLastActivePluginVersion(final String newLa return this; } + public PluginConfigurationBuilder withBaseDownloadUrl(@NotNull final String newBaseDownloadUrl) { + this.baseDownloadUrl = newBaseDownloadUrl; + return this; + } + public PluginConfiguration build() { return new PluginConfiguration( checkstyleVersion, @@ -145,7 +156,8 @@ public PluginConfiguration build() { Objects.requireNonNullElseGet(thirdPartyClasspath, ArrayList::new), Objects.requireNonNullElseGet(activeLocationIds, TreeSet::new), scanBeforeCheckin, - lastActivePluginVersion); + lastActivePluginVersion, + baseDownloadUrl); } private static ConfigurationLocationFactory configurationLocationFactory(final Project project) { diff --git a/src/main/java/org/infernus/idea/checkstyle/config/ProjectConfigurationState.java b/src/main/java/org/infernus/idea/checkstyle/config/ProjectConfigurationState.java index cbdd48eb..bcd271a3 100644 --- a/src/main/java/org/infernus/idea/checkstyle/config/ProjectConfigurationState.java +++ b/src/main/java/org/infernus/idea/checkstyle/config/ProjectConfigurationState.java @@ -88,6 +88,8 @@ static class ProjectSettings { private List activeLocationIds; @MapAnnotation private List locations; + @Tag + private String baseDownloadUrl; @MapAnnotation private Map configuration; @@ -116,6 +118,7 @@ static ProjectSettings create(@NotNull final PluginConfiguration currentPluginCo location.getProperties() )) .collect(Collectors.toList()); + projectSettings.baseDownloadUrl = currentPluginConfig.getBaseDownloadUrl(); return projectSettings; } @@ -147,7 +150,8 @@ PluginConfigurationBuilder populate(@NotNull final PluginConfigurationBuilder bu .withScanBeforeCheckin(scanBeforeCheckin) .withThirdPartyClassPath(requireNonNullElseGet(thirdPartyClasspath, ArrayList::new)) .withLocations(deserialiseLocations(project)) - .withActiveLocationIds(new TreeSet<>(requireNonNullElseGet(activeLocationIds, ArrayList::new))); + .withActiveLocationIds(new TreeSet<>(requireNonNullElseGet(activeLocationIds, ArrayList::new))) + .withBaseDownloadUrl(requireNonNullElseGet(baseDownloadUrl, () -> PluginConfigurationBuilder.defaultConfiguration(project).build().getBaseDownloadUrl())); } return new LegacyProjectConfigurationStateDeserialiser(project) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index eae4c1f4..d6d21020 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -49,6 +49,7 @@ + @@ -69,6 +70,9 @@ + diff --git a/src/main/resources/checkstyle-idea.properties b/src/main/resources/checkstyle-idea.properties index 34aa2ff7..fd97ca3e 100644 --- a/src/main/resources/checkstyle-idea.properties +++ b/src/main/resources/checkstyle-idea.properties @@ -1,117 +1,83 @@ # Checkstyle-IDEA plugin configuration checkstyle.versions.supported = \ - 9.0.1, 9.1, 9.2.1, 9.3, \ - 10.0, 10.1, 10.2, 10.3.4, 10.4, 10.5.0, 10.6.0, 10.7.0, 10.8.1, 10.9.3, \ - 10.10.0, 10.12.7, 10.13.0, 10.14.2, 10.15.0, 10.16.0, 10.17.0, 10.18.2, 10.19.0, \ - 10.20.2, 10.21.3, 10.22.0, 10.23.0, 10.24.0, 10.25.1, 10.26.1, \ + 8.0, \ + 8.1, \ + 8.2, \ + 8.3, \ + 8.4, \ + 8.5, \ + 8.6, \ + 8.7, \ + 8.8, \ + 8.9, \ + 8.10, 8.10.1, \ + 8.11, \ + 8.12, \ + 8.13, \ + 8.14, \ + 8.15, \ + 8.17, \ + 8.18, \ + 8.19, \ + 8.20, \ + 8.21, \ + 8.22, \ + 8.23, \ + 8.24, \ + 8.25, \ + 8.26, \ + 8.27, \ + 8.28, \ + 8.29, \ + 8.30, \ + 8.31, \ + 8.32, \ + 8.33, \ + 8.34, \ + 8.35, \ + 8.36, 8.36.1, 8.36.2, \ + 8.37, \ + 8.38, \ + 8.39, \ + 8.40, \ + 8.41, 8.41.1, \ + 8.42, \ + 8.43, \ + 8.44, \ + 8.45, 8.45.1, \ + 9.0, 9.0.1, \ + 9.1, \ + 9.2, 9.2.1, \ + 9.3, \ + 10.0, \ + 10.1, \ + 10.2, \ + 10.3, 10.3.1, 10.3.2, 10.3.3, 10.3.4, \ + 10.4, \ + 10.5.0, \ + 10.6.0, \ + 10.7.0, \ + 10.8.1, \ + 10.9.3, \ + 10.10.0, \ + 10.12.0, 10.12.1, 10.12.2, 10.12.3, 10.12.4, 10.12.5, 10.12.7, \ + 10.13.0, \ + 10.14.0, 10.14.2, \ + 10.15.0, \ + 10.16.0, \ + 10.17.0, \ + 10.18.0, 10.18.1, 10.18.2, \ + 10.19.0, \ + 10.20.1, 10.20.2, \ + 10.21.0, 10.21.1, 10.21.2, 10.21.3, \ + 10.22.0, \ + 10.23.0, \ + 10.24.0, \ + 10.25.0, 10.25.1, \ + 10.26.0, 10.26.1, \ 11.0.0 # The "base version" must be one of the versions listed above. The sources are compiled against this version, so it is # the dependency shown in the IDE and the runtime used when unit tests are run from the IDE or via 'runCsaccessTests'. baseVersion = 9.0.1 - -# Maps unsupported Checkstyle versions onto their supported alternative versions. If one of the keys in this map is -# found in the plugin configuration the value in this map is used instead. A mapping is valid only if both check -# behavior and API are compatible: https://checkstyle-addons.thomasjensen.com/checkstyle-compatibility-matrix.html -checkstyle.versions.map = \ - 8.0 -> 9.0.1, 8.1 -> 9.0.1, 8.2 -> 9.0.1, 8.3 -> 9.0.1, 8.5 -> 9.0.1, 8.6 -> 9.0.1, 8.7 -> 9.0.1, 8.8 -> 9.0.1, \ - 8.10.1 -> 9.0.1, 8.14 -> 9.0.1, 8.17 -> 9.0.1, 8.18 -> 9.0.1, 8.20 -> 9.0.1, 8.23 -> 9.0.1, 8.24 -> 9.0.1, \ - 8.25 -> 9.0.1, 8.26 -> 9.0.1, 8.27 -> 9.0.1, 8.28 -> 9.0.1, 8.29 -> 9.0.1, 8.30 -> 9.0.1, 8.31 -> 9.0.1, \ - 8.32 -> 9.0.1, 8.33 -> 9.0.1, 8.34 -> 9.0.1, 8.35 -> 9.0.1, 8.36.2 -> 9.0.1, 8.37 -> 9.0.1, 8.38 -> 9.0.1, \ - 8.39 -> 9.0.1, 8.40 -> 9.0.1, 8.41.1 -> 9.0.1, 8.42 -> 9.0.1, 8.43 -> 9.0.1, 8.44 -> 9.0.1, 8.45.1 -> 9.0.1, \ - 8.4 -> 9.0.1, 8.9 -> 9.0.1,8.10 -> 9.0.1, 8.11 -> 9.0.1,8.12 -> 9.0.1,8.13 -> 9.0.1, 8.15 -> 9.0.1, \ - 8.19 -> 9.0.1, 8.21 -> 9.0.1,8.22 -> 9.0.1, 8.36 -> 9.0.1,8.36.1 -> 9.0.1, 8.41 -> 9.0.1, 8.45 -> 9.0.1, \ - 9.0 -> 9.0.1, \ - 9.2 -> 9.2.1, \ - 10.3 -> 10.3.4, \ - 10.3.1 -> 10.3.4, \ - 10.3.2 -> 10.3.4, \ - 10.3.3 -> 10.3.4, \ - 10.12.0 -> 10.12.7, \ - 10.12.1 -> 10.12.7, \ - 10.12.2 -> 10.12.7, \ - 10.12.3 -> 10.12.7, \ - 10.12.4 -> 10.12.7, \ - 10.12.5 -> 10.12.7, \ - 10.14.0 -> 10.14.2, \ - 10.18.0 -> 10.18.2, \ - 10.18.1 -> 10.18.2, \ - 10.20.1 -> 10.20.2, \ - 10.21.0 -> 10.21.3, \ - 10.21.1 -> 10.21.3, \ - 10.21.2 -> 10.21.3, \ - 10.25.0 -> 10.25.1, \ - 10.26.0 -> 10.26.1 - -# Maps dependencies to compatible versions, allowing us to save archive space where the dependencies -# are sufficiently compatible. -checkstyle.dependencies.map = \ - animal-sniffer-annotations-1.14.jar -> animal-sniffer-annotations-1.18.jar, \ - animal-sniffer-annotations-1.17.jar -> animal-sniffer-annotations-1.18.jar, \ - antlr4-runtime-4.5.2-1.jar -> antlr4-runtime-4.5.3.jar, \ - antlr4-runtime-4.7.jar -> antlr4-runtime-4.7.2.jar, \ - antlr4-runtime-4.7.1.jar -> antlr4-runtime-4.7.2.jar, \ - antlr4-runtime-4.9.1.jar -> antlr4-runtime-4.9.3.jar, \ - antlr4-runtime-4.9.2.jar -> antlr4-runtime-4.9.3.jar, \ - antlr4-runtime-4.13.1.jar -> antlr4-runtime-4.13.2.jar, \ - checker-qual-3.48.1.jar -> checker-qual-3.48.4.jar, \ - checker-qual-3.48.2.jar -> checker-qual-3.48.4.jar, \ - checker-qual-3.49.1.jar -> checker-qual-3.49.3.jar, \ - commons-beanutils-1.9.2.jar -> commons-beanutils-1.9.4.jar, \ - commons-beanutils-1.9.3.jar -> commons-beanutils-1.9.4.jar, \ - commons-cli-1.3.1.jar -> commons-cli-1.4.jar, \ - commons-collections-3.2.1.jar -> commons-collections-3.2.2.jar, \ - error_prone_annotations-2.3.2.jar -> error_prone_annotations-2.3.4.jar, \ - guava-19.0.jar -> guava-23.2-jre.jar, \ - guava-21.0.jar -> guava-23.2-jre.jar, \ - guava-22.0.jar -> guava-23.2-jre.jar, \ - guava-23.0.jar -> guava-23.2-jre.jar, \ - guava-23.6-jre.jar -> guava-29.0-jre.jar, \ - guava-25.1-jre.jar -> guava-29.0-jre.jar, \ - guava-26.0-jre.jar -> guava-29.0-jre.jar, \ - guava-27.0.1-jre.jar -> guava-29.0-jre.jar, \ - guava-27.1-jre.jar -> guava-29.0-jre.jar, \ - guava-28.0-jre.jar -> guava-29.0-jre.jar, \ - guava-28.1-jre.jar -> guava-29.0-jre.jar, \ - guava-28.2-jre.jar -> guava-29.0-jre.jar, \ - guava-30.0-jre.jar -> guava-31.1-jre.jar, \ - guava-30.1.1-jre.jar -> guava-31.1-jre.jar, \ - guava-31.0.1-jre.jar -> guava-31.1-jre.jar, \ - guava-33.0.0-jre.jar -> guava-33.4.8-jre.jar, \ - guava-33.1.0-jre.jar -> guava-33.4.8-jre.jar, \ - guava-33.2.0-jre.jar -> guava-33.4.8-jre.jar, \ - guava-33.3.1-jre.jar -> guava-33.4.8-jre.jar, \ - guava-33.4.0-jre.jar -> guava-33.4.8-jre.jar, \ - guava-33.4.6-jre.jar -> guava-33.4.8-jre.jar, \ - httpcore-4.4.13.jar -> httpcore-4.4.14.jar, \ - picocli-3.9.0.jar -> picocli-3.9.6.jar, \ - picocli-3.9.5.jar -> picocli-3.9.6.jar, \ - picocli-4.0.3.jar -> picocli-4.0.4.jar, \ - picocli-4.1.1.jar -> picocli-4.1.4.jar, \ - picocli-4.3.1.jar -> picocli-4.3.2.jar, \ - picocli-4.5.1.jar -> picocli-4.5.2.jar, \ - picocli-4.6.1.jar -> picocli-4.6.3.jar, \ - picocli-4.6.2.jar -> picocli-4.6.3.jar, \ - picocli-4.7.0.jar -> picocli-4.7.7.jar, \ - picocli-4.7.1.jar -> picocli-4.7.7.jar, \ - picocli-4.7.3.jar -> picocli-4.7.7.jar, \ - picocli-4.7.5.jar -> picocli-4.7.7.jar, \ - picocli-4.7.6.jar -> picocli-4.7.7.jar, \ - Saxon-HE-9.8.0-4.jar -> Saxon-HE-9.8.0-14.jar, \ - Saxon-HE-9.8.0-5.jar -> Saxon-HE-9.8.0-14.jar, \ - Saxon-HE-9.8.0-7.jar -> Saxon-HE-9.8.0-14.jar, \ - Saxon-HE-9.8.0-12.jar -> Saxon-HE-9.8.0-14.jar, \ - Saxon-HE-9.9.0-2.jar -> Saxon-HE-9.9.1-7.jar, \ - Saxon-HE-9.9.1-1.jar -> Saxon-HE-9.9.1-7.jar, \ - Saxon-HE-9.9.1-2.jar -> Saxon-HE-9.9.1-7.jar, \ - Saxon-HE-9.9.1-4.jar -> Saxon-HE-9.9.1-7.jar, \ - Saxon-HE-9.9.1-5.jar -> Saxon-HE-9.9.1-7.jar, \ - Saxon-HE-9.9.1-6.jar -> Saxon-HE-9.9.1-7.jar, \ - Saxon-HE-10.2.jar -> Saxon-HE-10.6.jar, \ - Saxon-HE-10.5.jar -> Saxon-HE-10.6.jar, \ - Saxon-HE-10.3.jar -> Saxon-HE-10.6.jar, \ - Saxon-HE-11.3.jar -> Saxon-HE-11.4.jar, \ - Saxon-HE-12.0.jar -> Saxon-HE-12.5.jar, \ - Saxon-HE-12.1.jar -> Saxon-HE-12.5.jar, \ - Saxon-HE-12.4.jar -> Saxon-HE-12.5.jar diff --git a/src/main/resources/org/infernus/idea/checkstyle/CheckStyleBundle.properties b/src/main/resources/org/infernus/idea/checkstyle/CheckStyleBundle.properties index ecafd364..ef886f3b 100644 --- a/src/main/resources/org/infernus/idea/checkstyle/CheckStyleBundle.properties +++ b/src/main/resources/org/infernus/idea/checkstyle/CheckStyleBundle.properties @@ -146,6 +146,10 @@ config.module.exclude.tooltip=If selected then no checks will be run against thi config.module.module-file.text=Checkstyle Rules: config.module.module-file.tooltip=The selected Checkstyle rules will be used for this module +config-downloader.display-name=Checkstyle Downloader +config-downloader.downloads.table.downloaded=Downloaded +config-downloader.downloads.table.version=Version + action.CheckstylePluginActions=Close Checkstyle Window action.CheckstyleCurrentFileAction=Check Current File action.CheckstyleProjectFilesAction=Check Project diff --git a/src/test/java/org/infernus/idea/checkstyle/VersionListReaderTest.java b/src/test/java/org/infernus/idea/checkstyle/VersionListReaderTest.java index cd09af35..b9ab7c74 100644 --- a/src/test/java/org/infernus/idea/checkstyle/VersionListReaderTest.java +++ b/src/test/java/org/infernus/idea/checkstyle/VersionListReaderTest.java @@ -13,8 +13,6 @@ public void testNormalLoading() { Assert.assertNotNull(underTest.getSupportedVersions()); Assert.assertTrue(underTest.getSupportedVersions().size() > 1); Assert.assertNotNull(underTest.getDefaultVersion()); - Assert.assertNotNull(underTest.getReplacementMap()); - Assert.assertTrue(underTest.getReplacementMap().size() > 1); }