// 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.query2.query;

import com.google.devtools.build.lib.bugreport.BugReport;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.pkgcache.TargetEdgeObserver;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.util.DetailedExitCode;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;

/**
 * Record errors, such as missing package/target or rules containing errors, encountered during
 * visitation. Emit an error message upon encountering missing edges
 *
 * <p>The accessor {@link #hasErrors}) may not be called until the concurrent phase is over, i.e.
 * all external calls to visit() methods have completed.
 *
 * <p>If you need to report errors to the console during visitation, use the subclass {@link
 * ErrorPrintingTargetEdgeErrorObserver}.
 */
class TargetEdgeErrorObserver implements TargetEdgeObserver {

  /**
   * True iff errors were encountered. Note, may be set to "true" during the concurrent phase.
   * Volatile, because it is assigned by worker threads and read by the main thread without monitor
   * synchronization.
   */
  private volatile boolean hasErrors = false;

  private final AtomicReference<DetailedExitCode> errorCode = new AtomicReference<>();

  /**
   * Reports an unresolved label error and records the fact that an error was encountered.
   *
   * @param target the target that referenced the unresolved label
   * @param label the label that could not be resolved
   * @param e the exception that was thrown when the label could not be resolved
   */
  @ThreadSafety.ThreadSafe
  @Override
  public void missingEdge(Target target, Label label, NoSuchThingException e) {
    hasErrors = true;
    errorCode.compareAndSet(/*expect=*/ null, /*update=*/ e.getDetailedExitCode());
  }

  /**
   * Returns true iff any errors (such as missing targets or packages, or rules with errors) have
   * been encountered during any work.
   *
   * <p>Not thread-safe; do not call during visitation.
   *
   * @return true iff no errors (such as missing targets or packages, or rules with errors) have
   *     been encountered during any work.
   */
  public boolean hasErrors() {
    return hasErrors;
  }

  /** Returns the first {@link DetailedExitCode} encountered, or {@code null} if there were none. */
  @Nullable
  public DetailedExitCode getDetailedExitCode() {
    return errorCode.get();
  }

  @Override
  public void edge(Target from, Attribute attribute, Target to) {
    // No-op.
  }

  @Override
  public void node(Target node) {
    if (node.getPackage().containsErrors()
        || ((node instanceof Rule) && ((Rule) node).containsErrors())) {
      this.hasErrors = true;
      FailureDetail failureDetail = node.getPackage().getFailureDetail();
      if (failureDetail != null) {
        errorCode.compareAndSet(/*expect=*/ null, /*update=*/ DetailedExitCode.of(failureDetail));
      } else {
        BugReport.sendBugReport(
            new IllegalStateException("Undetailed error from package: " + node));
      }
    }
  }
}

@@ -720,16 +720,92 @@
 Report;%0A
+import com.google.devtools.build.lib.bugreport.BugReport.NonFatalBugReport;%0A
 import c
@@ -2804,32 +2804,39 @@
 eAndSet(/*expect
+edValue
 =*/ null, /*upda
@@ -2827,29 +2827,31 @@
 =*/ null, /*
-updat
+newValu
 e=*/ e.getDe
@@ -3912,24 +3912,37 @@
 eAndSet(
+%0A            
 /*expect
 =*/ null
@@ -3933,16 +3933,23 @@
 /*expect
+edValue
 =*/ null
@@ -3956,13 +3956,15 @@
 , /*
-updat
+newValu
 e=*/
@@ -4052,46 +4052,268 @@
 ort(
+new UndetailedErrorFromPackageException(node));
 %0A      
+%7D%0A
     
-  new IllegalStateException
+%7D%0A  %7D%0A%0A  private static final class UndetailedErrorFromPackageException extends RuntimeException%0A      implements NonFatalBugReport %7B%0A    UndetailedErrorFromPackageException(Target node) %7B%0A      super
 (%22Un
@@ -4346,34 +4346,25 @@
 e: %22 + node)
-);%0A      %7D
+;
 %0A    %7D%0A  %7D%0A%7D

Create non fatal exception when errors are not detailed with a FailureDetail.