com.google.devtools.build.xcode.xcodegen.PBXBuildFiles.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.xcode.xcodegen.PBXBuildFiles.java

Source

// Copyright 2014 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.xcode.xcodegen;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SetMultimap;
import com.google.devtools.build.xcode.util.Mapping;

import com.facebook.buck.apple.xcode.xcodeproj.PBXBuildFile;
import com.facebook.buck.apple.xcode.xcodeproj.PBXFileReference;
import com.facebook.buck.apple.xcode.xcodeproj.PBXReference;
import com.facebook.buck.apple.xcode.xcodeproj.PBXReference.SourceTree;
import com.facebook.buck.apple.xcode.xcodeproj.PBXVariantGroup;
import com.facebook.buck.apple.xcode.xcodeproj.XCVersionGroup;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A kind of cache which makes it easier to collect and manage PBXBuildFile and PBXReference
 * objects. It knows how to create new PBXBuildFile, PBXVariantGroup, and PBXFileReference objects
 * from {@link Path} objects and sequences thereof.
 * <p>
 * A PBXFileReference specifies a path to a file and its <em>name</em>. The name is confusingly
 * defined as the real file name for non-localized files (e.g. "foo" in "bar/foo"), and the language
 * name for localized files (e.g. "en" in "bar/en.lproj/foo").
 * <p>
 * A PBXVariantGroup is a set of PBXFileReferences with the same file name (the virtual name). Each
 * file is in some directory named *.lproj. For instance, the following files would belong to the
 * same PBXVariantGroup:
 *
 * <ul>
 *   <li>foo1/en.lproj/file.strings
 *   <li>foo2/ru.lproj/file.strings
 * </ul>
 *
 * Where the virtual name is "file.strings". Note that because of the way PBXVariantGroups are named
 * and specified in .xcodeproj files, it is possible Xcode does or will use it for groups not
 * defined by localization, but we currently ignore that possibility.
 * <p>
 * A PBXBuildFile is the simplest object - it is simply a reference to a PBXReference, which can be
 * either a PBXFileReference or PBXVariantGroup. The fact that PBXFileReference and PBXVariantGroup
 * are considered kinds of PBXReference is reflected in the Java inheritance hierarchy for the
 * classes that model these Xcode objects.
 * <p>
 * The PBXBuildFile is the object referred to in the build phases of targets, so it is seen as a
 * buildable or compilable unit. The PBXFileReference and PBXVariantGroup objects are referred to
 * by the PBXGroups, which define what is visible in the Project Navigator view in Xcode.
 * <p>
 * This class assumes that all paths given through the public API are specified relative to the
 * Xcodegen root, and creates PBXFileReferences that should be added to a group whose path is the
 * root.
 * TODO(bazel-team): Make this an immutable type, of which multiple instances are created as new
 * references and build files are added. The current API is side-effect-based and confusing.
 */
final class PBXBuildFiles implements HasProjectNavigatorFiles {
    /**
     * Map from Paths to the PBXBuildFile that encompasses all and only those Paths. Because the
     * {@link PBXBuildFile}s in this map encompass multiple files, their
     * {@link PBXBuildFile#getFileRef()} value is a {@link PBXVariantGroup} or {@link XCVersionGroup}.
     * <p>
     * Note that this Map reflects the intention of the API, namely that {"a"} does not map to the
     * same thing as {"a", "b"}, and you cannot get a build file with only one of the corresponding
     * files - you need the whole set.
     */
    private Map<ImmutableSet<Path>, PBXBuildFile> aggregateBuildFiles;

    private Map<FileReference, PBXBuildFile> standaloneBuildFiles;
    private PBXFileReferences pbxReferences;
    private List<PBXReference> mainGroupReferences;

    public PBXBuildFiles(PBXFileReferences pbxFileReferences) {
        this.aggregateBuildFiles = new HashMap<>();
        this.standaloneBuildFiles = new HashMap<>();
        this.pbxReferences = Preconditions.checkNotNull(pbxFileReferences);
        this.mainGroupReferences = new ArrayList<>();
    }

    private PBXBuildFile aggregateBuildFile(ImmutableSet<Path> paths, PBXReference reference) {
        Preconditions.checkArgument(!paths.isEmpty(), "paths must be non-empty");
        for (PBXBuildFile cached : Mapping.of(aggregateBuildFiles, paths).asSet()) {
            return cached;
        }
        PBXBuildFile buildFile = new PBXBuildFile(reference);
        mainGroupReferences.add(reference);
        aggregateBuildFiles.put(paths, buildFile);
        return buildFile;
    }

    /**
     * Returns new or cached instances of PBXBuildFiles corresponding to files that may or may not
     * belong to an aggregate reference (see {@link AggregateReferenceType}). Files specified by the
     * {@code paths} argument are grouped into individual PBXBuildFiles using the given
     * {@link AggregateReferenceType}. Files that are standalone are not put in an aggregate
     * reference, but are put in a standalone PBXBuildFile in the returned sequence.
     */
    public Iterable<PBXBuildFile> get(AggregateReferenceType type, Iterable<Path> paths) {
        ImmutableList.Builder<PBXBuildFile> result = new ImmutableList.Builder<>();
        SetMultimap<AggregateKey, Path> keyedPaths = type.aggregates(paths);
        for (Map.Entry<AggregateKey, Collection<Path>> aggregation : keyedPaths.asMap().entrySet()) {
            if (!aggregation.getKey().isStandalone()) {
                ImmutableSet<Path> itemPaths = ImmutableSet.copyOf(aggregation.getValue());
                result.add(aggregateBuildFile(itemPaths,
                        type.create(aggregation.getKey(), fileReferences(itemPaths))));
            }
        }
        for (Path generalResource : keyedPaths.get(AggregateKey.standalone())) {
            result.add(getStandalone(FileReference.of(generalResource.toString(), SourceTree.GROUP)));
        }

        return result.build();
    }

    /**
     * Returns a new or cached instance of a PBXBuildFile for a file that is not part of a variant
     * group.
     */
    public PBXBuildFile getStandalone(FileReference file) {
        for (PBXBuildFile cached : Mapping.of(standaloneBuildFiles, file).asSet()) {
            return cached;
        }
        PBXBuildFile buildFile = new PBXBuildFile(pbxReferences.get(file));
        mainGroupReferences.add(pbxReferences.get(file));
        standaloneBuildFiles.put(file, buildFile);
        return buildFile;
    }

    /**
     * Applies {@link #fileReference(Path)} to each item in the sequence.
     */
    private final Iterable<PBXFileReference> fileReferences(Iterable<Path> paths) {
        ImmutableList.Builder<PBXFileReference> result = new ImmutableList.Builder<>();
        for (Path path : paths) {
            result.add(fileReference(path));
        }
        return result.build();
    }

    /**
     * Returns a new or cached PBXFileReference for the given file. The name of the reference depends
     * on whether the file is in a localized (*.lproj) directory. If it is localized, then the name
     * of the reference is the name of the language (the text before ".lproj"). Otherwise, the name is
     * the same as the file name (e.g. Localizable.strings). This is confusing, but it is how Xcode
     * creates PBXFileReferences.
     */
    private PBXFileReference fileReference(Path path) {
        Optional<String> language = Resources.languageOfLprojDir(path);
        String name = language.isPresent() ? language.get() : path.getFileName().toString();
        return pbxReferences.get(FileReference.of(name, path.toString(), SourceTree.GROUP));
    }

    @Override
    public Iterable<PBXReference> mainGroupReferences() {
        return mainGroupReferences;
    }
}