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}