001/*
002 * Copyright (C) 2022 - 2024, 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.filemanagers;
017
018import io.github.ascopes.jct.ex.JctIllegalInputException;
019import io.github.ascopes.jct.utils.ToStringBuilder;
020import java.util.Objects;
021import java.util.Optional;
022import javax.tools.JavaFileManager.Location;
023import org.apiguardian.api.API;
024import org.apiguardian.api.API.Status;
025import org.jspecify.annotations.Nullable;
026
027/**
028 * Handle that represents the location of a module.
029 *
030 * @author Ashley Scopes
031 * @since 0.0.1
032 */
033@API(since = "0.0.1", status = Status.STABLE)
034public final class ModuleLocation implements Location {
035
036  private final Location parent;
037  private final String moduleName;
038  private final String name;
039
040  /**
041   * Initialize the location.
042   *
043   * @param parent     the parent location.
044   * @param moduleName the module name.
045   * @throws JctIllegalInputException if the parent location is not an output location or a
046   *                                  module-oriented location.
047   */
048  public ModuleLocation(Location parent, String moduleName) {
049    Objects.requireNonNull(parent, "parent");
050    Objects.requireNonNull(moduleName, "moduleName");
051
052    if (!parent.isOutputLocation() && !parent.isModuleOrientedLocation()) {
053      throw new JctIllegalInputException(
054          "The parent of a module location must be either an output location "
055              + "or be module-oriented, but got "
056              + parent.getName()
057      );
058    }
059
060    this.parent = parent;
061    this.moduleName = moduleName;
062    name = parent.getName() + "[" + moduleName + "]";
063  }
064
065  /**
066   * Get the parent location.
067   *
068   * @return the parent location.
069   */
070  public Location getParent() {
071    return parent;
072  }
073
074  /**
075   * Get the module name.
076   *
077   * @return the module name.
078   */
079  public String getModuleName() {
080    return moduleName;
081  }
082
083  @Override
084  public String getName() {
085    return name;
086  }
087
088  @Override
089  public boolean isOutputLocation() {
090    return parent.isOutputLocation();
091  }
092
093  @Override
094  public boolean isModuleOrientedLocation() {
095    // Module locations cannot be module-oriented, but their parents can be.
096    return false;
097  }
098
099  @Override
100  public boolean equals(@Nullable Object other) {
101    if (!(other instanceof ModuleLocation)) {
102      return false;
103    }
104
105    var that = (ModuleLocation) other;
106
107    return parent.equals(that.parent)
108        && moduleName.equals(that.moduleName);
109  }
110
111  @Override
112  public int hashCode() {
113    return Objects.hash(parent, moduleName);
114  }
115
116  @Override
117  public String toString() {
118    return new ToStringBuilder(this)
119        .attribute("parent", parent)
120        .attribute("moduleName", moduleName)
121        .toString();
122  }
123
124  /**
125   * Attempt to upcast a given location to a ModuleLocation if it is an instance of
126   * ModuleLocation. 
127   *
128   * @param location the location to attempt to upcast.
129   * @return an optional containing the upcast location if it is a module location,
130   *     or an empty optional if not.
131   */
132  @API(since = "1.1.5", status = Status.STABLE)
133  public static Optional<ModuleLocation> upcast(Location location) {
134    return Optional.of(location)
135        .filter(ModuleLocation.class::isInstance)
136        .map(ModuleLocation.class::cast);
137  }
138}