public static String encodeURL(String s) {
	try {
		return URLEncoder.encode(s, "UTF-8");
	} catch (UnsupportedEncodingException e) {
		e.printStackTrace();
		throw new HttpException(e);
	}
} @Entity("commits")
public class Commit extends GitHubEntity {
	@SerializedName("sha")
    @Indexed(unique=true, dropDups=true)
	@Id private String sha;
	
	@SerializedName("commiter")
	@Reference private User commiter;
	
	@SerializedName("message")
	private String message;
	
	@Reference private Project project;
	
	private Date commitDate;
	
	private int additionsCount;
	
	private int deletionsCount;
	
	public Commit(String sha, Project project) {
		this.sha = sha;
		this.project = project;
	}
	
	/**
	 * Informs the SHA checksum of the commit
	 * @return
	 */
	public String getSha() {
		return this.sha;
	}

	public void setSha(String sha) {
		this.sha = sha;
	}

	/**
	 * Informs the User who authored the commit
	 * @return
	 */
	public User getCommiter() {
		return this.commiter;
	}

	public void setCommiter(User commiter) {
		this.commiter = commiter;
	}

	/**
	 * Informs the commit message
	 * @return
	 */
	public String getMessage() {
		return this.message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	/**
	 * Informs the project to which the commit belongs
	 * @return a {@link Project} object
	 */
	public Project getProject() {
		return this.project;
	}

	public void setProject(Project project) {
		this.project = project;
	}

	/**
	 * Informs the date when the commit was done 
	 * @return a {@link Date} object
	 */
	public Date getCommitDate() {
		return this.commitDate;
	}

	public void setCommitDate(String date) {
		Date createAtDate = new Dates("yyyy-MM-dd HH:mm:ss").format(date.replaceAll("T", " ").replace("Z", ""));
		this.commitDate = createAtDate;
	}

	/**
	 * Informs the sum of added lines among the files committed
	 * @param deletionsCount
	 */
	public int getAdditionsCount() {
		return this.additionsCount;
	}

	public void setAdditionsCount(int additionsCount) {
		this.additionsCount = additionsCount;
	}

	/**
	 * Informs the sum of deleted lines among the files committed
	 * @param deletionsCount
	 */
	public int getDeletionsCount() {
		return this.deletionsCount;
	}

	public void setDeletionsCount(int deletionsCount) {
		this.deletionsCount = deletionsCount;
	}
	
	/**
	 * Gives the abbreviated SHA of the {@link Commit} object
	 * @return a {@link String} object
	 */
	public String getabbrevSHA() {
		return this.sha.substring(0, 7);
	}
	
	/**
	 * Two {@link Commit} objects are considered equal if and only if both have the same SHA hash
	 * @param commit
	 * @return
	 */
	public boolean equals(Commit commit) {
		return this.sha == commit.sha;
	}
	
	@Override
	public String toString() {
		return "Commit [" + (sha != null ? "sha=" + sha + ", " : "")
				+ (commiter != null ? "commiter=" + commiter + ", " : "")
				+ (message != null ? "message=" + message + ", " : "")
				+ (project != null ? "project=" + project + ", " : "")
				+ (commitDate != null ? "commitDate=" + commitDate : "") + "]";
	}

	@Override
	public String getURL() {
		return String.format("https://api.github.com/repos/%s/%s/commits/%s",
				this.getProject().getUser().getLogin(),
				this.getProject().getName(),
				this.sha);
	}
} @Entity("contributor")
public class Contributor extends GitHubEntity {
    @SerializedName("id")
    @Indexed(unique=true, dropDups=true)
    @Id private int id;
   
    @SerializedName("login")
    private String login;
    
    @SerializedName("avatar_url")
    private String avatar_url;
    
    @SerializedName("gravatar_id")
    private String gravatar_id;
    
    @SerializedName("html_url")
    private String html_url;
    
    @SerializedName("followers_url")
    private String followers_url;
    
    @SerializedName("following_url")
    private String following_url;
    
    @SerializedName("gists_url")
    private String gists_url; 
    
    @SerializedName("starred_url")
    private String starred_url;
    
    @SerializedName("subscriptions_url")
    private String subscriptions_url;
    
    @SerializedName("repos_url")
    private String repos_url;
    
    @SerializedName("events_url") 
    private String events_url;
    
    @SerializedName("received_events_url")
    private String received_events_url;
    
    @SerializedName("type")
    private String type;
    
    @SerializedName("site_admin")
    private boolean site_admin;
    
    @SerializedName("contributions")
    private int contributions;
    
    
    /**<p>This constructor that doesn't require all fields, but they <b>should</b> be setted after instantiation for a good use like we have described in
	 {@link Contributor} Class annotation <p>*/
    public Contributor(String login) {
    	this.login = login;
    }
    
    /**
     * <p>Informs the GitHub ID for the {@link Contributor} object in question
     * This ID is unique in GitHub, which means no two contributors can have the same ID on GitHub</p>
     * @return the integer ID
     */
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
	
	/**
	 *<p> The String returned by this method informs the {@link Contributor} <i>login</i>. </p> 
	 *@return
	 *<p>Return a String that represent the <i>login</i></p>
	 **/
	public String getLogin() {
		return login;
	}

	public void setLogin(String login) {
		this.login = login;
	}

	/**
	 * @return
	 * <p>Return a String that represent the <i>avatar_url</i></p>
	 * */
	public String getAvatar_url() {
		return avatar_url;
	}

	public void setAvatar_url(String avatar_url) {
		this.avatar_url = avatar_url;
	}
	
	/**
	 * @return
	 * <p>Return a String that represent the <i>gravatar_id</i></p>
	 * */
	public String getGravatar_id() {
		return gravatar_id;
	}

	public void setGravatar_id(String gravatar_id) {
		this.gravatar_id = gravatar_id;
	}
	
	/**
	 * @return
	 * <p>Return a String that represent the <i>html_url</i></p>
	 * */
	public String getHtml_url() {
		return html_url;
	}

	public void setHtml_url(String html_url) {
		this.html_url = html_url;
	}
	
	/**
	 * <p>The String returned by this method contains a URL for a list of all {@link User}'s that are
	 * following this {@link Contributor}</p>
	 * @return
	 * <p>Reeturn a String that represent the <i>followers_url</i></p>
	 * */
	public String getFollowers_url() {
		return followers_url;
	}

	public void setFollowers_url(String followers_url) {
		this.followers_url = followers_url;
	}

	/**
	 * <p>The String returned by this method contains a URL for a list of {@link User}'s
	 *  that this {@link Contributor} are following</p>
	 * @return
	 * <p>Reeturn a String that represent the <i>following_url</i></p>
	 * */
	public String getFollowing_url() {
		return following_url;
	}

	public void setFollowing_url(String following_url) {
		this.following_url = following_url;
	}
	
	/**
	 * @return
	 * <p>Return a String that represent the <i>gists_url</i></p>
	 * */
	public String getGists_url() {
		return gists_url;
	}

	public void setGists_url(String gists_url) {
		this.gists_url = gists_url;
	}

	/**
	 * @return
	 * <p>Return a String that represent the <i>starred_url</i></p>
	 * */
	public String getStarred_url() {
		return starred_url;
	}

	public void setStarred_url(String starred_url) {
		this.starred_url = starred_url;
	}
	
	
	/**
	 * @return
	 * <p>Return a String that represent the <i>subscriptions_url</i></p>
	 * */
	public String getSubscriptions_url() {
		return subscriptions_url;
	}

	public void setSubscriptions_url(String subscriptions_url) {
		this.subscriptions_url = subscriptions_url;
	}
	
	/**
	 * @return
	 * <p>Return a String that represent the <i>repos_url</i></p>
	 * */
	public String getRepos_url() {
		return repos_url;
	}

	public void setRepos_url(String repos_url) {
		this.repos_url = repos_url;
	}

	/**
	 * @return
	 * <p>Return a String that represent the <i>events_url</i></p>
	 * */
	public String getEvents_url() {
		return events_url;
	}

	public void setEvents_url(String events_url) {
		this.events_url = events_url;
	}

	/**
	 * @return
	 * <p>Returns a String that represents the <i>receveid_events_url</i> </p>
	 * */
	public String getReceived_events_url() {
		return received_events_url;
	}
	
	
	public void setReceived_events_url(String received_events_url) {
		this.received_events_url = received_events_url;
	}
	
	/**
	 * @return
	 * <p>Return a String that represents the {@link Contributor} type</p>
	 * */
	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}
	
	/**
	 * Informs is this {@link Contributor} is the site admin
	 * @return
	 * <p>A boolean that represents if the user is the site admin</p>
	 * */
	public boolean isSite_admin() {
		return site_admin;
	}

	public void setSite_admin(boolean site_admin) {
		this.site_admin = site_admin;
	}

	/**
	 * Informs the <b> number of contributions </b> of this {@link Contributor} in the {@link Project}
	 * @return
	 * <p>A integer that represents the number of contributions</p>
	 * */
	public int getContributions() {
		return contributions;
	}

	public void setContributions(int contributions) {
		this.contributions = contributions;
	}
	
    
    /**
     * <p>Two {@link Contributor} objects X and Y are the same <i>if and only</i> if both X and Y have the <b>same login</b> attribute </p>
     * @param 
     <p>A <i>Contributor</i> object that will be compared with the <i>Contributor</i> object in question: </p>
     * @return
     * 	<p><b>true</b> <i>if and only if</i> they have the same loging.</p>
     *  <p><b>else</b> otherwise.</p> 
     */
    public boolean equals(Contributor contributor) {
    	return this.login.equalsIgnoreCase(contributor.login);
    }
	
	/**<p>A {@link Contributor} does not have a API URL so we redirect to his {@link User} URL defined by his <i>login</i>.</p>
	 * 
	 * @return
	 * <p>A String to the URL that contains all information about the <i>User</i> that represent this <i>Contributor</i></p>
	 * */
	public String getURL() {
		return String.format("https://api.github.com/users/%s", this.getLogin());
	}
 
} public class GroundhogException extends RuntimeException {
	private static final long serialVersionUID = -3563928567447310893L;

	public GroundhogException() {
		super();
	}

	public GroundhogException(String msg) {
		super(msg);
	}

	public GroundhogException(Throwable cause) {
		super(cause);
	}

	public GroundhogException(String msg, Throwable cause) {
		super(msg, cause);
	}
} @Entity("issues")
public class Issue extends GitHubEntity {
    @Indexed(unique=true, dropDups=true)
    @SerializedName("id")
	@Id private int id;

	@SerializedName("number")
	private int number;

	@SerializedName("comments")
	private int commentsCount;

	@Reference private Project project;

	@SerializedName("pull_request")
	private transient PullRequest pullRequest;

    private List<IssueLabel> labels;
    
	@Reference private Milestone milestone;

	@SerializedName("title")
	private String title;

	@SerializedName("body")
	private String body;

	@SerializedName("state")
	private String state;

	@SerializedName("assignee")
	private User assignee;

	@SerializedName("closed_by")
	private User closedBy;

	@SerializedName("created_at")
	private Date createdAt;

	@SerializedName("updated_at")
	private Date updatedAt;

	@SerializedName("closed_at")
	private Date closedAt;

	public Issue(Project project, int number, String state) {
		this.number = number;
		this.project = project;
		this.state = state;
	}

	public Issue(Project project, int number, String state, String title) {
		this(project, number, state);
		this.title = title;
	}

	/**
	 * Returns the ID of the Issue on GitHub. This ID is unique for every Issue on GitHub, which means that
	 * no two (or more) Issues on GitHub may have the same ID
	 * @return
	 */
	public int getId() {
		return this.id;
	}

	public void setId(int id) {
		this.id = id;
	}

	/**
	 * Returns the number of the Issue on its Project. This number is unique within the project to which
	 * the Issues belong. Thus, the same project may not have two Issues with the same number but two (or more)
	 * different projects on GitHub may have Issues with the same number. 
	 * @return
	 */
	public int getNumber() {
		return this.number;
	}

	public void setNumber(int number) {
		this.number = number;
	}

	/**
	 * Informs the number of comments on the Issue
	 * @return
	 */
	public int getCommentsCount() {
		return this.commentsCount;
	}

	public void setCommentsCount(int commentsCount) {
		this.commentsCount = commentsCount;
	}

	/**
	 * Informs the Project object to which the Issue belongs
	 * No Issue exists without a project
	 * @return
	 */
	public Project getProject() {
		return this.project;
	}

	public void setProject(Project project) {
		this.project = project;
	}

	/**
	 * Informs the PullRequest related to the Issue. Not all Issues are Pull Request Issues.
	 * If this method return nulls then it means the Issue is not a PullRequest Issue
	 * @return
	 */
	public PullRequest getPullRequest() {
		return this.pullRequest;
	}

	public void setPullRequest(PullRequest pullRequest) {
		this.pullRequest = pullRequest;
	}

	public List<IssueLabel> getLabels() {
		return this.labels;
	}

	public void setLabels(List<IssueLabel> labels) {
		this.labels = labels;
	}

	public Milestone getMilestone() {
		return this.milestone;
	}

	public void setMilestone(Milestone milestone) {
		this.milestone = milestone;
	}

	/**
	 * Returns the title of the Issue
	 * @return
	 */
	public String getTitle() {
		return this.title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	/**
	 * Returns the Markdown-syntax-based description of the Issue
	 * @return
	 */
	public String getBody() {
		return this.body;
	}

	public void setBody(String body) {
		this.body = body;
	}

	/**
	 * Returns the current state of the Issue. Possible values are "open" and "closed"
	 * @return
	 */
	public String getState() {
		return this.state;
	}

	public void setState(String state) {
		this.state = state;
	}

	/**
	 * Returns the User assigned to that Issue.
	 * An Issue on GitHub may or may not have an assignee
	 * @return
	 */
	public User getAssignee() {
		return this.assignee;
	}

	public void setAssignee(User assignee) {
		this.assignee = assignee;
	}

	/**
	 * Informs the User who closed the Issue
	 * Every Issue gets closed by someone, so if this value is null
	 * then the Issue is currently open
	 * @return
	 */
	public User getClosedBy() {
		return this.closedBy;
	}

	public void setClosedBy(User closedBy) {
		this.closedBy = closedBy;
	}

	/**
	 * Informs the creation date of the Issue on GitHub
	 * @return
	 */
	public Date getCreatedAt() {
		return this.createdAt;
	}

	public void setCreatedAt(Date createdAt) {
		this.createdAt = createdAt;
	}

	/**
	 * Informs the date when the last modification was made upon the issue
	 * @return
	 */
	public Date getUpdatedAt() {
		return this.updatedAt;
	}

	public void setUpdatedAt(Date updatedAt) {
		this.updatedAt = updatedAt;
	}

	/**
	 * Informs the date when the Issue was closed
	 * If the value is null it means the issue is open
	 * @return
	 */
	public Date getClosedAt() {
		return this.closedAt;
	}

	public void setClosedAt(Date closedAt) {
		this.closedAt = closedAt;
	}

	/**
	 * Returns <code>true</code> if this Issue's state equals open, i.e., if
	 * <code>"open".equals(this.getState())</code> is <code>true</code>
	 * @return
	 */
	public boolean isOpen() {
		return "open".equals(this.getState());
	}

	/**
	 * Returns true if the Issue is a Pull Request Issue. Returns false otherwise
	 * @return
	 */
	public boolean isPullRequest() {
		return this.getPullRequest() != null ? true : false;
	}
	
	/**
	 * Two {@link Issue} objects are considered equal when they have the same GitHub API ID,
	 * the same number and the same {@link Project}. 
	 * @param issue
	 * @return
	 */
	public boolean equals(Issue issue) {
		return this.id == issue.id && this.number == issue.number && this.project.equals(issue.getProject());
	}
	
	public String getURL() {
		return String.format("https://api.github.com/repos/%s/%s/issues/%d",
				this.getProject().getOwner().getLogin(), this.getProject().getName(), this.getNumber());
	} 

	@Override
	public String toString() {
		String stringReturn = "Issue Number = "  + this.number;
		
		if (this.title != null) {
			stringReturn += ", title: " + this.title;
		}
		
		String url = this.getURL(); // This class doesn't contains a variable referring a URL, so we create one local
		
		if (url != null) {
			stringReturn += ", URL = " + url;   
		}
		
		return stringReturn;  
	}
} @Entity("issue_labels")
public class IssueLabel {
	@SerializedName("url")
	private String url;
	
	@SerializedName("name")
	private String name;
	
	@SerializedName("color")
	private String color;
	
	public IssueLabel(String url, String name, String color) {
		this.url = url;
		this.name = name;
		this.color = color;
	}
	
	public String getUrl() {
		return this.url;
	}
	
	public void setUrl(String url) {
		this.url = url;
	}
	
	public String getName() {
		return this.name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getColor() {
		return this.color;
	}
	
	public void setColor(String color) {
		this.color = color;
	}

	@Override
	public String toString() {
		return "IssueLabel [" + (url != null ? "url=" + url + ", " : "")
				+ (name != null ? "name=" + name + ", " : "")
				+ (color != null ? "color=" + color : "") + "]";
	}
} @Entity("languages")
public class Language implements Comparable<Language> {
	public static final String JAVA = "Java";
	
	private String name;
	private int byteCount;
	
	public Language(String name, int byteCount) {
		this.name = name;
		this.byteCount = byteCount;
	}

	/**
	 * Informs the language name
	 * @return a {@link String} object
	 */
	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Informs the byte count of the language in its project
	 *  @return a {@link String} object
	 */
	public int getByteCount() {
		return this.byteCount;
	}

	public void setByteCount(int byteCount) {
		this.byteCount = byteCount;
	}

	@Override
	public int compareTo(Language o) {
		if (this.getByteCount() < o.getByteCount()) return -1;
		if (this.getByteCount() > o.getByteCount()) return 1;
		
		return 0;
	}
	
	public String toString() {
		return this.name;
	}
} @Entity("milestones")
public class Milestone extends GitHubEntity {
    @Indexed(unique=true, dropDups=true)
	@SerializedName("id")
	@Id private int id;
	
	@SerializedName("number")
	private int number;
	
	@Reference private Project project;

	@SerializedName("state")
	private String state;
	
	@SerializedName("title")
	private String title;
	
	@SerializedName("description")
	private String description;
	
	@SerializedName("creator")
	@Reference private User creator;
	
    private List<IssueLabel> labels;
	
	@SerializedName("open_issues")
	private int openIssuesCount;
	
	@SerializedName("closed_issues")
	private int closedIssuesCount;
	
	@SerializedName("created_at")
	private Date createdAt;
	
	@SerializedName("updated_at")
	private Date updatedAt;
	
	@SerializedName("due_on")
	private Date dueOn;
	
	/**
	 * Default constructor for {@link Milestone} objects
	 * @param project the {@link Project} to which the {@link Milestone} belongs
	 * @param id the GitHub API ID of the {@link Milestone}
	 * @param number the number of the {@link Milestone}
	 * @param state the state of the {@link Milestone}
	 */
	public Milestone(Project project, int id, int number, String state) {
		this.project = project;
		this.id = id;
		this.number = number;
		this.state = state;
	}
	
	public Milestone(Project project, int id, int number, String state, String title) {
		this(project, id, number, state);
		this.title = title;
	}
	
	/**
	 * Informs the Milestone ID on GitHub. This id is unique to GitHub. No two Milestones can exist on GitHub
	 * with the same ID
	 * @return
	 */
	public int getId() {
		return this.id;
	}
	
	public void setId(int newId) {
		this.id = newId;
	}
	
	/**
	 * Informs the number of the Milestone. This attribute is unique in respect to the Milestone's {@link Project}.
	 * No two Milestones can exist with the same number in the same Project
	 * @return
	 */
	public int getNumber() {
		return this.number;
	}

	public void setNumber(int number) {
		this.number = number;
	}
	
	/**
	 * Informs the {@link Project} object to which the {@link Milestone} belongs
	 * @return a {@link Project} object
	 */
	public Project getProject() {
		return this.project;
	}

	public void setProject(Project project) {
		this.project = project;
	}

	/**
	 * Returns the state of the Milestone. Valid values are "open" and "closed"
	 * @return
	 */
	public String getState() {
		return this.state;
	}

	public void setState(String state) {
		this.state = state;
	}

	/**
	 * Informs the title of the Milestone
	 * @return a String
	 */
	public String getTitle() {
		return this.title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	/**
	 * Informs the description of the Milestone
	 * @return
	 */
	public String getDescription() {
		return this.description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	/**
	 * Informs the {@link User} who created the Milestone
	 * @return a {@link User} object
	 */
	public User getCreator() {
		return this.creator;
	}

	public void setCreator(User creator) {
		this.creator = creator;
	}
	
	public List<IssueLabel> getLabels() {
		return this.labels;
	}

	public void setLabels(List<IssueLabel> labels) {
		this.labels = labels;
	}

	/**
	 * Informs the number of open Issues currently in the {@link Milestone}
	 * @return an integer value
	 */
	public int getOpenIssuesCount() {
		return this.openIssuesCount;
	}

	public void setOpenIssuesCount(int openIssuesCount) {
		this.openIssuesCount = openIssuesCount;
	}

	/**
	 * Informs the number of closed Issues currently in the {@link Milestone}
	 * @return an integer value
	 */
	public int getClosedIssuesCount() {
		return this.closedIssuesCount;
	}

	public void setClosedIssuesCount(int closedIssuesCount) {
		this.closedIssuesCount = closedIssuesCount;
	}
	
	/**
	 * Informs the total number of Issues in the {@link Milestone}
	 * @return an integer value
	 */
	public int getIssuesCount() {
		return this.getOpenIssuesCount() + this.getClosedIssuesCount();
	}

	/**
	 * Informs the creation date of the Milestone
	 * @return a {@link Date} object
	 */
	public Date getCreatedAt() {
		return this.createdAt;
	}

	public void setCreatedAt(Date createdAt) {
		this.createdAt = createdAt;
	}
	
	/**
	 * Informs the update date of the Milestone
	 * @return a {@link Date} object
	 */
	public Date getUpdatedAt() {
		return this.updatedAt;
	}
	
	public void setUpdateAt(Date updatedAt) {
		this.updatedAt = updatedAt;
	}
	
	/**
	 * Informs the date when the Milestone is due to
	 * @return a {@link Date} object
	 */
	public Date getDueOn() {
		return this.dueOn;
	}

	public void setDueOn(Date dueOn) {
		this.dueOn = dueOn;
	}

	/**
	 * Two {@link Milestone} objects are considered equal when they have the same GitHub API ID,
	 * the same number and belong to the same {@link Project}. 
	 * @param issue
	 * @return
	 */
	public boolean equals(Milestone ms) {
		return this.id == ms.id && this.number == ms.number && this.project.equals(ms.getProject());
	}
	
	@Override
	public String getURL() {
		String result = String.format("https://api.github.com/repos/%s/%s/issues/%d",
				this.getProject().getOwner().getLogin(), this.getProject().getName(), this.getNumber());
		
		return result;
	}

	@Override
	public String toString() {
		return "Milestone number = " + number + ", "
				+ (this.state != null ? "state = " + this.state + ", " : "")
				+ (this.title != null ? "title = " + this.title + ", " : "")
				+ (this.getURL() != null ? "url= " + this.getURL() : " ");
	}
} @Entity("projects")
public class Project extends GitHubEntity {
	@SerializedName("id")
	@Indexed(unique=true, dropDups=true)
	@Id private int id;
	
	@SerializedName("name")
	private String name;

	@SerializedName("description")
	private String description;

	@SerializedName("language")
	private String language;
	private List<Language> languages;
	
	@Reference private List<Issue> issues;
	@Reference private List<Milestone> milestones;
	@Reference private List<Commit> commits;
	@Reference private List<Contributor> contributors;

	@Reference private User user;

	@SerializedName("clone_url")
	private String checkoutURL;

	@SerializedName("html_url")
	private String sourceCodeURL;

	@SerializedName("created_at")
	private Date createdAt;

	@SerializedName("pushed_at")
	private Date lastPushedAt;

	@SerializedName("fork")
	private boolean isFork;

	@SerializedName("has_downloads")
	private boolean hasDownloads;

	@SerializedName("has_issues")
	private boolean hasIssues;

	@SerializedName("has_wiki")
	private boolean hasWiki;

	@SerializedName("watchers_count")
	private int watchersCount;

	@SerializedName("forks")
	private int forks_count;

	@SerializedName("open_issues_count")
	private int issuesCount;

	/**
	 * 2-parameter complimentary constructor
	 * @param user the {@link User} to who the project belongs
	 * @param name the name of the {@link Project}
	 */
	public Project(User user, String name) {
		this.user = user;
		this.name = name;
		this.checkoutURL = "https://github.com/" + user.getLogin() + "/" + name + ".git";
		this.sourceCodeURL = "https://github.com/" + user.getLogin() + "/" + name;
	}
	
	/**
	 * 3-parameter constructor
	 * @param name the project name
	 * @param description the project description
	 * @param checkoutURL the project's source code URL
	 */
	public Project(String name, String description, String checkoutURL) {
		this.name = name;
		this.description = description;
		this.checkoutURL = checkoutURL;
	}

	/**
	 * Informs the name of the project
	 * @return the name of the project
	 */
	public String getName() {
		return this.name;
	}

	/**
	 * Sets the name of the project
	 * @param name a {@link String} for the project's name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Informs the description of the project
	 * @return the String description of the project
	 */
	public String getDescription() {
		return this.description;
	}

	/**
	 * Sets the description of the project
	 * @param description a String for setting the description
	 */
	public void setDescription(String description) {
		this.description = description;
	}

	/**
	 * Informs the project's author name
	 * @return a String correspondent to the name of the author of the project
	 */
	public User getOwner() {
		return this.user;
	}

	/**
	 * Informs the project's author name
	 * @param owner a {@link String} for the name of the project's author
	 */
	public void setOwner(User owner) {
		this.user = owner;
	}

	/**
	 * Informs the project's SCM
	 * @return
	 * @deprecated
	 */
	public SCM getSCM() {
		throw new UnsupportedOperationException("This method is not supported anymore.");
	}

	public void setSCM(SCM hg) {
		throw new UnsupportedOperationException("This method is not supported anymore.");
	}
	
	/**
	 * Informs the project's SCM URL
	 * @return
	 */
	public String getCheckoutURL() {
		return this.checkoutURL;
	}

	/**
	 * Sets the project's SCM URL
	 * @param scmURL
	 */
	public void setCheckoutURL(String scmURL) {
		this.checkoutURL = scmURL;
	}

	/**
	 * Informs the source code URL of the project
	 * @return a String correspondent to the source code URL of the project in question
	 */
//	public String getSourceCodeURL() {
//		return this.sourceCodeURL;
//	}

	/**
	 * Sets the source code URL for the project
	 * @param sourceCodeURL sets the URL String of the project's source code
	 */
//	public void setSourceCodeURL(String sourceCodeURL) {
//		this.sourceCodeURL = sourceCodeURL;
//	}

	/**
	 * Informs whether a project allow downloads or not
	 * @return true if the project allows source code download. Returns false otherwise.
	 */
	public boolean hasDownloads() {
		return this.hasDownloads;
	}

	/**
	 * Sets if a project allows downloads or not
	 * @param hasDownloads a {@link boolean} for setting whether the project enables downloads or not
	 */
	public void setHasDownloads(boolean hasDownloads) {
		this.hasDownloads = hasDownloads;
	}

	/**
	 * @return true if the project has issues. Returns false otherwise.
	 */
	public boolean hasIssues() {
		return this.hasIssues;
	}

	/**
	 * @param hasIssues a boolean value for setting if the project has Issues or not
	 */
	public void setHasIssues(boolean hasIssues) {
		this.hasIssues = hasIssues;
	}

	/**
	 * Informs whether the project has its own Wiki or not.
	 * @return true if the project has a Wiki. Returns false otherwise.
	 */
	public boolean hasWiki() {
		return this.hasWiki;
	}

	/**
	 * Sets whether a project has its own Wiki or not.
	 * @param hasWiki a {@link boolean} for setting whether the project has a Wiki or not.
	 */
	public void setHasWiki(boolean hasWiki) {
		this.hasWiki = hasWiki;
	}

	/**
	 * Informs how many people are watching the project or have "starred" it
	 * @return an {@link integer} informing how many people are currently watching the project on its forge
	 */
	public int getWatchersCount() {
		return this.watchersCount;
	}

	/**
	 * Sets how many people are watching the project
	 * @param watchersCount an integer for setting the number of people watching the project on its forge
	 */
	public void setWatchersCount(int watchersCount) {
		this.watchersCount = watchersCount;
	}

	/**
	 * Informs the number of forks the project has
	 * @return an integer correspondent to the number of forks
	 */
	public int getForksCount() {
		return this.forks_count;
	}

	/**
	 * An indicator of how many times the project has been forked.
	 * @param forksCount an integer for setting the number of forks the project has.
	 */
	public void setForksCount(int forksCount) {
		this.forks_count = forksCount;
	}

	/**
	 *
	 * Informs the number of open issues of the project
	 * @return an integer value correspondent to the amount of open issues
	 */
	public int getIssuesCount() {
		return this.issuesCount;
	}

	/**
	 * Sets the number of issues of a project
	 * @param issuesCount an integer for setting the number of Issues of the project
	 */
	public void setIssuesCount(int issuesCount) {
		this.issuesCount = issuesCount;
	}

	/**
	 * Tells whether a project is a fork of another or not
	 * @return a boolean value: true if it's a fork, false otherwise
	 */
	public boolean isFork() {
		return this.isFork;
	}

	/**
	 * Sets if the project is a fork of another or not
	 * @param value a boolean value for informing whether the project is a fork of another or not
	 */
	public void setIsFork(boolean value) {
		this.isFork = value;
	}

	/**
	 * Methods that deal with dates are below
	 * Notice that each setter method is overloaded to support Date and String parameters.
	 * When the parameter is provided as a String object, the setter method will perform the
	 * conversion to a date object
	 */

	/**
	 * Informs the creation date of the project
	 * @return a Date object correspondent to the project's creation date
	 */
	public Date getCreatedAt() {
		return this.createdAt;
	}

	/**
	 * Sets the creation date of the project
	 * @param createdAt a Date object for setting the creation date of the project
	 */
	public void setCreatedAt(Date createdAt) {
		this.createdAt = createdAt;
	}

	/**
	 * @param createdAtParam the String correspondent to the creation date of the project in question. e.g: 2012-04-28T15:40:35Z
	 */
	public void setCreatedAt(String createdAtParam) {
		Date createAtDate = new Dates("yyyy-MM-dd HH:mm:ss").format(createdAtParam);
		this.createdAt = createAtDate;
	}

	/**
	 * Returns the date of the latest push to the project
	 * @return a Date object of the latest push
	 */
	public Date getLastPushedAt() {
		return this.lastPushedAt;
	}

	/**
	 * Sets the date on which the last push has been submitted to the project's source code
	 * @param lastPushedAtParam the Date object correspondent to the date of the last push to the project
	 * in question
	 */
	public void setLastPushedAt(Date lastPushedAtParam) {
		this.lastPushedAt = lastPushedAtParam;
	}

	/**
	 * @param lastPushedAtParam the String correspondent to the date of the last push to the project
	 * in question. e.g: 2012-04-28T15:40:35Z
	 */
	public void setLastPushedAt(String lastPushedAtParam){
		Date lastPushDate = new Dates("yyyy-MM-dd HH:mm:ss").format(lastPushedAtParam);
		this.lastPushedAt = lastPushDate;
	}

	/**
	 * Informs the name of prevailing programming language in a project
	 * @return name of programming language
	 */
	public String getLanguage() {
		return this.language;
	}

	/**
	 * Sets the name of prevailing programming language in a project
	 */
	public void setLanguage(String language) {
		this.language = language;
	}

	/**
	 * Returns the list of languages that compose the Project
	 * @return a {@link List} of {@link Language} objects
	 */
	public List<Language> getLanguages() {
		return this.languages;
	}

	/**
	 * Sets the list of languages that compose the Project
	 * @param a {@link List} of {@link Language} objects
	 */
	public void setLanguages(List<Language> langs) {
		this.languages = langs;
	}
	
	/**
	 * Returns the list of milestones of the Project
	 * @return a {@link List} of {@link Milestone} objects
	 */
	public List<Milestone> getMilestones() {
		return this.milestones;
	}

	public void setMilestones(List<Milestone> milestones) {
		this.milestones = milestones;
	}
	
	/**
	 * Returns the list of issues of the project
	 * @return a {@link List} of {@link Issue} objects
	 */
	public List<Issue> getIssues() {
		return this.issues;
	}
	
	public void setIssues(List<Issue> issues) {
		this.issues = issues;
	}
	
	/**
	 * Returns the list of commits of the project
	 * @return a {@link List} of {@link Commit} objects
	 */
	public List<Commit> getCommits() {
		return this.commits;
	}
	
	public void setCommits(List<Commit> commits) {
		this.commits = commits;
	}
	
	/**
	 * Returns the list of contributors of the project as GitHub users
	 * @return
	 */
	public List<Contributor> getContributors() {
		return this.contributors;
	}
	
	public void setContributors(List<Contributor> contributors) {
		this.contributors = contributors;
	}

	/**
	 * Returns the {@link User} object who is the author of the Project
	 * @return
	 */
	public User getUser() {
		return this.user;
	}

	/**
	 * Sets the {@link User} object who is the author of the Project
	 * @param user
	 */
	public void setUser(User user) {
		this.user = user;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	/**
	 * Return true if the project is considered mature
	 * 
	 * A project is considered active if it has at least 1 {@link Commit} and more the
	 * 1 contributor
	 * 
	 * @return
	 */
	public boolean isActive() {
		if (commits == null && contributors == null) {
			return false;
		} else {
			return ((commits.size() > 1) && (contributors.size() > 2));			
		}
	}
	
	/**
	 * Returns true if the project is considered mature, and false otherwise.
	 * 
	 * A project is considered mature if it has at least three watchers, plus
	 * one fork, plus more than 100 commits in its own history, and more than
	 * five issues created in its own history.
	 * 
	 * @return
	 */
	public boolean isMature() {
		return ((watchersCount > 3) && (forks_count > 1) && (commits.size() > 100) && (issues.size() > 5));
	}
	
	/**
	 * Returns the well-formated github rest-api for this project
	 * @return 
	 */
	public String getURL() {
		return String.format("https://api.github.com/repos/%s/%s", this.getUser().getLogin(), this.getName());
	}

	/**
	 * If two projects have the same ID, then they are equals.
	 * */
	public boolean equals(Project project){
		if(this.id == project.getId()){
			return true;
		}
		else{
			return false;
		}
	}
	
	@Override
	public String toString() {
		return String.format("Project(%s, %s)", this.name, this.sourceCodeURL);
	}
} @Entity("releases")
public class Release extends GitHubEntity {
    @Indexed(unique=true, dropDups=true)
	@SerializedName("id")
	@Id private int id;

	@SerializedName("tag_name")
	private String tagName;

	@SerializedName("target_commitish")
	private String targetCommitish;

	@SerializedName("name")
	private String name;

	@SerializedName("assets_url")
	private String assetsUrl;

	@SerializedName("body")
	private String body;

	@SerializedName("draft")
	private boolean draft;

	@SerializedName("preRelease")
	private boolean preRelease;

	@SerializedName("created_at")
	private Date createdAt;

	@SerializedName("published_at")
	private Date publishedAt;

	@Reference private Project project; 

	public Release(Project project, int id) {
		this.project = project;
		this.id = id;
	}

	public Release(Project project, int id, String tagName) {
		this.project = project;
		this.id = id;
		this.tagName = tagName;
	}

	/**
	 * Returns the ID of the Release on GitHub. This ID is unique for every Release on GitHub, which means that
	 * no two (or more) Issues on GitHub may have the same ID
	 * @return
	 */
	public int getId() {
		return this.id;
	}

	public void setId(int id) {
		this.id = id;
	}

	/**
	 * Returns the Tag Name of the Release on GitHub.
	 * @return the tag_name
	 */
	public String getTagName() {
		return this.tagName;
	}

	public void setTagName(String tagName) {
		this.tagName = tagName;
	}

	/**
	 * Returns the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. Defaults to the repository’s default branch (usually “master”). Unused if the Git tag already exists.
	 * @return the target_commitish
	 */
	public String getTargetCommitish() {
		return targetCommitish;
	}

	public void setTargetCommitish(String targetCommitish) {
		this.targetCommitish = targetCommitish;
	}


	/**
	 * Informs the Project object to which the Release belongs
	 * No Release exists without a project
	 * @return the project
	 */
	public Project getProject() {
		return project;
	}

	public void setProject(Project project) {
		this.project = project;
	}


	/**
	 * Returns the name of the Release on GitHub.
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**Return the String to Assets url, that present a full project history beyond Git artifacts*/
	public String getAssetsUrl() {
		return this.assetsUrl;
	}

	public void setAssetsUrl(String assetsUrl) {
		this.assetsUrl = assetsUrl;
	}

	/**
	 * Returns the Markdown-syntax-based description of the Release
	 * @return the body
	 */
	public String getBody() {
		return this.body;
	}

	public void setBody(String body) {
		this.body = body;
	}

	/**
	 * Returns true if the Release is a draft (unpublished) or false if it's published
	 * @return the draft
	 */
	public boolean isDraft() {
		return this.draft;
	}

	public void setDraft(boolean draft) {
		this.draft = draft;
	}

	/**
	 * Returns true if the Release is a preRelease or false if the Release is a full release
	 * @return the preRelease
	 */
	public boolean isPreRelease() {
		return this.preRelease;
	}

	public void setPreRelease(boolean preRelease) {
		this.preRelease = preRelease;
	}

	/**
	 * Returns the date when the Release was created
	 * @return the createdAt
	 */
	public Date getCreatedAt() {
		return this.createdAt;
	}

	public void setCreatedAt(Date createdAt) {
		this.createdAt = createdAt;
	}

	/**
	 * Returns the date when the Release was published
	 * @return the published_at
	 */
	public Date getPublishedAt() {
		return this.publishedAt;
	}

	public void setPublishedAt(Date publishedAt) {
		this.publishedAt = publishedAt;
	}
	
	/**
	 * Two {@link Release} objects are considered equal if and only if they share the same
	 * GitHub API ID and belong to the same {@link Project}
	 * @param rel
	 * @return
	 */
	public boolean equals(Release rel) {
		return this.id == rel.id && this.project.equals(rel.getProject());
	}

	/**Return a String representation of the Release, this String contains (if they are not null):
	 * <ul>
	 * 		<li> The Release id
	 * 		<li> The Target Commitish
	 * 	    <li> A URL representation of the Release	
	 * </ul>
	 * 
	 * */
	@Override
	public String toString() {
		String stringReturn = "Release id = " + this.id;// we always have a Issue number, the programmer must read the doc to contribute
		
		if (this.name != null) stringReturn += ", Name = " + this.name;
		if (this.targetCommitish != null) stringReturn += ", Target Commitish = " + this.targetCommitish;
		
		String url = this.getURL(); // This class doesn't contain a variable referring to a URL, so we create one locally
		if (url != null) stringReturn += ", URL = " + url;
		
		return stringReturn;
	}

	
	/**This method return the URL of the release*/
	@Override
	public String getURL() {
		return String.format("https://api.github.com/repos/%s/%s/releases/%d",
				this.getProject().getOwner().getLogin(),
				this.getProject().getName(), this.id
			  );
	}
} @Entity("users")
public class User extends GitHubEntity {
    @SerializedName("id")
    @Indexed(unique=true, dropDups=true)
    @Id private int id;
  
    @SerializedName("name")
    private String name;
  
    @SerializedName("login")
    private String login;
  
    @SerializedName("email")
    private String email;
  
    @SerializedName("company")
    private String company;
  
    @SerializedName("location")
    private String location;
  
    @SerializedName("blog")
    private String blog;
  
    @SerializedName("hireable")
    private boolean hireable;
  
    @SerializedName("followers")
    private int followers;
  
    @SerializedName("following")
    private int following;
  
    @SerializedName("public_repos")
    public int public_repos;
  
    @SerializedName("public_gists")
    public int public_gists;
  
    @SerializedName("created_at")
    private String created_at;
  
    @SerializedName("updated_at")
    private String updated_at;
    
	@Reference private List<Commit> commits;
    private List<String> emailAddresses;
  
    public User(String login) {
    	this.login = login;
    }
  
    public User(String login, String name) {
        this(login);
        this.name = name;
    }
  
    /**
     * Informs the GitHub ID for the {@link User} object in question
     * This ID is unique in GitHub, which means no two users can have the same ID on GitHub
     * @return the integer ID
     */
    public int getId() {
        return this.id;
    }
  
    public void setId(int id) {
        this.id = id;
    }
  
    /**
     * Informs the User name. Useful for measuring commit authorship
     * @return a {@link String} object
     */
    public String getName() {
        return this.name;
    }
  
    /**
     * Sets the name of the User
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }
  
    /**
     * Informs the login (username) of the {@link User}
     * @return
     */
    public String getLogin() {
        return this.login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

   /**
    * Informs the {@link User}'s company name
    * @return
    */
    public String getCompany() {
        return this.company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

   /**
    * Informs the {@link User}'s blog URL
    * @return
    */
    public String getBlog() {
        return this.blog;
    }

    public void setBlog(String blog) {
        this.blog = blog;
    }

   /**
    * Informs whether a {@link User} is available for hire (hireable) or not.
    * @return true if the {@link User} is hireable. Returns false otherwise.
    */
    public boolean isHireable() {
        return this.hireable;
    }

    public void setHireable(boolean hireable) {
        this.hireable = hireable;
    }

   /**
    * Informs the sum of followers the User has
    * @return
    */
    public int getFollowers() {
        return this.followers;
    }

    public void setFollowers(int followers) {
        this.followers = followers;
    }

   /**
    * Informs how many other GitHub Users the User follows
    * @return
    */
    public int getFollowing() {
        return this.following;
    }

    public void setFollowing(int following) {
        this.following = following;
    }

   /**
    * Informs the date when the {@link User} signed up for GitHub
    * @return a {@link String} object
    */
    public String getCreated_at() {
        return this.created_at;
    }

    public void setCreated_at(String created_at) {
        this.created_at = created_at;
    }

   /**
    * Informs the last date when the {@link User} performed activity on GitHub
    * @return a {@link Date} object
    */
    public String getUpdatedAt() {
        return this.updated_at;
    }

    public void setUpdatedAt(String updated_at) {
        this.updated_at = updated_at;
    }
  
   /**
    * Returns the sum of public repositories that the User has
    * @return
    */
    public int getPublic_repos() {
        return this.public_repos;
    }

    public void setPublic_repos(int public_repos) {
        this.public_repos = public_repos;
    }
  
   /**
    * Returns the sum of public gists that the User has
    * @return
    */
    public int getPublic_gists() {
        return this.public_gists;
    }

    public void setPublic_gists(int public_gists) {
        this.public_gists = public_gists;
    }

   /**
    * Informs the email address of the {@link User}
    * @return a String representing an email address
    */
    public String getEmail() {
        return this.email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

   /**
    * The location String of the {@link User}
    * @return
    */
    public String getLocation() {
        return this.location;
    }

   public void setLocation(String location) {
       this.location = location;
   }
   
   public List<Commit> getCommits() {
		return this.commits;
	}

	public void setCommits(List<Commit> commits) {
		this.commits = commits;
	}
  
   /**
    * Informs the list of email addresses that belongs to the user.
    * This is useful for matching critical {@link Commit} authorship data
    * @return
    */
    public List<String> getEmailAddresses() {
        return this.emailAddresses;
    }
  
    public void setEmailAddresses(List<String> emails) {
        this.emailAddresses = emails;
    }
  
    public String getURL() {
        return String.format("https://api.github.com/users/%s", this.getLogin());
    }
    
    /**
     * Two {@link User} objects X and Y are the same if both X and Y have the same login attribute
     * @param user
     * @return
     */
    public boolean equals(User user) {
    	return this.login.equals(user.getLogin());
    }

    @Override
    public String toString() {
        return "id=" + id + ", "
            + (name != null ? "Name = " + name + ", " : "")
            + (login != null ? "Login = " + login + ", " : "")
            + "Public repos = " + public_repos + ", "
            + (created_at != null ? "Created at = " + created_at : "") + ", Url: " + this.getURL();
    }
} public class HttpModule extends AbstractModule {

	@Override
	protected void configure() {
		bind(Requests.class).in(Singleton.class);
		bind(String.class)
				.annotatedWith(Names.named("githubOauthToken"))
				.toInstance("access_token=b0244ac0d27f7e0c227cedbd557d875869a59111");
	}
} public class Requests {
	private final AsyncHttpClient httpClient;
	
	public Requests() {
		this.httpClient = new AsyncHttpClient();
	}
	
	/**
	 * Gets the response body of the given URL
	 * @param urlStr
	 * @return the entire html content
	 */
	public String get(String urlStr) {
		try {
			return this.httpClient.prepareGet(urlStr).execute().get().getResponseBody();
		} catch (Exception e) {
			e.printStackTrace();
			throw new HttpException(e);
		}
	}
	
	/**
	 * Gets the response body of the given URL using the preview flag in the request header
	 * This is for new API's that are still in preview mode
	 * @param urlStr
	 * @return the entire html content
	 */
	public String getWithPreviewHeader(String urlStr) {
		try {
			RequestBuilder builder = new RequestBuilder("GET");
		    Request request = builder.setUrl(urlStr)
		     .addHeader("Accept", "application/vnd.github.manifold-preview")
		     .build();
			return this.httpClient.prepareRequest(request).execute().get().getResponseBody();
		} catch (Exception e) {
			e.printStackTrace();
			throw new HttpException(e);
		}
	}
	
	/**
	 * Downloads the response body of the given URL
	 * 
	 * @param urlStr an URL of a page whose body will be downloaded
	 * @return the entire html content as an InputStream
	 */
	public InputStream download(String urlStr) {
		try {
			return this.httpClient.prepareGet(urlStr).setFollowRedirects(true).execute().get().getResponseBodyAsStream();
		} catch (Exception e) {
			e.printStackTrace();
			throw new HttpException(e);
		}
	}
	
	public <T> ListenableFuture<T> getAsync(String urlStr, AsyncCompletionHandler<T> callback) throws IOException {
		return httpClient.prepareGet(urlStr).execute(callback);
	}
} enum GithubAPI {
	LEGACY_V2 {
		@Override
		public String baseForm() {
			return String.format("%slegacy/repos/search/", ROOT.baseForm());
		}
	},
	ROOT {
		@Override
		public String baseForm() {
			return "https://api.github.com/";
		}
	}, 
	REPOSITORIES {
		@Override
		public String baseForm() {
			return String.format("%srepositories", ROOT.baseForm());
		}
	},
	USERS {
		@Override
		public String baseForm() {
			return "https://api.github.com/users/";
		}
	};

	public abstract String baseForm();
}
import static br.ufpe.cin.groundhog.http.URLsDecoder.encodeURL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import br.ufpe.cin.groundhog.Commit;
import br.ufpe.cin.groundhog.Contributor;
import br.ufpe.cin.groundhog.GroundhogException;
import br.ufpe.cin.groundhog.Issue;
import br.ufpe.cin.groundhog.IssueLabel;
import br.ufpe.cin.groundhog.Language;
import br.ufpe.cin.groundhog.Milestone;
import br.ufpe.cin.groundhog.Project;
import br.ufpe.cin.groundhog.Release;
import br.ufpe.cin.groundhog.User;
import br.ufpe.cin.groundhog.http.HttpModule;
import br.ufpe.cin.groundhog.http.Requests;
import br.ufpe.cin.groundhog.search.UrlBuilder.GithubAPI;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.inject.Guice;
import com.google.inject.Inject;
package br.ufpe.cin.groundhog.search;






/**
 * Performs the project search on GitHub, via its official JSON API
 * 
 * @author fjsj, gustavopinto, Rodrigo Alves, Jesus Jackson
 * @since 0.0.1
 */
public class SearchGitHub implements ForgeSearch {
	private static Logger logger = LoggerFactory.getLogger(SearchGitHub.class);
	
	public static int INFINITY = -1;

	private final Gson gson;
	private final Requests requests;
	private final UrlBuilder builder;

	@Inject
	public SearchGitHub(Requests requests) {
		this.requests = requests;
		this.gson = new Gson();
		this.builder = Guice.createInjector(new HttpModule()).getInstance(UrlBuilder.class);
	}

	public List<Project> getProjects(String term, int page, int limit) throws SearchException {
		logger.info("Searching project metadata");
		try {
			if (term == null) {
				return getAllForgeProjects(page, limit);
			}

			String searchUrl = builder.uses(GithubAPI.LEGACY_V2)
									  .withParam(encodeURL(term))
									  .withParam("start_page", page)
									  .withParam("language", "java")
									  .build();

			String json = requests.get(searchUrl);
			JsonObject jsonObject = gson.fromJson(json, JsonElement.class).getAsJsonObject();			
			JsonArray jsonArray = jsonObject.get("repositories").getAsJsonArray();

			List<Project> projects = new ArrayList<>();
			for (int i = 0; i < jsonArray.size() && (i < limit || limit < 0); i++) {
				String element = jsonArray.get(i).toString();

				Project p = gson.fromJson(element, Project.class);

				String owner = jsonArray.get(i).getAsJsonObject().get("owner").getAsString();
				p.setCheckoutURL(String.format("https://github.com/%s/%s.git", owner, p.getName()));

				String userUrl = builder.uses(GithubAPI.USERS).withParam(owner).build();
				String userJson = requests.get(userUrl);
				User user = gson.fromJson(userJson, User.class);