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}