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.eclipse; 020 021import java.io.File; 022import java.io.IOException; 023import java.nio.file.Files; 024import java.nio.file.Path; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Optional; 030import java.util.Properties; 031import java.util.Set; 032import java.util.function.BiConsumer; 033import java.util.function.Consumer; 034import java.util.function.Supplier; 035import java.util.stream.Collectors; 036import javax.xml.parsers.DocumentBuilderFactory; 037import javax.xml.parsers.ParserConfigurationException; 038import javax.xml.transform.OutputKeys; 039import javax.xml.transform.TransformerException; 040import javax.xml.transform.TransformerFactory; 041import javax.xml.transform.TransformerFactoryConfigurationError; 042import javax.xml.transform.dom.DOMSource; 043import javax.xml.transform.stream.StreamResult; 044import org.jdrupes.builder.api.BuildException; 045import org.jdrupes.builder.api.FileTree; 046import static org.jdrupes.builder.api.Intent.*; 047import org.jdrupes.builder.api.MergedTestProject; 048import org.jdrupes.builder.api.Project; 049import org.jdrupes.builder.api.Resource; 050import org.jdrupes.builder.api.ResourceRequest; 051import org.jdrupes.builder.api.ResourceType; 052import org.jdrupes.builder.core.AbstractGenerator; 053import org.jdrupes.builder.java.ClasspathElement; 054import org.jdrupes.builder.java.JavaCompiler; 055import org.jdrupes.builder.java.JavaProject; 056import static org.jdrupes.builder.java.JavaTypes.*; 057import org.jdrupes.builder.java.LibraryJarFile; 058import org.w3c.dom.Document; 059import org.w3c.dom.Element; 060import org.w3c.dom.Node; 061 062/// The [EclipseConfigurator] provides the resource [EclipseConfiguration]. 063/// "The configuration" consists of the Eclipse configuration files 064/// for a given project. The configurator generates the following 065/// files as W3C DOM documents (for XML files) or as [Properties] 066/// for a given project: 067/// 068/// * `.project`, 069/// * `.classpath`, 070/// * `.settings/org.eclipse.core.resources.prefs`, 071/// * `.settings/org.eclipse.core.runtime.prefs` and 072/// * `.settings/org.eclipse.jdt.core.prefs`. 073/// 074/// Each generated data structure can be post processed by a corresponding 075/// `adapt` method before being written to disk. Additional resources can 076/// be generated by the method [#adaptConfiguration]. 077/// 078/// Eclipse provides project nesting, but the outer project does not 079/// define a namespace. This can lead to problems if you have multiple 080/// (sub)projects with the same name in the workspace. The configurator 081/// allows you to define an alias for the project name to avoid this 082/// problem. This alias is used as Eclipse project name in all generated 083/// files. 084/// 085/// If a project is a [MergedTestProject], the configurator merges the 086/// information from this test project into the configuration files of 087/// its parent project. Resources that the test project depends 088/// on will be added as "test only" class path resources and the folder 089/// with the sources for the java compiler will be added as "test sources". 090/// 091@SuppressWarnings("PMD.TooManyMethods") 092public class EclipseConfigurator extends AbstractGenerator { 093 094 /// The Constant GENERATED_BY. 095 public static final String GENERATED_BY = "Generated by JDrupes Builder"; 096 private static DocumentBuilderFactory dbf 097 = DocumentBuilderFactory.newInstance(); 098 private Path outputDirectory = Path.of("bin"); 099 private Path testOutputDirectory = Path.of("test-bin"); 100 private Supplier<String> eclipseAlias = () -> project().name(); 101 private BiConsumer<Document, Node> classpathAdaptor = (_, _) -> { 102 }; 103 private Runnable configurationAdaptor = () -> { 104 }; 105 private Consumer<Properties> jdtCorePrefsAdaptor = _ -> { 106 }; 107 private Consumer<Properties> resourcesPrefsAdaptor = _ -> { 108 }; 109 private Consumer<Properties> runtimePrefsAdaptor = _ -> { 110 }; 111 private ProjectConfigurationAdaptor prjConfigAdaptor = (_, _, _) -> { 112 }; 113 114 /// Instantiates a new eclipse configurator. 115 /// 116 /// @param project the project 117 /// 118 public EclipseConfigurator(Project project) { 119 super(project); 120 } 121 122 /// Define the eclipse (alias) project name. 123 /// 124 /// @param eclipseAlias the eclipse alias 125 /// @return the eclipse configurator 126 /// 127 public EclipseConfigurator eclipseAlias(Supplier<String> eclipseAlias) { 128 this.eclipseAlias = eclipseAlias; 129 return this; 130 } 131 132 /// Define the eclipse (alias) project name. 133 /// 134 /// @param eclipseAlias the eclipse alias 135 /// @return the eclipse configurator 136 /// 137 public EclipseConfigurator eclipseAlias(String eclipseAlias) { 138 this.eclipseAlias = () -> eclipseAlias; 139 return this; 140 } 141 142 /// Returns the eclipse alias. 143 /// 144 /// @return the string 145 /// 146 public String eclipseAlias() { 147 return eclipseAlias.get(); 148 } 149 150 /// Sets the output directory (a.k.a. folder) for classes. Defaults 151 /// to "`bin`". If set to `null` the configurator makes an attempt 152 /// to derive the directory from the [JavaCompiler] in the same 153 /// project. 154 /// 155 /// @param outputDirectory the output directory 156 /// @return the eclipse configurator 157 /// 158 public EclipseConfigurator outputDirectory(Path outputDirectory) { 159 this.outputDirectory = outputDirectory; 160 return this; 161 } 162 163 /// Sets the output directory (a.k.a. folder) for test classes. Defaults 164 /// to "`test-bin`". If set to `null` the configurator makes an attempt 165 /// to derive the directory from the [JavaCompiler] in the test 166 /// project. 167 /// 168 /// @param outputDirectory the output directory 169 /// @return the eclipse configurator 170 /// 171 public EclipseConfigurator testOutputDirectory(Path outputDirectory) { 172 testOutputDirectory = outputDirectory; 173 return this; 174 } 175 176 /// Provides an [EclipseConfiguration]. 177 /// 178 /// @param <T> the generic type 179 /// @param requested the requested 180 /// @return the stream 181 /// 182 @Override 183 protected <T extends Resource> Collection<T> 184 doProvide(ResourceRequest<T> requested) { 185 if (!requested.accepts(new ResourceType<EclipseConfiguration>() {})) { 186 return Collections.emptyList(); 187 } 188 189 // Generate nothing for test projects. 190 if (project() instanceof MergedTestProject) { 191 return Collections.emptyList(); 192 } 193 194 // Make sure that the directories exist. 195 project().directory().resolve(".settings").toFile().mkdirs(); 196 197 // generate .project 198 generateXmlFile(this::generateProjectConfiguration, ".project"); 199 200 // generate .classpath 201 if (project() instanceof JavaProject) { 202 generateXmlFile(this::generateClasspathConfiguration, ".classpath"); 203 } 204 205 // Generate preferences 206 generateResourcesPrefs(); 207 generateRuntimePrefs(); 208 if (project() instanceof JavaProject) { 209 generateJdtCorePrefs(); 210 } 211 212 // General overrides 213 configurationAdaptor.run(); 214 215 // Create result 216 @SuppressWarnings({ "unchecked" }) 217 var result = (Collection<T>) List.of( 218 EclipseConfiguration.of(project(), eclipseAlias())); 219 return result; 220 } 221 222 private void generateXmlFile(Consumer<Document> generator, String name) { 223 try { 224 var doc = dbf.newDocumentBuilder().newDocument(); 225 generator.accept(doc); 226 var transformer = TransformerFactory.newInstance().newTransformer(); 227 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 228 transformer.setOutputProperty( 229 "{http://xml.apache.org/xslt}indent-amount", "4"); 230 try (var out = Files 231 .newBufferedWriter(project().directory().resolve(name))) { 232 transformer.transform(new DOMSource(doc), 233 new StreamResult(out)); 234 } 235 } catch (ParserConfigurationException | TransformerException 236 | TransformerFactoryConfigurationError | IOException e) { 237 throw new BuildException().from(this).cause(e); 238 } 239 } 240 241 /// Generates the content of the `.project` file into the given document. 242 /// 243 /// @param doc the document 244 /// 245 @SuppressWarnings("PMD.AvoidDuplicateLiterals") 246 protected void generateProjectConfiguration(Document doc) { 247 var prjDescr = doc.appendChild(doc.createElement("projectDescription")); 248 prjDescr.appendChild(doc.createElement("name")) 249 .appendChild(doc.createTextNode(eclipseAlias())); 250 prjDescr.appendChild(doc.createElement("comment")).appendChild( 251 doc.createTextNode(GENERATED_BY)); 252 prjDescr.appendChild(doc.createElement("projects")); 253 var buildSpec = prjDescr.appendChild(doc.createElement("buildSpec")); 254 var natures = prjDescr.appendChild(doc.createElement("natures")); 255 if (project() instanceof JavaProject) { 256 var cmd = buildSpec.appendChild(doc.createElement("buildCommand")); 257 cmd.appendChild(doc.createElement("name")).appendChild( 258 doc.createTextNode("org.eclipse.jdt.core.javabuilder")); 259 cmd.appendChild(doc.createElement("arguments")); 260 natures.appendChild(doc.createElement("nature")).appendChild( 261 doc.createTextNode("org.eclipse.jdt.core.javanature")); 262 } 263 264 // Allow derived class to adapt the project configuration 265 prjConfigAdaptor.accept(doc, buildSpec, natures); 266 } 267 268 /// Allow derived classes to post process the project configuration. 269 /// 270 @FunctionalInterface 271 public interface ProjectConfigurationAdaptor { 272 /// Execute the adaptor. 273 /// 274 /// @param doc the document 275 /// @param buildSpec shortcut to the `buildSpec` element 276 /// @param natures shortcut to the `natures` element 277 /// 278 void accept(Document doc, Node buildSpec, 279 Node natures); 280 } 281 282 /// Adapt project configuration. 283 /// 284 /// @param adaptor the adaptor 285 /// @return the eclipse configurator 286 /// 287 public EclipseConfigurator adaptProjectConfiguration( 288 ProjectConfigurationAdaptor adaptor) { 289 prjConfigAdaptor = adaptor; 290 return this; 291 } 292 293 /// Generates the content of the `.classpath` file into the given 294 /// document. 295 /// 296 /// @param doc the doc 297 /// 298 @SuppressWarnings({ "PMD.AvoidDuplicateLiterals" }) 299 protected void generateClasspathConfiguration(Document doc) { 300 var classpath = doc.appendChild(doc.createElement("classpath")); 301 addCompilationResources(doc, classpath, project()); 302 addJavaResources(doc, classpath, project()); 303 304 // Add projects 305 final Set<ClasspathElement> providedByProject = new HashSet<>(); 306 final Set<Project> exposed = project().providers().select(Expose) 307 .filter(p -> p instanceof Project).map(Project.class::cast) 308 .collect(Collectors.toSet()); 309 project().providers().filter(p -> p instanceof Project) 310 .select(Consume, Reveal, Expose, Forward) 311 .toList().stream().map(Project.class::cast).forEach(p -> { 312 if (p instanceof MergedTestProject) { 313 if (p.parentProject().get().equals(project())) { 314 // Test projects contribute their resources to the 315 // parent 316 addCompilationResources(doc, classpath, p); 317 addJavaResources(doc, classpath, p); 318 } 319 return; 320 } 321 var entry = (Element) classpath 322 .appendChild(doc.createElement("classpathentry")); 323 entry.setAttribute("kind", "src"); 324 var referenced = p.resources( 325 of(new ResourceType<EclipseConfiguration>() {}) 326 .using(Supply, Expose)) 327 .filter(c -> c.projectName().equals(p.name())).findFirst() 328 .map(EclipseConfiguration::eclipseAlias).orElse(p.name()); 329 entry.setAttribute("path", "/" + referenced); 330 if (exposed.contains(p)) { 331 entry.setAttribute("exported", "true"); 332 } 333 var attributes 334 = entry.appendChild(doc.createElement("attributes")); 335 var attribute = (Element) attributes 336 .appendChild(doc.createElement("attribute")); 337 attribute.setAttribute("without_test_code", "true"); 338 providedByProject.addAll( 339 p.resources(of(ClasspathElementType)).toList()); 340 }); 341 342 // Add jars 343 final Set<ClasspathElement> exposedByProject = new HashSet<>(); 344 exposedByProject.addAll(project() 345 .resources(of(ClasspathElementType).using(Expose)) 346 .toList()); 347 project().resources(of(LibraryJarFileType) 348 .using(Consume, Reveal, Expose)) 349 .filter(jf -> !providedByProject.contains(jf)) 350 .collect(Collectors.toSet()).stream().forEach(jf -> { 351 addJarFileEntry(doc, classpath, jf, 352 exposedByProject.contains(jf), false); 353 }); 354 355 // Allow derived class to override 356 classpathAdaptor.accept(doc, classpath); 357 } 358 359 @SuppressWarnings("PMD.AvoidDuplicateLiterals") 360 private void addJarFileEntry(Document doc, Node classpath, 361 LibraryJarFile jarFile, boolean exported, boolean test) { 362 var entry = (Element) classpath 363 .appendChild(doc.createElement("classpathentry")); 364 entry.setAttribute("kind", "lib"); 365 var jarPathName = jarFile.path().toString(); 366 entry.setAttribute("path", jarPathName); 367 if (exported) { 368 entry.setAttribute("exported", "true"); 369 } 370 if (test) { 371 var attr = (Element) entry 372 .appendChild(doc.createElement("attributes")) 373 .appendChild(doc.createElement("attribute")); 374 attr.setAttribute("name", "test"); 375 attr.setAttribute("value", "true"); 376 } 377 378 // Educated guesses 379 var sourcesJar 380 = new File(jarPathName.replaceFirst("\\.jar$", "-sources.jar")); 381 if (sourcesJar.canRead()) { 382 entry.setAttribute("sourcepath", sourcesJar.getAbsolutePath()); 383 } 384 var javadocJar = new File( 385 jarPathName.replaceFirst("\\.jar$", "-javadoc.jar")); 386 if (javadocJar.canRead()) { 387 var attr = (Element) entry 388 .appendChild(doc.createElement("attributes")) 389 .appendChild(doc.createElement("attribute")); 390 attr.setAttribute("name", "javadoc_location"); 391 attr.setAttribute("value", 392 "jar:file:" + javadocJar.getAbsolutePath() + "!/"); 393 } 394 } 395 396 private void addJavaResources(Document doc, Node classpath, 397 Project project) { 398 // TODO Generalize. Currently we assume a Java compiler exists 399 // and use it to obtain the output directory for all generators 400 var javaCompiler = project.providers().select(Consume, Reveal, Supply) 401 .filter(p -> p instanceof JavaCompiler) 402 .map(JavaCompiler.class::cast).findFirst(); 403 var outputDirectory = Optional.ofNullable( 404 (project instanceof MergedTestProject) ? testOutputDirectory 405 : this.outputDirectory) 406 .or(() -> javaCompiler 407 .map(jc -> project.relativize(jc.destination()))); 408 409 // Add resources 410 project.providers().without(Project.class).resources( 411 of(JavaResourceTreeType).using(Consume, Reveal, Supply)) 412 .map(FileTree::root).filter(p -> p.toFile().canRead()) 413 .collect(Collectors.toSet()).stream() 414 .map(project::relativize).forEach(p -> { 415 var entry = (Element) classpath 416 .appendChild(doc.createElement("classpathentry")); 417 entry.appendChild(doc.createComment("From " + project)); 418 entry.setAttribute("kind", "src"); 419 entry.setAttribute("path", p.toString()); 420 if (project instanceof MergedTestProject) { 421 outputDirectory.ifPresent(o -> { 422 entry.setAttribute("output", o.toString()); 423 }); 424 var attr = (Element) entry 425 .appendChild(doc.createElement("attributes")) 426 .appendChild(doc.createElement("attribute")); 427 attr.setAttribute("name", "test"); 428 attr.setAttribute("value", "true"); 429 } 430 }); 431 } 432 433 private void addCompilationResources(Document doc, Node classpath, 434 Project project) { 435 // TODO Generalize. Currently we assume a Java compiler exists 436 // and use it to obtain the output directory for all generators 437 var javaCompiler = project.providers().select(Consume, Reveal, Supply) 438 .filter(p -> p instanceof JavaCompiler) 439 .map(JavaCompiler.class::cast).findFirst(); 440 var outputDirectory = Optional.ofNullable( 441 (project instanceof MergedTestProject) ? testOutputDirectory 442 : this.outputDirectory) 443 .or(() -> javaCompiler 444 .map(jc -> project.relativize(jc.destination()))); 445 446 // Add source trees 447 project.providers().without(Project.class).resources( 448 of(JavaSourceTreeType).using(Consume, Reveal, Supply)) 449 .map(FileTree::root).filter(p -> p.toFile().canRead()) 450 .map(project::relativize).forEach(p -> { 451 var entry = (Element) classpath 452 .appendChild(doc.createElement("classpathentry")); 453 entry.appendChild(doc.createComment("From " + project)); 454 entry.setAttribute("kind", "src"); 455 entry.setAttribute("path", p.toString()); 456 if (project instanceof MergedTestProject) { 457 outputDirectory.ifPresent(o -> { 458 entry.setAttribute("output", o.toString()); 459 }); 460 var attr = (Element) entry 461 .appendChild(doc.createElement("attributes")) 462 .appendChild(doc.createElement("attribute")); 463 attr.setAttribute("name", "test"); 464 attr.setAttribute("value", "true"); 465 } 466 }); 467 468 // For merged test project also add compile path resources 469 if (project instanceof MergedTestProject) { 470 project.providers().without(project.parentProject().get()).filter( 471 p -> javaCompiler.map(jc -> !p.equals(jc)).orElse(true)) 472 .resources( 473 of(LibraryJarFileType).using(Consume, Reveal, Expose)) 474 .forEach(jf -> { 475 addJarFileEntry(doc, classpath, jf, false, true); 476 }); 477 return; 478 } 479 480 // For "normal projects" configure default output directory 481 outputDirectory.ifPresent(o -> { 482 var entry = (Element) classpath 483 .appendChild(doc.createElement("classpathentry")); 484 entry.setAttribute("kind", "output"); 485 entry.setAttribute("path", o.toString()); 486 }); 487 488 // Finally Add JRE 489 javaCompiler.ifPresent(jc -> { 490 jc.optionArgument("-target", "--target", "--release") 491 .ifPresentOrElse(v -> addSpecificJre(doc, classpath, v), 492 () -> addInheritedJre(doc, classpath)); 493 }); 494 } 495 496 private void addSpecificJre(Document doc, Node classpath, 497 String version) { 498 var entry = (Element) classpath 499 .appendChild(doc.createElement("classpathentry")); 500 entry.setAttribute("kind", "con"); 501 entry.setAttribute("path", 502 "org.eclipse.jdt.launching.JRE_CONTAINER" 503 + "/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType" 504 + "/JavaSE-" + version); 505 var attributes = entry.appendChild(doc.createElement("attributes")); 506 var attribute 507 = (Element) attributes.appendChild(doc.createElement("attribute")); 508 attribute.setAttribute("name", "module"); 509 attribute.setAttribute("value", "true"); 510 } 511 512 private void addInheritedJre(Document doc, Node classpath) { 513 var entry = (Element) classpath 514 .appendChild(doc.createElement("classpathentry")); 515 entry.setAttribute("kind", "con"); 516 entry.setAttribute("path", 517 "org.eclipse.jdt.launching.JRE_CONTAINER"); 518 var attributes = entry.appendChild(doc.createElement("attributes")); 519 var attribute 520 = (Element) attributes.appendChild(doc.createElement("attribute")); 521 attribute.setAttribute("name", "module"); 522 attribute.setAttribute("value", "true"); 523 } 524 525 /// Allow the user to post process the classpath configuration. 526 /// The node passed to the consumer is the `classpath` element. 527 /// 528 /// @param adaptor the adaptor 529 /// @return the eclipse configurator 530 /// 531 public EclipseConfigurator 532 adaptClasspathConfiguration(BiConsumer<Document, Node> adaptor) { 533 classpathAdaptor = adaptor; 534 return this; 535 } 536 537 /// Generate the properties for the 538 /// `.settings/org.eclipse.core.resources.prefs` file. 539 /// 540 protected void generateResourcesPrefs() { 541 var props = new Properties(); 542 props.setProperty("eclipse.preferences.version", "1"); 543 props.setProperty("encoding/<project>", "UTF-8"); 544 resourcesPrefsAdaptor.accept(props); 545 try (var out = new FixCommentsFilter(Files.newBufferedWriter( 546 project().directory().resolve( 547 ".settings/org.eclipse.core.resources.prefs")), 548 GENERATED_BY)) { 549 props.store(out, ""); 550 } catch (IOException e) { 551 throw new BuildException().from(this).cause(e); 552 } 553 } 554 555 /// Allow the user to adapt the properties for the 556 /// `.settings/org.eclipse.core.resources.prefs` file. 557 /// 558 /// @param adaptor the adaptor 559 /// @return the eclipse configurator 560 /// 561 public EclipseConfigurator 562 adaptResourcePrefs(Consumer<Properties> adaptor) { 563 resourcesPrefsAdaptor = adaptor; 564 return this; 565 } 566 567 /// Generate the properties for the 568 /// `.settings/org.eclipse.core.runtime.prefs` file. 569 /// 570 protected void generateRuntimePrefs() { 571 var props = new Properties(); 572 props.setProperty("eclipse.preferences.version", "1"); 573 props.setProperty("line.separator", "\n"); 574 runtimePrefsAdaptor.accept(props); 575 try (var out = new FixCommentsFilter(Files.newBufferedWriter( 576 project().directory().resolve( 577 ".settings/org.eclipse.core.runtime.prefs")), 578 GENERATED_BY)) { 579 props.store(out, ""); 580 } catch (IOException e) { 581 throw new BuildException().from(this).cause(e); 582 } 583 } 584 585 /// Allow the user to adapt the properties for the 586 /// `.settings/org.eclipse.core.runtime.prefs` file. 587 /// 588 /// @param adaptor the adaptor 589 /// @return the eclipse configurator 590 /// 591 public EclipseConfigurator adaptRuntimePrefs(Consumer<Properties> adaptor) { 592 runtimePrefsAdaptor = adaptor; 593 return this; 594 } 595 596 /// Generate the properties for the 597 /// `.settings/org.eclipse.jdt.core.prefs` file. 598 /// 599 protected void generateJdtCorePrefs() { 600 var props = new Properties(); 601 props.setProperty("eclipse.preferences.version", "1"); 602 project().providers().select(Supply) 603 .filter(p -> p instanceof JavaCompiler).map(p -> (JavaCompiler) p) 604 .findFirst().ifPresent(jc -> { 605 jc.optionArgument("-target", "--target", "--release") 606 .ifPresent(v -> { 607 props.setProperty("org.eclipse.jdt.core.compiler" 608 + ".codegen.targetPlatform", v); 609 }); 610 jc.optionArgument("-source", "--source", "--release") 611 .ifPresent(v -> { 612 props.setProperty("org.eclipse.jdt.core.compiler" 613 + ".source", v); 614 props.setProperty("org.eclipse.jdt.core.compiler" 615 + ".compliance", v); 616 }); 617 }); 618 jdtCorePrefsAdaptor.accept(props); 619 try (var out = new FixCommentsFilter(Files.newBufferedWriter( 620 project().directory() 621 .resolve(".settings/org.eclipse.jdt.core.prefs")), 622 GENERATED_BY)) { 623 props.store(out, ""); 624 } catch (IOException e) { 625 throw new BuildException().from(this).cause(e); 626 } 627 } 628 629 /// Allow the user to adapt the properties for the 630 /// `.settings/org.eclipse.jdt.core.prefs` file. 631 /// 632 /// @param adaptor the adaptor 633 /// @return the eclipse configurator 634 /// 635 public EclipseConfigurator adaptJdtCorePrefs(Consumer<Properties> adaptor) { 636 jdtCorePrefsAdaptor = adaptor; 637 return this; 638 } 639 640 /// Allow the user to add additional resources. 641 /// 642 /// @param adaptor the adaptor 643 /// @return the eclipse configurator 644 /// 645 public EclipseConfigurator adaptConfiguration(Runnable adaptor) { 646 configurationAdaptor = adaptor; 647 return this; 648 } 649 650}