@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("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);
	}
} public enum SCM {
	SOURCE_FORGE, GIT, SVN, HG, UNKNOWN, NONE;

	public String toString() {
		return name();
	}
} @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 CodeHistoryModule extends AbstractModule {

	@Override
	protected void configure() {
		bind(GitCodeHistory.class).in(Singleton.class);
		bind(SFCodeHistory.class).in(Singleton.class);
		bind(SvnCodeHistory.class).in(Singleton.class);
	}
} public class GitCodeHistory implements CodeHistory {
	
	private static Logger logger = LoggerFactory.getLogger(GitCodeHistory.class);
	
	private final GitClient gitClient;
	
	@Inject
	public GitCodeHistory(GitClient gitClient){
		this.gitClient = gitClient;
	}
	
	@Override
	/**
	 * Creates a directory with the name of the project, moves the content of the repository folder into it
	 * and performs the SCM checkout on this new directory
	 * 
	 * @param project the name of the project
	 * @param repositoryFolder the repository where the source code is located
	 * @param date the date on which the checkout will be based
	 */
	public File checkoutToDate(String project, File repositoryFolder, final Date date) {
			
		try {
			FileUtil files = FileUtil.getInstance();

			File projectFolder = new File(files.createTempDir(), project);
			files.copyDirectory(repositoryFolder, projectFolder);
			
			logger.info("Checking out to the specific date..");
			this.gitClient.checkout(projectFolder, date);
			return projectFolder;
		} catch (Exception e) {
			logger.error(e.getMessage());
			throw new CheckoutException(e.getMessage());
		}
	}
} public class SFCodeHistory implements CodeHistory {
	
	/**
	 * 
	 * @param project
	 * @param repositoryFolder
	 * @param Date
	 * @throws EmptyProjectAtDateException
	 */
	@Override
	public File checkoutToDate(String project, File repositoryFolder, Date date) throws EmptyProjectAtDateException {
		long dateLong = date.getTime();
		File closest = null;
		Deque<File> stack = new ArrayDeque<File>();
		stack.add(repositoryFolder);
		
		while (!stack.isEmpty()) {
			File f = stack.pollLast();
			if (f.isDirectory()) {
				stack.addAll(Arrays.asList(f.listFiles()));
			} else {
				long modified = f.lastModified();
				if (modified <= dateLong) {
					if (closest == null) {
						closest = f;
					} else {
						long closestModified = closest.lastModified();
						if (Math.abs(dateLong - modified) < Math.abs(dateLong - closestModified)) {
							closest = f;
						}
					} 
				}
			}
		}
		
		if (closest != null) {
			File projectFolder = new File(FileUtil.getInstance().createTempDir(), project);
			DefaultExtractor.getInstance().extractFile(closest, projectFolder);
			return projectFolder;
		} else {
			throw new EmptyProjectAtDateException(new SimpleDateFormat().format(date));
		}
	}
} public class CrawlGitHub extends ForgeCrawler {
	private final static Logger logger = LoggerFactory.getLogger(CrawlGitHub.class);

	private final GitClient gitClient;
	private final File destinationFolder;

	@Inject
	public CrawlGitHub(GitClient gitClient, File destinationFolder) {
		this.gitClient = gitClient;
		this.destinationFolder = destinationFolder;
	}

	@Override
	public File downloadProject(Project project) throws DownloadException {
		String projectName = project.getName();
		String projectUrl = project.getCheckoutURL();
		File projectDestinationFolder = new File(destinationFolder, projectName);

		logger.info(String.format("Downloading %s project..", project.getName()));

		try {
			this.gitClient.clone(projectUrl, projectDestinationFolder);
			logger.info(String.format("Done! The project is available at %s", projectDestinationFolder.getAbsolutePath()));
			return projectDestinationFolder;
		} catch (Exception e) {
			String error = String.format("Unable to download %s (%s) project", project.getName(), project.getCheckoutURL());
			logger.error(error);
			throw new DownloadException(error);
		}
	}
} public class CrawlGoogleCode extends ForgeCrawler {
	private static Logger logger = LoggerFactory.getLogger(CrawlGoogleCode.class);
	
	private final GitClient gitClient;
	private final File destinationFolder;
	
	public CrawlGoogleCode(GitClient gitClient, File destinationFolder) {
		super();
		this.gitClient = gitClient;
		this.destinationFolder = destinationFolder;
	}
	
	@Override
	public File downloadProject(Project project) throws DownloadException {
		String projectName = project.getName();
		SCM projectSCM = project.getSCM();
		File projectFolder = new File(destinationFolder, projectName);
		
		try {
			switch (projectSCM) {
			case SVN:
				// It is no advantage to checkout now, since SVN version history is stored remotely.
				// CodeHistory shall use checkout the desired version later.
				break;
			case GIT:
				String url = project.getCheckoutURL();
					gitClient.clone(url, projectFolder);
				break;
			case NONE:
				logger.warn(String.format("Project %s has no SCM.", projectName));
				break;
			case HG:
				String scm = project.getSCM().toString();
				logger.warn(String.format("Project %s has a unsupported SCM: %s", projectName, scm));
				break;
			case UNKNOWN:
				scm = project.getSCM().toString();
				logger.warn(String.format("Project %s has a unsupported SCM: %s", projectName, scm));
				break;
			default:
				scm = project.getSCM().toString();
				logger.error(String.format("Project %s doesn't have %s as SCM, which isn't a Google Code compatible SCM. " +
						"Are you sure this project came from Google Code?", projectName, scm));
			}
			return projectFolder;
		} catch (Exception e) {
			throw new DownloadException(e);
		}
	}
} @Deprecated
public class CrawlSourceForge extends ForgeCrawler {
	private static String rootUrl = "http://sourceforge.net";

	private ConcurrentHashMap<String, Date> mapModifiedDate;
	private SimpleDateFormat dateFormat;
	private Requests requests;
	private File destinationFolder;

	@Inject
	public CrawlSourceForge(Requests requests, File destinationFolder) {
		super();
		this.mapModifiedDate = new ConcurrentHashMap<String, Date>();
		this.dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
		this.requests = requests;
		this.destinationFolder = destinationFolder;
	}

	private void parseURLsFromPage(String project, String html,
			Stack<String> dirURLs, List<String> fileURLs) throws IOException,
			InterruptedException {
		Document doc = Jsoup.parse(html);
		Elements rows = doc.select("table#files_list tbody tr");

		for (Element row : rows) {
			String href = row.select("th a").first().attr("href");
			String modified = row.select("td[headers=files_date_h] abbr")
					.first().attr("title");
			String[] splitted = href.split("/");
			String filename = splitted[splitted.length - 2];
			String extension = null;

			if (filename.contains(".")) {
				extension = filename.substring(filename.lastIndexOf("."));
			}
			if (href.endsWith("/download")) { // file
				if (isParseable(filename, extension)) {
					String filePath = decodeURL(href.substring(
							href.indexOf("/files/") + "/files/".length(),
							href.indexOf("/download")));
					try {
						mapModifiedDate.put("/" + project + "/" + filePath,
								dateFormat.parse(modified));
					} catch (ParseException e) {
						e.printStackTrace();
					}
					fileURLs.add(href);
				}
			} else { // folder
				String folderPath = decodeURL(href.substring(
						href.indexOf("/files/") + "/files/".length(),
						href.lastIndexOf('/')));
				try {
					mapModifiedDate.put("/" + project + "/" + folderPath,
							dateFormat.parse(modified));
				} catch (ParseException e) {
					e.printStackTrace();
				}
				dirURLs.push(rootUrl + href);
			}
		}
	}

	private boolean isParseable(String filename, String extension) {
		filename = filename.toLowerCase();
		if (filename.contains("src") || 
				filename.contains("source") && 
				Formats.getInstance().isCompatible(extension)) {
			return true;
		}
		return false;
	}

	private List<String> getDownloadURLs(final String project)
			throws IOException, InterruptedException, ExecutionException {
		final Stack<String> dirURLs = new Stack<String>();
		final List<String> fileURLs = new Vector<String>();
		List<Future<Integer>> futures = new ArrayList<Future<Integer>>();
		dirURLs.add(String.format("http://sourceforge.net/projects/%s/files/",
				project));

		while (!dirURLs.isEmpty()) {
			final String pageURL = dirURLs.pop();
			ListenableFuture<Integer> future = requests.getAsync(pageURL,
					new AsyncCompletionHandler<Integer>() {
						@Override
						public Integer onCompleted(Response r) throws Exception {
							String html = r.getResponseBody();
							parseURLsFromPage(project, html, dirURLs, fileURLs);
							return r.getStatusCode();
						}
					});
			futures.add(future);
			if (dirURLs.isEmpty()) {
				for (Future<Integer> f : futures) {
					f.get();
					if (!dirURLs.isEmpty()) {
						break; // don't need to sync wait other requests
					}
				}
			}
		}
		return fileURLs;
	}

	private void downloadAndSaveFile(String projectName, String url,
			InputStream is, File destination) throws IOException {
		url = decodeURL(url);
		String fileSeparator = File.separator;
		String[] folders = url.substring(
				url.indexOf("/files/") + "/files/".length(),
				url.indexOf("/download")).split("/");
		String filename = folders[folders.length - 1];
		folders = Arrays.copyOf(folders, folders.length - 1);
		String path = destination.getAbsolutePath() + fileSeparator
				+ projectName;
		String relativePath = "/" + projectName;

		File pathF = new File(path);
		if (!pathF.exists()) {
			pathF.mkdir();
		}

		for (String folder : folders) {
			path = path + fileSeparator + folder;
			relativePath = relativePath + "/" + folder;
			File f = new File(path);
			if (!f.exists()) {
				f.mkdir();
			}
		}

		path = path + fileSeparator + filename;
		relativePath = relativePath + "/" + filename;

		File file = new File(path);
		FileOutputStream out = new FileOutputStream(file);
		int read = 0;
		byte[] bytes = new byte[1024];
		while ((read = is.read(bytes)) != -1) {
			out.write(bytes, 0, read);
		}
		out.close();

		file.setLastModified(mapModifiedDate.get(relativePath).getTime());
	}

	@Override
	public File downloadProject(Project project) throws DownloadException {
		String projectName = project.getName();
		try {
			List<String> urls = getDownloadURLs(projectName);
	
			for (String url : urls) {
				InputStream is = requests.download(url);
				downloadAndSaveFile(projectName, url, is, destinationFolder);
			}
			return new File(destinationFolder, projectName);

		} catch (IOException | InterruptedException | ExecutionException e) {
			throw new DownloadException(e);
		}
	}
} public abstract class ForgeCrawler {
	
	/**
	 * Downloads a single project and returns its repository folder. A project
	 * repository folder is useful to be manipulated by {@link CodeHistory}
	 * class and to be parsed by {@link Parser} class.
	 * 
	 * @param a project
	 * 
	 * @return a repository folder
	 * @throws DownloadException
	 *             when something nasty happens
	 */
	public abstract File downloadProject(Project project) throws DownloadException;
	
	/**
	 * Downloads the given list of projects, returning the files objects. Each
	 * file represents the repository folder of a project, according to
	 * ForgeProject list ordering. A project repository folder is useful to be
	 * manipulated by {@link CodeHistory} class and to be parsed by Parser class.
	 * 
	 * @param list of Projects
	 * @return list of repository folders as File objects
	 */
	public List<File> downloadProjects(List<Project> projects) {
		List<Future<File>> futures = asyncDownloadProjects(projects);
		
		try {
			List<File> files = new ArrayList<>();
			for (Future<File> future : futures) {
					File f = future.get();
					files.add(f);
			}
			return files;
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
			throw new DownloadException(e.getLocalizedMessage());
		}
	}

	/**
	 * Downloads the given list of projects, returning futures with files objects.
	 * Each file represents the repository folder of a project,
	 * according to ForgeProject list ordering. To get a future result, 
	 * just call its .get() method. A project repository folder is useful to be
	 * manipulated by {@link CodeHistory} class and to be parsed by {@link Parser} class.
	 * 
	 * @param list of Projects, usually given by a ForgeSearch subclass
	 * @return list of futures with repository folders as File objects
	 */
	public List<Future<File>> asyncDownloadProjects(List<Project> projects) {
		ExecutorService ex = Executors.newCachedThreadPool();
		
		List<Future<File>> fs = new ArrayList<Future<File>>();
		
		for (final Project p : projects) {
			Future<File> f = ex.submit(new Callable<File>() {
				public File call() throws Exception {
					return downloadProject(p);
				}
			});
			fs.add(f);
		}
		
		ex.shutdown();
		
		return fs;
	}
} public class GroundhogDB {
	private final String dbName;
	private final MongoClient mongo;
    private final Datastore datastore;
    
	public GroundhogDB(String host, String dbName) throws UnknownHostException {
		this.dbName = dbName;
		this.mongo = new MongoClient(host);
		this.datastore = new Morphia().createDatastore(this.mongo, dbName);
	}
	
	public GroundhogDB(MongoClient mongo, String dbName) throws UnknownHostException {
		this.dbName = dbName;
		this.mongo = null;
		this.datastore = new Morphia().createDatastore(mongo, dbName);
	}
	
	public static void query(GitHubEntity entity, String params) {
		String entityName = entity.getClass().getSimpleName().toLowerCase();
	}
	
	/**
	 * Receives a GitHub entity object and persists it to the database
	 * @param entity, a {@link Object} representing one of the many GitHub entities covered by Groundhog
	 */
	public void save(Object entity) {
		this.datastore.save(entity);
	}

	public String getDbName() {
		return dbName;
	}

	public MongoClient getMongo() {
		return this.mongo;
	}

	public Datastore getDatastore() {
		return this.datastore;
	}
} 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);
	}
} public class JavaParser implements Parser<HashMap<String, HashMap<String, MutableInt>>> {
	
	private static Logger logger = LoggerFactory.getLogger(JavaParser.class);
	
	private final File folder;  // this folder means the root folder of the downloaded project
	private final List<File> filesList;

	/**
	 * Constructs a new JavaParser which will extract metrics of all Java source
	 * files inside the given folder hierarchy.
	 * 
	 * @param a source folder
	 */
	public JavaParser(File folder) {
		this.folder = folder;
		this.filesList = new LinkedList<File>();
	}

	private void searchForJavaFiles(File root) {
		File files[] = root.listFiles();

		if (files != null) {
			for (File f : files) {
				
				if(!f.getName().startsWith(".")){
					if (f.isDirectory()) {
						searchForJavaFiles(f);
					} else if (f.isFile()) {
						String path = f.getAbsolutePath();
						if (!path.contains(File.separator + "__MACOSX") && f.getName().endsWith(".java")) {
							filesList.add(f);	
						}
					}
				}
			}
		}
	}
	
	private HashMap<String, HashMap<String, MutableInt>> invokeProcessor() throws IOException {
		if (filesList.isEmpty()) {
			return new HashMap<String, HashMap<String, MutableInt>> ();
		} 
		
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
		Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(filesList);
		
		List<String> options = new ArrayList<String>();
		options.add("-g:none"); // Do not generate any debugging information
		options.add("-nowarn"); // Disable warning messages
		options.add("-implicit:none"); // Suppress class file generation
		options.add("-proc:only"); // Only annotation processing is done, without any subsequent compilation
		
		CompilationTask task = compiler.getTask(null, fileManager, null, options, null, compilationUnits);
		CodeAnalyzerProcessor processor = new CodeAnalyzerProcessor();
		task.setProcessors(Arrays.asList(processor));
		task.call();
		fileManager.close();
		
		return processor.getCounters();
	}
	
	/**
	 * Parses all Java source files inside this.folder and returns extracted
	 * metrics.
	 * 
	 * @return a map of metrics to another map of metric value and count.
	 */
	public HashMap<String, HashMap<String, MutableInt>> parser() {
		logger.info("Running java parser..");
		searchForJavaFiles(folder);
		try {
			return invokeProcessor();
		} catch (IOException e) {
			throw new ParserException(e.getMessage());
		}
	}
} public class MutableInt {
	int value;
	
	public MutableInt() {
		value = 1;
	}
	
	public MutableInt(int value) {
		this.value = value;
	}

	public void increment() {
		++value;
	}

	public int get() {
		return value;
	}
	
	@Override
	public String toString() {
		return String.valueOf(value);
	}
} public class CSVFormater extends Formater {

	@Override
	public String format(HashMap<String, HashMap<String, MutableInt>> object) {
		StringWriter result = new StringWriter();
		try {
			CSVWriter writer = new CSVWriter(result, ';');
			writer.writeNext(new String[] { "Metric", "Entry", "Value" });
			for (String metric : object.keySet()) {
				HashMap<String, MutableInt> counter = object.get(metric);
				for (Entry<String, MutableInt> entry : counter.entrySet()) {
					writer.writeNext(new String[] { metric, entry.getKey(), entry.getValue().toString() });
				}
			}
			writer.flush();
			writer.close();
			return result.toString();
		} catch (IOException e) {
			e.printStackTrace();
			throw new ParserException();
		}
	}
} public class FormaterFactory {

	/**
	 * Factory method to create a {@link Formater} object
	 * @param formater
	 * @return
	 */
	public static Formater get(Class<? extends Formater> formater) {
		try {
			return formater.newInstance();
		} catch (Exception e) {
			String msg = String.format("I did not reconginze this output format (%s) :( I can only format in CSV or JSON", formater.getSimpleName());
			throw new GroundhogException(msg, e);
		}
	}
} public class JSONFormater extends Formater {

	@Override
	public String format(HashMap<String, HashMap<String, MutableInt>> object) {
		Gson gson = new GsonBuilder().serializeNulls().create();
		return gson.toJson(object);
	}
} public class GitClient {

	/**
	 * Performs a clone operation for the given project URL and places the
	 * fetched code into the destination directory.
	 * 
	 * @param url
	 *            the project's URL
	 * @param destination
	 */
	public void clone(final String url, final File destination) {
		ExecutorService executor = Executors.newSingleThreadExecutor();
		executor.execute(new Runnable() {
			public void run() {
				try {
					Git git = Git.cloneRepository().setURI(url).setDirectory(destination).call();
					git.getRepository().close();
				} catch (InvalidRemoteException e) {
					e.printStackTrace();
				} catch (TransportException e) {
					e.printStackTrace();
				} catch (GitAPIException e) {
					e.printStackTrace();
				}
			}
		});
		executor.shutdown();
		
		try {
			executor.awaitTermination(10, TimeUnit.MINUTES);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Points to head commit on master branch
	 * 
	 * @param repositoryFolder
	 */
	public void checkout(File repositoryFolder) {
		try {
			Git git = Git.open(repositoryFolder);
			Repository repo = git.getRepository();

			CommitFinder finder = new CommitFinder(repo);
			CommitCountFilter count = new CommitCountFilter();
			finder.setFilter(new AndCommitFilter(new CommitFilter() {

				public boolean include(RevWalk walker, RevCommit cmit)
						throws IOException {
					throw StopWalkException.INSTANCE;
				}

				public RevFilter clone() {
					return this;
				}
			}, count));
			finder.find();

			System.out.println(count.getCount());

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Performs a checkout to the given Git repository
	 * 
	 * @param repositoryFolder
	 *            the repository where the checkout will be performed
	 * @param date
	 * @throws IOException
	 * @throws RefAlreadyExistsException
	 * @throws InvalidRefNameException
	 * @throws CheckoutConflictException
	 * @throws GitAPIException
	 * @throws EmptyProjectAtDateException
	 */
	public void checkout(File repositoryFolder, Date date) throws IOException,
			RefAlreadyExistsException, InvalidRefNameException,
			CheckoutConflictException, GitAPIException,
			EmptyProjectAtDateException {

		Git git = Git.open(repositoryFolder);
		Repository rep = git.getRepository();
		CommitFinder finder = new CommitFinder(rep);
		final List<RevCommit> commits = new ArrayList<RevCommit>();

		finder.setFilter(new CommitterDateFilter(date).negate());
		AllCommitFilter filter = new AllCommitFilter() {
			@Override
			public boolean include(RevWalk walker, RevCommit commit)
					throws IOException {
				boolean include = super.include(walker, commit);
				if (include) {
					commits.add(commit);
				}
				return include;
			}
		};
		finder.setMatcher(filter);
		finder.find();

		if (commits.size() == 0) {
			rep.close();
			throw new EmptyProjectAtDateException(
					new Dates("yyyy-MM-dd").format(date));
		}

		RevCommit closest = Collections.max(commits,
				new Comparator<RevCommit>() {
					public int compare(RevCommit c1, RevCommit c2) {
						return c1.getCommitterIdent().getWhen()
								.compareTo(c2.getCommitterIdent().getWhen());
					}
				});

		/*
		 * Workaround ahead, since JGit in Windows automatically adds ^M
		 * (Carriage Returns) to some files after, leaving the working tree
		 * dirty. Neither JGit stash nor reset will work. So we need to commit!
		 * This commit doesn't affects metrics, since we do a checkout after it.
		 * To reproduce this bug, try to checkout
		 * https://github.com/playframework/ to 2012-05-01 12:00 TODO: report
		 * this bug to JGit team.
		 */
		Set<String> mods = git.status().call().getModified();
		if (!mods.isEmpty()) {
			AddCommand addCmd = git.add();
			for (String m : mods) {
				addCmd.addFilepattern(m);
			}
			addCmd.call();
			git.commit().setMessage("Groundhog commit").call();
		}
		/* workaround end. */

		git.checkout().setName("groundhog-analyze").setStartPoint(closest)
				.setCreateBranch(true).call();
		rep.close();
	}
} public class ScmModule extends AbstractModule {

	@Override
	protected void configure() {
		bind(GitClient.class).in(Singleton.class);
		bind(SVNClient.class).in(Singleton.class);
	}
} 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);

				p.setOwner(user);
				projects.add(p);
			}

			return projects;

		} catch (GroundhogException e) {
			e.printStackTrace();
			throw new SearchException(e);
		}
	}

	/**
	 * Obtains from the GitHub API the set of projects with more than one language
	 * 
	 * @param Start indicates the desired page
	 * @param limit the total of projects that will be returned
	 * @throws SearchException
	 */
	public List<Project> getProjectsWithMoreThanOneLanguage(int limit) throws SearchException {
		
		try {
			logger.info("Searching project with more than one language metadata");
			
			List<Project> rawData = getAllProjects(0, limit);

			List<Project> projects = new ArrayList<>();
			for (Project project : rawData) {
				List<Language> languages = getProjectLanguages(project);

				if (languages.size() > 1) {
					projects.add(project);
				}
			}

			return projects;

		} catch (GroundhogException e) {
			e.printStackTrace();
			throw new SearchException(e);
		}
	}

	/**
	 * Obtains from the GitHub API the set of projects within a specific
	 * language
	 * 
	 * @param lang the specific language
	 */
	public List<Project> getAllProjectsByLanguage(String lang) throws SearchException {

		logger.info("Searching all project by language metadata");
		
		String searchUrl = builder.uses(GithubAPI.LEGACY_V2).withSimpleParam("language=", lang).build();
		String json = requests.get(searchUrl);

		JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
		JsonArray jsonArray= jsonObject.get("repositories").getAsJsonArray();

		List<Project> projects = new ArrayList<>();
		for (JsonElement element : jsonArray) {
			Project p = gson.fromJson(element, Project.class);
			String owner = element.getAsJsonObject().get("owner").getAsString();
			p.setUser(new User(owner));
			projects.add(p);
		}

		return projects;
	}

	/**
	 * Obtains from the GitHub API the set of projects
	 * 
	 * @param Start indicates the desired page
	 * @param limit is the total of projects that are going to me returned 
	 * @throws SearchException
	 */
	public List<Project> getAllProjects(int start, int limit) throws SearchException {

		try {

			int since = start;
			int totalRepositories = 0;

			List<Project> projects = new ArrayList<>();
			JsonParser parser = new JsonParser();
			while(totalRepositories < limit || limit < 0){

				String searchUrl = builder.uses(GithubAPI.REPOSITORIES)
										  .withParam("language", "java")
										  .withParam("since", since)
										  .build();
				
				String response = getWithProtection(searchUrl);
				JsonArray jsonArray = parser.parse(response).getAsJsonArray();

				int counter = 0;

				for (Iterator<JsonElement> iterator = jsonArray.iterator(); (iterator
						.hasNext() && (totalRepositories + counter < limit || limit < 0));) {

					JsonElement element = (JsonElement) iterator.next();

					String repoName = element.getAsJsonObject().get("name").getAsString();	
					String searchUrlLegacy = builder.uses(GithubAPI.LEGACY_V2).withParam(repoName).build();
					
					String jsonLegacy = getWithProtection(searchUrlLegacy);
					JsonElement jsonElement = parser.parse(jsonLegacy);
					JsonObject jsonObject = jsonElement.getAsJsonObject();
					JsonArray jsonArrayLegacy = jsonObject.get("repositories").getAsJsonArray();

					if(jsonArrayLegacy.size() > 0) {
						JsonObject rawJsonObject = jsonArrayLegacy.get(0).getAsJsonObject();

						String stringElement = rawJsonObject.toString();
						Project p = gson.fromJson(stringElement, Project.class);

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

						projects.add(p);

						counter++;
						totalRepositories++;
					}
				}

				JsonElement lastPagesRepository = jsonArray.get(jsonArray.size() -1);
				since = lastPagesRepository.getAsJsonObject().get("id").getAsInt();
			}
			return projects;

		} catch (Exception e) {
			e.printStackTrace();
			throw new SearchException(e);
		}
	}

	@Override
	public List<Project> getProjects(String term, String username, int page) throws SearchException {
		try {
			logger.info("Searching project metadata by term");
			
			List<Project> projects = new ArrayList<>();
			
			String searchUrl = builder.uses(GithubAPI.LEGACY_V2)
					  .withParam(encodeURL(term))
					  .withParam("start_page", page)
					  .withParam("language", "java")
					  .build();

			String jsonLegacy = getWithProtection(searchUrl);
			JsonElement jsonElement = new JsonParser().parse(jsonLegacy);
			
			JsonObject jsonObject = jsonElement.getAsJsonObject();
			JsonArray jsonArray = jsonObject.get("repositories").getAsJsonArray();
			
			for (JsonElement j: jsonArray) {
				Project p = gson.fromJson(j, Project.class);
				JsonObject jsonObj = j.getAsJsonObject();	
				
				p.setCheckoutURL(String.format("git@github.com:%s/%s.git", username, p.getName()));
				User u = new User(jsonObj.get("owner").getAsString());
				p.setUser(u);
				
				projects.add(p);
			}

			return projects;
		} catch (Throwable e) {
			e.printStackTrace();
			throw new SearchException(e);
		}
	}

	/**
	 * Obtains from the GitHub API the set of languages that compose a
	 * {@link Project}
	 * 
	 * @param project
	 *            a {@link Project} object to have its languages fetched
	 */
	public List<Language> getProjectLanguages(Project project) {

	  String searchUrl = builder.uses(GithubAPI.ROOT)
			  .withParam("repos")
			  .withSimpleParam("/", project.getUser().getLogin())
			  .withSimpleParam("/", project.getName())
			  .withParam("/languages")
			  .build();

		String json = requests.get(searchUrl).replace("{", "").replace("}", "");

		List<Language> languages = new ArrayList<>();
		if(!json.equalsIgnoreCase("{}")){
			for (String str: json.split(",")) {
				String[] hash = str.split(":");
				String key = hash[0].trim().replaceAll("\"", "");
				Integer value = Integer.parseInt(hash[1].trim());
				Language lang = new Language(key, value);
				languages.add(lang);
			}
		}

		return languages;
	}

	/**
	 * Fetches all the Issues of the given {@link Project} from the GitHub API
	 * 
	 * @param project the @{link Project} of which the Issues are about
	 * @return a {@link List} of {@link Issues} objects
	 */
	public List<Issue> getAllProjectIssues(Project project) {

		logger.info("Searching project issues metadata");
		
		String searchUrl = builder.uses(GithubAPI.ROOT)
				  .withParam("repos")
				  .withSimpleParam("/", project.getOwner())
				  .withSimpleParam("/", project.getName())
				  .withParam("/issues")
				  .build();
				
		String jsonString = requests.get(searchUrl);
		JsonArray jsonArray = gson.fromJson(jsonString, JsonElement.class).getAsJsonArray();

		List<Issue> issues = new ArrayList<Issue>();
		List<IssueLabel> labels = new ArrayList<IssueLabel>();
		
		for (JsonElement element : jsonArray) {
			Issue issue = gson.fromJson(element, Issue.class);
			issue.setProject(project);
			
			for (JsonElement lab : element.getAsJsonObject().get("labels").getAsJsonArray()) {
				IssueLabel label = gson.fromJson(lab, IssueLabel.class);				
				labels.add(label);
			}
			
			issue.setLabels(labels);
			issues.add(issue);
		}

		return issues;
	}

	/**
	 * Fetches all the Milestones of the given {@link Project} from the GitHub API
	 * @param project the @{link Project} of which the Milestones are about
	 * @return a {@link List} of {@link Milestone} objects
	 */
	public List<Milestone> getAllProjectMilestones(Project project) {
		logger.info("Searching project milestones metadata");
		
		String searchUrl = builder.uses(GithubAPI.ROOT)
				  .withParam("repos")
				  .withSimpleParam("/", project.getUser().getLogin())
				  .withSimpleParam("/", project.getName())
				  .withParam("/milestones")
				  .build();
		
		String jsonString = requests.get(searchUrl);
		JsonArray jsonArray = gson.fromJson(jsonString, JsonElement.class).getAsJsonArray();

		List<Milestone> milestones = new ArrayList<>();
		
		for (JsonElement element : jsonArray) {
			Milestone milestone = gson.fromJson(element, Milestone.class);
			milestone.setProject(project);
			
			milestones.add(milestone);
		}

		return milestones;
	}
	
	/**
	 * Fetches all the Tags of the given {@link Project} from the GitHub API
	 * @param project the @{link Project} of which the Milestones are about
	 * @return a the size of the {@link List} of {@link Tags} objects
	 */
	public int getNumberProjectTags(Project project) {

		logger.info("Searching project tags metadata");
		
		String searchUrl = builder.uses(GithubAPI.ROOT)
				  .withParam("repos")
				  .withSimpleParam("/", project.getUser().getLogin())
				  .withSimpleParam("/", project.getName())
				  .withParam("/git/refs/tags")
				  .build();

		String jsonString = getWithProtection(searchUrl);

		int retorno = 0;
		
		if(!jsonString.contains("Not Found") && !jsonString.contains("Git Repository is empty.")){

			try {
				JsonElement element =gson.fromJson(jsonString, JsonElement.class);
				
				if(element.isJsonArray()){
					
					JsonArray jsonArray = element.getAsJsonArray();
					
					retorno = jsonArray.size();
				}
			} catch (Exception e) {
				e.printStackTrace();
				throw new GroundhogException(jsonString);
			}	
		}
		
		return retorno;
	}

	/**
	 * Fetches all the Commits of the given {@link Project} from the GitHub API
	 * @param project the @{link Project} to which the commits belong
	 * @return a {@link List} of {@link Commit} objects
	 */
	public List<Commit> getAllProjectCommits(Project project) {
		logger.info("Searching project commits metadata");
		
		String searchUrl = builder.uses(GithubAPI.ROOT)
				  .withParam("repos")
				  .withSimpleParam("/", project.getOwner().getLogin())
				  .withSimpleParam("/", project.getName())
				  .withParam("/commits")
				  .build();
		
		System.out.println(searchUrl);

		JsonElement jsonElement = gson.fromJson(requests.get(searchUrl), JsonElement.class);
		JsonArray jsonArray = jsonElement.getAsJsonArray();

		List<Commit> commits = new ArrayList<>();
		for (JsonElement element : jsonArray) {
			Commit commit = gson.fromJson(element, Commit.class);
			commit.setProject(project);
			
			User user = gson.fromJson(element.getAsJsonObject().get("committer"), User.class);
			commit.setCommiter(user);
			
			commit.setMessage(element.getAsJsonObject().get("commit").getAsJsonObject().get("message").getAsString());

			String date = element.getAsJsonObject().get("commit").getAsJsonObject().get("author").getAsJsonObject().get("date").getAsString();
			commit.setCommitDate(date);
			commits.add(commit);
		}

		return commits;
	}

	/**
	 * Fetches all the Commits of the given {@link Project} from the GitHub API
	 * @param project the @{link Project} to which the commits belong
	 * @return a {@link List} of {@link Commit} objects
	 */
	public List<Commit> getAllProjectCommitsByDate(Project project, String start, String end) {

		logger.info("Searching all project commits metadata by date");
		
		String searchUrl = builder.uses(GithubAPI.ROOT)
				  .withParam("repos")
				  .withSimpleParam("/", project.getUser().getLogin())
				  .withSimpleParam("/", project.getName())
				  .withSimpleParam("/", "commits")
				  .withParam("since", start)
				  .withParam("until", end)
				  .build();
		
		String response = getWithProtection(searchUrl);

		JsonElement jsonElement = gson.fromJson(response, JsonElement.class);
		JsonArray jsonArray = jsonElement.getAsJsonArray();

		List<Commit> commits = new ArrayList<>();
		for (JsonElement element : jsonArray) {
			Commit commit = gson.fromJson(element, Commit.class);
			commit.setProject(project);

			String date = element.getAsJsonObject().get("commit").getAsJsonObject().get("author").getAsJsonObject().get("date").getAsString();
			commit.setCommitDate(date);
			commits.add(commit);
		}

		return commits;
	}

	/**
	 * <p>Fetches all the contributors of the given {@link Project} from the GitHub API</p>
	 * @see <p>We can map all {@link Contributor} into a {@link User} by {@link Contributor#getUrl} method that return is the same for a Contributor and his correspondent User ({@link User#getUrl})</p>
	 * @param project the @{link Project} to get the contributors from
	 * @return a {@link List} of {@link Contributor} objects
	 */
	public List<Contributor> getAllProjectContributors(Project project) {
		logger.info("Searching project contributors metadata");
		
		
		List<Contributor> contributors = new ArrayList<>();
		String searchUrl = builder.uses(GithubAPI.ROOT)
				  .withParam("repos")
				  .withSimpleParam("/", project.getUser().getLogin())
				  .withSimpleParam("/", project.getName())
				  .withParam("/contributors")
				  .build();
		
		String jsonString = requests.getWithPreviewHeader(searchUrl);
        JsonArray jsonArray = gson.fromJson(jsonString, JsonElement.class).getAsJsonArray();
        
		for (JsonElement element: jsonArray) {
        	Contributor contributor = gson.fromJson(element, Contributor.class);
        	contributors.add(contributor);
        }

		return contributors;
	}

	public List<Project> getAllForgeProjects(int start, int limit) throws SearchException{
		try{
			logger.info("Searching all projects metadata");
			
			int since = start;
			int totalRepositories = 0;
			List<Project> projects = new ArrayList<>();
			
			while (totalRepositories < limit || limit < 0) {

				String searchUrl = builder.uses(GithubAPI.REPOSITORIES)
										  .withParam("since", since)
										  .withParam("language", "java")
										  .build();
				
				String jsonString = requests.get(searchUrl);
				JsonArray jsonArray = gson.fromJson(jsonString, JsonElement.class).getAsJsonArray();
				for (int i = 0; i < jsonArray.size() && 
						(totalRepositories + i < limit || limit < 0); i++) {

					String repoName = jsonArray.get(i).getAsJsonObject().get("name").getAsString();					
					String searchUrlLegacy = builder.uses(GithubAPI.LEGACY_V2)
													  .withParam(repoName)
													  .withParam("language", "java")
													  .build();
					
					String jsonLegacy = requests.get(searchUrlLegacy);

					JsonObject jsonObject = gson.fromJson(jsonLegacy, JsonElement.class).getAsJsonObject();			
					JsonArray jsonArrayLegacy = jsonObject.get("repositories").getAsJsonArray();

					if (jsonArrayLegacy.size() > 0) {
						JsonObject rawJsonObject = jsonArrayLegacy.get(0).getAsJsonObject();
						String stringElement = rawJsonObject.toString(); // verify here
						
						Project p = gson.fromJson(stringElement, Project.class);

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

						projects.add(p);
						totalRepositories++;
					}
				}
				
				JsonElement lastPagesRepository = jsonArray.get(jsonArray.size() -1);
				since = lastPagesRepository.getAsJsonObject().get("id").getAsInt();
			}
			return projects;
		} catch (GroundhogException e) {
			e.printStackTrace();
			throw new SearchException(e);
		}
	}
	
	/**
	 * Gets the top most Used languages among the projects considering the most used language
	 * in each project. 
	 * @param projects List of projects into consideration
	 * @param limit Limits the size of the returning list
	 * @return sorted list with the top most used languages
	 */
	public List<Language> getTopMostUsedLanguages(List<Project> projects, int limit){
		List<Language> topLanguages = new ArrayList<>();
		Map<String, Integer> languages = new HashMap<>(); 
		for (Project project: projects) {			
			String language = project.getLanguage();
			Integer count = 1;
			
			if (languages.containsKey(language)){ 
				count += languages.get(language);
			}						   
			
			languages.put(language, count);

		}
		for (Entry<String, Integer> language : languages.entrySet()) {
			topLanguages.add(new Language(language.getKey(), language.getValue()));
		}

		Collections.sort(topLanguages);
		if(limit < 0) limit = 0;
		
		topLanguages = topLanguages.subList(0, Math.min(limit, topLanguages.size()));
		return topLanguages;		
	}


	/**
	 * Gets the top most used languages among the projects according to the number 
	 * of LOC (lines of code) that they appear. 
	 * @param projects {@link List} of projects into consideration
	 * @param limit for limiting (upper bound) the size of the returning list
	 * @return sorted list with the top most used languages
	 */
	public List<Language> getTopMostUsedLanguagesLoc(List<Project> projects, int limit){
		List<Language> topLanguages = new ArrayList<>();
		Map<String, Integer> languages = new HashMap<>(); 
		
		for (Project project: projects){
			if (project.getLanguages() == null) {
				throw new GroundhogException("languages information required");
			}
			
			for (Language language : project.getLanguages()) {
				Integer newLoc = language.getByteCount();
				
				if (languages.containsKey(language.getName())) {
					newLoc += languages.get(language.getName());
				}
				
				languages.put(language.getName(), newLoc);
			}
		}
		
		for (Entry<String, Integer> language : languages.entrySet()) {
			topLanguages.add(new Language(language.getKey(), language.getValue()));
		}

		Collections.sort(topLanguages);
		if (limit < 0 ) limit = 0;
		
		topLanguages = topLanguages.subList(0, Math.min(limit, topLanguages.size()));
		return topLanguages;	
	}
	
	/**
	 * Fetches all the Releases of the given {@link Project} from the GitHub API
	 * 
	 * @param project the @{link Project} of which the Releases are about
	 * @return a {@link List} of {@link Release} objects
	 */
	public List<Release> getAllProjectReleases(Project project) {

		logger.info("Searching project releases metadata");
		
		String searchUrl = builder.uses(GithubAPI.ROOT)
				  .withParam("repos")
				  .withSimpleParam("/", project.getUser().getLogin())
				  .withSimpleParam("/", project.getName())
				  .withParam("/releases")
				  .build();
		
		String jsonString = requests.getWithPreviewHeader(searchUrl);
		JsonArray jsonArray = gson.fromJson(jsonString, JsonElement.class).getAsJsonArray();

		List<Release> releases = new ArrayList<>();
		for (JsonElement element : jsonArray) {
			Release release = gson.fromJson(element, Release.class);
			release.setProject(project);
			
			releases.add(release);
		}

		return releases;
	
	}

	private String getWithProtection(String url){
		String data = requests.get(url);

		if (data.contains("API rate limit exceeded for")) {
			try {
				Thread.sleep(1000 * 60 * 60);
				data = requests.get(url);

			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		}

		return data;
	}
} public class SearchGoogleCode implements ForgeSearch {
	private static String root = "http://code.google.com";
	private final Requests requests;

	@Inject
	public SearchGoogleCode(Requests requests) {
		this.requests = requests;
	}

	/**
	 * Fetches and returns the checkout command String for the project
	 * @param html the HTML content of the page to be parsed
	 * @return the checkout command within the given HTML page
	 */
	private String parseCheckoutCommand(String html) {
		Document doc = Jsoup.parse(html);
		Elements es = doc.select("#checkoutcmd");

		return es.isEmpty() ? "" : es.first().text();
	}

	/**
	 * Sets the checkout command of the project according to its SCM tool
	 * @param command the checkout command String
	 * @param project the project to which the checkout must be applied
	 */
	private void setCheckoutCommandToProject(String command, Project project) {
		String url = command.split(" ")[2];
		project.setCheckoutURL(url);

		if (command.startsWith("svn")) {
			project.setSCM(SCM.SVN);
		} else if (command.startsWith("git")) {
			project.setSCM(SCM.GIT);
		} else if (command.startsWith("hg")) {
			project.setSCM(SCM.HG);
		} else if (command.equals("")) {
			project.setSCM(SCM.NONE);
		} else {
			project.setSCM(SCM.UNKNOWN);
		}
	}

	public List<Project> getProjects(String term, int page, int limit) throws SearchException {
		if( term == null){
			return getAllForgeProjects(page, limit);
		}
		try {
			List<Project> projects = new ArrayList<Project>();
			String params = new ParamBuilder()
									.add("q", term + " label:Java")
									.add("start", String.valueOf((page - 1) * 10))
									.build();

			Document doc = Jsoup.parse(requests.get(root + "/hosting/search?" + params));
			int cont = 0;
			for (Element tr : doc.select("#serp table tbody tr")) {
				if ( cont >= limit && limit >=0 ) break;

				Element el = tr.child(0).child(0);

				// The span element within the main search result that contains the number
				// of people watching the project on Google Code
				Element span = tr.child(1).child(2).child(0);

				String projectName = el.attr("href").split("/")[2];
				String description = tr.child(1).ownText();

				String sourceCodeUrl = "https://code.google.com/p/" + projectName + "/source/browse/";

				Project forgeProject = new Project(projectName, description, sourceCodeUrl);

				int stars = Integer.parseInt(span.text());
				forgeProject.setWatchersCount(stars);
				projects.add(forgeProject);
				cont++;
			}

			// get checkout commands for each project in parallel (asynchronously)
			List<Future<Integer>> futures = new ArrayList<Future<Integer>>();
			for (final Project forgeProject : projects) {
				String projectName = forgeProject.getName();
				String checkoutPageURL = String.format("http://code.google.com/p/%s/source/checkout", projectName);
				Future<Integer> f = requests.getAsync(checkoutPageURL, new AsyncCompletionHandler<Integer>() {
					@Override
					public Integer onCompleted(Response response) throws Exception {
						String command = parseCheckoutCommand(response.getResponseBody());
						setCheckoutCommandToProject(command, forgeProject);
						return response.getStatusCode();
					}
				});

				futures.add(f);
			}

			// wait for all futures to have all
			for (Future<Integer> f : futures) {
				f.get();
			}
			return projects;
		} catch (Exception e) {
			throw new SearchException(e);
		}
	}

	@Override
	public List<Project> getProjects(String term, String username, int page)
			throws SearchException {
		throw new UnsupportedOperationException("not implemented yet");
	}

	public List<Project> getAllForgeProjects(int start, int limit)
			throws SearchException {
		throw new UnsupportedOperationException("not implemented yet");
	}

	@Override
	public List<Issue> getAllProjectIssues(Project gr) throws IOException {
		// TODO Auto-generated method stub
		return null;
	}
} public class SearchModule extends AbstractModule {

	@Override
	protected void configure() {
		bind(SearchGitHub.class).in(Singleton.class);
		bind(SearchGoogleCode.class).in(Singleton.class);
		bind(SearchSourceForge.class).in(Singleton.class);
	}
} public class SearchSourceForge implements ForgeSearch {
	
	private final Requests requests;
	
	@Inject
	public SearchSourceForge(Requests requests) {	
		this.requests = requests;
	}
	
	public List<Project> getProjects(String term, int page, int limit ) throws SearchException {
		if (term == null) {
			return getAllForgeProjects(page, limit);
		}
		
		try {
			String paramsStr = new ParamBuilder()
									.add("q", term)
									.add("sort", "popular")
									.add("page", String.valueOf(page))
									.build();
			
			Document doc = Jsoup.parse(requests.get("http://sourceforge.net/directory/language:java/?" + paramsStr));
			
			List<Project> projects = new ArrayList<Project>();
			int cont = 0;
			
			for (Element li: doc.select(".projects > li")) {
				if(cont >= limit && limit >=0 ) break;
				Element a = li.select("[itemprop=url]").first();
			    
				if (a != null) {
					String projectName, description, projectURL;
					
					projectName = a.attr("href").split("/")[2];
					description = li.select("[itemprop=description]").first().text();
					projectURL = String.format("http://sourceforge.net/projects/%s/files/", projectName);
					
					Project forgeProject = new Project(projectName, description, projectURL);
					projects.add(forgeProject);
					cont++;
				}
			}
			
			return projects;
		} catch (Exception e) {
			e.printStackTrace();
			throw new SearchException(e);
		}
	}

	@Override
	public List<Project> getProjects(String term, String username, int page) throws SearchException {
		throw new UnsupportedOperationException("not implemented yet");
	}
	
	// yet to be implemented
	@Override
	public List<Project> getAllForgeProjects(int start, int limit)
			throws SearchException {
		throw new UnsupportedOperationException("not implemented yet");
	}

	@Override
	public List<Issue> getAllProjectIssues(Project gr) throws IOException {
		// TODO Auto-generated method stub
		return null;
	}
} public class FileUtil {

	private static Logger logger = LoggerFactory.getLogger(FileUtil.class);
	
	
	private static FileUtil instance;
	private List<File> createdTempDirs;

	public static FileUtil getInstance() {
		if (instance == null) {
			instance = new FileUtil();
		}
		return instance;
	}

	private FileUtil() {
		this.createdTempDirs = new ArrayList<File>();
	}

	/**
	 * 
	 * @return A File object representing the created temporary directory
	 */
	public synchronized File createTempDir() {
		try {
			File tempDir = Files.createTempDir();
			this.createdTempDirs.add(tempDir);
			return tempDir;
		} catch (Exception e) {
			e.printStackTrace();
			String error = "Unable to create temporary directory. Are you running groundhog with admin privileges?";
			logger.error(error);
			throw new GroundhogException(error);
		}
	}

	/**
	 * Deletes the temporary directories used to store the downloaded the
	 * projects.
	 * 
	 * @throws IOException
	 */
	public synchronized void deleteTempDirs() throws IOException {
		for (File tempDir : this.createdTempDirs) {
			if (tempDir.exists()) {
				FileUtils.deleteDirectory(tempDir);
			}
		}
	}

	/**
	 * 
	 * @param file
	 *            the file to which the string will be written to
	 * @param data
	 *            the string to be written to the file
	 * @throws IOException
	 */
	public void writeStringToFile(File file, String data) throws IOException {
		FileUtils.writeStringToFile(file, data);
	}

	/**
	 * Takes a directory and moves it to another directory
	 * 
	 * @param srcDir
	 *            the directory to be moved
	 * @param destDir
	 *            the destination directory
	 * @throws IOException
	 */
	public void copyDirectory(File srcDir, File destDir) throws IOException {
		FileUtils.copyDirectory(srcDir, destDir);
		// TODO: add tests
	}
	
	public String readAllLines(File file) {
		try {
			StringBuffer buffer = new StringBuffer();
			Scanner scanner = new Scanner(file);
			
			while(scanner.hasNextLine()){
				buffer.append(scanner.nextLine());
			}
			
			scanner.close();
			return buffer.toString();
		} catch (FileNotFoundException e) {
			e.printStackTrace();

			String error = String.format("Unable to read the file (%s) content", file.getAbsoluteFile());
			logger.error(error);
			throw new GroundhogException(error);
		}
	}
	
	public boolean isTextFile(final File file) {
		
		if(file.isDirectory()) {
			return false;
		}
		
		final int BUFFER_SIZE = 10 * 1024;
		boolean isText = true;
		byte[] buffer = new byte[BUFFER_SIZE];

		RandomAccessFile fis = null;
		try {
			fis = new RandomAccessFile(file, "r");
		
			fis.seek(0);
			final int read = fis.read(buffer);
			int lastByteTranslated = 0;
			for (int i = 0; i < read && isText; i++) {
				final byte b = buffer[i];
				int ub = b & (0xff);
				int utf8value = lastByteTranslated + ub;
				lastByteTranslated = (ub) << 8;

				if (ub == 0x09
						|| ub == 0x0A
						|| ub == 0x0C
						|| ub == 0x0D
						|| (ub >= 0x20 && ub <= 0x7E)
						|| (ub >= 0xA0 && ub <= 0xEE)
						|| (utf8value >= 0x2E2E && utf8value <= 0xC3BF)) {

				} else {
					isText = false;
				}
			}
			return isText;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		} finally {
			try {
				fis.close();
			} catch (final Throwable th) {
			}
		}
	}
}
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import br.ufpe.cin.groundhog.Commit;
import br.ufpe.cin.groundhog.Project;
import br.ufpe.cin.groundhog.SCM;
import br.ufpe.cin.groundhog.User;
import br.ufpe.cin.groundhog.codehistory.CodeHistoryModule;
import br.ufpe.cin.groundhog.codehistory.GitCodeHistory;
import br.ufpe.cin.groundhog.codehistory.SFCodeHistory;
import br.ufpe.cin.groundhog.crawler.CrawlGitHub;
import br.ufpe.cin.groundhog.crawler.CrawlGoogleCode;
import br.ufpe.cin.groundhog.crawler.CrawlSourceForge;
import br.ufpe.cin.groundhog.crawler.ForgeCrawler;
import br.ufpe.cin.groundhog.database.GroundhogDB;
import br.ufpe.cin.groundhog.http.HttpModule;
import br.ufpe.cin.groundhog.http.Requests;
import br.ufpe.cin.groundhog.parser.java.JavaParser;
import br.ufpe.cin.groundhog.parser.java.MutableInt;
import br.ufpe.cin.groundhog.parser.java.formater.CSVFormater;
import br.ufpe.cin.groundhog.parser.java.formater.FormaterFactory;
import br.ufpe.cin.groundhog.parser.java.formater.JSONFormater;
import br.ufpe.cin.groundhog.scmclient.GitClient;
import br.ufpe.cin.groundhog.scmclient.ScmModule;
import br.ufpe.cin.groundhog.search.SearchGitHub;
import br.ufpe.cin.groundhog.search.SearchGoogleCode;
import br.ufpe.cin.groundhog.search.SearchModule;
import br.ufpe.cin.groundhog.search.SearchSourceForge;
import br.ufpe.cin.groundhog.util.FileUtil;
import com.google.inject.Guice;
import com.google.inject.Injector;
package br.ufpe.cin.groundhog.main;





/**
 * The main test class
 * @author fjsj, gustavopinto, Rodrigo Alves
 * @since 0.0.1
 */
public class TestMain {
	private static Logger logger = LoggerFactory.getLogger(TestMain.class);
	static Injector injector = Guice.createInjector(new SearchModule(), new HttpModule());
	static SearchGitHub searchGitHub = injector.getInstance(SearchGitHub.class);

	/**
	 * Test method for search the GitHub forge
	 * @param term the search term (project name)
	 * @throws Exception
	 */
	public static void gitHubExample(String term) throws Exception {
		File downloadFolder = FileUtil.getInstance().createTempDir();
		
		//1 - Search for projects according to term...
		Injector injector = Guice.createInjector(new SearchModule(), new CodeHistoryModule(), new CodeHistoryModule(), new ScmModule(), new HttpModule());
		SearchGitHub search = injector.getInstance(SearchGitHub.class);
		
		List<Project> projects = search.getProjects(term, 1, 2);
		Project project = projects.get(0);
		projects = Arrays.asList(project); // analyze only the first project 
		
		//2 - Download 1st result...
		ForgeCrawler crawler = new CrawlGitHub(injector.getInstance(GitClient.class), downloadFolder);
		File repositoryFolder = crawler.downloadProject(projects.get(0));
		
		//3 - Checkout repository to a given date...
		Date date = new GregorianCalendar(2012, 07, 10).getTime();
		GitCodeHistory codeHistory = injector.getInstance(GitCodeHistory.class);
		File temp = codeHistory.checkoutToDate(project.getName(), repositoryFolder, date);

		//4 - Parse...
		HashMap<String, HashMap<String, MutableInt>> javaMetrics = new JavaParser(temp).parser();
		String metrics = FormaterFactory.get(JSONFormater.class).format(javaMetrics);
		System.out.println(metrics);
				
		try {
			FileUtil.getInstance().deleteTempDirs();
			logger.info("Cleaning up the garbage..");
		} catch (IOException e) {
			logger.error("Could not delete temp files :( (but they will be eventually deleted)");
		}
		
		logger.info("That is it! Groundhog is done!");
	}
	
	/**
	 * Test method for search the SourceForge forge
	 * @throws Exception
	 */
	public static void sourceForgeExample() throws Exception {
		File downloadFolder = FileUtil.getInstance().createTempDir();
		
		logger.info("1 - Search for projects according to term...");
		Injector injector = Guice.createInjector(new SearchModule(), new HttpModule(), new CodeHistoryModule());
		SearchSourceForge search = injector.getInstance(SearchSourceForge.class);
		
		List<Project> projects = search.getProjects("facebook chat", 1, -1);
		if (projects.size() == 0) {
			logger.info("Ooops, no projects found! Aborting.");
			System.exit(0);
		}
		Project project = projects.get(0);
		projects = Arrays.asList(project); // analyze only the first project 
		
		logger.info("2 - Download 1st result...");
		Requests requests = injector.getInstance(Requests.class);
		
		ForgeCrawler crawler = new CrawlSourceForge(requests, downloadFolder);
		List<Future<File>> futures = crawler.asyncDownloadProjects(projects);
		File repositoryFolder = null;
		for (Future<File> f : futures) { // wait for download
			repositoryFolder = f.get();
		}
		
		logger.info("3 - Checkout repository to a given date...");
		Date date = new GregorianCalendar(2012, 2, 21).getTime();
		SFCodeHistory codeHistory = injector.getInstance(SFCodeHistory.class);
		File temp = codeHistory.checkoutToDate(project.getName(), repositoryFolder, date);
		
		logger.info("4 - Parse...");
		HashMap<String, HashMap<String, MutableInt>> javaMetrics = new JavaParser(temp).parser();
		String metrics = FormaterFactory.get(CSVFormater.class).format(javaMetrics);
		System.out.println(metrics);
		
		try {
			FileUtil.getInstance().deleteTempDirs();
		} catch (IOException e) {
			logger.info("Could not delete temp files :( (but they will be eventually deleted)");
		}
	}
	
	/**
	 * Test method for search the Google Code forge
	 * @param term the search term (project name)
	 * @throws Exception
	 */
	public static void googleCodeExample(String term) throws Exception {
		File downloadFolder = FileUtil.getInstance().createTempDir();
		
		logger.info("1 - Search for projects according to term...");
		Injector injector = Guice.createInjector(new SearchModule(), new CodeHistoryModule(), new ScmModule());
		SearchGoogleCode search = injector.getInstance(SearchGoogleCode.class);
		
		List<Project> projects = search.getProjects(term, 1, -1);
		Project project = projects.get(0);
		projects = Arrays.asList(project); // analyze only the first project 
		
		logger.info("2 - Download 1st result...");
		ForgeCrawler crawler = new CrawlGoogleCode(injector.getInstance(GitClient.class), downloadFolder);