001/* 002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others 003 * 004 * This program and the accompanying materials are made available under the 005 * terms of the Eclipse Public License 2.0 which is available at 006 * http://www.eclipse.org/legal/epl-2.0. 007 * 008 * SPDX-License-Identifier: EPL-2.0 009 */ 010 011package org.eclipse.golo.compiler; 012 013import gololang.ir.GoloType; 014import gololang.ir.GoloModule; 015 016import static java.util.Objects.requireNonNull; 017 018/** 019 * Represents a package and class. 020 */ 021public final class PackageAndClass { 022 023 private final String packageName; 024 private final String className; 025 026 /** 027 * Makes a new package and class definition. 028 * 029 * @param packageName the package name. 030 * @param className the class name. 031 */ 032 PackageAndClass(String packageName, String className) { 033 this.packageName = requireNonNull(packageName); 034 this.className = requireNonNull(className); 035 if (className.isEmpty()) { 036 throw new IllegalArgumentException("The class name can't be empty"); 037 } 038 } 039 040 /** 041 * Extracts a package and class definition from a string. 042 * 043 * @param qualifiedName a qualified name. 044 * @return a package and class definition. 045 */ 046 private static PackageAndClass fromString(String qualifiedName) { 047 return new PackageAndClass( 048 extractTargetJavaPackage(qualifiedName), 049 extractTargetJavaClass(qualifiedName)); 050 } 051 052 private static int packageClassSeparatorIndex(String moduleName) { 053 if (moduleName != null) { 054 return moduleName.lastIndexOf('.'); 055 } 056 return -1; 057 } 058 059 private static String extractTargetJavaPackage(String moduleName) { 060 int packageClassSeparatorIndex = packageClassSeparatorIndex(moduleName); 061 if (packageClassSeparatorIndex > 0) { 062 return moduleName.substring(0, packageClassSeparatorIndex); 063 } 064 return ""; 065 } 066 067 private static String extractTargetJavaClass(String moduleName) { 068 int packageClassSeparatorIndex = packageClassSeparatorIndex(moduleName); 069 if (packageClassSeparatorIndex > 0) { 070 return moduleName.substring(packageClassSeparatorIndex + 1); 071 } 072 return moduleName; 073 } 074 075 /** 076 * Creates a {@code PackageAndClass}. 077 * 078 * Extracts the name from the given object. 079 * 080 * @param o a {@code String}, {@code PackageAndClass}, {@code Class}, {@link GoloType} or {@link GoloModule} instance. 081 */ 082 public static PackageAndClass of(Object o) { 083 if (o instanceof PackageAndClass) { 084 return (PackageAndClass) o; 085 } 086 if (o instanceof String) { 087 return fromString((String) o); 088 } 089 if (o instanceof Class) { 090 return fromString(((Class) o).getName()); 091 } 092 if (o instanceof GoloType) { 093 return ((GoloType) o).getPackageAndClass(); 094 } 095 if (o instanceof GoloModule) { 096 return ((GoloModule) o).getPackageAndClass(); 097 } 098 throw new IllegalArgumentException("Can't create a PackageAndClass from a " + o.getClass().getName()); 099 } 100 101 /** 102 * Create an inner class. 103 * <p> 104 * For instance: 105 * <pre class="listing"><code class="lang-java" data-lang="java"> 106 * PackageAndClass cls = new PackageAndClass("foo.bar", "Spam"); 107 * PackageAndClass inner = cls.createInnerClass("Egg"); // foo.bar.Spam$Egg 108 * </code></pre> 109 * 110 * @param name the name of the inner class. 111 * @return a new {@code PackageAndClass} identifying an inner class of this class. 112 */ 113 public PackageAndClass createInnerClass(String name) { 114 return new PackageAndClass( 115 this.packageName, 116 this.className + "$" + name.replace('.', '$')); 117 } 118 119 /** 120 * Create a sibling class. 121 * <p> 122 * For instance: 123 * <pre class="listing"><code class="lang-java" data-lang="java"> 124 * PackageAndClass list = new PackageAndClass("java.util", "List"); 125 * PackageAndClass set = list.createSiblingClass("Set"); // java.util.Set 126 * </code></pre> 127 * 128 * @return a new {@code PackageAndClass} identifying an alternate class in the same package. 129 */ 130 public PackageAndClass createSiblingClass(String name) { 131 return new PackageAndClass(this.packageName, name); 132 } 133 134 /** 135 * Create a sub-package. 136 * <p> 137 * For instance: 138 * <pre class="listing"><code class="lang-java" data-lang="java"> 139 * PackageAndClass pc = new PackageAndClass("foo.bar", "Module"); 140 * PackageAndClass sub = pc.createSubPackage("types"); // foo.bar.Modules.types 141 * </code></pre> 142 * 143 * @return a new {@code PackageAndClass} identifying a sub-package of this package. 144 */ 145 public PackageAndClass createSubPackage(String name) { 146 return new PackageAndClass( 147 this.packageName.isEmpty() ? this.className : this.packageName + "." + this.className, name); 148 } 149 150 /** 151 * Create a class in another package. 152 * <p> 153 * For instance: 154 * <pre class="listing"><code class="lang-java" data-lang="java"> 155 * PackageAndClass pc = PackageAndClass.fromString("foo.bar.Baz"); 156 * PackageAndClass other = pc.inPackage("plic.ploc"); // plic.ploc.Baz 157 * </code></pre> 158 * 159 * @return a new {@code PackageAndClass} representing the same class in another package. 160 * @param qualifiedName the qualified name of the new package. 161 */ 162 public PackageAndClass inPackage(String qualifiedName) { 163 return new PackageAndClass(qualifiedName, className); 164 } 165 166 /** 167 * Create a class in the same package as another one. 168 * <p> 169 * For instance: 170 * <pre class="listing"><code class="lang-java" data-lang="java"> 171 * PackageAndClass pc = PackageAndClass.fromString("foo.bar.Baz"); 172 * PackageAndClass other = PackageAndClass.fromString("plic.ploc.Foo"); 173 * PackageAndClass newOne = pc.inPackage(other); // plic.ploc.Baz 174 * </code></pre> 175 * 176 * @return a new {@code PackageAndClass} representing the same class in another package. 177 * @param parent the {@code PackageAndClass} representing the new package. 178 */ 179 public PackageAndClass inPackage(PackageAndClass parent) { 180 return new PackageAndClass(parent.packageName(), className); 181 } 182 183 /** 184 * @return the package name. 185 */ 186 public String packageName() { 187 return packageName; 188 } 189 190 /** 191 * Check if this {@code PackageAndClass} has a package component. 192 */ 193 public boolean hasPackage() { 194 return !packageName.isEmpty(); 195 } 196 197 /** 198 * @return the package as a {@code PackageAndClass} 199 */ 200 public PackageAndClass parentPackage() { 201 return fromString(packageName); 202 } 203 204 /** 205 * @return the class name. 206 */ 207 public String className() { 208 return className; 209 } 210 211 /** 212 * @return the path of the corresponding class file. 213 */ 214 public String getFilename() { 215 return toJVMType() + ".class"; 216 } 217 218 @Override 219 public String toString() { 220 if (packageName.isEmpty()) { 221 return className; 222 } else { 223 return packageName + "." + className; 224 } 225 } 226 227 /** 228 * @return a JVM type representation for this object, e.g.: {@code foo.Bar} gives {@code foo/Bar}. 229 */ 230 public String toJVMType() { 231 return toString().replaceAll("\\.", "/"); 232 } 233 234 /** 235 * @return a JVM reference type representation for this object, e.g.: {@code foo.Bar} gives 236 * {@code Lfoo/Bar;} 237 */ 238 public String toJVMRef() { 239 return "L" + toJVMType() + ";"; 240 } 241 242 /** 243 * @return a mangled named for this class. 244 */ 245 public String mangledName() { 246 return toString().replace('.', '$'); 247 } 248 249 @Override 250 public boolean equals(Object o) { 251 if (this == o) { return true; } 252 if (o == null || getClass() != o.getClass()) { return false; } 253 254 PackageAndClass that = (PackageAndClass) o; 255 256 if (className != null ? !className.equals(that.className) : that.className != null) { return false; } 257 return !(packageName != null ? !packageName.equals(that.packageName) : that.packageName != null); 258 } 259 260 @Override 261 public int hashCode() { 262 int result = packageName != null ? packageName.hashCode() : 0; 263 result = 31 * result + (className != null ? className.hashCode() : 0); 264 return result; 265 } 266}