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.HashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.function.Consumer; 030import java.util.stream.Stream; 031import org.jdrupes.builder.api.BuildException; 032import org.jdrupes.builder.api.Project; 033import org.jdrupes.builder.api.Resource; 034import org.jdrupes.builder.api.ResourceRequest; 035import static org.jdrupes.builder.api.ResourceType.resourceType; 036import org.jdrupes.builder.core.AbstractGenerator; 037 038/// The [VscodeConfigurator] provides the resource [VscodeConfiguration]. 039/// The configuration consists of the configuration files: 040/// * .vscode/settings.json 041/// * .vscode/launch.json 042/// * .vscode/tasks.json 043/// 044/// Each generated data structure can be post processed by a corresponding 045/// `adapt` method before being written to disk. 046/// 047/// VS Code relies on the `.project` and `.classpath` files as used by 048/// eclipse for its java support. Currently, the configurator does not 049/// generate these files. Use the eclipse configurator in addition. 050/// 051public class VscodeConfigurator extends AbstractGenerator { 052 @SuppressWarnings({ "PMD.UseConcurrentHashMap", 053 "PMD.AvoidDuplicateLiterals" }) 054 private final Map<String, Path> jdkLocations = new HashMap<>(); 055 private Consumer<Map<String, Object>> settingsAdaptor = _ -> { 056 }; 057 private Consumer<Map<String, Object>> launchAdaptor = _ -> { 058 }; 059 private Consumer<Map<String, Object>> tasksAdaptor = _ -> { 060 }; 061 private Runnable configurationAdaptor = () -> { 062 }; 063 064 /// Initializes a new vscode configurator. 065 /// 066 /// @param project the project 067 /// 068 public VscodeConfigurator(Project project) { 069 super(project); 070 } 071 072 /** 073 * Allow the user to adapt the settings data structure before writing. 074 * 075 * @param adaptor the adaptor 076 * @return the vscode configurator 077 */ 078 public VscodeConfigurator 079 adaptSettings(Consumer<Map<String, Object>> adaptor) { 080 settingsAdaptor = adaptor; 081 return this; 082 } 083 084 /// VSCode does not have a central JDK registry. JDKs can therefore 085 /// be configured with this method. 086 /// 087 /// @param version the version 088 /// @param location the location 089 /// @return the vscode configurator 090 /// 091 public VscodeConfigurator jdk(String version, Path location) { 092 jdkLocations.put(version, location); 093 return this; 094 } 095 096 @Override 097 protected <T extends Resource> Stream<T> 098 doProvide(ResourceRequest<T> requested) { 099 if (!requested.accepts(resourceType(VscodeConfiguration.class))) { 100 return Stream.empty(); 101 } 102 103 Path vscodeDir = project().directory().resolve(".vscode"); 104 vscodeDir.toFile().mkdirs(); 105 try { 106 generateSettings(vscodeDir.resolve("settings.json")); 107 generateLaunch(vscodeDir.resolve("launch.json")); 108 generateTasks(vscodeDir.resolve("tasks.json")); 109 } catch (IOException e) { 110 throw new BuildException().from(this).cause(e); 111 } 112 113 // General overrides 114 configurationAdaptor.run(); 115 116 // Return a result 117 @SuppressWarnings({ "unchecked" }) 118 var result = (Stream<T>) Stream.of(VscodeConfiguration.of(project())); 119 return result; 120 } 121 122 private void generateSettings(Path file) throws IOException { 123 @SuppressWarnings({ "PMD.UseConcurrentHashMap" }) 124 Map<String, Object> settings = new HashMap<>(); 125 settings.put("java.configuration.updateBuildConfiguration", 126 "automatic"); 127 128 // Allow user to adapt settings 129 settingsAdaptor.accept(settings); 130 ObjectMapper mapper = new ObjectMapper(); 131 String json = mapper.writerWithDefaultPrettyPrinter() 132 .writeValueAsString(settings); 133 Files.writeString(file, json); 134 } 135 136 private void generateLaunch(Path file) throws IOException { 137 @SuppressWarnings("PMD.UseConcurrentHashMap") 138 Map<String, Object> launch = new HashMap<>(); 139 launch.put("version", "0.2.0"); 140 launch.put("configurations", new ArrayList<>()); 141 launchAdaptor.accept(launch); 142 if (!((List<?>) launch.get("configurations")).isEmpty()) { 143 ObjectMapper mapper = new ObjectMapper(); 144 String json = mapper.writerWithDefaultPrettyPrinter() 145 .writeValueAsString(launch); 146 Files.writeString(file, json); 147 } 148 } 149 150 /** 151 * Allow the user to adapt the launch data structure before writing. 152 * The version and an empty list for "configurations" has already be 153 * added. 154 * 155 * @param adaptor the adaptor 156 * @return the vscode configurator 157 */ 158 public VscodeConfigurator 159 adaptLaunch(Consumer<Map<String, Object>> adaptor) { 160 launchAdaptor = adaptor; 161 return this; 162 } 163 164 private void generateTasks(Path file) throws IOException { 165 @SuppressWarnings("PMD.UseConcurrentHashMap") 166 Map<String, Object> tasks = new HashMap<>(); 167 tasks.put("version", "2.0.0"); 168 tasks.put("tasks", new ArrayList<>()); 169 tasksAdaptor.accept(tasks); 170 if (!((List<?>) tasks.get("tasks")).isEmpty()) { 171 ObjectMapper mapper = new ObjectMapper(); 172 String json 173 = mapper.writerWithDefaultPrettyPrinter() 174 .writeValueAsString(tasks); 175 Files.writeString(file, json); 176 } 177 } 178 179 /** 180 * Allow the user to adapt the tasks data structure before writing. 181 * 182 * @param adaptor the adaptor 183 * @return the vscode configurator 184 */ 185 public VscodeConfigurator 186 adaptTasks(Consumer<Map<String, Object>> adaptor) { 187 tasksAdaptor = adaptor; 188 return this; 189 } 190 191 /// Allow the user to add additional resources. 192 /// 193 /// @param adaptor the adaptor 194 /// @return the eclipse configurator 195 /// 196 public VscodeConfigurator adaptConfiguration(Runnable adaptor) { 197 configurationAdaptor = adaptor; 198 return this; 199 } 200}