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 org.eclipse.golo.compiler.PackageAndClass;
014
015import java.util.*;
016
017import static java.util.Collections.unmodifiableCollection;
018
019public final class GoloModule extends GoloElement<GoloModule> implements FunctionContainer {
020
021  private String sourceFile;
022  private final PackageAndClass packageAndClass;
023  private final ReferenceTable globalReferences;
024  private final List<ModuleImport> imports = new LinkedList<>();
025  private final Set<GoloFunction> functions = new LinkedHashSet<>();
026  private final Map<PackageAndClass, Augmentation> augmentations = new LinkedHashMap<>();
027  private final Set<NamedAugmentation> namedAugmentations = new LinkedHashSet<>();
028  private final Set<GoloType<?>> types = new LinkedHashSet<>();
029  private final Set<LocalReference> moduleState = new LinkedHashSet<>();
030  private final Set<MacroInvocation> topLevelMacroInvocations = new LinkedHashSet<>();
031  private MacroInvocation decoratorMacro = null;
032  private GoloFunction moduleStateInitializer = null;
033  private boolean hasMain = false;
034  private boolean hasMacros = false;
035
036  private static final ModuleImport[] DEFAULT_IMPORTS = {
037    ModuleImport.implicit("gololang.Predefined"),
038    ModuleImport.implicit("gololang.StandardAugmentations"),
039    ModuleImport.implicit("gololang"),
040    ModuleImport.implicit("java.lang")
041  };
042
043  public static final String MODULE_INITIALIZER_FUNCTION = "<clinit>";
044  public static final String TYPE_SUBPACKAGE = "types";
045
046  private GoloModule(PackageAndClass name, ReferenceTable references) {
047    super();
048    this.packageAndClass = name;
049    this.globalReferences = references;
050  }
051
052  public static GoloModule create(PackageAndClass name, ReferenceTable references) {
053    return new GoloModule(name,
054        references == null ?  new ReferenceTable() : references);
055  }
056
057  protected GoloModule self() { return this; }
058
059  public PackageAndClass getPackageAndClass() {
060    return packageAndClass;
061  }
062
063  public PackageAndClass getTypesPackage() {
064    return packageAndClass.createSubPackage(TYPE_SUBPACKAGE);
065  }
066
067  public String sourceFile() {
068    return this.sourceFile == null ? "unknown" : this.sourceFile;
069  }
070
071  public GoloModule sourceFile(String file) {
072    this.sourceFile = file;
073    return this;
074  }
075
076  public ReferenceTable getReferenceTable() {
077    return this.globalReferences;
078  }
079
080  /**
081   * {@inheritDoc}
082   *
083   * @return the module itself.
084   */
085  @Override
086  public GoloModule enclosingModule() {
087    return this;
088  }
089
090  /**
091   * Returns the module imported by this module, including the implicit ones.
092   */
093  public Set<ModuleImport> getImports() {
094    Set<ModuleImport> imp = new LinkedHashSet<>();
095    if (!types.isEmpty()) {
096      imp.add(ModuleImport.implicit(this.getTypesPackage()));
097      for (GoloType<?> t : types) {
098        if (t instanceof Union) {
099          imp.add(ModuleImport.implicit(t.getPackageAndClass()));
100        }
101      }
102    }
103    imp.addAll(imports);
104    if (this.packageAndClass.hasPackage()) {
105      imp.add(ModuleImport.implicit(this.packageAndClass.parentPackage()));
106    }
107    Collections.addAll(imp, DEFAULT_IMPORTS);
108    return imp;
109  }
110
111  /**
112   * Returns the names of the modules used by this module.
113   *
114   * <p>Since the {@code use} macro can inject other dependencies, this list can be not complete.
115   * The macro is not expanded.
116   */
117  public Set<String> getUsedModules() {
118    Set<String> mods = new LinkedHashSet<>();
119    for (MacroInvocation m : topLevelMacroInvocations) {
120      if ("use".equals(m.getName())) {
121        Object name = ((ConstantStatement) m.getArguments().get(0)).value();
122        if (name instanceof String) { mods.add((String) name); }
123        else if (name instanceof ClassReference) { mods.add(((ClassReference) name).getName()); }
124      }
125    }
126    return mods;
127  }
128
129  public Collection<Augmentation> getAugmentations() {
130    return unmodifiableCollection(augmentations.values());
131  }
132
133  public GoloModule decoratorMacro(MacroInvocation macro) {
134    this.decoratorMacro = macro;
135    return this;
136  }
137
138  public Optional<MacroInvocation> decoratorMacro() {
139    return Optional.ofNullable(this.decoratorMacro);
140  }
141
142  /**
143   * {@inheritDoc}
144   */
145  @Override
146  public List<GoloFunction> getFunctions() {
147    return new ArrayList<>(functions);
148  }
149
150  /**
151   * {@inheritDoc}
152   */
153  @Override
154  public boolean hasFunctions() {
155    return !functions.isEmpty();
156  }
157
158  public boolean hasMacros() {
159    return hasMacros;
160  }
161
162  public boolean hasMain() {
163    return hasMain;
164  }
165
166  /**
167   * {@inheritDoc}
168   */
169  @Override
170  public void addFunction(GoloFunction function) {
171    function.getBlock().getReferenceTable().relinkTopLevel(globalReferences);
172    functions.add(makeParentOf(function));
173    if (function.isMain()) {
174      hasMain = true;
175    }
176    if (function.isMacro()) {
177      hasMacros = true;
178    }
179  }
180
181  /**
182   * {@inheritDoc}
183   */
184  @Override
185  public void addMacroInvocation(MacroInvocation macroCall) {
186    if (macroCall == null) return;
187    this.topLevelMacroInvocations.add(macroCall);
188    makeParentOf(macroCall);
189  }
190
191  private void addAugmentation(Augmentation augment) {
192    PackageAndClass target = augment.getTarget();
193    if (augmentations.containsKey(target)) {
194      augmentations.get(target).merge(augment);
195    } else {
196      augmentations.put(target, augment);
197    }
198  }
199
200  public GoloElement<?> getSubtypeByName(String name) {
201    if (name == null) { return null; }
202    for (GoloType<?> t : types) {
203      if (name.equals(t.getName())) {
204        return t;
205      }
206    }
207    return null;
208  }
209
210  private void addModuleStateInitializer(AssignmentStatement assignment) {
211    if (!assignment.isDeclaring()) {
212      throw new IllegalArgumentException("Module state must be a declaring assignment");
213    }
214    assignment.getLocalReference().moduleLevel();
215    this.moduleState.add(assignment.getLocalReference());
216    if (moduleStateInitializer == null) {
217      moduleStateInitializer = GoloFunction.function(MODULE_INITIALIZER_FUNCTION)
218        .block(Block.empty().ref(globalReferences));
219      functions.add(moduleStateInitializer);
220    }
221    moduleStateInitializer.getBlock().add(assignment);
222  }
223
224  public GoloModule add(GoloElement<?> element) {
225    if (element == null || element instanceof Noop) {
226      return this;
227    }
228    if (element instanceof ToplevelElements) {
229      for (GoloElement<?> e : (ToplevelElements) element) {
230        this.add(e);
231      }
232      return this;
233    }
234    makeParentOf(element);
235    if (element instanceof ModuleImport) {
236      imports.add((ModuleImport) element);
237    } else if (element instanceof GoloFunction) {
238      addFunction((GoloFunction) element);
239    } else if (element instanceof GoloType<?>) {
240      GoloType<?> t = (GoloType<?>) element;
241      types.add(t);
242    } else if (element instanceof Augmentation) {
243      addAugmentation((Augmentation) element);
244    } else if (element instanceof NamedAugmentation) {
245      namedAugmentations.add((NamedAugmentation) element);
246    } else if (element instanceof LocalReference) {
247      this.moduleState.add((LocalReference) element);
248    } else if (element instanceof MacroInvocation) {
249      addMacroInvocation((MacroInvocation) element);
250    } else if (element instanceof AssignmentStatement) {
251      addModuleStateInitializer((AssignmentStatement) element);
252    } else {
253      throw new IllegalArgumentException("Can't add a " + element.getClass() + " to a module");
254    }
255    return this;
256  }
257
258  /**
259   * {@inheritDoc}
260   */
261  @Override
262  public void accept(GoloIrVisitor visitor) {
263    visitor.visitModule(this);
264  }
265
266  /**
267   * {@inheritDoc}
268   */
269  @Override
270  public List<GoloElement<?>> children() {
271    LinkedList<GoloElement<?>> children = new LinkedList<>();
272    children.addAll(topLevelMacroInvocations);
273    children.addAll(getImports());
274    children.addAll(types);
275    children.addAll(augmentations.values());
276    children.addAll(namedAugmentations);
277    children.addAll(moduleState);
278    children.addAll(functions);
279    return children;
280  }
281
282  public boolean isEmpty() {
283    return imports.isEmpty()
284        && types.isEmpty()
285        && augmentations.isEmpty()
286        && namedAugmentations.isEmpty()
287        && moduleState.isEmpty()
288        && functions.isEmpty()
289        && topLevelMacroInvocations.isEmpty();
290  }
291
292  /**
293   * {@inheritDoc}
294   */
295  @Override
296  protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
297    if (original instanceof GoloFunction) {
298      this.functions.remove(original);
299    } else if (original instanceof MacroInvocation) {
300      topLevelMacroInvocations.remove(original);
301    } else {
302      throw cantReplace(original, newElement);
303    }
304    this.add(newElement);
305  }
306
307  @Override
308  public String toString() {
309    return String.format("GoloModule{name=%s, src=%s}", this.packageAndClass, this.sourceFile);
310  }
311}