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 gololang.ir;
012
013import java.util.*;
014
015import org.eclipse.golo.compiler.PackageAndClass;
016
017import static java.util.Collections.unmodifiableSet;
018import static gololang.Messages.message;
019
020/**
021 * "classical" augmentation.
022 * <p>
023 * Represents all the augmentations applied to a type, i.e. functions and named augmentations
024 * applied with the {@code with} construct.
025 * <p>
026 * This represents code such
027 * <pre class="listing"><code class="lang-golo" data-lang="golo">
028 * augment MyType {
029 *   function foo = |this| -> ...
030 * }
031 * </code></pre>
032 * or
033 * <pre class="listing"><code class="lang-golo" data-lang="golo">
034 * augment MyType with MyAugmentation
035 * </code></pre>
036 */
037public final class Augmentation extends GoloElement<Augmentation> implements FunctionContainer, ToplevelGoloElement {
038  private final PackageAndClass target;
039  private final Set<GoloFunction> functions = new LinkedHashSet<>();
040  private final Set<String> names = new LinkedHashSet<>();
041
042  private Augmentation(PackageAndClass target) {
043    super();
044    this.target = target;
045  }
046
047  protected Augmentation self() { return this; }
048
049  /**
050   * Creates an augmentation on the target name.
051   *
052   * @param target the name of the target (compatible with {@link PackageAndClass#of(Object)})
053   * @return a classical augmentation
054   * @see PackageAndClass#of(Object)
055   */
056  public static Augmentation of(Object target) {
057    return new Augmentation(PackageAndClass.of(target));
058  }
059
060  /**
061   * Returns the target type of this augmentation.
062   *
063   * <p>Note that since resolution is done at runtime, the target is only referenced by its name (here a
064   * {@link PackageAndClass}).
065   */
066  public PackageAndClass getTarget() {
067    if (target.packageName().isEmpty()) {
068      GoloModule mod = enclosingModule();
069      if (mod != null) {
070        return mod.getTypesPackage().createSubPackage(target.className());
071      }
072    }
073    return target;
074  }
075
076  @Override
077  public PackageAndClass getPackageAndClass() {
078    return getTarget();
079  }
080
081  /**
082   * {@inheritDoc}
083   */
084  @Override
085  public List<GoloFunction> getFunctions() {
086    return new ArrayList<>(functions);
087  }
088
089  /**
090   * {@inheritDoc}
091   */
092  @Override
093  public void addFunction(GoloFunction func) {
094    if (func.getArity() == 0) {
095      throw new IllegalArgumentException(message("augment_function_no_args", func.getName(), this.getPackageAndClass()));
096    }
097    functions.add(makeParentOf(func));
098  }
099
100  /**
101   * {@inheritDoc}
102   */
103  @Override
104  public boolean hasFunctions() {
105    return !functions.isEmpty();
106  }
107
108  /**
109   * Returns the names of the applied named augmentations.
110   */
111  public Set<String> getNames() {
112    return unmodifiableSet(names);
113  }
114
115  /**
116   * Checks if named augmentations were applied.
117   */
118  public boolean hasNames() {
119    return !names.isEmpty();
120  }
121
122  /**
123   * Define the functions or named augmentations to add to the target.
124   * <p>
125   * The objects to add can be a function or a string representing the name of a named
126   * augmentation.
127   *
128   * <p>This is a builder method.
129   *
130   * @param objects functions or named augmentations to add
131   * @return this augmentation
132   */
133  public Augmentation with(Object... objects) {
134    return with(java.util.Arrays.asList(objects));
135  }
136
137  /**
138   * Define the functions or named augmentations to add to the target.
139   * <p>
140   * The objects to add can be a function or a string representing the name of a named
141   * augmentation.
142   *
143   * <p>This is a builder method.
144   *
145   * @param objects a collection of functions or named augmentations to add
146   * @return this augmentation
147   */
148  public Augmentation with(Collection<?> objects) {
149    if (objects != null) {
150      for (Object o : objects) {
151        if (o instanceof String) {
152          names.add((String) o);
153        } else {
154          addElement(o);
155        }
156      }
157    }
158    return this;
159  }
160
161  /**
162   * Merge two augmentations applied to the same target.
163   *
164   * <p><strong>Warning:</strong>the functions contained in the other augmentation being <em>move</em> to this one and <em>not copied</em>,
165   * the other augmentation references functions but is no more their parent. Therefore, the other augmentation is not
166   * in a consistent state and should be dropped, since no more needed.
167   */
168  public void merge(Augmentation other) {
169    if (!other.getTarget().equals(getTarget())) {
170      throw new IllegalArgumentException("Can't merge augmentations to different targets");
171    }
172    if (other != this) {
173      this.names.addAll(other.getNames());
174      addFunctions(other.getFunctions());
175    }
176  }
177
178  @Override
179  public String toString() {
180    return String.format("Augmentation<target=%s, names=%s, functions=%s>",
181           getTarget(),
182           getNames(),
183           getFunctions());
184  }
185
186  /**
187   * {@inheritDoc}
188   */
189  @Override
190  protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
191    if (functions.contains(original)) {
192      functions.remove(original);
193    } else {
194      throw cantReplace(original, newElement);
195    }
196    addElement(newElement);
197  }
198
199  /**
200   * {@inheritDoc}
201   */
202  @Override
203  public void accept(GoloIrVisitor visitor) {
204    visitor.visitAugmentation(this);
205  }
206
207  /**
208   * {@inheritDoc}
209   */
210  @Override
211  public List<GoloElement<?>> children() {
212    LinkedList<GoloElement<?>> children = new LinkedList<>(functions);
213    return children;
214  }
215}