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