// Copyright 2018 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.analysis.config;

import static com.google.common.base.Predicates.not;

import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Interner;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
import com.google.devtools.build.lib.util.ClassName;
import com.google.devtools.build.lib.util.Fingerprint;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import javax.annotation.concurrent.Immutable;

/**
 * A wrapper class for an {@code ImmutableSortedSet<Class<? extends Fragment>>}. Interning these
 * objects allows us to do cheap reference equality checks when these sets are in frequently used
 * keys. For good measure, we also compute a fingerprint.
 */
@AutoCodec
@Immutable
public final class FragmentClassSet extends AbstractSet<Class<? extends Fragment>> {

  /**
   * Sorts fragments by class name. This produces a stable order which, e.g., facilitates consistent
   * output from buildMnemonic.
   */
  @SerializationConstant
  public static final Comparator<Class<? extends Fragment>> LEXICAL_FRAGMENT_SORTER =
      Comparator.comparing(Class::getName);

  private static final Interner<FragmentClassSet> interner = BlazeInterners.newWeakInterner();

  @AutoCodec.Instantiator
  public static FragmentClassSet of(Collection<Class<? extends Fragment>> fragments) {
    ImmutableSortedSet<Class<? extends Fragment>> sortedFragments =
        ImmutableSortedSet.copyOf(LEXICAL_FRAGMENT_SORTER, fragments);
    byte[] fingerprint = computeFingerprint(sortedFragments);
    return interner.intern(
        new FragmentClassSet(sortedFragments, fingerprint, Arrays.hashCode(fingerprint)));
  }

  public static FragmentClassSet union(
      FragmentClassSet firstFragments, FragmentClassSet secondFragments) {
    return of(
        ImmutableSortedSet.orderedBy(LEXICAL_FRAGMENT_SORTER)
            .addAll(firstFragments)
            .addAll(secondFragments)
            .build());
  }

  private static byte[] computeFingerprint(
      ImmutableSortedSet<Class<? extends Fragment>> fragments) {
    Fingerprint fingerprint = new Fingerprint();
    for (Class<?> fragment : fragments) {
      fingerprint.addString(fragment.getName());
    }
    return fingerprint.digestAndReset();
  }

  private final ImmutableSortedSet<Class<? extends Fragment>> fragments;
  private final byte[] fingerprint;
  private final int hashCode;

  private FragmentClassSet(
      ImmutableSortedSet<Class<? extends Fragment>> fragments, byte[] fingerprint, int hashCode) {
    this.fragments = fragments;
    this.fingerprint = fingerprint;
    this.hashCode = hashCode;
  }

  @Override
  public int size() {
    return fragments.size();
  }

  @Override
  public boolean contains(Object o) {
    return fragments.contains(o);
  }

  /** Returns a set of fragment classes identical to this one but without the given fragment. */
  public FragmentClassSet trim(Class<? extends Fragment> fragment) {
    if (!contains(fragment)) {
      return this;
    }
    return of(Sets.filter(fragments, not(fragment::equals)));
  }

  @Override
  public Iterator<Class<? extends Fragment>> iterator() {
    return fragments.iterator();
  }

  @Override
  @SuppressWarnings("ReferenceEquality") // Fast-path check of the underlying fragments set.
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if (!(other instanceof FragmentClassSet)) {
      return false;
    }
    FragmentClassSet that = (FragmentClassSet) other;
    return fragments == that.fragments || Arrays.equals(fingerprint, that.fingerprint);
  }

  @Override
  public int hashCode() {
    return hashCode;
  }

  @Override
  public String toString() {
    return Collections2.transform(fragments, ClassName::getSimpleNameWithOuter).toString();
  }
}

@@ -1203,109 +1203,29 @@
 ort 
-com.google.devtools.build.lib.util.Fingerprint;%0Aimport java.util.AbstractSet;%0Aimport java.util.Arrays
+java.util.AbstractSet
 ;%0Aim
@@ -1565,57 +1565,8 @@
 eys.
- For good measure, we also compute a fingerprint.
 %0A */
@@ -2327,70 +2327,8 @@
 s);%0A
-    byte%5B%5D fingerprint = computeFingerprint(sortedFragments);%0A
     
@@ -2350,25 +2350,16 @@
 .intern(
-%0A        
 new Frag
@@ -2392,48 +2392,33 @@
 ts, 
-fingerprint, Arrays.hashCode(fingerprint
+sortedFragments.hashCode(
 )));
@@ -2720,309 +2720,8 @@
  %7D%0A%0A
-  private static byte%5B%5D computeFingerprint(%0A      ImmutableSortedSet%3CClass%3C? extends Fragment%3E%3E fragments) %7B%0A    Fingerprint fingerprint = new Fingerprint();%0A    for (Class%3C?%3E fragment : fragments) %7B%0A      fingerprint.addString(fragment.getName());%0A    %7D%0A    return fingerprint.digestAndReset();%0A  %7D%0A%0A
   pr
@@ -2793,44 +2793,8 @@
 ts;%0A
-  private final byte%5B%5D fingerprint;%0A
   pr
@@ -2847,23 +2847,16 @@
 lassSet(
-%0A      
 Immutabl
@@ -2907,28 +2907,8 @@
 nts,
- byte%5B%5D fingerprint,
  int
@@ -2956,44 +2956,8 @@
 ts;%0A
-    this.fingerprint = fingerprint;%0A
     
@@ -3557,101 +3557,8 @@
 ide%0A
-  @SuppressWarnings(%22ReferenceEquality%22) // Fast-path check of the underlying fragments set.%0A
   pu
@@ -3786,82 +3786,68 @@
 urn 
-fragments == that.fragments %7C%7C Arrays.equals(fingerprint, that.fingerpri
+hashCode == that.hashCode && fragments.equals(that.fragme
 nt
+s
 );%0A 

Stop computing a fingerprint in `FragmentClassSet`.