com.facebook.buck.intellij.ideabuck.api.BuckTarget.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.intellij.ideabuck.api.BuckTarget.java

Source

/*
 * Copyright 2018-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.intellij.ideabuck.api;

import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jetbrains.annotations.Nullable;

/**
 * Syntactic representation of a buck target.
 *
 * @see <a href=https://buckbuild.com/concept/build_target.html">the Buck documentation for build
 *     targets</a>
 */
public class BuckTarget {
    @Nullable
    private final String cellName;
    @Nullable
    private final String cellPath;
    private final String ruleName;

    // This regex from https://buckbuild.com/concept/build_target.html,
    // which says [A-Za-z0-9._-]*//[A-Za-z0-9/._-]*:[A-Za-z0-9_/.=,@~+-]+
    private static final Pattern PATTERN = Pattern
            .compile("((@?(?<cell>[A-Za-z0-9._-]+))?//(?<path>[A-Za-z0-9/._-]*)?)?:(?<name>[A-Za-z0-9_/.=,@~+-]+)");

    /**
     * Parses the given string as a buck target, returning {@link Optional#empty()} if it is not a
     * syntactically valid buck target.
     *
     * <p>Note that no guarantee is given whether or not the target is *semantically* valid.
     */
    public static Optional<BuckTarget> parse(String s) {
        Matcher matcher = PATTERN.matcher(s);
        if (matcher.matches()) {
            @Nullable
            String cell = matcher.group("cell");
            @Nullable
            String path = matcher.group("path");
            String name = matcher.group("name");
            return Optional.of(new BuckTarget(cell, path, name));
        }
        return Optional.empty();
    }

    BuckTarget(@Nullable String cellName, @Nullable String cellPath, String ruleName) {
        if (cellName != null) {
            Preconditions.checkArgument(cellPath != null,
                    "Cannot create BuckTarget with a non-null cellName and a null cellPath");
        }
        this.cellName = cellName;
        this.cellPath = cellPath;
        this.ruleName = ruleName;
    }

    /** Returns the name of the cell, or {@link Optional#empty()} if none was specified. */
    public Optional<String> getCellName() {
        return Optional.ofNullable(cellName);
    }

    /**
     * Returns the relative path from the cell's root, or {@link Optional#empty()} if it was
     * unspecified (for a package-relative buck target).
     */
    public Optional<String> getCellPath() {
        return Optional.ofNullable(cellPath);
    }

    /**
     * If this target has a {@link #getCellPath()}, returns an {@link Iterable} over the path segments
     * from the cell's root to its terminal package.
     */
    public Optional<Iterable<String>> getCellPathSegments() {
        return getCellPath().map(Splitter.on('/').omitEmptyStrings()::split);
    }

    /** Returns the rule name. */
    public String getRuleName() {
        return ruleName;
    }

    /** Returns true if this is a fully-qualified target (including a cell). */
    public boolean isAbsolute() {
        return cellName != null;
    }

    /** Returns true if this is a relative target to the same build file. */
    public boolean isPackageRelative() {
        return cellName == null && cellPath == null;
    }

    /**
     * Returns this target as a {@link BuckTargetPattern}.
     *
     * <p>Every {@link BuckTarget} is, by definition, a valid {@link BuckTargetPattern}.
     */
    public BuckTargetPattern asPattern() {
        Optional<BuckTargetPattern> optionalPattern = BuckTargetPattern.parse(toString());
        // Every buck target should also be a valid buck target pattern
        if (!optionalPattern.isPresent()) {
            throw new AssertionError("Implementation error: the BuckTarget \"" + toString()
                    + "\" was unable to be parsed as a BuckTargetPattern");
        }
        return optionalPattern.get();
    }

    /**
     * Resolve the given buck target against this buck target.
     *
     * <p>Returns {@link Optional#empty()} if the "other" is not parsable as a relative target using
     * {@link #parse(String)}.
     *
     * <p>This is semantically similar to {@link java.nio.file.Path#resolve(String)}.
     */
    public Optional<BuckTarget> resolve(String other) {
        return parse(other).map(this::resolve);
    }

    /**
     * Resolve the given buck target against this target.
     *
     * <p>If this target is fully-qualified, the resulting target is also guaranteed to be fully
     * qualified.
     *
     * <p>This is semantically similar to {@link java.nio.file.Path#resolve(Path)}.
     */
    public BuckTarget resolve(BuckTarget other) {
        if (other.cellName != null) {
            return other; // no resolution necessary
        } else if (other.cellPath != null) {
            return new BuckTarget(cellName, other.cellPath, other.ruleName);
        } else {
            return new BuckTarget(cellName, cellPath, other.ruleName);
        }
    }

    /**
     * Constructs a relative path between this target and the given target.
     *
     * <p>This is semantically similar to {@link java.nio.file.Path#relativize(Path)}.
     */
    public BuckTarget relativize(BuckTarget other) {
        if (other.cellName != null && !other.cellName.equals(cellName)) {
            return other;
        } else if (other.cellPath != null && !other.cellPath.equals(cellPath)) {
            return new BuckTarget(null, other.cellPath, other.ruleName);
        } else {
            return new BuckTarget(null, null, other.ruleName);
        }
    }

    /**
     * Returns a syntatically valid (but semantically different) variation of this target, with
     * hierarchical path elements in the rule name moved to the path. Example, {@code foo//bar:baz/qux
     * => foo//bar/baz:qux}.
     *
     * <p>This conceit is imperfect but somewhat useful to resolve targets targets inside extension
     * files, as this sometimes allows intrafile targets to be syntactically resolved.
     *
     * <p>Note that while some tools will resolve these targets similarly, they are not semantically
     * interchangeable: {@code foo//bar:baz/qux} refers to a target in the buildfile in {@code
     * foo/bar}, while {@code foo//bar/baz:qux} refers to a target in the buildfile in {@code
     * foo/bar/baz}. Furthermore, when using a {@link BuckTarget} to represent the first argument to a
     * {@code load()} directive, if there were a buildfile present in foo/bar/baz, it would be
     * incorrect to use {@code foo/bar:baz/qux}, as this would cross a package boundary.
     */
    public Optional<BuckTarget> flatten() {
        if (!ruleName.contains("/")) {
            return Optional.of(this);
        }
        if (cellPath == null) {
            return Optional.empty();
        }
        List<String> pieces = new ArrayList<>();
        Splitter splitter = Splitter.on('/').omitEmptyStrings();
        splitter.split(cellPath).forEach(pieces::add);
        splitter.split(ruleName).forEach(pieces::add);
        String newRule = pieces.remove(pieces.size() - 1);
        String newPath = Joiner.on('/').join(pieces);
        if (ruleName.endsWith("/")) {
            newRule += '/';
        }
        return Optional.of(new BuckTarget(cellName, newPath, newRule));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        BuckTarget that = (BuckTarget) o;
        return Objects.equal(cellName, that.cellName) && Objects.equal(cellPath, that.cellPath)
                && Objects.equal(ruleName, that.ruleName);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(cellName, cellPath, ruleName);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        if (cellPath != null) {
            if (cellName != null) {
                sb.append(cellName);
            }
            sb.append("//").append(cellPath);
        }
        sb.append(":").append(ruleName);
        return sb.toString();
    }
}