Exemple #1
0
/**
 * Basic reference counter implementation that uses a Guava WeakInterner instance to manage
 * instances of the counter.
 *
 * <p>Calling acquire(V) returns the interned copy of the val object. Objects used need to properly
 * adhere to provide override impls of equals and hashcode to ensure they'll be interned properly.
 *
 * <p>Calling release releases the interned object and decrements the reference counter.
 */
public final class ReferenceCounter<V> {
  private final Monitor monitor = new Monitor();
  private final Interner<CountingReference<V>> interner = Interners.newWeakInterner();

  private CountingReference<V> intern(V s) {
    return interner.intern(ref(s));
  }

  public CountingReference<V> acquire(V val) {
    monitor.enter();
    try {
      return intern(val).increment();
    } finally {
      monitor.leave();
    }
  }

  public void release(V val) {
    monitor.enter();
    try {
      intern(val).decrement();
    } finally {
      monitor.leave();
    }
  }
}
/** Functionality to deserialize loaded packages. */
public class PackageDeserializer {

  private static final Logger LOG = Logger.getLogger(PackageDeserializer.class.getName());

  /** Provides the deserializer with tools it needs to build a package from its serialized form. */
  public interface PackageDeserializationEnvironment {

    /** Converts the serialized package's path string into a {@link Path} object. */
    Path getPath(String buildFilePath);

    /** Returns a {@link RuleClass} object for the serialized rule. */
    RuleClass getRuleClass(Build.Rule rulePb, Location ruleLocation);

    /** Description of what rule attributes of each rule should be deserialized. */
    AttributesToDeserialize attributesToDeserialize();
  }

  /**
   * A class that defines what attributes to keep after deserialization. Note that all attributes of
   * type label are kept in order to navigate between dependencies.
   *
   * <p>If {@code addSyntheticAttributeHash} is {@code true}, a synthetic attribute is added to each
   * Rule that contains a stable hash of the entire serialized rule for the sake of permitting
   * equality comparisons that respect the attributes that were dropped according to {@code
   * attributesToKeep}.
   */
  public static class AttributesToDeserialize {

    private final boolean addSyntheticAttributeHash;
    private final Predicate<String> shouldKeepAttributeWithName;

    public AttributesToDeserialize(
        boolean addSyntheticAttributeHash, Predicate<String> shouldKeepAttributeWithName) {
      this.addSyntheticAttributeHash = addSyntheticAttributeHash;
      this.shouldKeepAttributeWithName = shouldKeepAttributeWithName;
    }

    public boolean includeAttribute(String attr) {
      return shouldKeepAttributeWithName.apply(attr);
    }
  }

  public static final AttributesToDeserialize DESERIALIZE_ALL_ATTRS =
      new AttributesToDeserialize(false, Predicates.<String>alwaysTrue());

  // Workaround for Java serialization making it tough to pass in a deserialization environment
  // manually.
  // volatile is needed to ensure that the objects are published safely.
  // TODO(bazel-team): Subclass ObjectOutputStream to pass this through instead.
  public static volatile PackageDeserializationEnvironment defaultPackageDeserializationEnvironment;

  // Cache label deserialization across all instances- PackgeDeserializers need to be created on
  // demand due to initialiation constraints wrt the setting of static members.
  private static final Interner<Label> LABEL_INTERNER = Interners.newWeakInterner();

  /** Class encapsulating state for a single package deserialization. */
  private static class DeserializationContext {
    private final Package.Builder packageBuilder;

    private DeserializationContext(Package.Builder packageBuilder) {
      this.packageBuilder = packageBuilder;
    }
  }

  private final PackageDeserializationEnvironment packageDeserializationEnvironment;

  /**
   * Creates a {@link PackageDeserializer} using {@link #defaultPackageDeserializationEnvironment}.
   */
  public PackageDeserializer() {
    this.packageDeserializationEnvironment = defaultPackageDeserializationEnvironment;
  }

  public PackageDeserializer(PackageDeserializationEnvironment packageDeserializationEnvironment) {
    this.packageDeserializationEnvironment =
        Preconditions.checkNotNull(packageDeserializationEnvironment);
  }

  private static ParsedAttributeValue deserializeAttribute(
      Type<?> expectedType, Build.Attribute attrPb) throws PackageDeserializationException {
    Object value = deserializeAttributeValue(expectedType, attrPb);
    return new ParsedAttributeValue(
        attrPb.hasExplicitlySpecified() && attrPb.getExplicitlySpecified(), value);
  }

  private static void deserializeInputFile(
      DeserializationContext context, Build.SourceFile sourceFile)
      throws PackageDeserializationException {
    InputFile inputFile;
    try {
      inputFile =
          context.packageBuilder.createInputFile(
              deserializeLabel(sourceFile.getName()).getName(), EmptyLocation.INSTANCE);
    } catch (GeneratedLabelConflict e) {
      throw new PackageDeserializationException(e);
    }

    if (!sourceFile.getVisibilityLabelList().isEmpty() || sourceFile.hasLicense()) {
      context.packageBuilder.setVisibilityAndLicense(
          inputFile,
          PackageFactory.getVisibility(deserializeLabels(sourceFile.getVisibilityLabelList())),
          deserializeLicense(sourceFile.getLicense()));
    }
  }

  private static void deserializePackageGroup(
      DeserializationContext context, Build.PackageGroup packageGroupPb)
      throws PackageDeserializationException {
    List<String> specifications = new ArrayList<>();
    for (String containedPackage : packageGroupPb.getContainedPackageList()) {
      specifications.add("//" + containedPackage);
    }

    try {
      context.packageBuilder.addPackageGroup(
          deserializeLabel(packageGroupPb.getName()).getName(),
          specifications,
          deserializeLabels(packageGroupPb.getIncludedPackageGroupList()),
          NullEventHandler.INSTANCE, // TODO(bazel-team): Handle errors properly
          EmptyLocation.INSTANCE);
    } catch (LabelSyntaxException | Package.NameConflictException e) {
      throw new PackageDeserializationException(e);
    }
  }

  private void deserializeRule(DeserializationContext context, Build.Rule rulePb)
      throws PackageDeserializationException, InterruptedException {
    Location ruleLocation = EmptyLocation.INSTANCE;
    RuleClass ruleClass = packageDeserializationEnvironment.getRuleClass(rulePb, ruleLocation);
    Map<String, ParsedAttributeValue> attributeValues = new HashMap<>();
    AttributesToDeserialize attrToDeserialize =
        packageDeserializationEnvironment.attributesToDeserialize();

    Hasher hasher = Hashing.md5().newHasher();
    for (Build.Attribute attrPb : rulePb.getAttributeList()) {
      Type<?> type = ruleClass.getAttributeByName(attrPb.getName()).getType();
      attributeValues.put(attrPb.getName(), deserializeAttribute(type, attrPb));
      if (attrToDeserialize.addSyntheticAttributeHash) {
        // TODO(bazel-team): This might give false positives because of explicit vs implicit.
        hasher.putBytes(attrPb.toByteArray());
      }
    }
    AttributeContainerWithoutLocation attributeContainer =
        new AttributeContainerWithoutLocation(ruleClass, hasher.hash());

    Label ruleLabel = deserializeLabel(rulePb.getName());
    try {
      Rule rule =
          createRuleWithParsedAttributeValues(
              ruleClass,
              ruleLabel,
              context.packageBuilder,
              ruleLocation,
              attributeValues,
              NullEventHandler.INSTANCE,
              attributeContainer);
      context.packageBuilder.addRule(rule);

      // Remove the attribute after it is added to package in order to pass the validations
      // and be able to compute all the outputs.
      if (attrToDeserialize != DESERIALIZE_ALL_ATTRS) {
        for (String attrName : attributeValues.keySet()) {
          Attribute attribute = ruleClass.getAttributeByName(attrName);
          if (!(attrToDeserialize.shouldKeepAttributeWithName.apply(attrName)
              || BuildType.isLabelType(attribute.getType()))) {
            attributeContainer.clearIfNotLabel(attrName);
          }
        }
      }

      Preconditions.checkState(!rule.containsErrors());
    } catch (NameConflictException | LabelSyntaxException e) {
      throw new PackageDeserializationException(e);
    }
  }

  /** "Empty" location implementation, all methods should return non-null, but empty, values. */
  private static class EmptyLocation extends Location {
    private static final EmptyLocation INSTANCE = new EmptyLocation();

    private static final PathFragment DEV_NULL = new PathFragment("/dev/null");
    private static final LineAndColumn EMPTY_LINE_AND_COLUMN = new LineAndColumn(0, 0);

    private EmptyLocation() {
      super(0, 0);
    }

    @Override
    public PathFragment getPath() {
      return DEV_NULL;
    }

    @Override
    public LineAndColumn getStartLineAndColumn() {
      return EMPTY_LINE_AND_COLUMN;
    }

    @Override
    public LineAndColumn getEndLineAndColumn() {
      return EMPTY_LINE_AND_COLUMN;
    }

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

    @Override
    public boolean equals(Object other) {
      return other instanceof EmptyLocation;
    }
  }

  /** Exception thrown when something goes wrong during package deserialization. */
  public static class PackageDeserializationException extends Exception {
    private PackageDeserializationException(String message) {
      super(message);
    }

    private PackageDeserializationException(String message, Exception reason) {
      super(message, reason);
    }

    private PackageDeserializationException(Exception reason) {
      super(reason);
    }
  }

  private static Label deserializeLabel(String labelName) throws PackageDeserializationException {
    try {
      return LABEL_INTERNER.intern(Label.parseAbsolute(labelName));
    } catch (LabelSyntaxException e) {
      throw new PackageDeserializationException(
          "Invalid label '" + labelName + "':" + e.getMessage(), e);
    }
  }

  private static List<Label> deserializeLabels(List<String> labelNames)
      throws PackageDeserializationException {
    ImmutableList.Builder<Label> result = ImmutableList.builder();
    for (String labelName : labelNames) {
      result.add(deserializeLabel(labelName));
    }

    return result.build();
  }

  private static License deserializeLicense(Build.License licensePb)
      throws PackageDeserializationException {
    List<String> licenseStrings = new ArrayList<>();
    licenseStrings.addAll(licensePb.getLicenseTypeList());
    for (String exception : licensePb.getExceptionList()) {
      licenseStrings.add("exception=" + exception);
    }

    try {
      return License.parseLicense(licenseStrings);
    } catch (LicenseParsingException e) {
      throw new PackageDeserializationException(e);
    }
  }

  private static Set<DistributionType> deserializeDistribs(List<String> distributions)
      throws PackageDeserializationException {
    try {
      return License.parseDistributions(distributions);
    } catch (LicenseParsingException e) {
      throw new PackageDeserializationException(e);
    }
  }

  private static TriState deserializeTriStateValue(String value)
      throws PackageDeserializationException {
    if (value.equals("yes")) {
      return TriState.YES;
    } else if (value.equals("no")) {
      return TriState.NO;
    } else if (value.equals("auto")) {
      return TriState.AUTO;
    } else {
      throw new PackageDeserializationException(
          String.format("Invalid tristate value: '%s'", value));
    }
  }

  private static List<FilesetEntry> deserializeFilesetEntries(List<Build.FilesetEntry> filesetPbs)
      throws PackageDeserializationException {
    ImmutableList.Builder<FilesetEntry> result = ImmutableList.builder();
    for (Build.FilesetEntry filesetPb : filesetPbs) {
      Label srcLabel = deserializeLabel(filesetPb.getSource());
      List<Label> files =
          filesetPb.getFilesPresent() ? deserializeLabels(filesetPb.getFileList()) : null;
      List<String> excludes =
          filesetPb.getExcludeList().isEmpty()
              ? null
              : ImmutableList.copyOf(filesetPb.getExcludeList());
      String destDir = filesetPb.getDestinationDirectory();
      FilesetEntry.SymlinkBehavior symlinkBehavior =
          pbToSymlinkBehavior(filesetPb.getSymlinkBehavior());
      String stripPrefix = filesetPb.hasStripPrefix() ? filesetPb.getStripPrefix() : null;

      result.add(
          new FilesetEntry(srcLabel, files, excludes, destDir, symlinkBehavior, stripPrefix));
    }

    return result.build();
  }

  /**
   * Deserialize a package from its representation as a protocol message. The inverse of {@link
   * PackageSerializer#serialize}.
   *
   * @throws IOException
   * @throws InterruptedException
   */
  private void deserializeInternal(
      Build.Package packagePb,
      StoredEventHandler eventHandler,
      Package.Builder builder,
      InputStream in)
      throws PackageDeserializationException, IOException, InterruptedException {
    Path buildFile = packageDeserializationEnvironment.getPath(packagePb.getBuildFilePath());
    Preconditions.checkNotNull(buildFile);
    DeserializationContext context = new DeserializationContext(builder);
    builder.setFilename(buildFile);

    if (packagePb.hasDefaultVisibilitySet() && packagePb.getDefaultVisibilitySet()) {
      builder.setDefaultVisibility(
          PackageFactory.getVisibility(
              deserializeLabels(packagePb.getDefaultVisibilityLabelList())));
    }

    // It's important to do this after setting the default visibility, since that implicitly sets
    // this bit to true
    builder.setDefaultVisibilitySet(packagePb.getDefaultVisibilitySet());
    if (packagePb.hasDefaultTestonly()) {
      builder.setDefaultTestonly(packagePb.getDefaultTestonly());
    }
    if (packagePb.hasDefaultDeprecation()) {
      builder.setDefaultDeprecation(packagePb.getDefaultDeprecation());
    }

    builder.setDefaultCopts(packagePb.getDefaultCoptList());
    if (packagePb.hasDefaultHdrsCheck()) {
      builder.setDefaultHdrsCheck(packagePb.getDefaultHdrsCheck());
    }
    if (packagePb.hasDefaultLicense()) {
      builder.setDefaultLicense(deserializeLicense(packagePb.getDefaultLicense()));
    }
    builder.setDefaultDistribs(deserializeDistribs(packagePb.getDefaultDistribList()));

    for (String subinclude : packagePb.getSubincludeLabelList()) {
      Label label = deserializeLabel(subinclude);
      builder.addSubinclude(label, null);
    }

    ImmutableList.Builder<Label> skylarkFileDependencies = ImmutableList.builder();
    for (String skylarkFile : packagePb.getSkylarkLabelList()) {
      skylarkFileDependencies.add(deserializeLabel(skylarkFile));
    }
    builder.setSkylarkFileDependencies(skylarkFileDependencies.build());

    MakeEnvironment.Builder makeEnvBuilder = new MakeEnvironment.Builder();
    for (Build.MakeVar makeVar : packagePb.getMakeVariableList()) {
      for (Build.MakeVarBinding binding : makeVar.getBindingList()) {
        makeEnvBuilder.update(
            makeVar.getName(), binding.getValue(), binding.getPlatformSetRegexp());
      }
    }
    builder.setMakeEnv(makeEnvBuilder);

    for (Build.Event event : packagePb.getEventList()) {
      deserializeEvent(eventHandler, event);
    }

    if (packagePb.hasContainsErrors() && packagePb.getContainsErrors()) {
      builder.setContainsErrors();
    }

    builder.setWorkspaceName(packagePb.getWorkspaceName());

    deserializeTargets(in, context);
  }

  private void deserializeTargets(InputStream in, DeserializationContext context)
      throws IOException, PackageDeserializationException, InterruptedException {
    Build.TargetOrTerminator tot;
    while (!(tot = Build.TargetOrTerminator.parseDelimitedFrom(in)).getIsTerminator()) {
      Build.Target target = tot.getTarget();
      switch (target.getType()) {
        case SOURCE_FILE:
          deserializeInputFile(context, target.getSourceFile());
          break;
        case PACKAGE_GROUP:
          deserializePackageGroup(context, target.getPackageGroup());
          break;
        case RULE:
          deserializeRule(context, target.getRule());
          break;
        default:
          throw new IllegalStateException("Unexpected Target type: " + target.getType());
      }
    }
  }

  /**
   * Deserializes a {@link Package} from {@code in}. The inverse of {@link
   * PackageSerializer#serialize}.
   *
   * <p>Expects {@code in} to contain a single {@link
   * com.google.devtools.build.lib.query2.proto.proto2api.Build.Package} message followed by a
   * series of {@link com.google.devtools.build.lib.query2.proto.proto2api.Build.TargetOrTerminator}
   * messages encoding the associated targets.
   *
   * @param in stream to read from
   * @return a new {@link Package} as read from {@code in}
   * @throws PackageDeserializationException on failures deserializing the input
   * @throws IOException on failures reading from {@code in}
   * @throws InterruptedException
   */
  public Package deserialize(InputStream in)
      throws PackageDeserializationException, IOException, InterruptedException {
    try {
      return deserializeInternal(in);
    } catch (PackageDeserializationException | RuntimeException e) {
      LOG.log(Level.WARNING, "Failed to deserialize Package object", e);
      throw e;
    }
  }

  private Package deserializeInternal(InputStream in)
      throws PackageDeserializationException, IOException, InterruptedException {
    // Read the initial Package message so we have the data to initialize the builder. We will read
    // the Targets in individually later.
    Build.Package packagePb = Build.Package.parseDelimitedFrom(in);
    Package.Builder builder;
    try {
      builder =
          new Package.Builder(
              PackageIdentifier.create(
                  packagePb.getRepository(), new PathFragment(packagePb.getName())),
              null);
    } catch (LabelSyntaxException e) {
      throw new PackageDeserializationException(e);
    }
    StoredEventHandler eventHandler = new StoredEventHandler();
    deserializeInternal(packagePb, eventHandler, builder, in);
    builder.addEvents(eventHandler.getEvents());
    return builder.build();
  }

  private static void deserializeEvent(StoredEventHandler eventHandler, Build.Event event) {
    String message = event.getMessage();
    switch (event.getKind()) {
      case ERROR:
        eventHandler.handle(Event.error(message));
        break;
      case WARNING:
        eventHandler.handle(Event.warn(message));
        break;
      case INFO:
        eventHandler.handle(Event.info(message));
        break;
      case PROGRESS:
        eventHandler.handle(Event.progress(message));
        break;
      default:
        break; // Ignore
    }
  }

  private static List<?> deserializeGlobs(List<?> matches, Build.Attribute attrPb) {
    if (attrPb.getGlobCriteriaCount() == 0) {
      return matches;
    }

    Builder<GlobCriteria> criteriaBuilder = ImmutableList.builder();
    for (Build.GlobCriteria criteriaPb : attrPb.getGlobCriteriaList()) {
      if (criteriaPb.hasGlob() && criteriaPb.getGlob()) {
        criteriaBuilder.add(
            GlobCriteria.fromGlobCall(
                ImmutableList.copyOf(criteriaPb.getIncludeList()),
                ImmutableList.copyOf(criteriaPb.getExcludeList())));
      } else {
        criteriaBuilder.add(
            GlobCriteria.fromList(ImmutableList.copyOf(criteriaPb.getIncludeList())));
      }
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    GlobList<?> result = new GlobList(criteriaBuilder.build(), matches);
    return result;
  }

  // TODO(bazel-team): Verify that these put sane values in the attribute
  @VisibleForTesting
  static Object deserializeAttributeValue(Type<?> expectedType, Build.Attribute attrPb)
      throws PackageDeserializationException {
    switch (attrPb.getType()) {
      case INTEGER:
        return attrPb.hasIntValue() ? new Integer(attrPb.getIntValue()) : null;

      case STRING:
        if (!attrPb.hasStringValue()) {
          return null;
        } else if (expectedType == BuildType.NODEP_LABEL) {
          return deserializeLabel(attrPb.getStringValue());
        } else {
          return attrPb.getStringValue();
        }

      case LABEL:
      case OUTPUT:
        return attrPb.hasStringValue() ? deserializeLabel(attrPb.getStringValue()) : null;

      case STRING_LIST:
        if (expectedType == BuildType.NODEP_LABEL_LIST) {
          return deserializeGlobs(deserializeLabels(attrPb.getStringListValueList()), attrPb);
        } else {
          return deserializeGlobs(ImmutableList.copyOf(attrPb.getStringListValueList()), attrPb);
        }

      case LABEL_LIST:
      case OUTPUT_LIST:
        return deserializeGlobs(deserializeLabels(attrPb.getStringListValueList()), attrPb);

      case DISTRIBUTION_SET:
        return deserializeDistribs(attrPb.getStringListValueList());

      case LICENSE:
        return attrPb.hasLicense() ? deserializeLicense(attrPb.getLicense()) : null;

      case STRING_DICT:
        {
          // Building an immutable map will fail if the builder was given duplicate keys. These
          // entry
          // lists may contain duplicate keys if the serialized map value was configured (e.g. via
          // the select function) and the different configuration values had keys in common. This is
          // because serialization flattens configurable map-valued attributes.
          //
          // As long as serialization does this flattening, to avoid failure during deserialization,
          // we dedupe entries in the list by their keys.
          // TODO(bazel-team): Serialize and deserialize configured values with fidelity (without
          // flattening them).
          ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
          HashSet<String> keysSeenSoFar = Sets.newHashSet();
          for (Build.StringDictEntry entry : attrPb.getStringDictValueList()) {
            String key = entry.getKey();
            if (keysSeenSoFar.add(key)) {
              builder.put(key, entry.getValue());
            }
          }
          return builder.build();
        }

      case STRING_DICT_UNARY:
        {
          // See STRING_DICT case's comment about why this dedupes entries by their keys.
          ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
          HashSet<String> keysSeenSoFar = Sets.newHashSet();
          for (StringDictUnaryEntry entry : attrPb.getStringDictUnaryValueList()) {
            String key = entry.getKey();
            if (keysSeenSoFar.add(key)) {
              builder.put(key, entry.getValue());
            }
          }
          return builder.build();
        }

      case FILESET_ENTRY_LIST:
        return deserializeFilesetEntries(attrPb.getFilesetListValueList());

      case LABEL_LIST_DICT:
        {
          // See STRING_DICT case's comment about why this dedupes entries by their keys.
          ImmutableMap.Builder<String, List<Label>> builder = ImmutableMap.builder();
          HashSet<String> keysSeenSoFar = Sets.newHashSet();
          for (Build.LabelListDictEntry entry : attrPb.getLabelListDictValueList()) {
            String key = entry.getKey();
            if (keysSeenSoFar.add(key)) {
              builder.put(key, deserializeLabels(entry.getValueList()));
            }
          }
          return builder.build();
        }

      case STRING_LIST_DICT:
        {
          // See STRING_DICT case's comment about why this dedupes entries by their keys.
          ImmutableMap.Builder<String, List<String>> builder = ImmutableMap.builder();
          HashSet<String> keysSeenSoFar = Sets.newHashSet();
          for (Build.StringListDictEntry entry : attrPb.getStringListDictValueList()) {
            String key = entry.getKey();
            if (keysSeenSoFar.add(key)) {
              builder.put(key, ImmutableList.copyOf(entry.getValueList()));
            }
          }
          return builder.build();
        }

      case BOOLEAN:
        return attrPb.hasBooleanValue() ? attrPb.getBooleanValue() : null;

      case TRISTATE:
        return attrPb.hasStringValue() ? deserializeTriStateValue(attrPb.getStringValue()) : null;

      case INTEGER_LIST:
        return ImmutableList.copyOf(attrPb.getIntListValueList());

      default:
        throw new PackageDeserializationException("Invalid discriminator: " + attrPb.getType());
    }
  }

  private static FilesetEntry.SymlinkBehavior pbToSymlinkBehavior(
      Build.FilesetEntry.SymlinkBehavior symlinkBehavior) {
    switch (symlinkBehavior) {
      case COPY:
        return FilesetEntry.SymlinkBehavior.COPY;
      case DEREFERENCE:
        return FilesetEntry.SymlinkBehavior.DEREFERENCE;
      default:
        throw new IllegalStateException();
    }
  }

  /**
   * An special {@code AttributeContainer} implementation that does not keep the location and can
   * contain a hashcode of the target attributes.
   */
  public static class AttributeContainerWithoutLocation extends AttributeContainer {

    @Nullable private final HashCode syntheticAttrHash;

    private AttributeContainerWithoutLocation(
        RuleClass ruleClass, @Nullable HashCode syntheticAttrHash) {
      super(ruleClass, null);
      this.syntheticAttrHash = syntheticAttrHash;
    }

    @Override
    public Location getAttributeLocation(String attrName) {
      return EmptyLocation.INSTANCE;
    }

    @Override
    void setAttributeLocation(int attrIndex, Location location) {
      throw new UnsupportedOperationException("Setting location not supported");
    }

    @Override
    void setAttributeLocation(Attribute attribute, Location location) {
      throw new UnsupportedOperationException("Setting location not supported");
    }

    @Nullable
    public HashCode getSyntheticAttrHash() {
      return syntheticAttrHash;
    }

    private void clearIfNotLabel(String attr) {
      setAttributeValueByName(attr, null);
    }
  }

  /**
   * Creates a rule with the attribute values that are already parsed.
   *
   * <p><b>WARNING:</b> This assumes that the attribute values here have the right type and bypasses
   * some sanity checks. If they are of the wrong type, everything will come down burning.
   */
  @SuppressWarnings("unchecked")
  private static Rule createRuleWithParsedAttributeValues(
      RuleClass ruleClass,
      Label label,
      Package.Builder pkgBuilder,
      Location ruleLocation,
      Map<String, ParsedAttributeValue> attributeValues,
      EventHandler eventHandler,
      AttributeContainer attributeContainer)
      throws LabelSyntaxException, InterruptedException {
    Rule rule =
        pkgBuilder.newRuleWithLabelAndAttrContainer(
            label, ruleClass, null, ruleLocation, attributeContainer);
    rule.checkValidityPredicate(eventHandler);

    for (Attribute attribute : rule.getRuleClassObject().getAttributes()) {
      ParsedAttributeValue value = attributeValues.get(attribute.getName());
      if (attribute.isMandatory()) {
        Preconditions.checkState(value != null);
      }

      if (value == null) {
        continue;
      }

      rule.setAttributeValue(attribute, value.value, value.explicitlySpecified);
      ruleClass.checkAllowedValues(rule, attribute, eventHandler);

      if (attribute.getName().equals("visibility")) {
        // TODO(bazel-team): Verify that this cast works
        rule.setVisibility(PackageFactory.getVisibility((List<Label>) value.value));
      }
    }

    rule.populateOutputFiles(eventHandler, pkgBuilder);
    Preconditions.checkState(!rule.containsErrors());
    return rule;
  }

  private static class ParsedAttributeValue {
    private final boolean explicitlySpecified;
    private final Object value;

    private ParsedAttributeValue(boolean explicitlySpecified, Object value) {
      this.explicitlySpecified = explicitlySpecified;
      this.value = value;
    }
  }
}
@ManageLifecycle
public class SingleServerInventoryView extends ServerInventoryView<DataSegment>
    implements FilteredServerInventoryView {
  private static final EmittingLogger log = new EmittingLogger(SingleServerInventoryView.class);
  private static final Interner<DataSegment> DATA_SEGMENT_INTERNER = Interners.newWeakInterner();

  private final ConcurrentMap<SegmentCallback, Predicate<Pair<DruidServerMetadata, DataSegment>>>
      segmentPredicates = new MapMaker().makeMap();
  private final Predicate<Pair<DruidServerMetadata, DataSegment>> defaultFilter;

  @Inject
  public SingleServerInventoryView(
      final ZkPathsConfig zkPaths,
      final CuratorFramework curator,
      final ObjectMapper jsonMapper,
      final Predicate<Pair<DruidServerMetadata, DataSegment>> defaultFilter) {
    super(
        log,
        zkPaths.getAnnouncementsPath(),
        zkPaths.getServedSegmentsPath(),
        curator,
        jsonMapper,
        new TypeReference<DataSegment>() {});

    Preconditions.checkNotNull(defaultFilter);
    this.defaultFilter = defaultFilter;
  }

  @Override
  protected DruidServer addInnerInventory(
      DruidServer container, String inventoryKey, DataSegment inventory) {
    Predicate<Pair<DruidServerMetadata, DataSegment>> predicate =
        Predicates.or(defaultFilter, Predicates.or(segmentPredicates.values()));
    if (predicate.apply(Pair.of(container.getMetadata(), inventory))) {
      addSingleInventory(container, inventory);
    }
    return container;
  }

  @Override
  protected DruidServer updateInnerInventory(
      DruidServer container, String inventoryKey, DataSegment inventory) {
    return addInnerInventory(container, inventoryKey, inventory);
  }

  @Override
  protected DruidServer removeInnerInventory(DruidServer container, String inventoryKey) {
    removeSingleInventory(container, inventoryKey);
    return container;
  }

  public void registerSegmentCallback(
      final Executor exec,
      final SegmentCallback callback,
      final Predicate<Pair<DruidServerMetadata, DataSegment>> filter) {
    SegmentCallback filteringCallback =
        new SingleServerInventoryView.FilteringSegmentCallback(callback, filter);
    segmentPredicates.put(filteringCallback, filter);
    registerSegmentCallback(exec, filteringCallback);
  }

  @Override
  protected void segmentCallbackRemoved(SegmentCallback callback) {
    segmentPredicates.remove(callback);
  }

  static class FilteringSegmentCallback implements SegmentCallback {

    private final SegmentCallback callback;
    private final Predicate<Pair<DruidServerMetadata, DataSegment>> filter;

    FilteringSegmentCallback(
        SegmentCallback callback, Predicate<Pair<DruidServerMetadata, DataSegment>> filter) {
      this.callback = callback;
      this.filter = filter;
    }

    @Override
    public CallbackAction segmentAdded(DruidServerMetadata server, DataSegment segment) {
      final CallbackAction action;
      if (filter.apply(Pair.of(server, segment))) {
        action = callback.segmentAdded(server, segment);
      } else {
        action = CallbackAction.CONTINUE;
      }
      return action;
    }

    @Override
    public CallbackAction segmentRemoved(DruidServerMetadata server, DataSegment segment) {
      final CallbackAction action;
      if (filter.apply(Pair.of(server, segment))) {
        action = callback.segmentRemoved(server, segment);
      } else {
        action = CallbackAction.CONTINUE;
      }
      return action;
    }

    @Override
    public CallbackAction segmentViewInitialized() {
      return callback.segmentViewInitialized();
    }
  }

  @Override
  protected DataSegment internInventory(DataSegment sample) {
    return DATA_SEGMENT_INTERNER.intern(sample);
  }
}
/**
 * The class {@code StringUtilities} defines utility methods for strings.
 *
 * @coverage dart.engine.utilities
 */
public final class StringUtilities {
  /** The empty String {@code ""}. */
  public static final String EMPTY = "";

  /** An empty array of strings. */
  public static final String[] EMPTY_ARRAY = new String[0];

  /** The {@link Interner} instance to use for {@link #intern(String)}. */
  private static final Interner<String> INTERNER = Interners.newWeakInterner();

  /** Abbreviates a String using ellipses inserted at left. */
  public static String abbreviateLeft(String s, int width) {
    int length = s.length();
    if (length > width) {
      if (width < 4) {
        throw new IllegalArgumentException("Minimal width is 4");
      }
      return "..." + s.substring(length - (width - 3));
    }
    return s;
  }

  /**
   * Return {@code true} if the three-character substring occurs at the end of the given string.
   *
   * @param string the string being searched
   * @param char1 the first character in the substring
   * @param char2 the second character in the substring
   * @param char3 the third character in the substring
   * @return {@code true} if the substring occurs at the end of the string
   */
  public static boolean endsWith3(String string, int char1, int char2, int char3) {
    int length = string.length();
    return length >= 3
        && string.charAt(length - 3) == char1
        && string.charAt(length - 2) == char2
        && string.charAt(length - 1) == char3;
  }

  /**
   * Return {@code true} if the given string ends with the given character.
   *
   * @param string the string being searched
   * @param character the character being tested for
   * @return {@code true} if the string ends with the character
   */
  public static boolean endsWithChar(String string, int character) {
    int length = string.length();
    return length > 0 && string.charAt(length - 1) == character;
  }

  /**
   * Return the index of the first occurrence of the given character in the given string that is at
   * or after the given starting index. Return {@code -1} if the substring does not occur.
   *
   * @param string the string being searched
   * @param startIndex the index at which the search should begin
   * @param char1 the first character in the substring
   * @return the index of the first occurrence of the substring
   */
  public static int indexOf1(String string, int startIndex, int char1) {
    int index = startIndex;
    int last = string.length();
    while (index < last) {
      if (string.charAt(index) == char1) {
        return index;
      }
      index++;
    }
    return -1;
  }

  /**
   * Return the index of the first occurrence of the given characters as a substring of the given
   * string that is at or after the given starting index. Return {@code -1} if the substring does
   * not occur.
   *
   * @param string the string being searched
   * @param startIndex the index at which the search should begin
   * @param char1 the first character in the substring
   * @param char2 the second character in the substring
   * @return the index of the first occurrence of the substring
   */
  public static int indexOf2(String string, int startIndex, int char1, int char2) {
    int index = startIndex;
    int last = string.length() - 1;
    while (index < last) {
      if (string.charAt(index) == char1 && string.charAt(index + 1) == char2) {
        return index;
      }
      index++;
    }
    return -1;
  }

  /**
   * Return the index of the first occurrence of the given characters as a substring of the given
   * string that is at or after the given starting index. Return {@code -1} if the substring does
   * not occur.
   *
   * @param string the string being searched
   * @param startIndex the index at which the search should begin
   * @param char1 the first character in the substring
   * @param char2 the second character in the substring
   * @param char3 the third character in the substring
   * @param char4 the fourth character in the substring
   * @return the index of the first occurrence of the substring
   */
  public static int indexOf4(
      String string, int startIndex, int char1, int char2, int char3, int char4) {
    int index = startIndex;
    int last = string.length() - 3;
    while (index < last) {
      if (string.charAt(index) == char1
          && string.charAt(index + 1) == char2
          && string.charAt(index + 2) == char3
          && string.charAt(index + 3) == char4) {
        return index;
      }
      index++;
    }
    return -1;
  }

  /**
   * Return the index of the first occurrence of the given characters as a substring of the given
   * string that is at or after the given starting index. Return {@code -1} if the substring does
   * not occur.
   *
   * @param string the string being searched
   * @param startIndex the index at which the search should begin
   * @param char1 the first character in the substring
   * @param char2 the second character in the substring
   * @param char3 the third character in the substring
   * @param char4 the fourth character in the substring
   * @param char5 the fifth character in the substring
   * @return the index of the first occurrence of the substring
   */
  public static int indexOf5(
      String string, int startIndex, int char1, int char2, int char3, int char4, int char5) {
    int index = startIndex;
    int last = string.length() - 4;
    while (index < last) {
      if (string.charAt(index) == char1
          && string.charAt(index + 1) == char2
          && string.charAt(index + 2) == char3
          && string.charAt(index + 3) == char4
          && string.charAt(index + 4) == char5) {
        return index;
      }
      index++;
    }
    return -1;
  }

  /**
   * Return the index of the first not letter/digit character in the given string that is at or
   * after the given starting index. Return the length of the given string if the all characters to
   * the end are letters/digits.
   *
   * @param string the string being searched
   * @param startIndex the index at which the search should begin
   * @return the index of the first not letter/digit character
   */
  public static int indexOfFirstNotLetterDigit(String string, int startIndex) {
    int index = startIndex;
    int last = string.length();
    while (index < last) {
      char c = string.charAt(index);
      if (!Character.isLetterOrDigit(c)) {
        return index;
      }
      index++;
    }
    return last;
  }

  /**
   * Returns a canonical representation for the given {@link String}.
   *
   * @return the given {@link String} or its canonical representation.
   */
  public static String intern(String str) {
    if (str == null) {
      return null;
    }
    str = new String(str);
    return INTERNER.intern(str);
  }

  /**
   * Checks if the CharSequence contains only Unicode letters.
   *
   * <p>{@code null} will return {@code false}. An empty CharSequence (length()=0) will return
   * {@code false}.
   *
   * <pre>
   * StringUtils.isAlpha(null)   = false
   * StringUtils.isAlpha("")     = false
   * StringUtils.isAlpha("  ")   = false
   * StringUtils.isAlpha("abc")  = true
   * StringUtils.isAlpha("ab2c") = false
   * StringUtils.isAlpha("ab-c") = false
   * </pre>
   *
   * @param cs the CharSequence to check, may be null
   * @return {@code true} if only contains letters, and is non-null
   */
  public static boolean isAlpha(CharSequence cs) {
    if (cs == null || cs.length() == 0) {
      return false;
    }
    int sz = cs.length();
    for (int i = 0; i < sz; i++) {
      if (Character.isLetter(cs.charAt(i)) == false) {
        return false;
      }
    }
    return true;
  }

  /**
   * Return {@code true} if the given CharSequence is empty ("") or null.
   *
   * <pre>
   * StringUtils.isEmpty(null)      = true
   * StringUtils.isEmpty("")        = true
   * StringUtils.isEmpty(" ")       = false
   * StringUtils.isEmpty("bob")     = false
   * StringUtils.isEmpty("  bob  ") = false
   * </pre>
   *
   * @param cs the CharSequence to check, may be null
   * @return {@code true} if the CharSequence is empty or null
   */
  public static boolean isEmpty(CharSequence cs) {
    return cs == null || cs.length() == 0;
  }

  /**
   * Checks if the String can be used as a tag name.
   *
   * <p>{@code null} will return {@code false}. An empty String (length()=0) will return {@code
   * false}.
   *
   * <pre>
   * StringUtils.isAlpha(null)   = false
   * StringUtils.isAlpha("")     = false
   * StringUtils.isAlpha("  ")   = false
   * StringUtils.isAlpha("ab c") = false
   * StringUtils.isAlpha("abc")  = true
   * StringUtils.isAlpha("ab2c") = true
   * StringUtils.isAlpha("ab-c") = true
   * </pre>
   *
   * @param s the String to check, may be null
   * @return {@code true} if can be used as a tag name, and is non-null
   */
  public static boolean isTagName(String s) {
    if (s == null || s.length() == 0) {
      return false;
    }
    int sz = s.length();
    for (int i = 0; i < sz; i++) {
      char c = s.charAt(i);
      if (!Character.isLetter(c)) {
        if (i == 0) {
          return false;
        }
        if (!Character.isDigit(c) && c != '-') {
          return false;
        }
      }
    }
    return true;
  }

  /**
   * Produce a string containing all of the names in the given array, surrounded by single quotes,
   * and separated by commas. The list must contain at least two elements.
   *
   * @param names the names to be printed
   * @return the result of printing the names
   */
  public static String printListOfQuotedNames(String[] names) {
    if (names == null) {
      throw new IllegalArgumentException("The list must not be null");
    }
    int count = names.length;
    if (count < 2) {
      throw new IllegalArgumentException("The list must contain at least two names");
    }
    StringBuilder builder = new StringBuilder();
    builder.append("'");
    builder.append(names[0]);
    builder.append("'");
    for (int i = 1; i < count - 1; i++) {
      builder.append(", '");
      builder.append(names[i]);
      builder.append("'");
    }
    builder.append(" and '");
    builder.append(names[count - 1]);
    builder.append("'");
    return builder.toString();
  }

  /**
   * Return {@code true} if the two-character substring occurs at the given index in the given
   * string.
   *
   * @param string the string being searched
   * @param startIndex the index at which the search should begin
   * @param char1 the first character in the substring
   * @param char2 the second character in the substring
   * @return {@code true} if the substring occurs at the given index in the string
   */
  public static boolean startsWith2(String string, int startIndex, int char1, int char2) {
    return string.length() - startIndex >= 2
        && string.charAt(startIndex) == char1
        && string.charAt(startIndex + 1) == char2;
  }

  /**
   * Return {@code true} if the three-character substring occurs at the given index in the given
   * string.
   *
   * @param string the string being searched
   * @param startIndex the index at which the search should begin
   * @param char1 the first character in the substring
   * @param char2 the second character in the substring
   * @param char3 the third character in the substring
   * @return {@code true} if the substring occurs at the given index in the string
   */
  public static boolean startsWith3(
      String string, int startIndex, int char1, int char2, int char3) {
    return string.length() - startIndex >= 3
        && string.charAt(startIndex) == char1
        && string.charAt(startIndex + 1) == char2
        && string.charAt(startIndex + 2) == char3;
  }

  /**
   * Return {@code true} if the four-character substring occurs at the given index in the given
   * string.
   *
   * @param string the string being searched
   * @param startIndex the index at which the search should begin
   * @param char1 the first character in the substring
   * @param char2 the second character in the substring
   * @param char3 the third character in the substring
   * @param char4 the fourth character in the substring
   * @return {@code true} if the substring occurs at the given index in the string
   */
  public static boolean startsWith4(
      String string, int startIndex, int char1, int char2, int char3, int char4) {
    return string.length() - startIndex >= 4
        && string.charAt(startIndex) == char1
        && string.charAt(startIndex + 1) == char2
        && string.charAt(startIndex + 2) == char3
        && string.charAt(startIndex + 3) == char4;
  }

  /**
   * Return {@code true} if the five-character substring occurs at the given index in the given
   * string.
   *
   * @param string the string being searched
   * @param startIndex the index at which the search should begin
   * @param char1 the first character in the substring
   * @param char2 the second character in the substring
   * @param char3 the third character in the substring
   * @param char4 the fourth character in the substring
   * @param char5 the fifth character in the substring
   * @return {@code true} if the substring occurs at the given index in the string
   */
  public static boolean startsWith5(
      String string, int startIndex, int char1, int char2, int char3, int char4, int char5) {
    return string.length() - startIndex >= 5
        && string.charAt(startIndex) == char1
        && string.charAt(startIndex + 1) == char2
        && string.charAt(startIndex + 2) == char3
        && string.charAt(startIndex + 3) == char4
        && string.charAt(startIndex + 4) == char5;
  }

  /**
   * Return {@code true} if the six-character substring occurs at the given index in the given
   * string.
   *
   * @param string the string being searched
   * @param startIndex the index at which the search should begin
   * @param char1 the first character in the substring
   * @param char2 the second character in the substring
   * @param char3 the third character in the substring
   * @param char4 the fourth character in the substring
   * @param char5 the fifth character in the substring
   * @param char6 the sixth character in the substring
   * @return {@code true} if the substring occurs at the given index in the string
   */
  public static boolean startsWith6(
      String string,
      int startIndex,
      int char1,
      int char2,
      int char3,
      int char4,
      int char5,
      int char6) {
    return string.length() - startIndex >= 6
        && string.charAt(startIndex) == char1
        && string.charAt(startIndex + 1) == char2
        && string.charAt(startIndex + 2) == char3
        && string.charAt(startIndex + 3) == char4
        && string.charAt(startIndex + 4) == char5
        && string.charAt(startIndex + 5) == char6;
  }

  /**
   * Return {@code true} if the given string starts with the given character.
   *
   * @param string the string being searched
   * @param character the character being tested for
   * @return {@code true} if the string starts with the character
   */
  public static boolean startsWithChar(String string, int character) {
    return string.length() > 0 && string.charAt(0) == character;
  }

  /**
   * Gets the substring after the first occurrence of a separator. The separator is not returned.
   *
   * <p>A {@code null} string input will return {@code null}. An empty ("") string input will return
   * the empty string. A {@code null} separator will return the empty string if the input string is
   * not {@code null}.
   *
   * <p>If nothing is found, the empty string is returned.
   *
   * <pre>
   * StringUtils.substringAfter(null, *)      = null
   * StringUtils.substringAfter("", *)        = ""
   * StringUtils.substringAfter(*, null)      = ""
   * StringUtils.substringAfter("abc", "a")   = "bc"
   * StringUtils.substringAfter("abcba", "b") = "cba"
   * StringUtils.substringAfter("abc", "c")   = ""
   * StringUtils.substringAfter("abc", "d")   = ""
   * StringUtils.substringAfter("abc", "")    = "abc"
   * </pre>
   *
   * @param str the String to get a substring from, may be null
   * @param separator the String to search for, may be null
   * @return the substring after the first occurrence of the separator, {@code null} if null String
   *     input
   */
  public static String substringAfter(String str, String separator) {
    if (isEmpty(str)) {
      return str;
    }
    if (separator == null) {
      return EMPTY;
    }
    int pos = str.indexOf(separator);
    if (pos == -1) {
      return EMPTY;
    }
    return str.substring(pos + separator.length());
  }

  /**
   * Return the substring before the first occurrence of a separator. The separator is not returned.
   *
   * <p>A {@code null} string input will return {@code null}. An empty ("") string input will return
   * the empty string. A {@code null} separator will return the input string.
   *
   * <p>If nothing is found, the string input is returned.
   *
   * <pre>
   * StringUtils.substringBefore(null, *)      = null
   * StringUtils.substringBefore("", *)        = ""
   * StringUtils.substringBefore("abc", "a")   = ""
   * StringUtils.substringBefore("abcba", "b") = "a"
   * StringUtils.substringBefore("abc", "c")   = "ab"
   * StringUtils.substringBefore("abc", "d")   = "abc"
   * StringUtils.substringBefore("abc", "")    = ""
   * StringUtils.substringBefore("abc", null)  = "abc"
   * </pre>
   *
   * @param str the string to get a substring from, may be null
   * @param separator the string to search for, may be null
   * @return the substring before the first occurrence of the separator
   */
  public static String substringBefore(String str, String separator) {
    if (isEmpty(str) || separator == null) {
      return str;
    }
    if (separator.length() == 0) {
      return EMPTY;
    }
    int pos = str.indexOf(separator);
    if (pos == -1) {
      return str;
    }
    return str.substring(0, pos);
  }

  /**
   * Return the substring before the first occurrence of a separator. The separator is not included
   * in the returned value.
   *
   * <p>A {@code null} string input will return {@code null}. An empty ("") string input will return
   * the empty string.
   *
   * <p>If nothing is found, the string input is returned.
   *
   * <pre>
   * StringUtils.substringBefore(null, *)      = null
   * StringUtils.substringBefore("", *)        = ""
   * StringUtils.substringBefore("abc", 'a')   = ""
   * StringUtils.substringBefore("abcba", 'b') = "a"
   * StringUtils.substringBefore("abc", 'c')   = "ab"
   * StringUtils.substringBefore("abc", 'd')   = "abc"
   * </pre>
   *
   * @param str the string to get a substring from, may be null
   * @param separator the character to search for
   * @return the substring before the first occurrence of the separator
   */
  public static String substringBeforeChar(String str, int separator) {
    if (isEmpty(str)) {
      return str;
    }
    int pos = str.indexOf(separator);
    if (pos < 0) {
      return str;
    }
    return str.substring(0, pos);
  }

  /** Prevent the creation of instances of this class. */
  private StringUtilities() {}
}