Skip to content

Commit 277e3a7

Browse files
Vassiliy-Kudryashovtamarinvs19
authored andcommitted
Self-documenting settings template (#1420)
* Convert 'UTSettings.kt' into '.utbot/settings.properties' * Add Apache 2.0 license to settings.properties output and tune comments wording in sourcecode * Add template generation to gradle build process * Add AbstractSettings.areCustomized() ability to tell template from user-defined custom settings * Make all properties commented to be free switch our hardcoded defaults later if any. * Fix an issue with always-default settings state. * Change copyright in the license template to utbot.org * Fix a misprint in UtSettings property name "treatAssertAsErrorSuite"
1 parent c32a6e6 commit 277e3a7

File tree

7 files changed

+278
-67
lines changed

7 files changed

+278
-67
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import org.gradle.api.*;
2+
3+
import java.io.*;
4+
import java.text.*;
5+
import java.util.*;
6+
import java.util.function.*;
7+
8+
/**
9+
* The purpose of this helper is to convert UtSettings.kt source code
10+
* to template resource file settings.properties (top-level entry in plugin JAR file).
11+
* There are two stages: parsing of input to build models and then rendering models to output file
12+
*/
13+
14+
public class SettingsTemplateHelper {
15+
private static final String[] apacheLines =
16+
("Copyright (c) " + new SimpleDateFormat("yyyy").format(System.currentTimeMillis()) + " utbot.org\n" +
17+
"\n" +
18+
"Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
19+
"you may not use this file except in compliance with the License.\n" +
20+
"You may obtain a copy of the License at\n" +
21+
"\n" +
22+
" http://www.apache.org/licenses/LICENSE-2.0\n" +
23+
"\n" +
24+
"Unless required by applicable law or agreed to in writing, software\n" +
25+
"distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
26+
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
27+
"See the License for the specific language governing permissions and\n" +
28+
"limitations under the License.").split("\n");
29+
30+
public static void proceed(Project project) {
31+
File settingsSourceDir = new File(project.getBuildDir().getParentFile().getParentFile(), "utbot-framework-api/src/main/kotlin/org/utbot/framework/");
32+
String sourceFileName = "UtSettings.kt";
33+
File settingsResourceDir = new File(project.getBuildDir().getParentFile().getParentFile(), "utbot-intellij/src/main/resources/");
34+
String settingsFileName = "settings.properties";
35+
36+
Map<String, String> dictionary = new HashMap<>();
37+
dictionary.put("Int.MAX_VALUE", String.valueOf(Integer.MAX_VALUE));
38+
39+
List<PropertyModel> models = new ArrayList<>();
40+
List<EnumInfo> enums = new ArrayList<>();
41+
StringBuilder acc = new StringBuilder();
42+
List<String> docAcc = new ArrayList<>();
43+
// Stage one: parsing sourcecode
44+
try(BufferedReader reader = new BufferedReader(new FileReader(new File(settingsSourceDir, sourceFileName)))) {
45+
for (String s = reader.readLine(); s != null; s = reader.readLine()) {
46+
s = s.trim();
47+
if (s.startsWith("enum class ")) {//Enum class declaration
48+
enums.add(new EnumInfo(s.substring(11, s.length() - 2)));
49+
} else if (s.matches("[A-Z_]+,?") && !enums.isEmpty()) {//Enum value
50+
var enumValue = s.substring(0, s.length());
51+
if (enumValue.endsWith(",")) enumValue = enumValue.substring(0, enumValue.length() - 1);
52+
enums.get(enums.size() - 1).docMap.put(enumValue, new ArrayList<>(docAcc));
53+
} else if (s.startsWith("const val ")) {//Constand value to be substitute later if need
54+
int pos = s.indexOf(" = ");
55+
dictionary.put(s.substring(10, pos), s.substring(pos + 3));
56+
} else if (s.equals("/**")) {//Start of docuemntation block
57+
docAcc.clear();
58+
} else if (s.startsWith("* ")) {
59+
if (!s.contains("href")) {//Links are not supported, skip them
60+
docAcc.add(s.substring(2));
61+
}
62+
} else if (s.startsWith("var") || s.startsWith("val")) {//Restart accumulation
63+
acc.delete(0, acc.length());
64+
acc.append(s);
65+
} else if (s.isEmpty() && acc.length() > 0) {//Build model from accumulated lines
66+
s = acc.toString();
67+
acc.delete(0, acc.length());
68+
if (s.startsWith("var") || s.startsWith("val")) {
69+
var i = s.indexOf(" by ", 3);
70+
if (i > 0) {
71+
var key = s.substring(3, i).trim();
72+
if (key.contains(":")) {
73+
key = key.substring(0, key.lastIndexOf(':'));
74+
}
75+
PropertyModel model = new PropertyModel(key);
76+
models.add(model);
77+
s = s.substring(i + 7);
78+
i = s.indexOf("Property");
79+
if (i > 0) model.type = s.substring(0, i);
80+
if (i == 0) {
81+
i = s.indexOf('<', i);
82+
if (i != -1) {
83+
s = s.substring(i+1);
84+
i = s.indexOf('>');
85+
if (i != -1) {
86+
model.type = s.substring(0, i);
87+
}
88+
}
89+
}
90+
91+
i = s.indexOf('(', i);
92+
if (i > 0) {
93+
s = s.substring(i + 1);
94+
var defaultValue = s.substring(0, s.indexOf(')')).trim();
95+
if (defaultValue.contains(",")) defaultValue = defaultValue.substring(0, defaultValue.indexOf(','));
96+
defaultValue = dictionary.getOrDefault(defaultValue, defaultValue);
97+
if (defaultValue.matches("[\\d_]+L")) {
98+
defaultValue = defaultValue.substring(0, defaultValue.length() - 1).replace("_", "");
99+
}
100+
if (defaultValue.matches("^\".+\"$")) {
101+
defaultValue = defaultValue.substring(1, defaultValue.length() - 1);
102+
}
103+
model.defaultValue = defaultValue;
104+
model.docLines.addAll(docAcc);
105+
}
106+
}
107+
}
108+
} else if (acc.length() > 0) {
109+
acc.append(" " + s);
110+
}
111+
}
112+
} catch (IOException ioe) {
113+
System.err.println("Unexpected error when processing " + sourceFileName);
114+
ioe.printStackTrace();
115+
}
116+
117+
// Stage two: properties file rendering
118+
try (PrintWriter writer = new PrintWriter(new File(settingsResourceDir, settingsFileName))) {
119+
for (String apacheLine : apacheLines) {
120+
writer.println("# " + apacheLine);
121+
}
122+
for (PropertyModel model : models) {
123+
if (model.type.equals("Enum")) {
124+
String[] split = model.defaultValue.split("\\.");
125+
if (split.length > 1) {
126+
model.defaultValue = split[1];
127+
EnumInfo enumInfo = enums.stream().filter(new Predicate<EnumInfo>() {
128+
@Override
129+
public boolean test(EnumInfo enumInfo) {
130+
return enumInfo.className.equals(split[0]);
131+
}
132+
}).findFirst().orElse(null);
133+
if (enumInfo != null) {
134+
model.docLines.add("");
135+
for (Map.Entry<String, List<String>> entry : enumInfo.docMap.entrySet()) {
136+
String enumValue = entry.getKey();
137+
if (entry.getValue().size() == 1) {
138+
model.docLines.add(enumValue + ": " + entry.getValue().get(0));
139+
} else {
140+
model.docLines.add(enumValue);
141+
for (String line : entry.getValue()) {
142+
model.docLines.add(line);
143+
}
144+
}
145+
}
146+
}
147+
}
148+
}
149+
writer.println();
150+
writer.println("#");
151+
for (String docLine : model.docLines) {
152+
if (docLine.isEmpty()) {
153+
writer.println("#");
154+
} else {
155+
writer.println("# " + docLine);
156+
}
157+
}
158+
boolean defaultIsAlreadyMentioned = model.docLines.stream().anyMatch(new Predicate<String>() {
159+
@Override
160+
public boolean test(String s) {
161+
return s.toLowerCase(Locale.ROOT).contains("default");
162+
}
163+
});
164+
if (!defaultIsAlreadyMentioned) {
165+
writer.println("#");
166+
writer.println("# Default value is [" + model.defaultValue + "]");
167+
}
168+
writer.println("#" + model.key + "=" + model.defaultValue);
169+
}
170+
writer.flush();
171+
writer.close();
172+
} catch (IOException ioe) {
173+
System.err.println("Unexpected error when saving " + settingsFileName);
174+
ioe.printStackTrace();
175+
}
176+
}
177+
178+
private static class PropertyModel {
179+
final String key;
180+
String type = "";
181+
String defaultValue = "";
182+
List<String> docLines = new ArrayList<>();
183+
184+
PropertyModel(String key) {
185+
this.key = key;
186+
}
187+
}
188+
189+
private static class EnumInfo {
190+
final String className;
191+
Map<String, List<String>> docMap = new LinkedHashMap<>();
192+
193+
public EnumInfo(String className) {
194+
this.className = className;
195+
}
196+
}
197+
}

utbot-core/src/main/kotlin/org/utbot/common/AbstractSettings.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import kotlin.reflect.KProperty
1111

1212
interface SettingsContainer {
1313
fun <T> settingFor(defaultValue: T, converter: (String) -> T): PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>>
14+
15+
// Returns true iff some properties have non-default values
16+
fun isCustomized() = false
1417
}
1518

1619
interface SettingsContainerFactory {
@@ -47,6 +50,7 @@ class PropertiesSettingsContainer(
4750
}
4851

4952
private val settingsValues: MutableMap<KProperty<*>, Any?> = mutableMapOf()
53+
private var customized: Boolean = false
5054

5155
inner class SettingDelegate<T>(val property: KProperty<*>, val initializer: () -> T): ReadWriteProperty<Any?, T> {
5256
private var value = initializer()
@@ -74,7 +78,12 @@ class PropertiesSettingsContainer(
7478
return PropertyDelegateProvider { _, property ->
7579
SettingDelegate(property) {
7680
try {
77-
properties.getProperty(property.name)?.let(converter) ?: defaultValue
81+
properties.getProperty(property.name)?.let {
82+
val parsedValue = converter.invoke(it)
83+
customized = customized or (parsedValue != defaultValue)
84+
return@SettingDelegate parsedValue
85+
}
86+
defaultValue
7887
} catch (e: Throwable) {
7988
logger.info(e) { e.message }
8089
defaultValue
@@ -83,6 +92,10 @@ class PropertiesSettingsContainer(
8392
}
8493
}
8594

95+
override fun isCustomized(): Boolean {
96+
return customized
97+
}
98+
8699
override fun toString(): String =
87100
settingsValues
88101
.mapKeys { it.key.name }
@@ -115,6 +128,8 @@ abstract class AbstractSettings(
115128
}
116129
}
117130

131+
fun areCustomized(): Boolean = container.isCustomized()
132+
118133
protected fun <T> getProperty(
119134
defaultValue: T,
120135
converter: (String) -> T

0 commit comments

Comments
 (0)