1 /*
  2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package org.openjdk.skara.test;
 24 
 25 import org.openjdk.skara.host.*;
 26 import org.openjdk.skara.vcs.Hash;
 27 
 28 import java.io.*;
 29 import java.net.*;
 30 import java.time.ZonedDateTime;
 31 import java.util.*;
 32 import java.util.function.Function;
 33 import java.util.stream.Collectors;
 34 
 35 public class TestPullRequest implements PullRequest {
 36     private final TestHostedRepository repository;
 37     private final String id;
 38     private final HostUserDetails author;
 39     private final HostUserDetails user;
 40     private final String targetRef;
 41     private final String sourceRef;
 42     private final String title;
 43     private final List<String> body;
 44     private final PullRequestData data;
 45 
 46     private static class PullRequestData {
 47         private Hash headHash;
 48         PullRequest.State state = PullRequest.State.OPEN;
 49         String body = "";
 50         final List<Comment> comments = new ArrayList<>();
 51         final List<ReviewComment> reviewComments = new ArrayList<>();
 52         final Set<Check> checks = new HashSet<>();
 53         final Set<String> labels = new HashSet<>();
 54         final List<Review> reviews = new ArrayList<>();
 55         ZonedDateTime created = ZonedDateTime.now();
 56         ZonedDateTime lastUpdate = created;
 57     }
 58 
 59     private TestPullRequest(TestHostedRepository repository, String id, HostUserDetails author, HostUserDetails user, String targetRef, String sourceRef, String title, List<String> body, PullRequestData data) {
 60         this.repository = repository;
 61         this.id = id;
 62         this.author = author;
 63         this.user = user;
 64         this.targetRef = targetRef;
 65         this.sourceRef = sourceRef;
 66         this.title = title;
 67         this.body = body;
 68         this.data = data;
 69 
 70         try {
 71             var headHash = repository.localRepository().resolve(sourceRef).orElseThrow();
 72             if (!headHash.equals(data.headHash)) {
 73                 data.headHash = headHash;
 74                 data.lastUpdate = ZonedDateTime.now();
 75             }
 76         } catch (IOException e) {
 77             throw new UncheckedIOException(e);
 78         }
 79     }
 80 
 81     static TestPullRequest createNew(TestHostedRepository repository, String id, String targetRef, String sourceRef, String title, List<String> body) {
 82         var data = new PullRequestData();
 83         data.body = String.join("\n", body);
 84         var pr = new TestPullRequest(repository, id, repository.host().getCurrentUserDetails(), repository.host().getCurrentUserDetails(), targetRef, sourceRef, title, body, data);
 85         return pr;
 86     }
 87 
 88     static TestPullRequest createFrom(TestHostedRepository repository, TestPullRequest other) {
 89         var pr = new TestPullRequest(repository, other.id, other.author, repository.host().getCurrentUserDetails(), other.targetRef, other.sourceRef, other.title, other.body, other.data);
 90         return pr;
 91     }
 92 
 93     @Override
 94     public HostedRepository repository() {
 95         return repository;
 96     }
 97 
 98     @Override
 99     public String getId() {
100         return id;
101     }
102 
103     @Override
104     public HostUserDetails getAuthor() {
105         return author;
106     }
107 
108     @Override
109     public List<Review> getReviews() {
110         return new ArrayList<>(data.reviews);
111     }
112 
113     @Override
114     public void addReview(Review.Verdict verdict, String body) {
115         try {
116             var review = new Review(repository.host().getCurrentUserDetails(),
117                                     verdict, repository.localRepository().resolve(sourceRef).orElseThrow(),
118                                     data.reviews.size(),
119                                     body);
120 
121             data.reviews.add(review);
122             data.lastUpdate = ZonedDateTime.now();
123 
124         } catch (IOException e) {
125             throw new UncheckedIOException(e);
126         }
127     }
128 
129     @Override
130     public ReviewComment addReviewComment(Hash base, Hash hash, String path, int line, String body) {
131         var comment = new ReviewComment(null, String.valueOf(data.reviewComments.size()), hash, path, line, String.valueOf(data.reviewComments.size()), body, user, ZonedDateTime.now(), ZonedDateTime.now());
132         data.reviewComments.add(comment);
133         data.lastUpdate = ZonedDateTime.now();
134         return comment;
135     }
136 
137     @Override
138     public ReviewComment addReviewCommentReply(ReviewComment parent, String body) {
139         if (parent.parent().isPresent()) {
140             throw new RuntimeException("Can only reply to top-level review comments");
141         }
142         var comment = new ReviewComment(parent, parent.threadId(), parent.hash(), parent.path(), parent.line(), String.valueOf(data.reviewComments.size()), body, user, ZonedDateTime.now(), ZonedDateTime.now());
143         data.reviewComments.add(comment);
144         data.lastUpdate = ZonedDateTime.now();
145         return comment;
146     }
147 
148     @Override
149     public List<ReviewComment> getReviewComments() {
150         return new ArrayList<>(data.reviewComments);
151     }
152 
153     @Override
154     public Hash getHeadHash() {
155         return data.headHash;
156     }
157 
158     @Override
159     public String getSourceRef() {
160         return sourceRef;
161     }
162 
163     @Override
164     public String getTargetRef() {
165         return targetRef;
166     }
167 
168     @Override
169     public Hash getTargetHash() {
170         return repository.getBranchHash(targetRef);
171     }
172 
173     @Override
174     public String getTitle() {
175         return title;
176     }
177 
178     @Override
179     public String getBody() {
180         return data.body;
181     }
182 
183     @Override
184     public void setBody(String body) {
185         data.body = body;
186         data.lastUpdate = ZonedDateTime.now();
187     }
188 
189     @Override
190     public List<Comment> getComments() {
191         return new ArrayList<>(data.comments);
192     }
193 
194     @Override
195     public Comment addComment(String body) {
196         var comment = new Comment(String.valueOf(data.comments.size()),
197                                   body,
198                                   user,
199                                   ZonedDateTime.now(),
200                                   ZonedDateTime.now());
201         data.comments.add(comment);
202         data.lastUpdate = ZonedDateTime.now();
203         return comment;
204     }
205 
206     @Override
207     public Comment updateComment(String id, String body) {
208         var originalComment = data.comments.get(Integer.parseInt(id));
209         var comment = new Comment(originalComment.id(),
210                                   body,
211                                   originalComment.author(),
212                                   originalComment.createdAt(),
213                                   ZonedDateTime.now());
214         data.comments.remove(Integer.parseInt(id));
215         data.comments.add(Integer.parseInt(id), comment);
216         data.lastUpdate = ZonedDateTime.now();
217         return comment;
218     }
219 
220     @Override
221     public ZonedDateTime getCreated() {
222         return data.created;
223     }
224 
225     @Override
226     public ZonedDateTime getUpdated() {
227         return data.lastUpdate;
228     }
229 
230     @Override
231     public Map<String, Check> getChecks(Hash hash) {
232         return data.checks.stream()
233                 .filter(check -> check.hash().equals(hash))
234                 .collect(Collectors.toMap(Check::name, Function.identity()));
235     }
236 
237     @Override
238     public void createCheck(Check check) {
239         var existing = data.checks.stream()
240                                   .filter(c -> check.name().equals(check.name()))
241                                   .findAny();
242         existing.ifPresent(data.checks::remove);
243         data.checks.add(check);
244         data.lastUpdate = ZonedDateTime.now();
245     }
246 
247     @Override
248     public void updateCheck(Check updated) {
249         var existing = data.checks.stream()
250                 .filter(check -> check.name().equals(updated.name()))
251                 .findAny()
252                 .orElseThrow();
253 
254         data.checks.remove(existing);
255         data.checks.add(updated);
256         data.lastUpdate = ZonedDateTime.now();
257     }
258 
259     @Override
260     public void setState(State state) {
261         data.state = state;
262         data.lastUpdate = ZonedDateTime.now();
263     }
264 
265     boolean isOpen() {
266         return data.state.equals(PullRequest.State.OPEN);
267     }
268 
269     @Override
270     public void addLabel(String label) {
271         data.labels.add(label);
272         data.lastUpdate = ZonedDateTime.now();
273     }
274 
275     @Override
276     public void removeLabel(String label) {
277         data.labels.remove(label);
278         data.lastUpdate = ZonedDateTime.now();
279     }
280 
281     @Override
282     public List<String> getLabels() {
283         return new ArrayList<>(data.labels);
284     }
285 
286     @Override
287     public URI getWebUrl() {
288         try {
289             return new URI(repository.getUrl().toString() + "/pr/" + getId());
290         } catch (URISyntaxException e) {
291             throw new RuntimeException(e);
292         }
293     }
294 
295     @Override
296     public List<HostUserDetails> getAssignees() {
297         throw new RuntimeException("not implemented yet");
298     }
299 
300     @Override
301     public void setAssignees(List<HostUserDetails> assignees) {
302         throw new RuntimeException("not implemented yet");
303     }
304 }