blob: d70c45941e718e4999cbd666d0273ff43017ec8c [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.rules;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import java.io.File;
import java.nio.ByteBuffer;
import javax.annotation.Nullable;
/**
* OutputKey encapsulates computation of SHA-1 content hashing for outputs.
*/
public class OutputKey implements Comparable<OutputKey> {
@Nullable private final HashCode hashCode;
private final boolean idempotent;
/**
* OutputKeys that have no associated outputs (output == null) are treated as sentinels that are
* distinct from other idempotent OutputKeys. OutputKeys associated with actual outputs are
* non-idempotent if the outputs cannot be read during key generation.
*/
public OutputKey(@Nullable File output) {
if (output == null) {
this.hashCode = null;
this.idempotent = true;
} else {
this.hashCode = hashOutput(output);
this.idempotent = (hashCode != null);
}
}
@Nullable
private HashCode hashOutput(File output) {
if (output == null) {
return null;
}
// Use RuleKey's Builder to do the hard work of hashing the output file.
RuleKey.Builder builder = RuleKey.builder();
builder.set(output.getPath(), output);
RuleKey ruleKey = builder.build();
if (!ruleKey.isIdempotent()) {
return null;
}
return ruleKey.getHashCode();
}
public boolean isIdempotent() {
return idempotent;
}
public boolean isSentinel() {
return (isIdempotent() && hashCode == null);
}
/**
* Sentinel OutputKeys are output as a string of 's' characters, and non-idempotent OutputKeys are
* normally output as a string of 'x' characters, but when comparing two sets of OutputKeys in
* textual form it is necessary to mangle one of the two sets, so that non-idempotent OutputKeys
* are never considered equal.
*/
public String toString(boolean mangleNonIdempotent) {
if (isSentinel() || !isIdempotent()) {
String replacementChar = isSentinel() ? "s" : (!mangleNonIdempotent ? "x" : "y");
return new String (new char[Hashing.sha1().bits() / 4]).replace("\0", replacementChar);
}
return hashCode.toString();
}
@Override
public String toString() {
return toString(false);
}
/**
* Order non-idempotent OutputKeys as less than sentinels, which in turn are less than all
* idempotent non-sentinels.
*
* non-idempotent < sentinel < idempotent non-sentinel
*/
@Override
public int compareTo(OutputKey other) {
// Handle non-idempotent keys.
if (!isIdempotent()) {
if (!other.isIdempotent()) {
return 0;
}
return -1;
} else if (!other.isIdempotent()) {
return 1;
}
// Handle sentinel keys.
if (isSentinel()) {
if (other.isSentinel()) {
return 0;
}
return -1;
} else if (other.isSentinel()) {
return 1;
}
// Handle idempotent non-sentinel keys.
return ByteBuffer.wrap(hashCode.asBytes()).compareTo(ByteBuffer.wrap(other.hashCode.asBytes()));
}
/**
* Treat non-idempotent OutputKeys as unequal to everything, including other non-idempotent
* OutputKeys.
*/
@Override
public boolean equals(Object that) {
if (!(that instanceof OutputKey)) {
return false;
}
OutputKey other = (OutputKey) that;
if (!isIdempotent() || !other.isIdempotent()) {
return false;
}
return (compareTo(other) == 0);
}
@Override
public int hashCode() {
if (!isIdempotent()) {
return 0;
}
return super.hashCode();
}
/**
* Helper method used to avoid memoizing non-idempotent OutputKeys.
*/
public static OutputKey filter(OutputKey outputKey) {
if (!outputKey.isIdempotent()) {
return null;
}
return outputKey;
}
}