View Javadoc
1   /*
2    * The MIT License (MIT)
3    *
4    * Copyright (c) 2013-2026 The Coveralls Maven Plugin Project Contributors:
5    *     https://github.com/hazendaz/coveralls-maven-plugin/graphs/contributors
6    *
7    * Permission is hereby granted, free of charge, to any person obtaining a copy
8    * of this software and associated documentation files (the "Software"), to deal
9    * in the Software without restriction, including without limitation the rights
10   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11   * copies of the Software, and to permit persons to whom the Software is
12   * furnished to do so, subject to the following conditions:
13   *
14   * The above copyright notice and this permission notice shall be included in
15   * all copies or substantial portions of the Software.
16   *
17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23   * THE SOFTWARE.
24   */
25  package org.eluder.coveralls.maven.plugin.json;
26  
27  import com.fasterxml.jackson.core.JsonEncoding;
28  import com.fasterxml.jackson.core.JsonGenerator;
29  import com.fasterxml.jackson.core.JsonProcessingException;
30  import com.fasterxml.jackson.databind.MappingJsonFactory;
31  
32  import java.io.Closeable;
33  import java.io.File;
34  import java.io.IOException;
35  import java.time.Instant;
36  import java.time.ZoneId;
37  import java.time.format.DateTimeFormatter;
38  import java.util.Map.Entry;
39  import java.util.Properties;
40  
41  import org.eluder.coveralls.maven.plugin.ProcessingException;
42  import org.eluder.coveralls.maven.plugin.domain.Job;
43  import org.eluder.coveralls.maven.plugin.domain.Source;
44  import org.eluder.coveralls.maven.plugin.source.SourceCallback;
45  
46  /**
47   * The Class JsonWriter.
48   */
49  public class JsonWriter implements SourceCallback, Closeable {
50  
51      /** The Constant TIMESTAMP_FORMAT. */
52      protected static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss Z";
53  
54      /** The job. */
55      private final Job job;
56  
57      /** The coveralls file. */
58      private final File coverallsFile;
59  
60      /** The generator. */
61      private final JsonGenerator generator;
62  
63      /**
64       * Instantiates a new json writer.
65       *
66       * @param job
67       *            the job
68       * @param coverallsFile
69       *            the coveralls file
70       *
71       * @throws IOException
72       *             Signals that an I/O exception has occurred.
73       */
74      public JsonWriter(final Job job, final File coverallsFile) throws IOException {
75          final var directory = coverallsFile.getParentFile();
76          if (!directory.exists()) {
77              directory.mkdirs();
78          }
79          this.job = job;
80          this.coverallsFile = coverallsFile;
81          this.generator = new MappingJsonFactory().createGenerator(coverallsFile, JsonEncoding.UTF8);
82      }
83  
84      /**
85       * Gets the job.
86       *
87       * @return the job
88       */
89      public final Job getJob() {
90          return this.job;
91      }
92  
93      /**
94       * Gets the coveralls file.
95       *
96       * @return the coveralls file
97       */
98      public final File getCoverallsFile() {
99          return this.coverallsFile;
100     }
101 
102     @Override
103     public void onBegin() throws ProcessingException, IOException {
104         try {
105             this.generator.writeStartObject();
106             this.writeOptionalString("repo_token", this.job.getRepoToken());
107             this.writeOptionalString("service_name", this.job.getServiceName());
108             this.writeOptionalString("service_job_id", this.job.getServiceJobId());
109             this.writeOptionalString("service_number", this.job.getServiceBuildNumber());
110             this.writeOptionalString("service_build_url", this.job.getServiceBuildUrl());
111             this.writeOptionalString("service_branch", this.job.getBranch());
112             this.writeOptionalString("service_pull_request", this.job.getPullRequest());
113             this.writeOptionalBoolean("parallel", this.job.isParallel());
114             this.writeOptionalTimestamp("run_at", this.job.getTimestamp());
115             this.writeOptionalEnvironment("environment", this.job.getServiceEnvironment());
116             this.writeOptionalObject("git", this.job.getGit());
117             this.generator.writeArrayFieldStart("source_files");
118         } catch (final JsonProcessingException e) {
119             throw new ProcessingException(e);
120         }
121     }
122 
123     @Override
124     public void onSource(final Source source) throws ProcessingException, IOException {
125         try {
126             this.generator.writeObject(source);
127         } catch (final JsonProcessingException e) {
128             throw new ProcessingException(e);
129         }
130     }
131 
132     @Override
133     public void onComplete() throws ProcessingException, IOException {
134         try {
135             this.generator.writeEndArray();
136             this.generator.writeEndObject();
137         } catch (final JsonProcessingException e) {
138             throw new ProcessingException(e);
139         }
140     }
141 
142     @Override
143     public void close() throws IOException {
144         this.generator.close();
145     }
146 
147     /**
148      * Write optional string.
149      *
150      * @param field
151      *            the field
152      * @param value
153      *            the value
154      *
155      * @throws IOException
156      *             Signals that an I/O exception has occurred.
157      */
158     private void writeOptionalString(final String field, final String value) throws IOException {
159         if (value != null && !value.isBlank()) {
160             this.generator.writeStringField(field, value);
161         }
162     }
163 
164     /**
165      * Write optional boolean.
166      *
167      * @param field
168      *            the field
169      * @param value
170      *            the value
171      *
172      * @throws IOException
173      *             Signals that an I/O exception has occurred.
174      */
175     private void writeOptionalBoolean(final String field, final boolean value) throws IOException {
176         if (value) {
177             this.generator.writeBooleanField(field, value);
178         }
179     }
180 
181     /**
182      * Write optional object.
183      *
184      * @param field
185      *            the field
186      * @param value
187      *            the value
188      *
189      * @throws IOException
190      *             Signals that an I/O exception has occurred.
191      */
192     private void writeOptionalObject(final String field, final Object value) throws IOException {
193         if (value != null) {
194             this.generator.writeObjectField(field, value);
195         }
196     }
197 
198     /**
199      * Write optional timestamp.
200      *
201      * @param field
202      *            the field
203      * @param value
204      *            the value
205      *
206      * @throws IOException
207      *             Signals that an I/O exception has occurred.
208      */
209     private void writeOptionalTimestamp(final String field, final Long value) throws IOException {
210         if (value != null) {
211             final var formatter = DateTimeFormatter.ofPattern(JsonWriter.TIMESTAMP_FORMAT)
212                     .withZone(ZoneId.systemDefault());
213             this.writeOptionalString(field, formatter.format(Instant.ofEpochMilli(value)));
214         }
215     }
216 
217     /**
218      * Write optional environment.
219      *
220      * @param field
221      *            the field
222      * @param properties
223      *            the properties
224      *
225      * @throws IOException
226      *             Signals that an I/O exception has occurred.
227      */
228     private void writeOptionalEnvironment(final String field, final Properties properties) throws IOException {
229         if (properties != null) {
230             this.generator.writeObjectFieldStart(field);
231             for (final Entry<Object, Object> property : properties.entrySet()) {
232                 this.writeOptionalString(property.getKey().toString(), property.getValue().toString());
233             }
234             this.generator.writeEndObject();
235         }
236     }
237 }