001/*
002 * Copyright (C) 2022 - 2023, the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *    http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.github.ascopes.jct.assertions;
017
018import static java.util.Objects.requireNonNull;
019
020import io.github.ascopes.jct.containers.ModuleContainerGroup;
021import io.github.ascopes.jct.filemanagers.ModuleLocation;
022import io.github.ascopes.jct.repr.LocationRepresentation;
023import java.util.stream.Collectors;
024import me.xdrop.fuzzywuzzy.FuzzySearch;
025import me.xdrop.fuzzywuzzy.model.BoundExtractedResult;
026import org.apiguardian.api.API;
027import org.apiguardian.api.API.Status;
028import org.jspecify.annotations.Nullable;
029
030/**
031 * Assertions for module container groups.
032 *
033 * @author Ashley Scopes
034 * @since 0.0.1
035 */
036@API(since = "0.0.1", status = Status.STABLE)
037public final class ModuleContainerGroupAssert
038    extends AbstractContainerGroupAssert<ModuleContainerGroupAssert, ModuleContainerGroup> {
039
040  /**
041   * Initialize the container group assertions.
042   *
043   * @param containerGroup the container group to assert upon.
044   */
045  public ModuleContainerGroupAssert(@Nullable ModuleContainerGroup containerGroup) {
046    super(containerGroup, ModuleContainerGroupAssert.class);
047  }
048
049  /**
050   * Assert that the given module does not exist.
051   *
052   * @param module the module name.
053   * @return this assertion object for further assertion calls.
054   * @throws AssertionError       if the container group is null or if the module exists.
055   * @throws NullPointerException if the module parameter is null.
056   */
057  public ModuleContainerGroupAssert moduleDoesNotExist(String module) {
058    requireNonNull(module, "module must not be null");
059    isNotNull();
060
061    var moduleGroup = actual.getModule(module);
062
063    if (moduleGroup == null) {
064      return this;
065    }
066
067    var locationName = LocationRepresentation.getInstance().toStringOf(actual.getLocation());
068
069    throw failure(
070        "Expected module %s to not exist in %s but it did",
071        module,
072        locationName
073    );
074  }
075
076  /**
077   * Assert that the given module exists and then return assertions to perform on that module.
078   *
079   * @param module the module name.
080   * @return the assertions to perform on the package container group.
081   * @throws AssertionError       if the container group is null or if the module does not exist.
082   * @throws NullPointerException if the module parameter is null.
083   */
084  public PackageContainerGroupAssert moduleExists(String module) {
085    requireNonNull(module, "module must not be null");
086    isNotNull();
087
088    var moduleGroup = actual.getModule(module);
089
090    if (moduleGroup != null) {
091      return new PackageContainerGroupAssert(moduleGroup);
092    }
093
094    var closestMatches = FuzzySearch
095        .extractSorted(
096            module,
097            actual.getModules().keySet(),
098            ModuleLocation::getModuleName
099        )
100        .stream()
101        .limit(FUZZY_CUTOFF)
102        .map(BoundExtractedResult::getReferent)
103        .sorted()
104        .collect(Collectors.toList());
105
106    var locationName = LocationRepresentation.getInstance().toStringOf(actual.getLocation());
107
108    if (closestMatches.isEmpty()) {
109      throw failure("No module in %s modules found named %s", locationName, module);
110    } else {
111      throw failure(
112          "No module found named \"%s\" in %s. Did you mean...%s",
113          locationName,
114          module,
115          closestMatches
116              .stream()
117              .map(ModuleLocation::getModuleName)
118              .map("\n\t - "::concat)
119              .collect(Collectors.joining())
120      );
121    }
122  }
123}