Skip to content

Commit 830a887

Browse files
authored
Fix renderer memory issues by writing to OutputStream (#544)
Closes #543
1 parent 37b2d06 commit 830a887

File tree

15 files changed

+275
-196
lines changed

15 files changed

+275
-196
lines changed

cli/src/main/java/org/openapitools/openapidiff/cli/Main.java

+19-27
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import ch.qos.logback.classic.Level;
44
import io.swagger.v3.parser.core.models.AuthorizationValue;
5-
import java.io.File;
6-
import java.io.IOException;
7-
import java.nio.charset.StandardCharsets;
5+
import java.io.ByteArrayOutputStream;
6+
import java.io.FileOutputStream;
7+
import java.io.OutputStreamWriter;
88
import java.util.Collections;
99
import java.util.List;
1010
import org.apache.commons.cli.CommandLine;
@@ -14,7 +14,6 @@
1414
import org.apache.commons.cli.Option;
1515
import org.apache.commons.cli.Options;
1616
import org.apache.commons.cli.ParseException;
17-
import org.apache.commons.io.FileUtils;
1817
import org.apache.commons.lang3.exception.ExceptionUtils;
1918
import org.openapitools.openapidiff.core.OpenApiCompare;
2019
import org.openapitools.openapidiff.core.model.ChangedOpenApi;
@@ -175,29 +174,33 @@ public static void main(String... args) {
175174
ChangedOpenApi result = OpenApiCompare.fromLocations(oldPath, newPath, auths);
176175
ConsoleRender consoleRender = new ConsoleRender();
177176
if (!logLevel.equals("OFF")) {
178-
System.out.println(consoleRender.render(result));
177+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
178+
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
179+
consoleRender.render(result, outputStreamWriter);
180+
System.out.println(outputStream);
179181
}
180182
if (line.hasOption("html")) {
181183
HtmlRender htmlRender = new HtmlRender();
182-
String output = htmlRender.render(result);
183-
String outputFile = line.getOptionValue("html");
184-
writeOutput(output, outputFile);
184+
FileOutputStream outputStream = new FileOutputStream(line.getOptionValue("html"));
185+
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
186+
htmlRender.render(result, outputStreamWriter);
185187
}
186188
if (line.hasOption("markdown")) {
187189
MarkdownRender mdRender = new MarkdownRender();
188-
String output = mdRender.render(result);
189-
String outputFile = line.getOptionValue("markdown");
190-
writeOutput(output, outputFile);
190+
FileOutputStream outputStream = new FileOutputStream(line.getOptionValue("markdown"));
191+
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
192+
mdRender.render(result, outputStreamWriter);
191193
}
192194
if (line.hasOption("text")) {
193-
String output = consoleRender.render(result);
194-
String outputFile = line.getOptionValue("text");
195-
writeOutput(output, outputFile);
195+
FileOutputStream outputStream = new FileOutputStream(line.getOptionValue("text"));
196+
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
197+
consoleRender.render(result, outputStreamWriter);
196198
}
197199
if (line.hasOption("json")) {
198200
JsonRender jsonRender = new JsonRender();
199-
String outputFile = line.getOptionValue("json");
200-
jsonRender.renderToFile(result, outputFile);
201+
FileOutputStream outputStream = new FileOutputStream(line.getOptionValue("json"));
202+
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
203+
jsonRender.render(result, outputStreamWriter);
201204
}
202205
if (line.hasOption("state")) {
203206
System.out.println(result.isChanged().getValue());
@@ -222,17 +225,6 @@ public static void main(String... args) {
222225
}
223226
}
224227

225-
private static void writeOutput(String output, String outputFile) {
226-
File file = new File(outputFile);
227-
logger.debug("Output file: {}", file.getAbsolutePath());
228-
try {
229-
FileUtils.writeStringToFile(file, output, StandardCharsets.UTF_8);
230-
} catch (IOException e) {
231-
logger.error("Impossible to write output to file {}", outputFile, e);
232-
System.exit(2);
233-
}
234-
}
235-
236228
public static void printHelp(Options options) {
237229
HelpFormatter formatter = new HelpFormatter();
238230
formatter.printHelp("openapi-diff <old> <new>", options);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.openapitools.openapidiff.core.exception;
2+
3+
public class RendererException extends RuntimeException {
4+
5+
public RendererException(Throwable cause) {
6+
super(cause);
7+
}
8+
9+
public RendererException(String message, Throwable cause) {
10+
super(message, cause);
11+
}
12+
}

core/src/main/java/org/openapitools/openapidiff/core/output/ConsoleRender.java

+50-50
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
import io.swagger.v3.oas.models.media.Schema;
77
import io.swagger.v3.oas.models.parameters.Parameter;
88
import io.swagger.v3.oas.models.responses.ApiResponse;
9+
import java.io.IOException;
10+
import java.io.OutputStreamWriter;
911
import java.util.List;
1012
import java.util.Map;
1113
import java.util.Map.Entry;
1214
import java.util.Optional;
1315
import org.apache.commons.lang3.StringUtils;
16+
import org.openapitools.openapidiff.core.exception.RendererException;
1417
import org.openapitools.openapidiff.core.model.*;
1518
import org.openapitools.openapidiff.core.utils.RefPointer;
1619
import org.openapitools.openapidiff.core.utils.RefType;
@@ -21,81 +24,78 @@ public class ConsoleRender implements Render {
2124
protected ChangedOpenApi diff;
2225

2326
@Override
24-
public String render(ChangedOpenApi diff) {
27+
public void render(ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) {
2528
this.diff = diff;
26-
StringBuilder output = new StringBuilder();
2729
if (diff.isUnchanged()) {
28-
output.append("No differences. Specifications are equivalents");
30+
safelyAppend(outputStreamWriter, "No differences. Specifications are equivalents");
2931
} else {
30-
output
31-
.append(bigTitle("Api Change Log"))
32-
.append(StringUtils.center(diff.getNewSpecOpenApi().getInfo().getTitle(), LINE_LENGTH))
33-
.append(System.lineSeparator());
32+
safelyAppend(outputStreamWriter, bigTitle("Api Change Log"));
33+
safelyAppend(
34+
outputStreamWriter,
35+
StringUtils.center(diff.getNewSpecOpenApi().getInfo().getTitle(), LINE_LENGTH));
36+
safelyAppend(outputStreamWriter, System.lineSeparator());
3437

3538
List<Endpoint> newEndpoints = diff.getNewEndpoints();
36-
String ol_newEndpoint = listEndpoints(newEndpoints, "What's New");
39+
listEndpoints(newEndpoints, "What's New", outputStreamWriter);
3740

3841
List<Endpoint> missingEndpoints = diff.getMissingEndpoints();
39-
String ol_missingEndpoint = listEndpoints(missingEndpoints, "What's Deleted");
42+
listEndpoints(missingEndpoints, "What's Deleted", outputStreamWriter);
4043

4144
List<Endpoint> deprecatedEndpoints = diff.getDeprecatedEndpoints();
42-
String ol_deprecatedEndpoint = listEndpoints(deprecatedEndpoints, "What's Deprecated");
45+
listEndpoints(deprecatedEndpoints, "What's Deprecated", outputStreamWriter);
4346

4447
List<ChangedOperation> changedOperations = diff.getChangedOperations();
45-
String ol_changed = ol_changed(changedOperations);
46-
47-
output
48-
.append(renderBody(ol_newEndpoint, ol_missingEndpoint, ol_deprecatedEndpoint, ol_changed))
49-
.append(title("Result"))
50-
.append(
51-
StringUtils.center(
52-
diff.isCompatible()
53-
? "API changes are backward compatible"
54-
: "API changes broke backward compatibility",
55-
LINE_LENGTH))
56-
.append(System.lineSeparator())
57-
.append(separator('-'));
48+
ol_changed(changedOperations, outputStreamWriter);
49+
50+
safelyAppend(
51+
outputStreamWriter,
52+
StringUtils.center(
53+
diff.isCompatible()
54+
? "API changes are backward compatible"
55+
: "API changes broke backward compatibility",
56+
LINE_LENGTH));
57+
safelyAppend(outputStreamWriter, System.lineSeparator());
58+
safelyAppend(outputStreamWriter, separator('-'));
59+
}
60+
try {
61+
outputStreamWriter.close();
62+
} catch (IOException e) {
63+
throw new RendererException(e);
5864
}
59-
return output.toString();
6065
}
6166

62-
private String ol_changed(List<ChangedOperation> operations) {
67+
private void ol_changed(
68+
List<ChangedOperation> operations, OutputStreamWriter outputStreamWriter) {
6369
if (null == operations || operations.isEmpty()) {
64-
return "";
70+
return;
6571
}
66-
StringBuilder sb = new StringBuilder();
67-
sb.append(title("What's Changed"));
72+
safelyAppend(outputStreamWriter, title("What's Changed"));
6873
for (ChangedOperation operation : operations) {
6974
String pathUrl = operation.getPathUrl();
7075
String method = operation.getHttpMethod().toString();
7176
String desc =
7277
Optional.ofNullable(operation.getSummary()).map(ChangedMetadata::getRight).orElse("");
7378

74-
StringBuilder ul_detail = new StringBuilder();
7579
if (result(operation.getParameters()).isDifferent()) {
76-
ul_detail
77-
.append(StringUtils.repeat(' ', 2))
78-
.append("Parameter:")
79-
.append(System.lineSeparator())
80-
.append(ul_param(operation.getParameters()));
80+
safelyAppend(outputStreamWriter, StringUtils.repeat(' ', 2));
81+
safelyAppend(outputStreamWriter, "Parameter:");
82+
safelyAppend(outputStreamWriter, System.lineSeparator());
83+
safelyAppend(outputStreamWriter, ul_param(operation.getParameters()));
8184
}
8285
if (operation.resultRequestBody().isDifferent()) {
83-
ul_detail
84-
.append(StringUtils.repeat(' ', 2))
85-
.append("Request:")
86-
.append(System.lineSeparator())
87-
.append(ul_content(operation.getRequestBody().getContent(), true));
86+
safelyAppend(outputStreamWriter, StringUtils.repeat(' ', 2));
87+
safelyAppend(outputStreamWriter, "Request:");
88+
safelyAppend(outputStreamWriter, System.lineSeparator());
89+
safelyAppend(outputStreamWriter, ul_content(operation.getRequestBody().getContent(), true));
8890
}
8991
if (operation.resultApiResponses().isDifferent()) {
90-
ul_detail
91-
.append(StringUtils.repeat(' ', 2))
92-
.append("Return Type:")
93-
.append(System.lineSeparator())
94-
.append(ul_response(operation.getApiResponses()));
92+
safelyAppend(outputStreamWriter, StringUtils.repeat(' ', 2));
93+
safelyAppend(outputStreamWriter, "Return Type:");
94+
safelyAppend(outputStreamWriter, System.lineSeparator());
95+
safelyAppend(outputStreamWriter, ul_response(operation.getApiResponses()));
9596
}
96-
sb.append(itemEndpoint(method, pathUrl, desc)).append(ul_detail);
97+
safelyAppend(outputStreamWriter, itemEndpoint(method, pathUrl, desc));
9798
}
98-
return sb.toString();
9999
}
100100

101101
private String ul_response(ChangedApiResponse changedApiResponse) {
@@ -279,7 +279,8 @@ private String li_changedParam(ChangedParameter changeParam) {
279279
}
280280
}
281281

282-
private String listEndpoints(List<Endpoint> endpoints, String title) {
282+
private String listEndpoints(
283+
List<Endpoint> endpoints, String title, OutputStreamWriter outputStreamWriter) {
283284
if (null == endpoints || endpoints.isEmpty()) {
284285
return "";
285286
}
@@ -317,8 +318,7 @@ public String title(String title, char ch) {
317318
separator(ch), little, StringUtils.center(title, LINE_LENGTH - 4), little, separator(ch));
318319
}
319320

320-
public StringBuilder separator(char ch) {
321-
StringBuilder sb = new StringBuilder();
322-
return sb.append(StringUtils.repeat(ch, LINE_LENGTH)).append(System.lineSeparator());
321+
public String separator(char ch) {
322+
return StringUtils.repeat(ch, LINE_LENGTH) + System.lineSeparator();
323323
}
324324
}

core/src/main/java/org/openapitools/openapidiff/core/output/HtmlRender.java

+21-4
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,20 @@
2626
import io.swagger.v3.oas.models.media.Schema;
2727
import io.swagger.v3.oas.models.parameters.Parameter;
2828
import io.swagger.v3.oas.models.responses.ApiResponse;
29+
import j2html.rendering.FlatHtml;
2930
import j2html.tags.ContainerTag;
3031
import j2html.tags.specialized.DivTag;
3132
import j2html.tags.specialized.HtmlTag;
3233
import j2html.tags.specialized.LiTag;
3334
import j2html.tags.specialized.OlTag;
3435
import j2html.tags.specialized.UlTag;
36+
import java.io.IOException;
37+
import java.io.OutputStreamWriter;
3538
import java.util.List;
3639
import java.util.Map;
3740
import java.util.Map.Entry;
3841
import java.util.Optional;
42+
import org.openapitools.openapidiff.core.exception.RendererException;
3943
import org.openapitools.openapidiff.core.model.ChangedApiResponse;
4044
import org.openapitools.openapidiff.core.model.ChangedContent;
4145
import org.openapitools.openapidiff.core.model.ChangedMediaType;
@@ -71,7 +75,7 @@ public HtmlRender(String title, String linkCss) {
7175
this.linkCss = linkCss;
7276
}
7377

74-
public String render(ChangedOpenApi diff) {
78+
public void render(ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) {
7579
this.diff = diff;
7680

7781
List<Endpoint> newEndpoints = diff.getNewEndpoints();
@@ -86,10 +90,16 @@ public String render(ChangedOpenApi diff) {
8690
List<ChangedOperation> changedOperations = diff.getChangedOperations();
8791
OlTag ol_changed = ol_changed(changedOperations);
8892

89-
return renderHtml(ol_newEndpoint, ol_missingEndpoint, ol_deprecatedEndpoint, ol_changed);
93+
renderHtml(
94+
ol_newEndpoint, ol_missingEndpoint, ol_deprecatedEndpoint, ol_changed, outputStreamWriter);
9095
}
9196

92-
public String renderHtml(OlTag ol_new, OlTag ol_miss, OlTag ol_deprec, OlTag ol_changed) {
97+
public void renderHtml(
98+
OlTag ol_new,
99+
OlTag ol_miss,
100+
OlTag ol_deprec,
101+
OlTag ol_changed,
102+
OutputStreamWriter outputStreamWriter) {
93103
HtmlTag html =
94104
html()
95105
.attr("lang", "en")
@@ -110,7 +120,14 @@ public String renderHtml(OlTag ol_new, OlTag ol_miss, OlTag ol_deprec, OlTag ol_
110120
div().with(h2("What's Deprecated"), hr(), ol_deprec),
111121
div().with(h2("What's Changed"), hr(), ol_changed))));
112122

113-
return document().render() + html.render();
123+
try {
124+
FlatHtml<OutputStreamWriter> flatHtml = FlatHtml.into(outputStreamWriter);
125+
document().render(flatHtml);
126+
html.render(flatHtml);
127+
outputStreamWriter.close();
128+
} catch (IOException e) {
129+
throw new RendererException("Problem rendering html document.", e);
130+
}
114131
}
115132

116133
private OlTag ol_newEndpoint(List<Endpoint> endpoints) {

core/src/main/java/org/openapitools/openapidiff/core/output/JsonRender.java

+7-13
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import com.fasterxml.jackson.core.JsonProcessingException;
55
import com.fasterxml.jackson.databind.ObjectMapper;
66
import java.io.IOException;
7-
import java.nio.file.Paths;
7+
import java.io.OutputStreamWriter;
8+
import org.openapitools.openapidiff.core.exception.RendererException;
89
import org.openapitools.openapidiff.core.model.ChangedOpenApi;
910

1011
public class JsonRender implements Render {
@@ -17,21 +18,14 @@ public JsonRender() {
1718
}
1819

1920
@Override
20-
public String render(ChangedOpenApi diff) {
21+
public void render(ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) {
2122
try {
22-
return objectMapper.writeValueAsString(diff);
23+
objectMapper.writeValue(outputStreamWriter, diff);
24+
outputStreamWriter.close();
2325
} catch (JsonProcessingException e) {
24-
throw new RuntimeException("Could not serialize diff as JSON", e);
25-
}
26-
}
27-
28-
public void renderToFile(ChangedOpenApi diff, String file) {
29-
try {
30-
objectMapper.writeValue(Paths.get(file).toFile(), diff);
31-
} catch (JsonProcessingException e) {
32-
throw new RuntimeException("Could not serialize diff as JSON", e);
26+
throw new RendererException("Could not serialize diff as JSON", e);
3327
} catch (IOException e) {
34-
throw new RuntimeException("Could not write to JSON file", e);
28+
throw new RendererException(e);
3529
}
3630
}
3731
}

0 commit comments

Comments
 (0)