1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package com.jcabi.github;
31
32 import com.jcabi.aspects.Immutable;
33 import com.jcabi.aspects.Loggable;
34 import java.io.IOException;
35 import java.net.URL;
36 import java.text.ParseException;
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.Date;
40 import javax.json.Json;
41 import javax.json.JsonArray;
42 import javax.json.JsonObject;
43 import lombok.EqualsAndHashCode;
44 import lombok.ToString;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 @Immutable
69 @SuppressWarnings
70 (
71 {
72 "PMD.TooManyMethods", "PMD.GodClass", "PMD.ExcessivePublicCount"
73 }
74 )
75 public interface Issue extends Comparable<Issue>, JsonReadable, JsonPatchable {
76
77
78
79
80 String OPEN_STATE = "open";
81
82
83
84
85 String CLOSED_STATE = "closed";
86
87
88
89
90
91 Repo repo();
92
93
94
95
96
97 int number();
98
99
100
101
102
103
104 Comments comments();
105
106
107
108
109
110
111 IssueLabels labels();
112
113
114
115
116
117
118
119 Iterable<Event> events() throws IOException;
120
121
122
123
124
125
126 boolean exists() throws IOException;
127
128
129
130
131
132
133 void react(Reaction reaction) throws IOException;
134
135
136
137
138
139 Iterable<Reaction> reactions();
140
141
142
143
144
145 void lock(String reason);
146
147
148
149
150 void unlock();
151
152
153
154
155
156 boolean isLocked();
157
158
159
160
161 @Immutable
162 @ToString
163 @Loggable(Loggable.DEBUG)
164 @EqualsAndHashCode(of = {"issue", "jsn"})
165 final class Smart implements Issue {
166
167
168
169 private final transient Issue issue;
170
171
172
173 private final transient SmartJson jsn;
174
175
176
177
178 public Smart(final Issue iss) {
179 this.issue = iss;
180 this.jsn = new SmartJson(iss);
181 }
182
183
184
185
186
187 public User author() throws IOException {
188 return this.issue.repo().github().users().get(
189 this.jsn.value(
190 "user", JsonObject.class
191 ).getString("login")
192 );
193 }
194
195
196
197
198
199 public boolean isOpen() throws IOException {
200 return Issue.OPEN_STATE.equals(this.state());
201 }
202
203
204
205
206 public void open() throws IOException {
207 this.state(Issue.OPEN_STATE);
208 }
209
210
211
212
213 public void close() throws IOException {
214 this.state(Issue.CLOSED_STATE);
215 }
216
217
218
219
220
221 public String state() throws IOException {
222 return this.jsn.text("state");
223 }
224
225
226
227
228
229 public void state(final String state) throws IOException {
230 this.issue.patch(
231 Json.createObjectBuilder().add("state", state).build()
232 );
233 }
234
235
236
237
238
239 public String title() throws IOException {
240 return this.jsn.text("title");
241 }
242
243
244
245
246
247 public void title(final String text) throws IOException {
248 this.issue.patch(
249 Json.createObjectBuilder().add("title", text).build()
250 );
251 }
252
253
254
255
256
257 public String body() throws IOException {
258 return this.jsn.text("body");
259 }
260
261
262
263
264
265 public void body(final String text) throws IOException {
266 this.issue.patch(
267 Json.createObjectBuilder().add("body", text).build()
268 );
269 }
270
271
272
273
274
275
276 public boolean hasBody() throws IOException {
277 return this.jsn.hasNotNull("body");
278 }
279
280
281
282
283
284 public boolean hasAssignee() throws IOException {
285 return this.jsn.hasNotNull("assignee");
286 }
287
288
289
290
291
292 public User assignee() throws IOException {
293 if (!this.hasAssignee()) {
294 throw new IllegalArgumentException(
295 String.format(
296 "issue #%d doesn't have an assignee, use hasAssignee()",
297 this.number()
298 )
299 );
300 }
301 return this.issue.repo().github().users().get(
302 this.jsn.value(
303 "assignee", JsonObject.class
304 ).getString("login")
305 );
306 }
307
308
309
310
311
312 public void assign(final String login) throws IOException {
313 this.issue.patch(
314 Json.createObjectBuilder().add("assignee", login).build()
315 );
316 }
317
318
319
320
321
322 public URL url() throws IOException {
323 return new URL(this.jsn.text("url"));
324 }
325
326
327
328
329
330 public URL htmlUrl() throws IOException {
331 return new URL(this.jsn.text("html_url"));
332 }
333
334
335
336
337
338 public Date createdAt() throws IOException {
339 try {
340 return new Github.Time(
341 this.jsn.text("created_at")
342 ).date();
343 } catch (final ParseException ex) {
344 throw new IllegalStateException(ex);
345 }
346 }
347
348
349
350
351
352
353 public Date closedAt() throws IOException {
354 try {
355 return new Github.Time(
356 this.jsn.text("closed_at")
357 ).date();
358 } catch (final ParseException ex) {
359 throw new IllegalStateException(ex);
360 }
361 }
362
363
364
365
366
367 public Date updatedAt() throws IOException {
368 try {
369 return new Github.Time(
370 this.jsn.text("updated_at")
371 ).date();
372 } catch (final ParseException ex) {
373 throw new IllegalStateException(ex);
374 }
375 }
376
377
378
379
380
381 public boolean isPull() throws IOException {
382 return this.json().containsKey("pull_request")
383 && !this.jsn.value("pull_request", JsonObject.class)
384 .isNull("html_url");
385 }
386
387
388
389
390
391
392 public Pull pull() throws IOException {
393 final String url = this.jsn.value(
394 "pull_request", JsonObject.class
395 ).getString("html_url");
396 return this.issue.repo().pulls().get(
397 Integer.parseInt(url.substring(url.lastIndexOf('/') + 1))
398 );
399 }
400
401
402
403
404
405
406
407
408 public Event latestEvent(final String type) throws IOException {
409 final Iterable<Event.Smart> events = new Smarts<>(
410 this.issue.events()
411 );
412 Event found = null;
413 for (final Event.Smart event : events) {
414 if (event.type().equals(type) && (found == null
415 || found.number() < event.number())) {
416 found = event;
417 }
418 }
419 if (found == null) {
420 throw new IllegalStateException(
421 String.format(
422 "event of type '%s' not found in issue #%d",
423 type, this.issue.number()
424 )
425 );
426 }
427 return found;
428 }
429
430
431
432
433
434
435 public IssueLabels roLabels() throws IOException {
436 final Collection<JsonObject> array =
437 this.jsn.value("labels", JsonArray.class)
438 .getValuesAs(JsonObject.class);
439 final Collection<Label> labels = new ArrayList<>(array.size());
440 for (final JsonObject obj : array) {
441 labels.add(
442 new Label.Unmodified(
443 this.repo(),
444 obj.toString()
445 )
446 );
447 }
448
449 return new IssueLabels() {
450 @Override
451 public Issue issue() {
452 return Issue.Smart.this;
453 }
454 @Override
455 public void add(
456 final Iterable<String> labels) {
457 throw new UnsupportedOperationException(
458 "The issue is read-only."
459 );
460 }
461 @Override
462 public void replace(
463 final Iterable<String> labels) {
464 throw new UnsupportedOperationException(
465 "The issue is read-only."
466 );
467 }
468 @Override
469 public Iterable<Label> iterate() {
470 return labels;
471 }
472 @Override
473 public void remove(
474 final String name) {
475 throw new UnsupportedOperationException(
476 "This issue is read-only."
477 );
478 }
479 @Override
480 public void clear() {
481 throw new UnsupportedOperationException(
482 "This issue is read-only."
483 );
484 }
485 };
486 }
487
488
489
490
491
492 public boolean hasMilestone() throws IOException {
493 return this.jsn.hasNotNull("milestone");
494 }
495
496
497
498
499
500 public Milestone milestone() throws IOException {
501 return this.repo().milestones().get(
502 this.jsn.value("milestone", JsonObject.class)
503 .getInt("number")
504 );
505 }
506
507
508
509
510
511 public void milestone(final Milestone milestone) throws IOException {
512 this.patch(
513 Json.createObjectBuilder().add(
514 "milestone", milestone.number()
515 ).build()
516 );
517 }
518 @Override
519 public Repo repo() {
520 return this.issue.repo();
521 }
522 @Override
523 public int number() {
524 return this.issue.number();
525 }
526 @Override
527 public Comments comments() {
528 return this.issue.comments();
529 }
530 @Override
531 public IssueLabels labels() {
532 return this.issue.labels();
533 }
534 @Override
535 public Iterable<Event> events() throws IOException {
536 return this.issue.events();
537 }
538 @Override
539 public JsonObject json() throws IOException {
540 return this.issue.json();
541 }
542 @Override
543 public void patch(final JsonObject json) throws IOException {
544 this.issue.patch(json);
545 }
546 @Override
547 public int compareTo(final Issue obj) {
548 return this.issue.compareTo(obj);
549 }
550 @Override
551 public boolean exists() throws IOException {
552 return new Existence(this.issue).check();
553 }
554
555 @Override
556 public void react(final Reaction reaction) {
557 throw new UnsupportedOperationException("react() not implemented");
558 }
559
560 @Override
561 public Collection<Reaction> reactions() {
562 throw new UnsupportedOperationException(
563 "reactions() not implemented"
564 );
565 }
566
567 @Override
568 public void lock(final String reason) {
569 throw new UnsupportedOperationException("lock not implemented");
570 }
571
572 @Override
573 public void unlock() {
574 throw new UnsupportedOperationException("unlock not implemented");
575 }
576
577 @Override
578 public boolean isLocked() {
579 throw new UnsupportedOperationException("isLocked not implemented");
580 }
581 }
582
583 }