001/* 002 * JDrupes Builder 003 * Copyright (C) 2025 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.vscode; 020 021import com.fasterxml.jackson.databind.ObjectMapper; 022import java.io.IOException; 023import java.nio.file.Files; 024import java.nio.file.Path; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.function.Consumer; 032import org.jdrupes.builder.api.BuildException; 033import org.jdrupes.builder.api.Project; 034import org.jdrupes.builder.api.Resource; 035import org.jdrupes.builder.api.ResourceRequest; 036import static org.jdrupes.builder.api.ResourceType.resourceType; 037import org.jdrupes.builder.core.AbstractGenerator; 038 039/// The [VscodeConfigurator] provides the resource [VscodeConfiguration]. 040/// The configuration consists of the configuration files: 041/// * .vscode/settings.json 042/// * .vscode/launch.json 043/// * .vscode/tasks.json 044/// 045/// Each generated data structure can be post processed by a corresponding 046/// `adapt` method before being written to disk. 047/// 048/// VS Code relies on the `.project` and `.classpath` files as used by 049/// eclipse for its java support. Currently, the configurator does not 050/// generate these files. Use the eclipse configurator in addition. 051/// 052public class VscodeConfigurator extends AbstractGenerator { 053 @SuppressWarnings({ "PMD.UseConcurrentHashMap", 054 "PMD.AvoidDuplicateLiterals" }) 055 private final Map<String, Path> jdkLocations = new HashMap<>(); 056 private Consumer<Map<String, Object>> settingsAdaptor = _ -> { 057 }; 058 private Consumer<Map<String, Object>> launchAdaptor = _ -> { 059 }; 060 private Consumer<Map<String, Object>> tasksAdaptor = _ -> { 061 }; 062 private Runnable configurationAdaptor = () -> { 063 }; 064 065 /// Initializes a new vscode configurator. 066 /// 067 /// @param project the project 068 /// 069 public VscodeConfigurator(Project project) { 070 super(project); 071 } 072 073 /** 074 * Allow the user to adapt the settings data structure before writing. 075 * 076 * @param adaptor the adaptor 077 * @return the vscode configurator 078 */ 079 public VscodeConfigurator 080 adaptSettings(Consumer<Map<String, Object>> adaptor) { 081 settingsAdaptor = adaptor; 082 return this; 083 } 084 085 /// VSCode does not have a central JDK registry. JDKs can therefore 086 /// be configured with this method. 087 /// 088 /// @param version the version 089 /// @param location the location 090 /// @return the vscode configurator 091 /// 092 public VscodeConfigurator jdk(String version, Path location) { 093 jdkLocations.put(version, location); 094 return this; 095 } 096 097 @Override 098 protected <T extends Resource> Collection<T> 099 doProvide(ResourceRequest<T> requested) { 100 if (!requested.accepts(resourceType(VscodeConfiguration.class))) { 101 return Collections.emptyList(); 102 } 103 104 Path vscodeDir = project().directory().resolve(".vscode"); 105 vscodeDir.toFile().mkdirs(); 106 try { 107 generateSettings(vscodeDir.resolve("settings.json")); 108 generateLaunch(vscodeDir.resolve("launch.json")); 109 generateTasks(vscodeDir.resolve("tasks.json")); 110 } catch (IOException e) { 111 throw new BuildException().from(this).cause(e); 112 } 113 114 // General overrides 115 configurationAdaptor.run(); 116 117 // Return a result 118 @SuppressWarnings({ "unchecked" }) 119 var result = (Collection<T>) List.of(VscodeConfiguration.of(project())); 120 return result; 121 } 122 123 private void generateSettings(Path file) throws IOException { 124 @SuppressWarnings({ "PMD.UseConcurrentHashMap" }) 125 Map<String, Object> settings = new HashMap<>(); 126 settings.put("java.configuration.updateBuildConfiguration", 127 "automatic"); 128 129 // Allow user to adapt settings 130 settingsAdaptor.accept(settings); 131 ObjectMapper mapper = new ObjectMapper(); 132 String json = mapper.writerWithDefaultPrettyPrinter() 133 .writeValueAsString(settings); 134 Files.writeString(file, json); 135 } 136 137 private void generateLaunch(Path file) throws IOException { 138 @SuppressWarnings("PMD.UseConcurrentHashMap") 139 Map<String, Object> launch = new HashMap<>(); 140 launch.put("version", "0.2.0"); 141 launch.put("configurations", new ArrayList<>()); 142 launchAdaptor.accept(launch); 143 if (!((List<?>) launch.get("configurations")).isEmpty()) { 144 ObjectMapper mapper = new ObjectMapper(); 145 String json = mapper.writerWithDefaultPrettyPrinter() 146 .writeValueAsString(launch); 147 Files.writeString(file, json); 148 } 149 } 150 151 /** 152 * Allow the user to adapt the launch data structure before writing. 153 * The version and an empty list for "configurations" has already be 154 * added. 155 * 156 * @param adaptor the adaptor 157 * @return the vscode configurator 158 */ 159 public VscodeConfigurator 160 adaptLaunch(Consumer<Map<String, Object>> adaptor) { 161 launchAdaptor = adaptor; 162 return this; 163 } 164 165 private void generateTasks(Path file) throws IOException { 166 @SuppressWarnings("PMD.UseConcurrentHashMap") 167 Map<String, Object> tasks = new HashMap<>(); 168 tasks.put("version", "2.0.0"); 169 tasks.put("tasks", new ArrayList<>()); 170 tasksAdaptor.accept(tasks); 171 if (!((List<?>) tasks.get("tasks")).isEmpty()) { 172 ObjectMapper mapper = new ObjectMapper(); 173 String json 174 = mapper.writerWithDefaultPrettyPrinter() 175 .writeValueAsString(tasks); 176 Files.writeString(file, json); 177 } 178 } 179 180 /** 181 * Allow the user to adapt the tasks data structure before writing. 182 * 183 * @param adaptor the adaptor 184 * @return the vscode configurator 185 */ 186 public VscodeConfigurator 187 adaptTasks(Consumer<Map<String, Object>> adaptor) { 188 tasksAdaptor = adaptor; 189 return this; 190 } 191 192 /// Allow the user to add additional resources. 193 /// 194 /// @param adaptor the adaptor 195 /// @return the eclipse configurator 196 /// 197 public VscodeConfigurator adaptConfiguration(Runnable adaptor) { 198 configurationAdaptor = adaptor; 199 return this; 200 } 201}