com.facebook.buck.rules.RuleKeyBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.rules.RuleKeyBuilder.java

Source

/*
 * Copyright 2015-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.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.Either;
import com.facebook.buck.model.HasBuildTarget;
import com.facebook.buck.model.UnflavoredBuildTarget;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.cache.FileHashCache;
import com.facebook.buck.util.hash.AppendingHasher;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.common.primitives.Primitives;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.Stack;

import javax.annotation.Nullable;

public class RuleKeyBuilder {

    @VisibleForTesting
    static final byte SEPARATOR = '\0';

    private static final Logger logger = Logger.get(RuleKeyBuilder.class);

    private final SourcePathResolver resolver;
    private final Hasher hasher;
    private final FileHashCache hashCache;
    private final RuleKeyBuilderFactory defaultRuleKeyBuilderFactory;
    private Stack<String> keyStack;

    @Nullable
    private List<String> logElms;

    public RuleKeyBuilder(SourcePathResolver resolver, FileHashCache hashCache,
            RuleKeyBuilderFactory defaultRuleKeyBuilderFactory) {
        this.resolver = resolver;
        this.hasher = new AppendingHasher(Hashing.sha1(), /* numHashers */ 2);
        this.hashCache = hashCache;
        this.defaultRuleKeyBuilderFactory = defaultRuleKeyBuilderFactory;
        this.keyStack = new Stack<>();
        if (logger.isVerboseEnabled()) {
            this.logElms = Lists.newArrayList();
        }
    }

    private RuleKeyBuilder feed(byte[] bytes) {
        while (!keyStack.isEmpty()) {
            String key = keyStack.pop();
            if (logElms != null) {
                logElms.add(String.format("key(%s):", key));
            }
            hasher.putBytes(key.getBytes());
            hasher.putByte(SEPARATOR);
        }

        hasher.putBytes(bytes);
        hasher.putByte(SEPARATOR);
        return this;
    }

    protected RuleKeyBuilder setSourcePath(SourcePath sourcePath) {
        // And now we need to figure out what this thing is.
        Optional<BuildRule> buildRule = resolver.getRule(sourcePath);
        if (buildRule.isPresent()) {
            feed(sourcePath.toString().getBytes());
            return setSingleValue(buildRule.get());
        } else {
            // The original version of this expected the path to be relative, however, sometimes the
            // deprecated method returned an absolute path, which is obviously less than ideal. If we can,
            // grab the relative path to the output. We also need to hash the contents of the absolute
            // path no matter what.
            Path ideallyRelative;
            try {
                ideallyRelative = resolver.getRelativePath(sourcePath);
            } catch (IllegalStateException e) {
                // Expected relative path was absolute. Yay.
                ideallyRelative = resolver.getAbsolutePath(sourcePath);
            }
            Path absolutePath = resolver.getAbsolutePath(sourcePath);
            try {
                return setPath(absolutePath, ideallyRelative);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    protected RuleKeyBuilder setNonHashingSourcePath(SourcePath sourcePath) {
        String pathForKey;
        if (sourcePath instanceof ResourceSourcePath) {
            pathForKey = ((ResourceSourcePath) sourcePath).getResourceIdentifier();
        } else {
            pathForKey = resolver.getRelativePath(sourcePath).toString();
        }

        if (logElms != null) {
            logElms.add(String.format("path(%s):", pathForKey));
        }

        feed(pathForKey.getBytes());
        return this;
    }

    protected RuleKeyBuilder setBuildRule(BuildRule rule) {
        return setSingleValue(defaultRuleKeyBuilderFactory.build(rule));
    }

    /**
     * Implementations can override this to provide context-specific caching.
     *
     * @return the {@link RuleKey} to be used for the given {@code appendable}.
     */
    protected RuleKey getAppendableRuleKey(SourcePathResolver resolver, FileHashCache hashCache,
            RuleKeyAppendable appendable) {
        RuleKeyBuilder subKeyBuilder = new RuleKeyBuilder(resolver, hashCache, defaultRuleKeyBuilderFactory);
        appendable.appendToRuleKey(subKeyBuilder);
        return subKeyBuilder.build();
    }

    public RuleKeyBuilder setAppendableRuleKey(String key, RuleKeyAppendable appendable) {
        setReflectively(key + ".appendableSubKey", getAppendableRuleKey(resolver, hashCache, appendable));
        return this;
    }

    public RuleKeyBuilder setReflectively(String key, @Nullable Object val) {
        if (val instanceof RuleKeyAppendable) {
            setAppendableRuleKey(key, (RuleKeyAppendable) val);
            if (!(val instanceof BuildRule)) {
                return this;
            }

            // Explicitly fall through for BuildRule objects so we include
            // their cache keys (which may include more data than
            // appendToRuleKey() does).
        }

        // Optionals get special handling. Unwrap them if necessary and recurse.
        if (val instanceof Optional) {
            Object o = ((Optional<?>) val).orNull();
            return setReflectively(key, o);
        }

        int oldSize = keyStack.size();
        keyStack.push(key);
        try {
            // Check to see if we're dealing with a collection of some description. Note
            // java.nio.file.Path implements "Iterable", so we don't check for that.
            if (val instanceof Collection) {
                val = ((Collection<?>) val).iterator();
                // Fall through to the Iterator handling
            }

            if (val instanceof Iterable && !(val instanceof Path)) {
                val = ((Iterable<?>) val).iterator();
                // Fall through to the Iterator handling
            }

            if (val instanceof Iterator) {
                Iterator<?> iterator = (Iterator<?>) val;
                while (iterator.hasNext()) {
                    setReflectively(key, iterator.next());
                }
                return this;
            }

            if (val instanceof Map) {
                if (!(val instanceof SortedMap | val instanceof ImmutableMap)) {
                    logger.info("Adding an unsorted map to the rule key (%s). "
                            + "Expect unstable ordering and caches misses: %s", key, val);
                }
                feed("{".getBytes());
                for (Map.Entry<?, ?> entry : ((Map<?, ?>) val).entrySet()) {
                    setReflectively(key, entry.getKey());
                    feed(" -> ".getBytes());
                    setReflectively(key, entry.getValue());
                }
                return feed("}".getBytes());
            }

            if (val instanceof Supplier) {
                Object newVal = ((Supplier<?>) val).get();
                return setReflectively(key, newVal);
            }

            return setSingleValue(val);
        } finally {
            while (keyStack.size() > oldSize) {
                keyStack.pop();
            }
        }
    }

    // Paths get added as a combination of the file name and file hash. If the path is absolute
    // then we only include the file name (assuming that it represents a tool of some kind
    // that's being used for compilation or some such). This does mean that if a user renames a
    // file without changing the contents, we have a cache miss. We're going to assume that this
    // doesn't happen that often in practice.
    public RuleKeyBuilder setPath(Path absolutePath, Path ideallyRelative) throws IOException {
        // TODO(shs96c): Enable this precondition once setPath(Path) has been removed.
        // Preconditions.checkState(absolutePath.isAbsolute());
        HashCode sha1 = hashCache.get(absolutePath);
        if (sha1 == null) {
            throw new RuntimeException("No SHA for " + absolutePath);
        }

        Path addToKey;
        if (ideallyRelative.isAbsolute()) {
            logger.warn("Attempting to add absolute path to rule key. Only using file name: %s", ideallyRelative);
            addToKey = ideallyRelative.getFileName();
        } else {
            addToKey = ideallyRelative;
        }

        if (logElms != null) {
            logElms.add(String.format("path(%s:%s):", addToKey, sha1));
        }

        feed(addToKey.toString().getBytes());
        feed(sha1.toString().getBytes());
        return this;
    }

    protected RuleKeyBuilder setSingleValue(@Nullable Object val) {

        if (val == null) { // Null value first
            return feed(new byte[0]);
        } else if (val instanceof Boolean) { // JRE types
            if (logElms != null) {
                logElms.add(String.format("boolean(\"%s\"):", (boolean) val ? "true" : "false"));
            }
            feed(((boolean) val ? "t" : "f").getBytes());
        } else if (val instanceof Enum) {
            feed(String.valueOf(val).getBytes());
        } else if (val instanceof Number) {
            if (logElms != null) {
                logElms.add(String.format("number(%s):", val));
            }
            Class<?> wrapped = Primitives.wrap(val.getClass());
            if (Double.class.equals(wrapped)) {
                hasher.putDouble(((Double) val).doubleValue());
            } else if (Float.class.equals(wrapped)) {
                hasher.putFloat(((Float) val).floatValue());
            } else if (Integer.class.equals(wrapped)) {
                hasher.putInt(((Integer) val).intValue());
            } else if (Long.class.equals(wrapped)) {
                hasher.putLong(((Long) val).longValue());
            } else if (Short.class.equals(wrapped)) {
                hasher.putShort(((Short) val).shortValue());
            } else {
                throw new RuntimeException(("Unhandled number type: " + val.getClass()));
            }
        } else if (val instanceof Path) {
            throw new HumanReadableException(
                    "It's not possible to reliably disambiguate Paths. They are disallowed from rule keys");
        } else if (val instanceof String) {
            if (logElms != null) {
                logElms.add(String.format("string(\"%s\"):", val));
            }
            feed(((String) val).getBytes());
        } else if (val instanceof BuildRule) { // Buck types
            return setBuildRule((BuildRule) val);
        } else if (val instanceof BuildRuleType) {
            if (logElms != null) {
                logElms.add(String.format("ruleKeyType(%s):", val));
            }
            feed(val.toString().getBytes());
        } else if (val instanceof RuleKey) {
            if (logElms != null) {
                logElms.add(String.format("ruleKey(sha1=%s):", val));
            }
            feed(val.toString().getBytes());
        } else if (val instanceof BuildTarget || val instanceof UnflavoredBuildTarget) {
            if (logElms != null) {
                logElms.add(String.format("target(%s):", val));
            }
            feed(((HasBuildTarget) val).getBuildTarget().getFullyQualifiedName().getBytes());
        } else if (val instanceof Either) {
            Either<?, ?> either = (Either<?, ?>) val;
            if (either.isLeft()) {
                setSingleValue(either.getLeft());
            } else {
                setSingleValue(either.getRight());
            }
        } else if (val instanceof SourcePath) {
            return setSourcePath((SourcePath) val);
        } else if (val instanceof NonHashableSourcePathContainer) {
            NonHashableSourcePathContainer nonHashableSourcePathContainer = (NonHashableSourcePathContainer) val;
            return setNonHashingSourcePath(nonHashableSourcePathContainer.getSourcePath());
        } else if (val instanceof SourceRoot) {
            if (logElms != null) {
                logElms.add(String.format("sourceroot(%s):", val));
            }
            feed(((SourceRoot) val).getName().getBytes());
        } else if (val instanceof SourceWithFlags) {
            SourceWithFlags source = (SourceWithFlags) val;
            setSingleValue(source.getSourcePath());
            feed("[".getBytes());
            for (String flag : source.getFlags()) {
                feed(flag.getBytes());
                feed(",".getBytes());
            }
            feed("]".getBytes());
        } else if (val instanceof Sha1HashCode) {
            setSingleValue(((Sha1HashCode) val).getHash());
        } else if (val instanceof byte[]) {
            if (logElms != null) {
                logElms.add(String.format("byteArray(%s):", val));
            }
            feed((byte[]) val);
        } else {
            throw new RuntimeException("Unsupported value type: " + val.getClass());
        }

        return this;
    }

    public RuleKey build() {
        RuleKey ruleKey = new RuleKey(hasher.hash());
        if (logElms != null) {
            logger.verbose("RuleKey %s=%s", ruleKey, Joiner.on("").join(logElms));
        }
        return ruleKey;
    }

}