eu.trentorise.opendata.josman.Josmans.java Source code

Java tutorial

Introduction

Here is the source code for eu.trentorise.opendata.josman.Josmans.java

Source

package eu.trentorise.opendata.josman;

import static com.google.common.base.Preconditions.checkNotNull;
import eu.trentorise.opendata.commons.NotFoundException;
import eu.trentorise.opendata.commons.TodUtils;
import static eu.trentorise.opendata.commons.validation.Preconditions.checkNotEmpty;
import eu.trentorise.opendata.commons.SemVersion;
import static eu.trentorise.opendata.josman.JosmanProject.CHANGES_MD;
import static eu.trentorise.opendata.josman.JosmanProject.DOCS_FOLDER;
import static eu.trentorise.opendata.josman.JosmanProject.README_MD;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.eclipse.egit.github.core.Repository;
import org.eclipse.egit.github.core.RepositoryTag;
import org.eclipse.egit.github.core.client.GitHubClient;
import org.eclipse.egit.github.core.service.RepositoryService;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.parboiled.common.ImmutableList;

import com.google.common.io.Files;

/**
 * Utilities for Josman
 *
 * @author David Leoni
 */
public final class Josmans {

    private static final Logger LOG = Logger.getLogger(Josmans.class.getName());

    public static final int CONNECTION_TIMEOUT = 1000;

    private Josmans() {
    }

    /**
     * Reading file with Jgit:
     * https://github.com/centic9/jgit-cookbook/blob/master/src/main/java/org/dstadler/jgit/api/ReadFileFromCommit.java
     */
    /**
     * Fetches all tags from a github repository. Beware of API limits of 60
     * requests per hour
     */
    public static ImmutableList<RepositoryTag> fetchTags(String organization, String repoName) {
        TodUtils.checkNotEmpty(organization, "Invalid organization!");
        TodUtils.checkNotEmpty(repoName, "Invalid repo name!");

        LOG.log(Level.FINE, "Fetching {0}/{1} tags.", new Object[] { organization, repoName });

        try {
            GitHubClient client = new GitHubClient();
            RepositoryService service = new RepositoryService(client);
            Repository repo = service.getRepository(organization, repoName);
            List<RepositoryTag> tags = service.getTags(repo);
            return ImmutableList.copyOf(tags);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

    }

    /**
     * Reads a local project current branch.
     *
     * @param projectPath path to project root folder (the one that _contains_
     * the .git folder)
     * @return the current branch of provided repo
     */
    public static String readRepoCurrentBranch(File projectPath) {
        checkNotNull(projectPath);
        try {
            FileRepositoryBuilder builder = new FileRepositoryBuilder();
            org.eclipse.jgit.lib.Repository repo = builder.setGitDir(new File(projectPath, ".git"))
                    .readEnvironment() // scan environment GIT_* variables
                    .findGitDir() // scan up the file system tree
                    .build();
            return repo.getBranch();
        } catch (IOException ex) {
            throw new RuntimeException("Couldn't read current branch from " + projectPath.getAbsolutePath());
        }
    }

    /**
     * Returns major.minor as string
     */
    public static String majorMinor(SemVersion version) {
        return version.getMajor() + "." + version.getMinor();
    }

    /**
     * Constructs a SemVersion out of a release tag, like i.e. josman-1.2.3
     */
    public static SemVersion version(String repoName, String releaseTag) {
        String versionString = releaseTag.replace(repoName + "-", "");
        return SemVersion.of(versionString);
    }

    /**
     * Returns a tag with given major and minor in provided list of git repository tags
     * @param repoName i.e. "josman"
     * @throws NotFoundException if tag is not found
     */
    static RepositoryTag find(String repoName, int major, int minor, Iterable<RepositoryTag> tags) {
        for (RepositoryTag tag : tags) {
            SemVersion tagVersion = version(repoName, tag.getName());
            if (tagVersion.getMajor() == major && tagVersion.getMinor() == minor) {
                return tag;
            }
        }
        throw new NotFoundException("Couldn't find any tag matching " + major + "." + minor + " pattern");
    }

    /**
     * Returns a semantic version from branch name in the format "branch-x.y".
     * Result patch number will be 0.
     *
     * @param branchName Must be in format "branch-x.y"
     * @throws IllegalArguemntException if branchname is not in the expected
     * format.
     */
    public static SemVersion versionFromBranchName(String branchName) {
        checkNotNull(branchName);

        if (!branchName.startsWith("branch-")) {
            throw new IllegalArgumentException("Tried to extract version from branch name '" + branchName
                    + "', but it does not start with 'branch-'  !!!");
        }

        try {
            SemVersion ret = SemVersion.of(branchName.replace("branch-", "").concat(".0"));
            return ret;
        } catch (Throwable tr) {
            throw new IllegalArgumentException("Error while extracting version from branch name " + branchName, tr);
        }

    }

    /**
     * Adds the candidate tag to the provided tags if no other tag has same
     * major and minor or if its patch is greater.
     */
    @Nullable
    private static void updateTags(String repoName, RepositoryTag candidateTag, List<RepositoryTag> tags) {

        SemVersion candidateSemVarsion = version(repoName, candidateTag.getName());

        for (int i = 0; i < tags.size(); i++) {
            RepositoryTag tag = tags.get(i);
            SemVersion semVersion = version(repoName, tag.getName());
            if (candidateSemVarsion.getMajor() == semVersion.getMajor()
                    && candidateSemVarsion.getMinor() == semVersion.getMinor()) {
                if (candidateSemVarsion.getPatch() > semVersion.getPatch()) {
                    tags.set(i, candidateTag);
                }
                return;
            }
        }
        tags.add(candidateTag);
    }

    /**
     * Returns a new list with given relpaths ordered by importance, the first
     * being the most important.
     */
    static List<String> orderRelpaths(List<String> relpaths) {
        List<String> ret = new ArrayList(relpaths);

        String readme = DOCS_FOLDER + "/" + README_MD;
        String changes = DOCS_FOLDER + "/" + CHANGES_MD;

        List<String> toSkip = ImmutableList.of(readme, changes);

        ret.removeAll(toSkip);

        if (relpaths.contains(readme)) {
            ret.add(0, readme);
        }

        if (relpaths.contains(changes)) {
            ret.add(ret.size(), changes);
        }
        return ImmutableList.copyOf(ret);
    }

    /**
     * Returns new sorted map of only version tags of the format repoName-x.y.z
     * filtered tags, the latter having the highest version.
     *
     * @param repoName the github repository name i.e. josman
     * @param tags a list of tags from the repository
     * @return map of version as string and correspondig RepositoryTag
     */
    public static SortedMap<String, RepositoryTag> versionTags(String repoName, List<RepositoryTag> tags) {

        List<RepositoryTag> ret = new ArrayList();
        for (RepositoryTag candidateTag : tags) {
            if (candidateTag.getName().startsWith(repoName + "-")) {
                updateTags(repoName, candidateTag, ret);
            }
        }

        TreeMap map = new TreeMap();
        for (RepositoryTag tag : ret) {
            map.put(tag.getName(), tag);
        }
        return map;
    }

    /**
     * Returns new sorted map of only version tags to be processed of the format
     * repoName-x.y.z filtered tags, the latter having the highest version.
     *
     * @param repoName the github repository name i.e. josman
     * @param tags a list of tags from the repository
     * @param ignoredVersions These versions will be filtered in the output.
     * @return map of version as string and correspondig RepositoryTag
     */
    public static SortedMap<String, RepositoryTag> versionTagsToProcess(String repoName, List<RepositoryTag> tags,
            List<SemVersion> ignoredVersions) {
        SortedMap<String, RepositoryTag> map = versionTags(repoName, tags);
        for (SemVersion versionToSkip : ignoredVersions) {
            String tag = releaseTag(repoName, versionToSkip);
            if (map.containsKey(tag)) {
                map.remove(tag);
            }
        }
        return map;
    }

    /**
     * Returns the release tag formed by inserting a minus between the repoName
     * and the version
     *
     * @param repoName i.e. josman
     * @param version i.e. 1.2.3
     * @return i.e. josman-1.2.3
     */
    public static String releaseTag(String repoName, SemVersion version) {
        return repoName + "-" + version;
    }

    /**
     * Returns the github repo url, i.e.
     * https://github.com/opendatatrentino/josman
     *
     * @param organization i.e. opendatatrentino
     * @param name i.e. josman
     */
    public static String repoUrl(String organization, String name) {
        return "https://github.com/" + organization + "/" + name;
    }

    /**
     * Returns the github release code url, i.e.
     * https://github.com/opendatatrentino/josman/blob/todo-releaseTag
     *
     * @param repoName i.e. josman
     * @param version i.e. 1.2.3
     */
    public static String repoRelease(String organization, String repoName, SemVersion version) {
        return repoUrl(organization, repoName) + "/blob/" + releaseTag(repoName, version);
    }

    /**
     * Returns the github wiki url, i.e.
     * https://github.com/opendatatrentino/josman/wiki
     *
     * @param organization i.e. opendatatrentino
     * @param repoName i.e. josman
     */
    public static String repoWiki(String organization, String repoName) {
        return repoUrl(organization, repoName) + "/wiki";
    }

    /**
     * Returns the github issues url, i.e.
     * https://github.com/opendatatrentino/josman/issues
     *
     * @param organization i.e. opendatatrentino
     * @param repoName i.e. josman
     */
    public static String repoIssues(String organization, String repoName) {
        return repoUrl(organization, repoName) + "/issues";
    }

    /**
     * Returns the github milestones url, i.e.
     * https://github.com/opendatatrentino/josman/milestones
     *
     * @param organization i.e. opendatatrentino
     * @param repoName i.e. josman
     */
    public static String repoMilestones(String organization, String repoName) {
        return repoUrl(organization, repoName) + "/milestones";
    }

    /**
     * Returns the github wiki url, i.e.
     *
     * @param organization i.e. opendatatrentino
     * @param repoName i.e. josman
     */
    public static String repoWebsite(String organization, String repoName) {
        return "https://" + organization + ".github.io/" + repoName;
    }

    public static SemVersion latestVersion(String repoName, List<RepositoryTag> tags) {
        TodUtils.checkNotEmpty(tags, "Invalid repository tags!");
        SortedMap<String, RepositoryTag> filteredTags = Josmans.versionTags(repoName, tags);
        if (filteredTags.isEmpty()) {
            throw new NotFoundException("Couldn't find any released version!");
        }
        return Josmans.version(repoName, filteredTags.lastKey());
    }

    /**
     * Returns a new url friendly and normalized path.
     *
     * @param path a path that may contain .md files
     */
    public static String htmlizePath(String path) {
        checkNotEmpty(path, "Invalid path!");
        String slashPath = path.replace("\\", "/");
        if (slashPath.endsWith(README_MD)) {
            return slashPath.replace(README_MD, "index.html");
        } else if (slashPath.endsWith(".md")) {
            return slashPath.substring(0, slashPath.length() - 3) + ".html";
        }
        String ret = TodUtils.removeTrailingSlash(slashPath);
        if (ret.length() == 0) {
            return "/";
        } else {
            return ret;
        }
    }

    /**
     * Checks if the input string contains only spaces, tabs, etc
     * @return non-null input string
     * @param string
     * @throws IllegalArgumentException on empty string
     */
    public static String checkNotMeaningful(@Nullable String string, @Nullable Object prependedErrorMessage) {
        TodUtils.checkNotEmpty(string, prependedErrorMessage);
        for (int i = 0; i < string.length(); i++) {
            if (string.charAt(i) != '\n' && string.charAt(i) != '\t' && string.charAt(i) != ' ') {
                return string;
            }
        }
        throw new IllegalArgumentException(String.valueOf(prependedErrorMessage)
                + " -- Reason: String contains only empty spaces/tabs/carriage returns!");
    }

    /**
     * Returns the maven style javadoc file name (i.e.
     * my-prog-1.2.3-javadoc.jar)
     */
    public static String javadocJarName(String artifactId, SemVersion version) {
        checkNotEmpty(artifactId, "Invalid artifactId!");
        checkNotNull(version);
        return artifactId + "-" + version + "-javadoc.jar";
    }

    /**
     * Fetches Javadoc of released artifact and writes it into {@code destFile}
     *     
     */
    public static File fetchJavadoc(String groupId, String artifactId, SemVersion version) {
        checkNotEmpty(groupId, "Invalid groupId!");
        checkNotEmpty(artifactId, "Invalid artifactId!");
        checkNotNull(version);

        File destFile;

        try {
            destFile = File.createTempFile(groupId + "-" + artifactId + "-javadoc", ".jar");
            destFile.deleteOnExit();
        } catch (IOException ex) {
            throw new RuntimeException("Couldn't create target javadoc file!", ex);
        }

        URL url;
        try {
            url = new URL("http://repo1.maven.org/maven2/" + groupId.replace(".", "/") + "/" + artifactId + "/"
                    + version + "/" + javadocJarName(artifactId, version));
        } catch (MalformedURLException ex) {
            throw new RuntimeException("Error while forming javadoc URL!", ex);
        }
        LOG.log(Level.INFO, "Fetching javadoc from {0} into {1} ...",
                new Object[] { url, destFile.getAbsolutePath() });
        try {
            FileUtils.copyURLToFile(url, destFile, CONNECTION_TIMEOUT, CONNECTION_TIMEOUT);
            LOG.log(Level.INFO, "Done copying javadoc.");
        } catch (IOException ex) {
            throw new RuntimeException("Error while fetch-and-write javadoc for " + groupId + "/" + artifactId + "-"
                    + version + " into file " + destFile.getAbsoluteFile(), ex);
        }
        return destFile;
    }

    /**
     * Extracts the directory at resource path to target directory. First
     * directory is searched in local "src/main/resources" so the thing also
     * works when developing in the IDE. If not found then searches in jar file.
     */
    public static void copyDirFromResource(Class clazz, String dirPath, File destDir) {
        String sep = File.separator;
        File sourceDir = new File("src" + sep + "main" + sep + "resources", dirPath);

        if (sourceDir.exists()) {
            LOG.log(Level.INFO, "Copying directory from {0} to {1}  ...",
                    new Object[] { sourceDir.getAbsolutePath(), destDir.getAbsolutePath() });
            try {
                FileUtils.copyDirectory(sourceDir, destDir);
                LOG.log(Level.INFO, "Done copying directory");
            } catch (IOException ex) {
                throw new RuntimeException("Couldn't copy the directory!", ex);
            }
        } else {

            File jarFile = new File(clazz.getProtectionDomain().getCodeSource().getLocation().getPath());
            if (jarFile.isDirectory()
                    && jarFile.getAbsolutePath().endsWith("target" + File.separator + "classes")) {
                LOG.info("Seems like you have Josman sources, will take resources from there");
                try {
                    FileUtils.copyDirectory(
                            new File(jarFile.getAbsolutePath() + "/../../src/main/resources", dirPath), destDir);
                    LOG.log(Level.INFO, "Done copying directory");
                } catch (IOException ex) {
                    throw new RuntimeException("Couldn't copy the directory!", ex);
                }
            } else {
                LOG.log(Level.INFO, "Extracting jar {0} to {1}",
                        new Object[] { jarFile.getAbsolutePath(), destDir.getAbsolutePath() });
                copyDirFromJar(jarFile, destDir, dirPath);
                LOG.log(Level.INFO, "Done copying directory from JAR.");
            }

        }
    }

    /**
     *
     * Extracts the files starting with dirPath from {@code file} to
     * {@code destDir}
     *
     * @param dirPath the prefix used for filtering. If empty the whole jar
     * content is extracted.
     */
    public static void copyDirFromJar(File jarFile, File destDir, String dirPath) {
        checkNotNull(jarFile);
        checkNotNull(destDir);
        checkNotNull(dirPath);

        String normalizedDirPath;
        if (dirPath.startsWith("/")) {
            normalizedDirPath = dirPath.substring(1);
        } else {
            normalizedDirPath = dirPath;
        }

        try {
            JarFile jar = new JarFile(jarFile);
            java.util.Enumeration enumEntries = jar.entries();
            while (enumEntries.hasMoreElements()) {
                JarEntry jarEntry = (JarEntry) enumEntries.nextElement();
                if (jarEntry.getName().startsWith(normalizedDirPath)) {
                    File f = new File(
                            destDir + File.separator + jarEntry.getName().substring(normalizedDirPath.length()));

                    if (jarEntry.isDirectory()) { // if its a directory, create it
                        f.mkdirs();
                        continue;
                    } else {
                        f.getParentFile().mkdirs();
                    }

                    InputStream is = jar.getInputStream(jarEntry); // get the input stream
                    FileOutputStream fos = new FileOutputStream(f);
                    IOUtils.copy(is, fos);
                    fos.close();
                    is.close();
                }

            }
        } catch (Exception ex) {
            throw new RuntimeException("Error while extracting jar file! Jar source: " + jarFile.getAbsolutePath()
                    + " destDir = " + destDir.getAbsolutePath(), ex);
        }
    }

    /**
     * Searches resource indicated by path first in src/main/resources (so it
     * works even when developing), then in proper classpath resources. If
     * resource is found it is returned as input stream, otherwise an exception
     * is thrown.
     *
     * @throws NotFoundException if path can't be found.
     */
    public static InputStream findResourceStream(String path) {

        checkNotNull(path, "invalid path!");

        String localPath = "src/main/resources" + path;

        try {
            return new FileInputStream(localPath);
        } catch (FileNotFoundException ex) {
        }

        LOG.log(Level.INFO, "Can''t find file {0}", new File(localPath).getAbsolutePath());

        try {
            URL url = JosmanProject.class.getResource(path);
            LOG.log(Level.INFO, "Found file in {0}", url);
            InputStream ret = JosmanProject.class.getResourceAsStream(path);
            return ret;
        } catch (Exception ex) {
            throw new NotFoundException("Can't load file in resources! " + path);
        }

    }

    /**
     * Returns the name displayed on the website as menu item for a given page.
     *
     * @param relPath path relative to the {@link JosmanProject#sourceRepoDir()} (i.e.
     * LICENSE.txt or docs/README.md)
     */
    public static String targetName(String relPath) {
        String htmlizedPath = htmlizePath(relPath);
        if (htmlizedPath.endsWith("/index.html")) {
            return "Usage";
        }
        if (htmlizedPath.endsWith("/CHANGES.html")) {
            return "Release notes";
        }
        String withoutFiletype = htmlizedPath.replace(".html", "");
        int lastSlash = withoutFiletype.lastIndexOf("/");
        String fileName = withoutFiletype;
        if (lastSlash != -1) {
            fileName = withoutFiletype.substring(lastSlash + 1);
        }
        StringBuilder sb = new StringBuilder();
        sb.append(Character.toUpperCase(fileName.charAt(0)));

        int i = 1;
        while (i < fileName.length()) {
            char ch = fileName.charAt(i);
            if (i + 1 < fileName.length()) {
                char nextCh = fileName.charAt(i + 1);
                if (Character.isLowerCase(ch) && Character.isUpperCase(nextCh)) {
                    sb.append(ch + " ");
                    i += 1;
                    continue;
                } else {
                    if (i + 2 < fileName.length()) {
                        char nextNextCh = fileName.charAt(i + 2);
                        if (Character.isUpperCase(ch) && Character.isUpperCase(nextCh)
                                && Character.isLowerCase(nextNextCh)) {
                            sb.append(ch);
                            sb.append(" ");
                            sb.append(Character.toLowerCase(nextCh));
                            i += 2;
                            continue;
                        } else {
                            if (Character.isUpperCase(ch) && Character.isUpperCase(nextCh)) {
                                sb.append(ch);
                                i += 1;
                                continue;
                            }
                        }
                    }
                }
            }

            sb.append(Character.toLowerCase(ch));
            i += 1;
        }
        return sb.toString();
    }

    /**
     * Returns the target file where a source path should be transfered into.
     *
     * @param relPath path relative to the {@link #sourceRepoDir} (i.e.
     * LICENSE.txt or docs/README.md)
     */
    static File targetFile(File pagesDir, String relPath, final SemVersion version) {

        if (Josmans.isRootpath(relPath)) {
            return new File(pagesDir, Josmans.htmlizePath(relPath));
        } else {
            return new File(pagesDir, Josmans.majorMinor(version) + File.separator
                    + Josmans.htmlizePath(relPath.substring((DOCS_FOLDER + "/").length())));
        }

    }

    /**
     * Returns either "../" or "" according to {@code relPath}
     *
     * @param relPath may start with "docs"
     */
    static String prependedPath(String relPath) {
        checkNotNull(relPath);
        // todo it handles only one level....
        if (isRootpath(relPath)) {
            return "";
        } else {
            return "../";

        }
    }

    /**
     * Returns true if provided {@code relPath} is a website root path
     *
     * @param relPath the website path, i.e. README.md or docs/CHANGES.md or
     * docs\CHANGES.md
     */
    static boolean isRootpath(String relPath) {
        checkNotNull(relPath);
        return !(relPath.equals(DOCS_FOLDER) || relPath.startsWith(DOCS_FOLDER + "/")
                || relPath.startsWith(DOCS_FOLDER + "\\"));
    }

    /**
     * Returns a string representation of the provided git file mode
     */
    static String gitFileModeToString(FileMode fileMode) {
        if (fileMode.equals(FileMode.EXECUTABLE_FILE)) {
            return "Executable File";
        } else if (fileMode.equals(FileMode.REGULAR_FILE)) {
            return "Normal File";
        } else if (fileMode.equals(FileMode.TREE)) {
            return "Directory";
        } else if (fileMode.equals(FileMode.SYMLINK)) {
            return "Symlink";
        } else if (fileMode.equals(FileMode.GITLINK)) {
            return "submodule link";
        } else {
            return fileMode.toString();
        }

    }
}