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