001/*
002 * Copyright (c) 2012-2018 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  public 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   * @return a new {@code PackageAndClass} identifying an inner class of this class.
111   */
112  public PackageAndClass createInnerClass(String name) {
113    return new PackageAndClass(
114        this.packageName,
115        this.className + "$" + name.replace('.', '$'));
116  }
117
118  /**
119   * Create a sibling class.
120   * <p>
121   * For instance:
122   * <pre class="listing"><code class="lang-java" data-lang="java">
123   *  PackageAndClass list = new PackageAndClass("java.util", "List");
124   *  PackageAndClass set = list.createSiblingClass("Set"); // java.util.Set
125   * </code></pre>
126   *
127   * @return a new {@code PackageAndClass} identifying an alternate class in the same package.
128   */
129  public PackageAndClass createSiblingClass(String name) {
130    return new PackageAndClass(this.packageName, name);
131  }
132
133  /**
134   * Create a sub-package.
135   * <p>
136   * For instance:
137   * <pre class="listing"><code class="lang-java" data-lang="java">
138   *  PackageAndClass pc = new PackageAndClass("foo.bar", "Module");
139   *  PackageAndClass sub = pc.createSubPackage("types"); // foo.bar.Modules.types
140   * </code></pre>
141   *
142   * @return a new {@code PackageAndClass} identifying a sub-package of this package.
143   */
144  public PackageAndClass createSubPackage(String name) {
145    return new PackageAndClass(
146        this.packageName.isEmpty() ? this.className : this.packageName + "." + this.className, name);
147  }
148
149  /**
150   * Create a class in another package.
151   * <p>
152   * For instance:
153   * <pre class="listing"><code class="lang-java" data-lang="java">
154   * PackageAndClass pc = PackageAndClass.fromString("foo.bar.Baz");
155   * PackageAndClass other = pc.inPackage("plic.ploc"); // plic.ploc.Baz
156   * </code></pre>
157   *
158   * @return a new {@code PackageAndClass} representing the same class in another package.
159   * @param qualifiedName the qualified name of the new package.
160   */
161  public PackageAndClass inPackage(String qualifiedName) {
162    return new PackageAndClass(qualifiedName, className);
163  }
164
165  /**
166   * Create a class in the same package as another one.
167   * <p>
168   * For instance:
169   * <pre class="listing"><code class="lang-java" data-lang="java">
170   * PackageAndClass pc = PackageAndClass.fromString("foo.bar.Baz");
171   * PackageAndClass other = PackageAndClass.fromString("plic.ploc.Foo");
172   * PackageAndClass newOne = pc.inPackage(other); // plic.ploc.Baz
173   * </code></pre>
174   *
175   * @return a new {@code PackageAndClass} representing the same class in another package.
176   * @param parent the {@code PackageAndClass} representing the new package.
177   */
178  public PackageAndClass inPackage(PackageAndClass parent) {
179    return new PackageAndClass(parent.packageName(), className);
180  }
181
182  /**
183   * @return the package name.
184   */
185  public String packageName() {
186    return packageName;
187  }
188
189  /**
190   * Check if this {@code PackageAndClass} has a package component.
191   */
192  public boolean hasPackage() {
193    return !packageName.isEmpty();
194  }
195
196  /**
197   * @return the package as a {@code PackageAndClass}
198   */
199  public PackageAndClass parentPackage() {
200    return fromString(packageName);
201  }
202
203  /**
204   * @return the class name.
205   */
206  public String className() {
207    return className;
208  }
209
210  @Override
211  public String toString() {
212    if (packageName.isEmpty()) {
213      return className;
214    } else {
215      return packageName + "." + className;
216    }
217  }
218
219  /**
220   * @return a JVM type representation for this object, e.g.: {@code foo.Bar} gives {@code foo/Bar}.
221   */
222  public String toJVMType() {
223    return toString().replaceAll("\\.", "/");
224  }
225
226  /**
227   * @return a JVM reference type representation for this object, e.g.: {@code foo.Bar} gives
228   * {@code Lfoo/Bar;}
229   */
230  public String toJVMRef() {
231    return "L" + toJVMType() + ";";
232  }
233
234  /**
235   * @return a mangled named for this class.
236   */
237  public String mangledName() {
238    return toString().replace('.', '$');
239  }
240
241  @Override
242  public boolean equals(Object o) {
243    if (this == o) { return true; }
244    if (o == null || getClass() != o.getClass()) { return false; }
245
246    PackageAndClass that = (PackageAndClass) o;
247
248    if (className != null ? !className.equals(that.className) : that.className != null) { return false; }
249    return !(packageName != null ? !packageName.equals(that.packageName) : that.packageName != null);
250  }
251
252  @Override
253  public int hashCode() {
254    int result = packageName != null ? packageName.hashCode() : 0;
255    result = 31 * result + (className != null ? className.hashCode() : 0);
256    return result;
257  }
258}