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