| // Copyright 2014 The Bazel Authors. All rights reserved. | |
| // | |
| // 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.devtools.build.lib.events; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import com.google.common.base.Preconditions; | |
| import com.google.devtools.build.lib.util.io.FileOutErr; | |
| import com.google.devtools.build.lib.util.io.FileOutErr.OutputReference; | |
| import java.io.Serializable; | |
| import java.util.Arrays; | |
| import java.util.Objects; | |
| import javax.annotation.Nullable; | |
| import javax.annotation.concurrent.Immutable; | |
| /** | |
| * An event is a situation encountered by the build system that's worth | |
| * reporting: A 3-tuple of ({@link EventKind}, {@link Location}, message). | |
| */ | |
| @Immutable | |
| public final class Event implements Serializable { | |
| private final EventKind kind; | |
| private final Location location; | |
| private final String message; | |
| @Nullable private final FileOutErr outErr; | |
| /** | |
| * An alternative representation for message. | |
| * | |
| * <p>Exactly one of message or messageBytes will be non-null. If messageBytes is non-null, then | |
| * it contains the UTF-8-encoded bytes of the message. We do this to avoid converting back and | |
| * forth between Strings and bytes. | |
| */ | |
| private final byte[] messageBytes; | |
| @Nullable | |
| private final String tag; | |
| private int hashCode; | |
| private Event( | |
| EventKind kind, | |
| @Nullable Location location, | |
| String message, | |
| @Nullable String tag, | |
| @Nullable FileOutErr outErr) { | |
| this.kind = Preconditions.checkNotNull(kind); | |
| this.location = location; | |
| this.message = Preconditions.checkNotNull(message); | |
| this.messageBytes = null; | |
| this.tag = tag; | |
| this.outErr = outErr; | |
| } | |
| private Event( | |
| EventKind kind, | |
| @Nullable Location location, | |
| byte[] messageBytes, | |
| @Nullable String tag, | |
| @Nullable FileOutErr outErr) { | |
| this.kind = Preconditions.checkNotNull(kind); | |
| this.location = location; | |
| this.message = null; | |
| this.messageBytes = Preconditions.checkNotNull(messageBytes); | |
| this.tag = tag; | |
| this.outErr = outErr; | |
| } | |
| public Event withTag(String tag) { | |
| if (Objects.equals(tag, this.tag)) { | |
| return this; | |
| } | |
| if (this.message != null) { | |
| return new Event(this.kind, this.location, this.message, tag, this.outErr); | |
| } else { | |
| return new Event(this.kind, this.location, this.messageBytes, tag, this.outErr); | |
| } | |
| } | |
| public Event withStdoutStderr(FileOutErr outErr) { | |
| if (this.message != null) { | |
| return new Event(this.kind, this.location, this.message, this.tag, outErr); | |
| } else { | |
| return new Event(this.kind, this.location, this.messageBytes, this.tag, outErr); | |
| } | |
| } | |
| public String getMessage() { | |
| return message != null ? message : new String(messageBytes, UTF_8); | |
| } | |
| public byte[] getMessageBytes() { | |
| return messageBytes != null ? messageBytes : message.getBytes(UTF_8); | |
| } | |
| /** Provide the message as a reference object. */ | |
| public OutputReference getMessageReference() { | |
| // The message is short and we have it in memory anyway; so just wrap it into | |
| // the common interface. | |
| return new ArrayOutputReference(getMessageBytes()); | |
| } | |
| public EventKind getKind() { | |
| return kind; | |
| } | |
| /** | |
| * the tag is typically the action that generated the event. | |
| */ | |
| @Nullable | |
| public String getTag() { | |
| return tag; | |
| } | |
| /** Indicate if any output is associated with this event. */ | |
| public boolean hasStdoutStderr() { | |
| return outErr != null; | |
| } | |
| /** | |
| * Get the stdout bytes associated with this event; typically, the event will report where the | |
| * output originated from. | |
| */ | |
| @Nullable | |
| public String getStdOut() { | |
| if (outErr == null) { | |
| return null; | |
| } | |
| return outErr.outAsLatin1(); | |
| } | |
| /** | |
| * Get the stdout bytes associated with this event; typically, the event will report where the | |
| * output originated from. | |
| */ | |
| @Nullable | |
| public String getStdErr() { | |
| if (outErr == null) { | |
| return null; | |
| } | |
| return outErr.errAsLatin1(); | |
| } | |
| /** Get a reference to the associated output. */ | |
| public OutputReference getStdOutReference() { | |
| return outErr.getOutReference(); | |
| } | |
| /** Get a reference to the associated output. */ | |
| public OutputReference getStdErrReference() { | |
| return outErr.getErrReference(); | |
| } | |
| /** | |
| * Returns the location of this event, if any. Returns null iff the event | |
| * wasn't associated with any particular location, for example, a progress | |
| * message. | |
| */ | |
| @Nullable public Location getLocation() { | |
| return location; | |
| } | |
| /** | |
| * Returns <i>some</i> moderately sane representation of the event. Should never be used in | |
| * user-visible places, only for debugging and testing. | |
| */ | |
| @Override | |
| public String toString() { | |
| return kind + " " + (location != null ? location.print() : "<no location>") + ": " | |
| + getMessage(); | |
| } | |
| @Override | |
| public int hashCode() { | |
| // We defer the computation of hashCode until it is needed to avoid the overhead of computing it | |
| // and then never using it. In particular, we use Event for streaming stdout and stderr, which | |
| // are both large and the hashCode is never used. | |
| // | |
| // This uses the same construction as String.hashCode. We don't lock, so reads and writes to the | |
| // field can race. However, integer reads and writes are atomic, and this code guarantees that | |
| // all writes have the same value, so the memory location can only be either 0 or the final | |
| // value. Note that a reader could see the final value on the first read and 0 on the second | |
| // read, so we must take care to only read the field once. | |
| int h = hashCode; | |
| if (h == 0) { | |
| h = Objects.hash(kind, location, message, tag, Arrays.hashCode(messageBytes), outErr); | |
| hashCode = h; | |
| } | |
| return h; | |
| } | |
| @Override | |
| public boolean equals(Object other) { | |
| if (other == this) { | |
| return true; | |
| } | |
| if (other == null || !other.getClass().equals(getClass())) { | |
| return false; | |
| } | |
| Event that = (Event) other; | |
| return Objects.equals(this.kind, that.kind) | |
| && Objects.equals(this.location, that.location) | |
| && Objects.equals(this.tag, that.tag) | |
| && Objects.equals(this.message, that.message) | |
| && Objects.equals(this.outErr, that.outErr) | |
| && Arrays.equals(this.messageBytes, that.messageBytes); | |
| } | |
| /** | |
| * Replay a sequence of events on an {@link EventHandler}. | |
| */ | |
| public static void replayEventsOn(EventHandler eventHandler, Iterable<Event> events) { | |
| for (Event event : events) { | |
| eventHandler.handle(event); | |
| } | |
| } | |
| public static Event of(EventKind kind, @Nullable Location location, String message) { | |
| return new Event(kind, location, message, null, null); | |
| } | |
| /** | |
| * Construct an event by passing in the {@code byte[]} array instead of a String. | |
| * | |
| * The bytes must be decodable as UTF-8 text. | |
| */ | |
| public static Event of(EventKind kind, @Nullable Location location, byte[] messageBytes) { | |
| return new Event(kind, location, messageBytes, null, null); | |
| } | |
| /** Reports an error. */ | |
| public static Event error(@Nullable Location location, String message) { | |
| return new Event(EventKind.ERROR, location, message, null, null); | |
| } | |
| /** Reports an error. */ | |
| public static Event error(String message) { | |
| return error(null, message); | |
| } | |
| /** Reports a warning. */ | |
| public static Event warn(@Nullable Location location, String message) { | |
| return new Event(EventKind.WARNING, location, message, null, null); | |
| } | |
| /** Reports a warning. */ | |
| public static Event warn(String message) { | |
| return warn(null, message); | |
| } | |
| /** | |
| * Reports atemporal statements about the build, i.e. they're true for the duration of execution. | |
| */ | |
| public static Event info(@Nullable Location location, String message) { | |
| return new Event(EventKind.INFO, location, message, null, null); | |
| } | |
| /** | |
| * Reports atemporal statements about the build, i.e. they're true for the duration of execution. | |
| */ | |
| public static Event info(String message) { | |
| return info(null, message); | |
| } | |
| /** Reports a temporal statement about the build. */ | |
| public static Event progress(@Nullable Location location, String message) { | |
| return new Event(EventKind.PROGRESS, location, message, null, null); | |
| } | |
| /** Reports a temporal statement about the build. */ | |
| public static Event progress(String message) { | |
| return progress(null, message); | |
| } | |
| /** Reports a debug message. */ | |
| public static Event debug(@Nullable Location location, String message) { | |
| return new Event(EventKind.DEBUG, location, message, null, null); | |
| } | |
| /** | |
| * Reports a debug message. | |
| */ | |
| public static Event debug(String message) { | |
| return debug(null, message); | |
| } | |
| private static class ArrayOutputReference implements OutputReference { | |
| private final byte[] message; | |
| ArrayOutputReference(byte[] message) { | |
| this.message = message; | |
| } | |
| @Override | |
| public long getLength() { | |
| return message.length; | |
| } | |
| @Override | |
| public byte[] getFinalBytes(int count) { | |
| if (count >= message.length) { | |
| return message; | |
| } else { | |
| return Arrays.copyOfRange(message, message.length - count, message.length); | |
| } | |
| } | |
| } | |
| } |