View Javadoc
1   /*
2    * The MIT License (MIT)
3    *
4    * Copyright (c) 2013 - 2023, Tapio Rautonen
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy
7    * of this software and associated documentation files (the "Software"), to deal
8    * in the Software without restriction, including without limitation the rights
9    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in
14   * all copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22   * THE SOFTWARE.
23   */
24  package org.eluder.coveralls.maven.plugin.domain;
25  
26  import com.fasterxml.jackson.annotation.JsonIgnore;
27  import com.fasterxml.jackson.annotation.JsonProperty;
28  
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.List;
32  import java.util.ListIterator;
33  import java.util.Objects;
34  import java.util.regex.Matcher;
35  import java.util.regex.Pattern;
36  
37  public final class Source implements JsonObject {
38  
39      private static final long serialVersionUID = 1L;
40  
41      private static final Pattern NEWLINE = Pattern.compile("\r\n|\r|\n");
42      //private static final String CLASSIFIER_SEPARATOR = "#";
43  
44      String name;
45      String digest;
46      Integer[] coverage;
47      List<Branch> branches;
48      String classifier;
49  
50      public Source(final String name, final String source, final String digest) {
51          this(name, getLines(source), digest, null);
52      }
53  
54      public Source(final String name, final int lines, final String digest, final String classifier) {
55          this.name = name;
56          this.digest = digest;
57          this.coverage = new Integer[lines];
58          this.classifier = classifier;
59          this.branches = new ArrayList<>();
60      }
61  
62      @JsonIgnore
63      public String getName() {
64          return name;
65      }
66  
67      @JsonProperty("name")
68      public String getFullName() {
69          return name;
70  
71          // #45: cannot use identifier due to unfetchable source files
72          //return (classifier == null ? name : name + CLASSIFIER_SEPARATOR + classifier);
73      }
74  
75      @JsonProperty("source_digest")
76      public String getDigest() {
77          return digest;
78      }
79  
80      @JsonProperty("coverage")
81      public Integer[] getCoverage() {
82          return coverage;
83      }
84  
85      @JsonProperty("branches")
86      public Integer[] getBranches() {
87          final List<Integer> branchesRaw = new ArrayList<>(branches.size() * 4);
88          for (final Branch b : branches) {
89              branchesRaw.add(b.getLineNumber());
90              branchesRaw.add(b.getBlockNumber());
91              branchesRaw.add(b.getBranchNumber());
92              branchesRaw.add(b.getHits());
93          }
94          return branchesRaw.toArray(new Integer[branchesRaw.size()]);
95      }
96  
97      public List<Branch> getBranchesList() {
98          return Collections.unmodifiableList(branches);
99      }
100 
101     @JsonIgnore
102     public String getClassifier() {
103         return classifier;
104     }
105 
106     public void setClassifier(final String classifier) {
107         this.classifier = classifier;
108     }
109 
110     private void checkLineRange(final int lineNumber) {
111         int index = lineNumber - 1;
112         if (index >= this.coverage.length) {
113             throw new IllegalArgumentException("Line number " + lineNumber + " is greater than the source file " + name + " size");
114         }
115     }
116 
117     public void addCoverage(final int lineNumber, final Integer coverage) {
118         checkLineRange(lineNumber);
119         this.coverage[lineNumber - 1] = coverage;
120     }
121 
122     public void addBranchCoverage(final int lineNumber,
123                                   final int blockNumber,
124                                   final int branchNumber,
125                                   final int hits) {
126         addBranchCoverage(false, lineNumber, blockNumber, branchNumber, hits);
127     }
128 
129     private void addBranchCoverage(final boolean merge,
130                                    final int lineNumber,
131                                    final int blockNumber,
132                                    final int branchNumber,
133                                    final int hits) {
134         checkLineRange(lineNumber);
135         int hitSum = hits;
136         final ListIterator<Branch> it = this.branches.listIterator();
137         while (it.hasNext()) {
138             final Branch b = it.next();
139             if (b.getLineNumber() == lineNumber &&
140                 b.getBlockNumber() == blockNumber &&
141                 b.getBranchNumber() == branchNumber) {
142                     it.remove();
143                     if (merge) {
144                         hitSum += b.getHits();
145                     }
146                 }
147         }
148         this.branches.add(new Branch(lineNumber, blockNumber, branchNumber, hitSum));
149     }
150 
151     public Source merge(final Source source) {
152         Source copy = new Source(this.name, this.coverage.length, this.digest, this.classifier);
153         System.arraycopy(this.coverage, 0, copy.coverage, 0, this.coverage.length);
154         copy.branches.addAll(this.branches);
155         if (copy.equals(source)) {
156             for (int i = 0; i < copy.coverage.length; i++) {
157                 if (source.coverage[i] != null) {
158                     int base = copy.coverage[i] != null ? copy.coverage[i] : 0;
159                     copy.coverage[i] = base + source.coverage[i];
160                 }
161             }
162             for (final Branch b : source.branches) {
163                 copy.addBranchCoverage(true,
164                         b.getLineNumber(),
165                         b.getBlockNumber(),
166                         b.getBranchNumber(),
167                         b.getHits());
168             }
169         }
170         return copy;
171     }
172 
173     @Override
174     public boolean equals(final Object obj) {
175         if (!(obj instanceof Source)) {
176             return false;
177         }
178         Source other = (Source) obj;
179         return (Objects.equals(this.name, other.name) &&
180                 Objects.equals(this.digest, other.digest) &&
181                this.coverage.length == other.coverage.length);
182     }
183 
184     @Override
185     public int hashCode() {
186         return Objects.hash(this.name, this.digest, this.coverage.length);
187     }
188 
189     private static int getLines(final String source) {
190         int lines = 1;
191         Matcher matcher = NEWLINE.matcher(source);
192         while (matcher.find()) {
193             lines++;
194         }
195         return lines;
196     }
197 }