Coverage Report - com.jcabi.github.Issue
 
Classes in this File Line Coverage Branch Coverage Complexity
Issue
N/A
N/A
1.479
Issue$Smart
56%
60/106
18%
7/38
1.479
Issue$Smart$1
42%
3/7
N/A
1.479
Issue$Smart$AjcClosure1
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure11
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure13
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure15
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure17
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure19
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure21
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure23
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure25
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure27
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure29
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure3
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure31
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure33
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure35
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure37
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure39
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure41
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure43
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure45
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure47
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure49
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure5
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure51
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure53
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure55
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure57
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure59
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure61
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure63
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure65
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure67
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure69
0%
0/1
N/A
1.479
Issue$Smart$AjcClosure7
100%
1/1
N/A
1.479
Issue$Smart$AjcClosure9
100%
1/1
N/A
1.479
 
 1  2
 /**
 2  
  * Copyright (c) 2013-2017, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 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  
  * Github issue.
 48  
  *
 49  
  * <p>Use a supplementary "smart" decorator to get other properties
 50  
  * from an issue, for example:
 51  
  *
 52  
  * <pre> Issue.Smart issue = new Issue.Smart(origin);
 53  
  * if (issue.isOpen()) {
 54  
  *   issue.close();
 55  
  * }</pre>
 56  
  *
 57  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 58  
  * @version $Id: c7181ce632dbfefb9c41eda83ac5caaf673d6215 $
 59  
  * @since 0.1
 60  
  * @see <a href="http://developer.github.com/v3/issues/">Issues API</a>
 61  
  * @checkstyle MultipleStringLiterals (500 lines)
 62  
  */
 63  
 @Immutable
 64  
 @SuppressWarnings
 65  
     (
 66  
         {
 67  
             "PMD.TooManyMethods", "PMD.GodClass", "PMD.ExcessivePublicCount"
 68  
         }
 69  
     )
 70  
 public interface Issue extends Comparable<Issue>, JsonReadable, JsonPatchable {
 71  
 
 72  
     /**
 73  
      * Issue state.
 74  
      */
 75  
     String OPEN_STATE = "open";
 76  
 
 77  
     /**
 78  
      * Issue state.
 79  
      */
 80  
     String CLOSED_STATE = "closed";
 81  
 
 82  
     /**
 83  
      * Repository we're in.
 84  
      * @return Repo
 85  
      */
 86  
     Repo repo();
 87  
 
 88  
     /**
 89  
      * Get its number.
 90  
      * @return Issue number
 91  
      */
 92  
     int number();
 93  
 
 94  
     /**
 95  
      * Get all comments of the issue.
 96  
      * @return Comments
 97  
      * @see <a href="http://developer.github.com/v3/issues/comments/">Issue Comments API</a>
 98  
      */
 99  
     Comments comments();
 100  
 
 101  
     /**
 102  
      * Get all labels of the issue.
 103  
      * @return Labels
 104  
      * @see <a href="http://developer.github.com/v3/issues/labels/">Labels API</a>
 105  
      */
 106  
     IssueLabels labels();
 107  
 
 108  
     /**
 109  
      * Get all events of the issue.
 110  
      * @return Events
 111  
      * @throws IOException If there is any I/O problem
 112  
      * @see <a href="http://developer.github.com/v3/issues/events/#list-events-for-an-issue">List Events for an Issue</a>
 113  
      */
 114  
     Iterable<Event> events() throws IOException;
 115  
 
 116  
     /**
 117  
      * Does this issue exist in Github?
 118  
      * @return TRUE if this issue exists
 119  
      * @throws IOException If there is any I/O problem
 120  
      */
 121  
     boolean exists() throws IOException;
 122  
 
 123  
     /**
 124  
      * Smart Issue with extra features.
 125  
      */
 126  0
     @Immutable
 127  0
     @ToString
 128  
     @Loggable(Loggable.DEBUG)
 129  0
     @EqualsAndHashCode(of = {"issue", "jsn"})
 130  
     final class Smart implements Issue {
 131  
         /**
 132  
          * Encapsulated issue.
 133  
          */
 134  
         private final transient Issue issue;
 135  
         /**
 136  
          * SmartJson object for convenient JSON parsing.
 137  
          */
 138  
         private final transient SmartJson jsn;
 139  
         /**
 140  
          * Public ctor.
 141  
          * @param iss Issue
 142  
          */
 143  33
         public Smart(final Issue iss) {
 144  33
             this.issue = iss;
 145  33
             this.jsn = new SmartJson(iss);
 146  33
         }
 147  
         /**
 148  
          * Get its author.
 149  
          * @return Author of issue (who submitted it)
 150  
          * @throws IOException If there is any I/O problem
 151  
          */
 152  
         public User author() throws IOException {
 153  9
             return this.issue.repo().github().users().get(
 154  3
                 this.jsn.value(
 155  
                     "user", JsonObject.class
 156  3
                 ).getString("login")
 157  
             );
 158  
         }
 159  
         /**
 160  
          * Is it open?
 161  
          * @return TRUE if it's open
 162  
          * @throws IOException If there is any I/O problem
 163  
          */
 164  
         public boolean isOpen() throws IOException {
 165  32
             return Issue.OPEN_STATE.equals(this.state());
 166  
         }
 167  
         /**
 168  
          * Open it (make sure it's open).
 169  
          * @throws IOException If there is any I/O problem
 170  
          */
 171  
         public void open() throws IOException {
 172  2
             this.state(Issue.OPEN_STATE);
 173  1
         }
 174  
         /**
 175  
          * Close it (make sure it's closed).
 176  
          * @throws IOException If there is any I/O problem
 177  
          */
 178  
         public void close() throws IOException {
 179  6
             this.state(Issue.CLOSED_STATE);
 180  3
         }
 181  
         /**
 182  
          * Get its state.
 183  
          * @return State of issue
 184  
          * @throws IOException If there is any I/O problem
 185  
          */
 186  
         public String state() throws IOException {
 187  32
             return this.jsn.text("state");
 188  
         }
 189  
         /**
 190  
          * Change its state.
 191  
          * @param state State of issue
 192  
          * @throws IOException If there is any I/O problem
 193  
          */
 194  
         public void state(final String state) throws IOException {
 195  12
             this.issue.patch(
 196  4
                 Json.createObjectBuilder().add("state", state).build()
 197  
             );
 198  4
         }
 199  
         /**
 200  
          * Get its title.
 201  
          * @return Title of issue
 202  
          * @throws IOException If there is any I/O problem
 203  
          */
 204  
         public String title() throws IOException {
 205  10
             return this.jsn.text("title");
 206  
         }
 207  
         /**
 208  
          * Change its title.
 209  
          * @param text Title of issue
 210  
          * @throws IOException If there is any I/O problem
 211  
          */
 212  
         public void title(final String text) throws IOException {
 213  3
             this.issue.patch(
 214  1
                 Json.createObjectBuilder().add("title", text).build()
 215  
             );
 216  1
         }
 217  
         /**
 218  
          * Get its body.
 219  
          * @return Body of issue
 220  
          * @throws IOException If there is any I/O problem
 221  
          */
 222  
         public String body() throws IOException {
 223  4
             return this.jsn.text("body");
 224  
         }
 225  
         /**
 226  
          * Change its body.
 227  
          * @param text Body of issue
 228  
          * @throws IOException If there is any I/O problem
 229  
          */
 230  
         public void body(final String text) throws IOException {
 231  3
             this.issue.patch(
 232  1
                 Json.createObjectBuilder().add("body", text).build()
 233  
             );
 234  1
         }
 235  
         /**
 236  
          * Has body?
 237  
          * @return TRUE if body exists
 238  
          * @throws IOException If there is any I/O problem
 239  
          * @since 0.22
 240  
          */
 241  
         public boolean hasBody() throws IOException {
 242  0
             return this.jsn.hasNotNull("body");
 243  
         }
 244  
         /**
 245  
          * Has assignee?
 246  
          * @return TRUE if assignee exists
 247  
          * @throws IOException If there is any I/O problem
 248  
          */
 249  
         public boolean hasAssignee() throws IOException {
 250  2
             return this.jsn.hasNotNull("assignee");
 251  
         }
 252  
         /**
 253  
          * Get its assignee.
 254  
          * @return User Assignee of issue
 255  
          * @throws IOException If there is any I/O problem
 256  
          */
 257  
         public User assignee() throws IOException {
 258  2
             if (!this.hasAssignee()) {
 259  0
                 throw new IllegalArgumentException(
 260  0
                     String.format(
 261  
                         "issue #%d doesn't have an assignee, use hasAssignee()",
 262  0
                         this.number()
 263  
                     )
 264  
                 );
 265  
             }
 266  2
             return this.issue.repo().github().users().get(
 267  1
                 this.jsn.value(
 268  
                     "assignee", JsonObject.class
 269  1
                 ).getString("login")
 270  
             );
 271  
         }
 272  
         /**
 273  
          * Assign this issue to another user.
 274  
          * @param login Login of the user to assign to
 275  
          * @throws IOException If there is any I/O problem
 276  
          */
 277  
         public void assign(final String login) throws IOException {
 278  3
             this.issue.patch(
 279  1
                 Json.createObjectBuilder().add("assignee", login).build()
 280  
             );
 281  1
         }
 282  
         /**
 283  
          * Get its URL.
 284  
          * @return URL of issue
 285  
          * @throws IOException If there is any I/O problem
 286  
          */
 287  
         public URL url() throws IOException {
 288  0
             return new URL(this.jsn.text("url"));
 289  
         }
 290  
         /**
 291  
          * Get its HTML URL.
 292  
          * @return URL of issue
 293  
          * @throws IOException If there is any I/O problem
 294  
          */
 295  
         public URL htmlUrl() throws IOException {
 296  2
             return new URL(this.jsn.text("html_url"));
 297  
         }
 298  
         /**
 299  
          * When this issue was created.
 300  
          * @return Date of creation
 301  
          * @throws IOException If there is any I/O problem
 302  
          */
 303  
         public Date createdAt() throws IOException {
 304  
             try {
 305  3
                 return new Github.Time(
 306  1
                     this.jsn.text("created_at")
 307  1
                 ).date();
 308  0
             } catch (final ParseException ex) {
 309  0
                 throw new IllegalStateException(ex);
 310  
             }
 311  
         }
 312  
         /**
 313  
          * When this issue was closed.
 314  
          * @return Date of creation
 315  
          * @throws IOException If there is any I/O problem
 316  
          * @since 0.34
 317  
          */
 318  
         public Date closedAt() throws IOException {
 319  
             try {
 320  0
                 return new Github.Time(
 321  0
                     this.jsn.text("closed_at")
 322  0
                 ).date();
 323  0
             } catch (final ParseException ex) {
 324  0
                 throw new IllegalStateException(ex);
 325  
             }
 326  
         }
 327  
         /**
 328  
          * When this issue was updated.
 329  
          * @return Date of update
 330  
          * @throws IOException If there is any I/O problem
 331  
          */
 332  
         public Date updatedAt() throws IOException {
 333  
             try {
 334  3
                 return new Github.Time(
 335  1
                     this.jsn.text("updated_at")
 336  1
                 ).date();
 337  0
             } catch (final ParseException ex) {
 338  0
                 throw new IllegalStateException(ex);
 339  
             }
 340  
         }
 341  
         /**
 342  
          * Is it a pull request?
 343  
          * @return TRUE if it is a pull request
 344  
          * @throws IOException If there is any I/O problem
 345  
          */
 346  
         public boolean isPull() throws IOException {
 347  15
             return this.json().containsKey("pull_request")
 348  4
                 && !this.jsn.value("pull_request", JsonObject.class)
 349  4
                 .isNull("html_url");
 350  
         }
 351  
 
 352  
         /**
 353  
          * Get pull request.
 354  
          * @return Pull request
 355  
          * @throws IOException If there is any I/O problem
 356  
          */
 357  
         public Pull pull() throws IOException {
 358  2
             final String url = this.jsn.value(
 359  
                 "pull_request", JsonObject.class
 360  1
             ).getString("html_url");
 361  2
             return this.issue.repo().pulls().get(
 362  1
                 Integer.parseInt(url.substring(url.lastIndexOf('/') + 1))
 363  
             );
 364  
         }
 365  
         /**
 366  
          * Get the latest event of a given type.
 367  
          * Throws {@link IllegalStateException} if the issue has no events of
 368  
          * the given type.
 369  
          * @param type Type of event
 370  
          * @return Latest event of the given type
 371  
          * @throws IOException If there is any I/O problem
 372  
          */
 373  
         public Event latestEvent(final String type) throws IOException {
 374  0
             final Iterable<Event.Smart> events = new Smarts<Event.Smart>(
 375  0
                 this.issue.events()
 376  
             );
 377  0
             Event found = null;
 378  0
             for (final Event.Smart event : events) {
 379  0
                 if (event.type().equals(type) && (found == null
 380  0
                     || found.number() < event.number())) {
 381  0
                     found = event;
 382  
                 }
 383  0
             }
 384  0
             if (found == null) {
 385  0
                 throw new IllegalStateException(
 386  0
                     String.format(
 387  
                         "event of type '%s' not found in issue #%d",
 388  0
                         type, this.issue.number()
 389  
                     )
 390  
                 );
 391  
             }
 392  0
             return found;
 393  
         }
 394  
         /**
 395  
          * Get read-only labels.
 396  
          * @return Collection of labels
 397  
          * @throws IOException If there is any I/O problem
 398  
          * @since 0.6.2
 399  
          */
 400  
         public IssueLabels roLabels() throws IOException {
 401  4
             final Collection<JsonObject> array =
 402  2
                 this.jsn.value("labels", JsonArray.class)
 403  2
                     .getValuesAs(JsonObject.class);
 404  2
             final Collection<Label> labels = new ArrayList<Label>(array.size());
 405  2
             for (final JsonObject obj : array) {
 406  4
                 labels.add(
 407  
                     new Label.Unmodified(
 408  2
                         Issue.Smart.this.repo(),
 409  2
                         obj.toString()
 410  
                     )
 411  
                 );
 412  2
             }
 413  
             // @checkstyle AnonInnerLength (1 line)
 414  2
             return new IssueLabels() {
 415  
                 @Override
 416  
                 public Issue issue() {
 417  0
                     return Issue.Smart.this;
 418  
                 }
 419  
                 @Override
 420  
                 public void add(
 421  
                     final Iterable<String> labels) throws IOException {
 422  1
                     throw new UnsupportedOperationException(
 423  
                         "The issue is read-only."
 424  
                     );
 425  
                 }
 426  
                 @Override
 427  
                 public void replace(
 428  
                     final Iterable<String> labels) throws IOException {
 429  0
                     throw new UnsupportedOperationException(
 430  
                         "The issue is read-only."
 431  
                     );
 432  
                 }
 433  
                 @Override
 434  
                 public Iterable<Label> iterate() {
 435  1
                     return labels;
 436  
                 }
 437  
                 @Override
 438  
                 public void remove(
 439  
                     final String name) throws IOException {
 440  0
                     throw new UnsupportedOperationException(
 441  
                         "This issue is read-only."
 442  
                     );
 443  
                 }
 444  
                 @Override
 445  
                 public void clear() throws IOException {
 446  0
                     throw new UnsupportedOperationException(
 447  
                         "This issue is read-only."
 448  
                     );
 449  
                 }
 450  
             };
 451  
         }
 452  
         /**
 453  
          * Does issue have milestone?
 454  
          * @return True if has
 455  
          * @throws IOException If fails
 456  
          */
 457  
         public boolean hasMilestone() throws IOException {
 458  0
             return this.jsn.hasNotNull("milestone");
 459  
         }
 460  
         /**
 461  
          * Get milestone for this issue.
 462  
          * @return Milestone
 463  
          * @throws IOException If fails
 464  
          */
 465  
         public Milestone milestone() throws IOException {
 466  0
             return this.repo().milestones().get(
 467  0
                 this.jsn.value("milestone", JsonObject.class)
 468  0
                     .getInt("number")
 469  
             );
 470  
         }
 471  
         /**
 472  
          * Add issueto milestone.
 473  
          * @param milestone Milestone
 474  
          * @throws IOException If fails
 475  
          */
 476  
         public void milestone(final Milestone milestone) throws IOException {
 477  0
             this.patch(
 478  0
                 Json.createObjectBuilder().add(
 479  0
                     "milestone", milestone.number()
 480  0
                 ).build()
 481  
             );
 482  0
         }
 483  
         @Override
 484  
         public Repo repo() {
 485  4
             return this.issue.repo();
 486  
         }
 487  
         @Override
 488  
         public int number() {
 489  0
             return this.issue.number();
 490  
         }
 491  
         @Override
 492  
         public Comments comments() {
 493  0
             return this.issue.comments();
 494  
         }
 495  
         @Override
 496  
         public IssueLabels labels() {
 497  0
             return this.issue.labels();
 498  
         }
 499  
         @Override
 500  
         public Iterable<Event> events() throws IOException {
 501  8
             return this.issue.events();
 502  
         }
 503  
         @Override
 504  
         public JsonObject json() throws IOException {
 505  10
             return this.issue.json();
 506  
         }
 507  
         @Override
 508  
         public void patch(final JsonObject json) throws IOException {
 509  0
             this.issue.patch(json);
 510  0
         }
 511  
         @Override
 512  
         public int compareTo(final Issue obj) {
 513  0
             return this.issue.compareTo(obj);
 514  
         }
 515  
         @Override
 516  
         public boolean exists() throws IOException {
 517  0
             return new Existence(this.issue).check();
 518  
         }
 519  
     }
 520  
 
 521  
 }