org.jboss.pull.player.LabelProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.pull.player.LabelProcessor.java

Source

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.pull.player;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;

/**
 * Process pull requests to determine if the pull request requires a change to the labels.
 * <p/>
 * This class is not thread safe, but also shouldn't matter for now.
 *
 * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
 */
class LabelProcessor {
    private final Path path = Util.BASE_DIR.toPath().resolve("issues.json");
    private final Labels labels;

    private final ModelNode issuesModel;
    private final HttpClient client = new DefaultHttpClient();
    private final PrintStream err = System.err;

    LabelProcessor() {
        labels = new Labels();
        // If the file exists, load it
        if (Files.exists(path)) {
            try (final InputStream in = Files.newInputStream(path)) {
                issuesModel = ModelNode.fromJSONStream(in);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            // No file exists, create a new model
            issuesModel = new ModelNode();
        }
    }

    void process(final List<ModelNode> pulls) {
        for (ModelNode pull : pulls) {
            add(pull);
        }
        process();
    }

    /**
     * Adds a pull request to be processed.
     *
     * @param pull the pull request to be processed
     */
    void add(final ModelNode pull) {
        final int pullNumber = pull.get("number").asInt();
        final String sha1 = pull.get("head", "sha").asString();
        final String issueUrl = pull.get("issue_url").asString();

        final String key = "pr-" + pullNumber;

        if (issuesModel.hasDefined(key)) {
            final ModelNode n = issuesModel.get(key);
            if (!n.get("sha").asString().equals(sha1)) {
                // Add the new sha key to be checked during processing
                n.get("new-sha").set(sha1);
            }
        } else {
            final ModelNode model = new ModelNode();
            model.get("sha").set(sha1);
            model.get("issue_url").set(issueUrl);
            model.get("pull_request_url").set(pull.get("url"));
            issuesModel.get(key).set(model);
        }
    }

    /**
     * Processes the pull requests {@link #add(org.jboss.dmr.ModelNode) added}.
     * <p/>
     * This should normally only be invoked once as it makes API calls to GitHub.
     */
    void process() {
        try {
            // Get all the open issues to lessen the hits to the API
            final ModelNode openIssues = getIssues();

            // Process each issue in the model
            for (Property property : issuesModel.asPropertyList()) {
                final ModelNode value = property.getValue();
                // Get the PR url
                final String prUrl = value.get("pull_request_url").asString();
                if (openIssues.hasDefined(prUrl)) {
                    final ModelNode openIssue = openIssues.get(prUrl);

                    // Get the current labels
                    final List<String> currentLabels = getLabels(openIssue);
                    // If no labels are present, we can delete the issue
                    if (currentLabels.isEmpty()) {
                        issuesModel.remove(property.getName());
                    } else {
                        boolean changeRequired = false;

                        // Process the labels only requiring a change if the label was defined in the configuration
                        final List<String> newLabels = new ArrayList<>();
                        for (String label : currentLabels) {
                            if (labels.isRemovable(label)) {
                                final String newLabel = labels.getReplacement(label);
                                if (newLabel != null) {
                                    newLabels.add(newLabel);
                                }
                                changeRequired = true;
                            } else {
                                newLabels.add(label);
                            }
                        }
                        // Check that the PR has been changed and a change is required
                        if (changeRequired && value.hasDefined("new-sha")) {
                            final String issueUrl = value.get("issue_url").asString();
                            // Set the new labels
                            setLabels(issueUrl, newLabels);
                            // Node needs to be removed
                            issuesModel.remove(property.getName());
                        } else if (!changeRequired) {
                            // No change in labels has been required, remove the issue
                            issuesModel.remove(property.getName());
                        }
                    }
                } else {
                    // The issue/PR may be closed, we can just delete it
                    issuesModel.remove(property.getName());
                }
            }

            // Write the issues out to a file
            try (final PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path, StandardCharsets.UTF_8,
                    StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC,
                    StandardOpenOption.CREATE))) {
                issuesModel.writeJSONString(writer, false);
            }
        } catch (IOException e) {
            e.printStackTrace(err);
        }
    }

    private ModelNode getIssues() throws IOException {
        final ModelNode resultIssues = new ModelNode();
        try {
            // Get all the issues for the repository
            final HttpGet get = new HttpGet(GitHubApi.GITHUB_BASE_URL + "/issues");
            GitHubApi.addDefaultHeaders(get);
            final HttpResponse response = execute(get, HttpURLConnection.HTTP_OK);
            ModelNode responseResult = ModelNode.fromJSONStream(response.getEntity().getContent());
            for (ModelNode node : responseResult.asList()) {
                // We only want issues with a pull request
                if (node.hasDefined("pull_request") && node.hasDefined("labels")) {
                    // Verify the labels aren't empty
                    if (!node.get("labels").asList().isEmpty()) {
                        // The key will be the PR URL
                        resultIssues.get(node.get("pull_request", "url").asString()).set(node);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace(err);
        }
        return resultIssues;
    }

    private List<String> getLabels(final ModelNode issue) {
        final List<String> result = new ArrayList<>();
        final ModelNode node = issue.get("labels");
        if (node.isDefined()) {
            final List<ModelNode> labels = node.asList();
            for (ModelNode label : labels) {
                result.add(label.get("name").asString());
            }
        }
        return result;
    }

    private void setLabels(final String issueUrl, final Collection<String> labels) {
        // Build a list of the new labels
        final StringBuilder sb = new StringBuilder(32).append('[');
        final int size = labels.size();
        int counter = 0;
        for (String label : labels) {
            sb.append('"').append(label).append('"');
            if (++counter < size) {
                sb.append(',');
            }
        }
        sb.append(']');
        try {
            final HttpPut put = new HttpPut(issueUrl + "/labels");
            GitHubApi.addDefaultHeaders(put);
            put.setEntity(new StringEntity(sb.toString()));
            execute(put, HttpURLConnection.HTTP_OK);
        } catch (Exception e) {
            e.printStackTrace(err);
        }

    }

    private HttpResponse execute(final HttpUriRequest request, final int status) throws IOException {
        final HttpResponse response = client.execute(request);
        if (response.getStatusLine().getStatusCode() != status) {
            err.printf("Could not %s to %s %n\t%s%n", request.getMethod(), request.getURI(),
                    response.getStatusLine());
        }
        return response;
    }
}