com.google.gerrit.server.query.change.ListChanges.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gerrit.server.query.change.ListChanges.java

Source

// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.server.query.change;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.events.AccountAttribute;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gson.reflect.TypeToken;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;

import org.kohsuke.args4j.Option;

import java.io.IOException;
import java.io.Writer;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

public class ListChanges {
    private final QueryProcessor imp;
    private final Provider<ReviewDb> db;
    private final ApprovalTypes approvalTypes;
    private final CurrentUser user;
    private final ChangeControl.Factory changeControlFactory;
    private boolean reverse;
    private Map<Account.Id, AccountAttribute> accounts;

    @Option(name = "--format", metaVar = "FMT", usage = "Output display format")
    private OutputFormat format = OutputFormat.TEXT;

    @Option(name = "--query", aliases = { "-q" }, metaVar = "QUERY", multiValued = true, usage = "Query string")
    private List<String> queries;

    @Option(name = "--limit", aliases = { "-n" }, metaVar = "CNT", usage = "Maximum number of results to return")
    void setLimit(int limit) {
        imp.setLimit(limit);
    }

    @Option(name = "-P", metaVar = "SORTKEY", usage = "Previous changes before SORTKEY")
    void setSortKeyAfter(String key) {
        // Querying for the prior page of changes requires sortkey_after predicate.
        // Changes are shown most recent->least recent. The previous page of
        // results contains changes that were updated after the given key.
        imp.setSortkeyAfter(key);
        reverse = true;
    }

    @Option(name = "-N", metaVar = "SORTKEY", usage = "Next changes after SORTKEY")
    void setSortKeyBefore(String key) {
        // Querying for the next page of changes requires sortkey_before predicate.
        // Changes are shown most recent->least recent. The next page contains
        // changes that were updated before the given key.
        imp.setSortkeyBefore(key);
    }

    @Inject
    ListChanges(QueryProcessor qp, Provider<ReviewDb> db, ApprovalTypes at, CurrentUser u,
            ChangeControl.Factory cf) {
        this.imp = qp;
        this.db = db;
        this.approvalTypes = at;
        this.user = u;
        this.changeControlFactory = cf;

        accounts = Maps.newHashMap();
    }

    public OutputFormat getFormat() {
        return format;
    }

    public ListChanges setFormat(OutputFormat fmt) {
        this.format = fmt;
        return this;
    }

    public void query(Writer out) throws OrmException, QueryParseException, IOException {
        if (imp.isDisabled()) {
            throw new QueryParseException("query disabled");
        }
        if (queries == null || queries.isEmpty()) {
            queries = Collections.singletonList("status:open");
        } else if (queries.size() > 10) {
            // Hard-code a default maximum number of queries to prevent
            // users from submitting too much to the server in a single call.
            throw new QueryParseException("limit of 10 queries");
        }

        List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(queries.size());
        for (String query : queries) {
            List<ChangeData> changes = imp.queryChanges(query);
            boolean moreChanges = imp.getLimit() > 0 && changes.size() > imp.getLimit();
            if (moreChanges) {
                if (reverse) {
                    changes = changes.subList(1, changes.size());
                } else {
                    changes = changes.subList(0, imp.getLimit());
                }
            }
            ChangeData.ensureChangeLoaded(db, changes);
            ChangeData.ensureCurrentPatchSetLoaded(db, changes);
            ChangeData.ensureCurrentApprovalsLoaded(db, changes);

            List<ChangeInfo> info = Lists.newArrayListWithCapacity(changes.size());
            for (ChangeData cd : changes) {
                info.add(toChangeInfo(cd));
            }
            if (moreChanges && !info.isEmpty()) {
                if (reverse) {
                    info.get(0)._moreChanges = true;
                } else {
                    info.get(info.size() - 1)._moreChanges = true;
                }
            }
            res.add(info);
        }

        if (!accounts.isEmpty()) {
            for (Account account : db.get().accounts().get(accounts.keySet())) {
                AccountAttribute a = accounts.get(account.getId());
                a.name = Strings.emptyToNull(account.getFullName());
            }
        }

        if (format.isJson()) {
            format.newGson().toJson(res.size() == 1 ? res.get(0) : res, new TypeToken<List<ChangeInfo>>() {
            }.getType(), out);
            out.write('\n');
        } else {
            boolean firstQuery = true;
            for (List<ChangeInfo> info : res) {
                if (firstQuery) {
                    firstQuery = false;
                } else {
                    out.write('\n');
                }
                for (ChangeInfo c : info) {
                    String id = new Change.Key(c.id).abbreviate();
                    String subject = c.subject;
                    if (subject.length() + id.length() > 80) {
                        subject = subject.substring(0, 80 - id.length());
                    }
                    out.write(id);
                    out.write(' ');
                    out.write(subject.replace('\n', ' '));
                    out.write('\n');
                }
            }
        }
    }

    private ChangeInfo toChangeInfo(ChangeData cd) throws OrmException {
        ChangeInfo out = new ChangeInfo();
        Change in = cd.change(db);
        out.project = in.getProject().get();
        out.branch = in.getDest().getShortName();
        out.topic = in.getTopic();
        out.id = in.getKey().get();
        out.subject = in.getSubject();
        out.status = in.getStatus();
        out.owner = asAccountAttribute(in.getOwner());
        out.created = in.getCreatedOn();
        out.updated = in.getLastUpdatedOn();
        out._number = in.getId().get();
        out._sortkey = in.getSortKey();
        out.starred = user.getStarredChanges().contains(in.getId()) ? true : null;
        out.reviewed = in.getStatus().isOpen() && isChangeReviewed(cd) ? true : null;
        out.labels = labelsFor(cd);
        return out;
    }

    private AccountAttribute asAccountAttribute(Account.Id user) {
        if (user == null) {
            return null;
        }
        AccountAttribute a = accounts.get(user);
        if (a == null) {
            a = new AccountAttribute();
            accounts.put(user, a);
        }
        return a;
    }

    private Map<String, LabelInfo> labelsFor(ChangeData cd) throws OrmException {
        Change in = cd.change(db);
        ChangeControl ctl = cd.changeControl();
        if (ctl == null || ctl.getCurrentUser() != user) {
            try {
                ctl = changeControlFactory.controlFor(in);
            } catch (NoSuchChangeException e) {
                return null;
            }
        }

        PatchSet ps = cd.currentPatchSet(db);
        Map<String, LabelInfo> labels = Maps.newLinkedHashMap();
        for (SubmitRecord rec : ctl.canSubmit(db.get(), ps, cd, true, false)) {
            if (rec.labels == null) {
                continue;
            }
            for (SubmitRecord.Label r : rec.labels) {
                LabelInfo p = labels.get(r.label);
                if (p == null || p._status.compareTo(r.status) < 0) {
                    LabelInfo n = new LabelInfo();
                    n._status = r.status;
                    switch (r.status) {
                    case OK:
                        n.approved = asAccountAttribute(r.appliedBy);
                        break;
                    case REJECT:
                        n.rejected = asAccountAttribute(r.appliedBy);
                        break;
                    }
                    n.optional = n._status == SubmitRecord.Label.Status.MAY ? true : null;
                    labels.put(r.label, n);
                }
            }
        }

        Collection<PatchSetApproval> approvals = null;
        for (Map.Entry<String, LabelInfo> e : labels.entrySet()) {
            if (e.getValue().approved != null || e.getValue().rejected != null) {
                continue;
            }

            ApprovalType type = approvalTypes.byLabel(e.getKey());
            if (type == null || type.getMin() == null || type.getMax() == null) {
                // Unknown or misconfigured type can't have intermediate scores.
                continue;
            }

            short min = type.getMin().getValue();
            short max = type.getMax().getValue();
            if (-1 <= min && max <= 1) {
                // Types with a range of -1..+1 can't have intermediate scores.
                continue;
            }

            if (approvals == null) {
                approvals = cd.currentApprovals(db);
            }
            for (PatchSetApproval psa : approvals) {
                short val = psa.getValue();
                if (val != 0 && min < val && val < max && psa.getCategoryId().equals(type.getCategory().getId())) {
                    if (0 < val) {
                        e.getValue().recommended = asAccountAttribute(psa.getAccountId());
                        e.getValue().value = val != 1 ? val : null;
                    } else {
                        e.getValue().disliked = asAccountAttribute(psa.getAccountId());
                        e.getValue().value = val != -1 ? val : null;
                    }
                }
            }
        }
        return labels;
    }

    private boolean isChangeReviewed(ChangeData cd) throws OrmException {
        if (user instanceof IdentifiedUser) {
            PatchSet currentPatchSet = cd.currentPatchSet(db);
            if (currentPatchSet == null) {
                return false;
            }

            List<ChangeMessage> messages = db.get().changeMessages().byPatchSet(currentPatchSet.getId()).toList();

            if (messages.isEmpty()) {
                return false;
            }

            // Sort messages to let the most recent ones at the beginning.
            Collections.sort(messages, new Comparator<ChangeMessage>() {
                @Override
                public int compare(ChangeMessage a, ChangeMessage b) {
                    return b.getWrittenOn().compareTo(a.getWrittenOn());
                }
            });

            Account.Id currentUserId = ((IdentifiedUser) user).getAccountId();
            Account.Id changeOwnerId = cd.change(db).getOwner();
            for (ChangeMessage cm : messages) {
                if (currentUserId.equals(cm.getAuthor())) {
                    return true;
                } else if (changeOwnerId.equals(cm.getAuthor())) {
                    return false;
                }
            }
        }
        return false;
    }

    static class ChangeInfo {
        String project;
        String branch;
        String topic;
        String id;
        String subject;
        Change.Status status;
        Timestamp created;
        Timestamp updated;
        Boolean starred;
        Boolean reviewed;

        String _sortkey;
        int _number;

        AccountAttribute owner;
        Map<String, LabelInfo> labels;
        Boolean _moreChanges;
    }

    static class LabelInfo {
        transient SubmitRecord.Label.Status _status;
        AccountAttribute approved;
        AccountAttribute rejected;

        AccountAttribute recommended;
        AccountAttribute disliked;
        Short value;
        Boolean optional;
    }
}