From 3a0ee635f2c09498218bde903d071e40c13142fc Mon Sep 17 00:00:00 2001 From: aboyko Date: Wed, 20 Mar 2024 16:31:44 -0400 Subject: [PATCH] Yaml -> Props conversion --- .../META-INF/MANIFEST.MF | 3 - .../test/ConvertYamlToPropertiesTest.java | 286 ++++++++++++++++++ .../.classpath | 2 +- .../.settings/org.eclipse.jdt.core.prefs | 10 +- .../plugin.xml | 67 +++- .../ConvertYamlToPropertiesHandler.java | 93 ++++++ .../ConvertYamlToPropertiesRefactoring.java | 162 ++++++++++ .../YamlToPropertiesConverter.java | 85 ++++++ 8 files changed, 690 insertions(+), 18 deletions(-) create mode 100644 eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring.test/src/org/springframework/ide/eclipse/boot/refactoring/test/ConvertYamlToPropertiesTest.java create mode 100644 eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/ConvertYamlToPropertiesHandler.java create mode 100644 eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/ConvertYamlToPropertiesRefactoring.java create mode 100644 eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/YamlToPropertiesConverter.java diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring.test/META-INF/MANIFEST.MF b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring.test/META-INF/MANIFEST.MF index 33ef00fda..b4ff7573e 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring.test/META-INF/MANIFEST.MF +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring.test/META-INF/MANIFEST.MF @@ -13,11 +13,9 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.jdt.core, org.springsource.ide.eclipse.commons.livexp, org.springsource.ide.eclipse.commons.frameworks.core, - org.springsource.ide.eclipse.commons.frameworks.test.util, org.springframework.ide.eclipse.boot.launch, org.eclipse.jdt.launching, org.eclipse.debug.ui, - org.mockito, org.springframework.ide.eclipse.boot, org.eclipse.core.expressions, org.springframework.ide.eclipse.boot.test, @@ -26,7 +24,6 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.equinox.security, org.reactivestreams.reactive-streams;bundle-version="1.0.2", io.projectreactor.reactor-core;bundle-version="[3.3.1,3.3.2)", - javax.ws.rs;bundle-version="2.0.1", org.eclipse.osgi, org.hamcrest.library Bundle-RequiredExecutionEnvironment: JavaSE-17 diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring.test/src/org/springframework/ide/eclipse/boot/refactoring/test/ConvertYamlToPropertiesTest.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring.test/src/org/springframework/ide/eclipse/boot/refactoring/test/ConvertYamlToPropertiesTest.java new file mode 100644 index 000000000..6d62bd8e7 --- /dev/null +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring.test/src/org/springframework/ide/eclipse/boot/refactoring/test/ConvertYamlToPropertiesTest.java @@ -0,0 +1,286 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.eclipse.boot.refactoring.test; + +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.springsource.ide.eclipse.commons.tests.util.StsTestCase.assertContains; +import static org.springsource.ide.eclipse.commons.tests.util.StsTestCase.createFile; + +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.PerformChangeOperation; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; +import org.junit.Before; +import org.junit.Test; +import org.springframework.ide.eclipse.boot.refactoring.ConvertYamlToPropertiesRefactoring; +import org.springframework.ide.eclipse.boot.refactoring.test.ConvertPropertiesToYamlTest.Checker; +import org.springframework.ide.eclipse.boot.test.BootProjectTestHarness; +import org.springsource.ide.eclipse.commons.frameworks.core.util.IOUtil; +import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil; + +public class ConvertYamlToPropertiesTest { + + BootProjectTestHarness projects = new BootProjectTestHarness(getWorkspace()); + + protected IWorkspace getWorkspace() { + return ResourcesPlugin.getWorkspace(); + } + + @Before public void setup() throws Exception { + StsTestUtil.deleteAllProjects(); + } + + private void do_conversionTest(String input, String expectedOutput) throws Exception { + do_conversionTest(input, expectedOutput, (status) -> { + assertEquals(RefactoringStatus.OK, status.getSeverity()); + }); + } + + private void do_conversionTest(String input, String expectedOutput, Checker statusChecker) throws Exception { + IProject project = projects.createProject("conversionTestProject"); + IFile yamlFile = createFile(project, "application.yml", input); + IFile propsFile = project.getFile("application.properties"); + if (propsFile.exists()) { + propsFile.delete(true, new NullProgressMonitor()); + } + + assertTrue(yamlFile.exists()); + assertFalse(propsFile.exists()); + + ConvertYamlToPropertiesRefactoring refactoring = new ConvertYamlToPropertiesRefactoring(yamlFile); + statusChecker.check(refactoring.checkAllConditions(new NullProgressMonitor())); + perform(refactoring); + + assertFalse(yamlFile.exists()); + assertTrue(propsFile.exists()); + + assertEquals(expectedOutput, IOUtil.toString(propsFile.getContents())); + } + + private void assertFile(IProject project, String path, String expectedContents) throws Exception { + IFile file = project.getFile(path); + assertTrue(file.getFullPath().toString(), file.exists()); + assertEquals(expectedContents, IOUtil.toString(file.getContents())); + } + + private void perform(ConvertYamlToPropertiesRefactoring refactoring) throws Exception { + Change change = refactoring.createChange(new NullProgressMonitor()); + IWorkspace workspace = getWorkspace(); + CompletableFuture result = new CompletableFuture<>(); + Job job = new Job(refactoring.getName()) { + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + workspace.run(new PerformChangeOperation(change), monitor); + result.complete(null); + } catch (Throwable e) { + result.completeExceptionally(e); + } + return Status.OK_STATUS; + } + }; + job.setRule(workspace.getRuleFactory().buildRule()); + job.schedule(); + result.get(); + } + + private ConvertYamlToPropertiesRefactoring do_hasComments_test(IProject project, String comment) throws Exception { + IFile yamlFile = createFile(project, "src/main/resources/application.yml", + "some:\n property: somevalue\n"+ + comment +"\n" + + "other:\n property: othervalue" + ); + ConvertYamlToPropertiesRefactoring refactoring = new ConvertYamlToPropertiesRefactoring(yamlFile); + assertOkStatus(refactoring.checkInitialConditions(new NullProgressMonitor())); + assertStatus(refactoring.checkFinalConditions(new NullProgressMonitor()), RefactoringStatus.WARNING, "has comments, which will be lost"); + return refactoring; + } + + private ConvertYamlToPropertiesRefactoring do_hasInlineComments_test(IProject project, String comment) throws Exception { + IFile yamlFile = createFile(project, "src/main/resources/application.yml", + "some:\n property: somevalue\n"+ + "foo: bar " + comment +"\n" + + "other:\n property: othervalue" + ); + ConvertYamlToPropertiesRefactoring refactoring = new ConvertYamlToPropertiesRefactoring(yamlFile); + assertOkStatus(refactoring.checkInitialConditions(new NullProgressMonitor())); + assertStatus(refactoring.checkFinalConditions(new NullProgressMonitor()), RefactoringStatus.WARNING, "has comments, which will be lost"); + return refactoring; + } + + private void assertOkStatus(RefactoringStatus s) { + assertEquals(RefactoringStatus.OK, s.getSeverity()); + } + + private void assertStatus(RefactoringStatus status, int expectedSeverity, String expectedMessageFragment) { + assertEquals(expectedSeverity, status.getSeverity()); + StringBuilder allMessages = new StringBuilder(); + for (RefactoringStatusEntry entry : status.getEntries()) { + allMessages.append(entry.getMessage()); + allMessages.append("\n-------------\n"); + } + assertContains(expectedMessageFragment, allMessages.toString()); + } + + @Test public void fileIsInTheWay() throws Exception { + IProject project = projects.createBootProject("fileIsInTheWay"); + IFile yamlFile = createFile(project, "src/main/resources/application.yml", "someting: already-in-here"); + IFile propsFile = project.getFile("src/main/resources/application.properties"); + assertTrue(propsFile.exists()); + ConvertYamlToPropertiesRefactoring refactoring = new ConvertYamlToPropertiesRefactoring(yamlFile); + + RefactoringStatus status = refactoring.checkInitialConditions(new NullProgressMonitor()); + assertStatus(status, RefactoringStatus.FATAL, "'/fileIsInTheWay/src/main/resources/application.properties' already exists"); + } + + @Test public void hasLineComments() throws Exception { + IProject project = projects.createBootProject("hasComments"); + IFile propsFile = project.getFile("src/main/resources/application.properties"); + if (propsFile.exists()) { + propsFile.delete(true, new NullProgressMonitor()); + } + + do_hasComments_test(project, "#comment"); + ConvertYamlToPropertiesRefactoring refactoring = do_hasComments_test(project, " #!comment"); + + perform(refactoring); //Despite the warning, the refactoring should be executable. + assertFalse(project.getFile("src/main/resources/application.yml").exists()); + assertFile(project, "src/main/resources/application.properties", + "some.property=somevalue\n" + + "other.property=othervalue\n" + ); + } + + @Test public void hasInlineComments() throws Exception { + IProject project = projects.createBootProject("hasComments"); + IFile propsFile = project.getFile("src/main/resources/application.properties"); + if (propsFile.exists()) { + propsFile.delete(true, new NullProgressMonitor()); + } + + do_hasInlineComments_test(project, "#comment"); + ConvertYamlToPropertiesRefactoring refactoring = do_hasInlineComments_test(project, " #!comment"); + + perform(refactoring); //Despite the warning, the refactoring should be executable. + assertFalse(project.getFile("src/main/resources/application.yml").exists()); + assertFile(project, "src/main/resources/application.properties", + "some.property=somevalue\n" + + "foo=bar\n" + + "other.property=othervalue\n" + ); + } + + @Test public void almostHasComments() throws Exception { + do_conversionTest( + "my:\n" + + " goodbye: 'See ya # later'\n" + + " hello: Good morning!\n" + , // ==> + "my.goodbye=See ya \\# later\n" + + "my.hello=Good morning\\!\n" + ); + } + + @Test public void conversionWithListItems() throws Exception { + do_conversionTest( + "some:\n" + + " thing:\n" + + " - a: first-a\n" + + " b: first-b\n" + + " - a: second-a\n" + + " b: second-b\n" + , // ==> + "some.thing[0].a=first-a\n" + + "some.thing[0].b=first-b\n" + + "some.thing[1].a=second-a\n" + + "some.thing[1].b=second-b\n" + ); + } + + @Test public void simpleConversion() throws Exception { + do_conversionTest( + "some:\n" + + " other:\n" + + " thing: blah\n" + + " thing: vvvv\n" + , // ==> + "some.other.thing=blah\n" + + "some.thing=vvvv\n" + ); + } + + @Test public void emptyFileConversion() throws Exception { + do_conversionTest( + "" + , // ==> + "" + ); + } + + @Test public void unusualName() throws Exception { + IProject project = projects.createProject("unusualName"); + IFile input = createFile(project, "no-extension", + "server:\n port: 6789\n" + ); + ConvertYamlToPropertiesRefactoring refactoring = new ConvertYamlToPropertiesRefactoring(input); + assertOkStatus(refactoring.checkAllConditions(new NullProgressMonitor())); + perform(refactoring); + assertEquals( + "server.port=6789\n", + IOUtil.toString(project.getFile("no-extension.properties").getContents()) + ); + } + + @Test public void list() throws Exception { + do_conversionTest( + "some:\n" + + " property:\n" + + " - something\n" + + " - something-else\n" + , // ==> + "some.property[0]=something\n" + + "some.property[1]=something-else\n" + ); + } + + @Test public void mapAndSequenceConflict() throws Exception { + do_conversionTest( + "some:\n" + + " property:\n" + + " '0': zero\n" + + " '1': one\n" + + " abc: val1\n" + + " def: val2\n" + , + "some.property.0=zero\n" + + "some.property.1=one\n" + + "some.property.abc=val1\n" + + "some.property.def=val2\n" + ); + } + + + +} diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/.classpath b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/.classpath index 43b986286..8d8612144 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/.classpath +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/.classpath @@ -1,6 +1,6 @@ - + diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/.settings/org.eclipse.jdt.core.prefs b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/.settings/org.eclipse.jdt.core.prefs index 87b7a7a3a..9478cb165 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/.settings/org.eclipse.jdt.core.prefs +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/.settings/org.eclipse.jdt.core.prefs @@ -1,13 +1,15 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/plugin.xml b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/plugin.xml index 3ff429fb9..1484a24d7 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/plugin.xml +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/plugin.xml @@ -10,6 +10,12 @@ id="org.springframework.ide.eclipse.boot.properties.editor.convertPropertiesToYaml" name="Convert .properties to .yaml"> + + @@ -24,22 +30,63 @@ - - - - - + - - + ifEmpty="false"> + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/ConvertYamlToPropertiesHandler.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/ConvertYamlToPropertiesHandler.java new file mode 100644 index 000000000..52e19471e --- /dev/null +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/ConvertYamlToPropertiesHandler.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.eclipse.boot.refactoring; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.filebuffers.FileBuffers; +import org.eclipse.core.filebuffers.ITextFileBuffer; +import org.eclipse.core.filebuffers.LocationKind; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; +import org.eclipse.ui.handlers.HandlerUtil; +import org.springsource.ide.eclipse.commons.livexp.util.Log; + +public class ConvertYamlToPropertiesHandler extends AbstractHandler { + + private static ITextFileBuffer getDirtyFileBuffer(IFile file) { + ITextFileBuffer buffer = FileBuffers.getTextFileBufferManager().getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); + if (buffer!=null && buffer.isDirty()) { + return buffer; + } + return null; + } + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IFile file = getTarget(event); + try { + if (file!=null) { + ITextFileBuffer dirtyBuffer = getDirtyFileBuffer(file); + if (dirtyBuffer!=null) { + dirtyBuffer.commit(null, true); + } + ConvertYamlToPropertiesRefactoring refactoring = new ConvertYamlToPropertiesRefactoring(file); + RefactoringWizard wizard = new RefactoringWizard(refactoring, + RefactoringWizard.DIALOG_BASED_USER_INTERFACE | + RefactoringWizard.CHECK_INITIAL_CONDITIONS_ON_OPEN | + RefactoringWizard.NO_BACK_BUTTON_ON_STATUS_DIALOG + ) { + + @Override + protected void addUserInputPages() { + //no inputs required + } + }; + new RefactoringWizardOpenOperation(wizard).run(HandlerUtil.getActiveShell(event), "Convert '"+file.getName()+"' to .properties"); + } + } catch (Exception e) { + Log.log(e); + } + return null; + } + + private IFile getTarget(ExecutionEvent event) { + ISelection selection = HandlerUtil.getActiveMenuSelection(event); + IStructuredSelection ss = null; + if (selection instanceof IStructuredSelection) { + ss = (IStructuredSelection) selection; + } else { + selection = HandlerUtil.getActiveMenuEditorInput(event); + if (selection instanceof IStructuredSelection) { + ss = (IStructuredSelection) selection; + } + } + if (ss!=null && !ss.isEmpty()) { + return asFile(ss.getFirstElement()); + } + return null; + } + + private IFile asFile(Object selectedElement) { + if (selectedElement instanceof IFile) { + return (IFile) selectedElement; + } + if (selectedElement instanceof IAdaptable) { + return ((IAdaptable) selectedElement).getAdapter(IFile.class); + } + return null; + } +} diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/ConvertYamlToPropertiesRefactoring.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/ConvertYamlToPropertiesRefactoring.java new file mode 100644 index 000000000..717743d5a --- /dev/null +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/ConvertYamlToPropertiesRefactoring.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.eclipse.boot.refactoring; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.util.Map; +import java.util.Properties; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.ltk.core.refactoring.resource.RenameResourceChange; +import org.eclipse.text.edits.ReplaceEdit; +import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.events.CommentEvent; +import org.yaml.snakeyaml.events.Event; +import org.yaml.snakeyaml.events.StreamEndEvent; + +public class ConvertYamlToPropertiesRefactoring extends Refactoring { + + private static final String YAML_EXT = ".yml"; + private static final String PROPERTIES_EXT = ".properties"; + + private IFile propsFile; + private final IFile yamlFile; + private String propsContent; + private int inputTextLen; + + public ConvertYamlToPropertiesRefactoring(IFile yamlFile) { + this.yamlFile = yamlFile; + } + + @Override + public String getName() { + return "Convert .yaml to .properties"; + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) + throws CoreException, OperationCanceledException { + if (!yamlFile.isAccessible()) { + return RefactoringStatus.createFatalErrorStatus("The resource '"+yamlFile.getFullPath()+"' is not accessible"); + } + this.propsFile = propsFileFor(yamlFile); + if (propsFile.exists()) { + return RefactoringStatus.createFatalErrorStatus("File '"+propsFile.getFullPath()+"' already exists!"); + } + return new RefactoringStatus(); + } + + private static IFile propsFileFor(IFile yamlFile) { + IContainer container = yamlFile.getParent(); + String name = yamlFile.getName(); + if (name.endsWith(YAML_EXT)) { + name = name.substring(0, name.length()-YAML_EXT.length())+PROPERTIES_EXT; + } else { + name = name + PROPERTIES_EXT; + } + return container.getFile(Path.EMPTY.append(name)); + } + + @Override + public RefactoringStatus checkFinalConditions(IProgressMonitor pm) + throws CoreException, OperationCanceledException { + RefactoringStatus status = new RefactoringStatus(); + try { + if (hasComments(yamlFile)) { + status.merge(RefactoringStatus.createWarningStatus("The yaml file has comments, which will be lost in the refactoring!")); + } + } catch (Exception e) { + status.merge(RefactoringStatus.create(ExceptionUtil.status(e, "Problems reading file: "+yamlFile.getFullPath()))); + return status; + } + + Map o = null; + try (InputStream content = yamlFile.getContents()) { + o = new Yaml().load(content); + } catch (Exception e) { + status.merge(RefactoringStatus.create(ExceptionUtil.status(e, "Problems parsing as a .yaml file: "+yamlFile.getFullPath()))); + } + if (o != null) { + try { + YamlToPropertiesConverter converter = new YamlToPropertiesConverter(o); + Properties props = converter.getProperties(); + StringWriter write = new StringWriter(); + props.store(write, null); + write.flush(); + write.close(); + // Skip over the date header. Comments are not present but date header is. + if (write.getBuffer().charAt(0) == '#') { + int idx = write.getBuffer().indexOf("\n"); + this.propsContent = idx >= 0 && idx < write.getBuffer().length() ? write.getBuffer().substring(idx + 1) : write.getBuffer().toString(); + } else { + this.propsContent = write.getBuffer().toString(); + } + status.merge(converter.getStatus()); + } catch (IOException e) { + status.merge(RefactoringStatus.create(ExceptionUtil.status(e, "Problems writing to .properties file: "+propsFile.getFullPath()))); + } + } else { + propsContent = ""; + } + return status; + } + + private boolean hasComments(IFile yamlFile) throws Exception { + InputStream is = null; + try { + is = yamlFile.getContents(); + LoaderOptions loaderOptions = new LoaderOptions(); + loaderOptions.setProcessComments(true); + boolean hasComments = false; + for (Event e : new Yaml(loaderOptions).parse(new InputStreamReader(is))) { + if (e instanceof StreamEndEvent) { + inputTextLen = e.getEndMark().getIndex(); + } + if (!hasComments && e instanceof CommentEvent) { + hasComments = true; + } + } + return hasComments; + } catch (Throwable t) { + if (is != null) { + is.close(); + } + return true; + } + } + + @Override + public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { + CompositeChange changes = new CompositeChange(getName()); + TextFileChange textChange = new TextFileChange(getName(), yamlFile); + textChange.setEdit(new ReplaceEdit(0, inputTextLen, propsContent)); + changes.add(textChange); + changes.add(new RenameResourceChange(yamlFile.getFullPath(), propsFile.getName())); + changes.initializeValidationData(pm); + return changes; + } + +} diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/YamlToPropertiesConverter.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/YamlToPropertiesConverter.java new file mode 100644 index 000000000..2d15a8bd4 --- /dev/null +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.refactoring/src/org/springframework/ide/eclipse/boot/refactoring/YamlToPropertiesConverter.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.eclipse.boot.refactoring; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.eclipse.ltk.core.refactoring.RefactoringStatus; + +public class YamlToPropertiesConverter { + + private final RefactoringStatus status; + + private final Properties properties; + + + public YamlToPropertiesConverter(Map yaml) { + this.status = new RefactoringStatus(); + this.properties = new Properties() { + + private static final long serialVersionUID = 1L; + + private LinkedHashMap delegate = new LinkedHashMap<>(); + + @Override + public synchronized Object put(Object key, Object value) { + delegate.put(key, value); + return super.put(key, value); + } + + @Override + public Set> entrySet() { + return delegate.entrySet(); + } + + }; + + for (Map.Entry e : yaml.entrySet()) { + readProperties(e.getValue(), e.getKey()); + } + } + + private void readPropertiesFromYamlMap(Map map, String prefix) { + for (Map.Entry e : map.entrySet()) { + readProperties(e.getValue(), "%s.%s".formatted(prefix, e.getKey())); + } + } + + private void readPropertiesFromYamlList(List l, String prefix) { + for (int i = 0; i < l.size(); i++) { + readProperties(l.get(i), "%s[%d]".formatted(prefix, i)); + } + } + + @SuppressWarnings("unchecked") + private void readProperties(Object o, String prefix) { + if (o instanceof Map) { + readPropertiesFromYamlMap((Map) o, prefix); + } else if ( o instanceof List) { + readPropertiesFromYamlList((List) o, prefix); + } else { + properties.put(prefix, o.toString()); + } + } + + public RefactoringStatus getStatus() { + return status; + } + + public Properties getProperties() { + return properties; + } + +}