Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1b36230
First draft of changes
ishish07 Aug 11, 2024
cb8dbe4
Added test to ensure application.properties doesn't change if no addi…
ishish07 Aug 11, 2024
ad78269
Used Auto Formatter
ishish07 Aug 11, 2024
75673fa
More Formatting
ishish07 Aug 11, 2024
27b2398
More Formatting 2
ishish07 Aug 11, 2024
b390d3a
fixing edge case 1
ishish07 Aug 12, 2024
26f3c19
unable to append to existing application-prof.properties
ishish07 Aug 12, 2024
e1eb520
testing for no application.properties
ishish07 Aug 12, 2024
b8c6239
removed cycles from tests
ishish07 Aug 12, 2024
cce5019
This code works!
ishish07 Aug 13, 2024
780250e
Forgot to format test file
ishish07 Aug 13, 2024
b1e4719
More formatting
ishish07 Aug 13, 2024
5b65012
Fixed weird spacing issue
ishish07 Aug 13, 2024
0cf74f9
Formatting and Code Cleanup
ishish07 Aug 13, 2024
e7398d3
Changed approach to create blank properties files and append to every…
ishish07 Aug 13, 2024
35ae8c7
Converted existingPropertiesFiles to Set of Strings
ishish07 Aug 13, 2024
825d68c
New properties files will go into same folder as application.properties
ishish07 Aug 23, 2024
46d75d0
Merge branch 'main' into feature/SeparateApplicationProperties
timtebeek Sep 11, 2024
5aef7ae
Apply suggestions from code review
timtebeek Sep 11, 2024
87e7146
Add missing braces, language hints and apply formatter
timtebeek Sep 11, 2024
6c3cdd7
Minor polish
timtebeek Sep 11, 2024
4f23297
Add test showing multi module project structure
timtebeek Sep 21, 2024
fbca498
Merge branch 'main' into feature/SeparateApplicationProperties
timtebeek Sep 21, 2024
9363b0f
Merge branch 'openrewrite:main' into feature/SeparateApplicationPrope…
ishish07 Jul 8, 2025
3631135
Merge branch 'openrewrite:main' into feature/SeparateApplicationPrope…
ishish07 Jul 8, 2025
798caa3
Works for multi-modular projects
ishish07 Jul 8, 2025
ef99fbb
responding to bot's comment
ishish07 Jul 9, 2025
85a9aa1
Merge branch 'openrewrite:main' into feature/SeparateApplicationPrope…
ishish07 Aug 1, 2025
dc7d1de
Merge branch 'openrewrite:main' into feature/SeparateApplicationPrope…
ishish07 Aug 4, 2025
967e809
using JavaProject
ishish07 Aug 5, 2025
c90923d
Merge branch 'main' into feature/SeparateApplicationProperties
timtebeek Aug 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.openrewrite.java.spring;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.java.marker.JavaProject;
import org.openrewrite.marker.Markers;
import org.openrewrite.properties.CreatePropertiesFile;
import org.openrewrite.properties.PropertiesVisitor;
import org.openrewrite.properties.tree.Properties;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import java.util.stream.Collectors;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;

import java.util.stream.Stream;

@Value
@EqualsAndHashCode(callSuper = false)
public class SeparateApplicationPropertiesByProfile extends ScanningRecipe<SeparateApplicationPropertiesByProfile.Accumulator> {

@Override
public String getDisplayName() {
return "Separate `application.properties` by profile";
}

@Override
public String getDescription() {
return "Separating `application.properties` into separate files based on profiles.";
}

@Override
public Accumulator getInitialValue(ExecutionContext ctx) {
return new Accumulator();
}

@Override
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) {
return new TreeVisitor<Tree, ExecutionContext>() {
@Override
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
if (!(tree instanceof Properties.File)) {
return tree;
}

Properties.File propertyFile = (Properties.File) tree;
Optional<JavaProject> javaProject = propertyFile.getMarkers().findFirst(JavaProject.class);
if (!javaProject.isPresent()) {
return tree;
}

String sourcePath = PathUtils.separatorsToUnix(propertyFile.getSourcePath().toString());
String[] pathArray = sourcePath.split("/");

// Get or create the module info using the JavaProject marker as the key
ModulePropertyInfo moduleInfo = acc.moduleProperties.computeIfAbsent(
javaProject.get(),
k -> new ModulePropertyInfo()
);

if (moduleInfo.javaProject == null) {
moduleInfo.javaProject = javaProject.get();
}

if (propertyFile.getSourcePath().endsWith("application.properties")) {
moduleInfo.pathToApplicationProperties = getPathToApplicationProperties(pathArray);
moduleInfo.propertyFileContent = getNewApplicationPropertyFileInfo(propertyFile.getContent());

}

if (propertyFile.getSourcePath().getFileName().toString().matches("application-[^/]+\\.properties")) {
moduleInfo.fileNameToFilePath.put(pathArray[pathArray.length - 1], sourcePath);
}

return tree;
}
};
}



@Override
public Collection<? extends SourceFile> generate(Accumulator acc, ExecutionContext ctx) {
Set<SourceFile> newApplicationPropertiesFiles = new HashSet<>();

// Change the loop to iterate over the entrySet to access the full module info
for (ModulePropertyInfo moduleInfo : acc.moduleProperties.values()) {
if (moduleInfo.propertyFileContent.isEmpty() || moduleInfo.javaProject == null) {
continue;
}

for (Map.Entry<String, List<Properties.Content>> entry : moduleInfo.propertyFileContent.entrySet()) {
if (!moduleInfo.fileNameToFilePath.containsKey(entry.getKey())) {

// 1. Generate the new file as before
SourceFile newFile = new CreatePropertiesFile(
moduleInfo.pathToApplicationProperties + entry.getKey(),
"",
null
).generate(new AtomicBoolean(true), ctx).iterator().next();

// 2. Get the stored project marker
JavaProject projectMarker = moduleInfo.javaProject;

// 3. Use withMarkers() to attach it to the new file
SourceFile newFileWithMarker = newFile.withMarkers(
// Markers.build() creates the container for our marker
Markers.build(Collections.singletonList(projectMarker))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Markers.build(Collections.singletonList(projectMarker))
Markers.build(singletonList(projectMarker))

);

// 4. Add the file *with the new marker* to our results
newApplicationPropertiesFiles.add(newFileWithMarker);
}
}
}

return newApplicationPropertiesFiles;
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) {
return new PropertiesVisitor<ExecutionContext>() {
@Override
public Properties visitFile(Properties.File file, ExecutionContext ctx) {
Optional<JavaProject> javaProject = file.getMarkers().findFirst(JavaProject.class);
if (!javaProject.isPresent()) {
return file;
}

ModulePropertyInfo moduleInfo = acc.moduleProperties.get(javaProject.get());
if (moduleInfo == null || moduleInfo.propertyFileContent.isEmpty()) {
return file;
}

String[] filePathArray = file.getSourcePath().toString().split("/");
String fileName = filePathArray[filePathArray.length - 1];

return fileName.matches("application.properties") ?
deleteFromApplicationProperties(file) :
appendToExistingPropertiesFile(file, moduleInfo.propertyFileContent.get(fileName));
}
};
}

private Properties appendToExistingPropertiesFile(Properties.File file, List<Properties.Content> contentToAppend) {
return file.withContent(
Stream.concat(file.getContent().stream(), contentToAppend.stream()).
collect(Collectors.toList()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
collect(Collectors.toList()));
collect(toList()));

}

private Properties deleteFromApplicationProperties(Properties.File applicationProperties) {
List<Properties.Content> newContent = new ArrayList<>();
for (Properties.Content c : applicationProperties.getContent()) {
if (isSeparator(c)) {
break;
}
newContent.add(c);
}
return applicationProperties.getContent().equals(newContent) ? applicationProperties :
applicationProperties.withContent(newContent);
}

private Map<String, List<Properties.Content>> getNewApplicationPropertyFileInfo(List<Properties.Content> contentList) {
Map<String, List<Properties.Content>> map = new HashMap<>();
int index = 0;
while (index < contentList.size()) {
if (isSeparator(contentList.get(index))) {
List<Properties.Content> newContent = getContentForNewFile(contentList, ++index);
if (!newContent.isEmpty() && newContent.get(0) instanceof Properties.Entry) {
map.put("application-" + ((Properties.Entry) newContent.get(0)).getValue().getText() + ".properties",
newContent.subList(1, newContent.size()));
}
}
index++;
}
return map;
}

private List<Properties.Content> getContentForNewFile(List<Properties.Content> contentList, int index) {
List<Properties.Content> list = new ArrayList<>();
while (index < contentList.size() && !isSeparator(contentList.get(index))) {
if (contentList.get(index) instanceof Properties.Entry &&
((Properties.Entry) contentList.get(index)).getKey().equals
("spring.config.activate.on-profile")) {
Comment on lines +199 to +200
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
((Properties.Entry) contentList.get(index)).getKey().equals
("spring.config.activate.on-profile")) {
"spring.config.activate.on-profile".equals
(((Properties.Entry) contentList.get(index)).getKey())) {

list.add(0, contentList.get(index));
} else {
list.add(contentList.get(index));
}
index++;
}
return list;
}

private String getPathToApplicationProperties(String[] pathArray) {
return pathArray.length == 1 ? "" : String.join("/", Arrays.copyOfRange(pathArray, 0, pathArray.length - 1)) + "/";
}

private boolean isSeparator(Properties.Content c) {
return c instanceof Properties.Comment &&
((Properties.Comment) c).getMessage().equals("---") &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
((Properties.Comment) c).getMessage().equals("---") &&
"---".equals(((Properties.Comment) c).getMessage()) &&

((((Properties.Comment) c).getDelimiter() ==
Properties.Comment.Delimiter.valueOf("HASH_TAG")) ||

((Properties.Comment) c).getDelimiter() ==
Properties.Comment.Delimiter.valueOf("EXCLAMATION_MARK"));
}

public static class Accumulator {
// Map from a module's JavaProject marker to its property file info
Map<JavaProject, ModulePropertyInfo> moduleProperties = new HashMap<>();
}


public static class ModulePropertyInfo {
String pathToApplicationProperties = "";
Map<String, String> fileNameToFilePath = new HashMap<>();
Map<String, List<Properties.Content>> propertyFileContent = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Map<String, List<Properties.Content>> propertyFileContent = new HashMap<>();
Map<String, List<Properties.Content>> propertyFileContent = new HashMap<>();

@Nullable
JavaProject javaProject;
}
}
Loading