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.runtime.augmentation;
012
013import org.eclipse.golo.runtime.Loader;
014import org.eclipse.golo.runtime.Module;
015
016import java.util.function.Predicate;
017import java.util.stream.Stream;
018
019import static org.eclipse.golo.runtime.augmentation.AugmentationApplication.Kind;
020
021/**
022 * Encapsulate a module defining an augmentation.
023 */
024public final class DefiningModule {
025
026  public enum Scope { LOCAL, IMPORT, CALLSTACK }
027
028  private final Class<?> module;
029  private final Scope scope;
030
031  DefiningModule(Class<?> module, Scope scope) {
032    this.module = module;
033    this.scope = scope;
034  }
035
036  public Class<?> module() {
037    return this.module;
038  }
039
040  public static DefiningModule of(Class<?> module, Scope scope) {
041    return new DefiningModule(module, scope);
042  }
043
044  public static DefiningModule ofLocal(Class<?> module) {
045    return new DefiningModule(module, Scope.LOCAL);
046  }
047
048  public static DefiningModule ofImport(Class<?> module) {
049    return new DefiningModule(module, Scope.IMPORT);
050  }
051
052  public static DefiningModule ofCallstack(Class<?> module) {
053    return new DefiningModule(module, Scope.CALLSTACK);
054  }
055
056  /**
057   * Returns a stream of augmentations definitions defined directly on a type in the corresponding
058   * module.
059   * e.g.
060   * <pre class="listing"><code class="lang-golo" data-lang="golo">
061   * augment module.Type {
062   *    # ...
063   * }
064   * </code></pre>
065   */
066  private Stream<AugmentationApplication> simpleAugmentationsFor(Loader loader, Class<?> receiverType) {
067    return Stream.of(Module.augmentations(module))
068      .map(loader)
069      .filter(isAssignableFrom(receiverType))
070      .map(target -> new AugmentationApplication(
071            loader.load(module.getName() + "$" + target.getName().replace(".", "$")),
072            target, scope, Kind.SIMPLE));
073  }
074
075  private static Predicate<Class<?>> isAssignableFrom(Class<?> receiver) {
076    return target -> target != null && target.isAssignableFrom(receiver);
077  }
078
079  private Stream<AugmentationApplication> fullyNamedAugmentationsFor(Loader loader, Class<?> receiverType) {
080    return Stream.of(Module.augmentationApplications(module))
081      .map(loader::load)
082      .filter(target -> target != null && target.isAssignableFrom(receiverType))
083      .flatMap(target -> qualifyAugmentations(loader, target));
084  }
085
086  private Stream<AugmentationApplication> qualifyAugmentations(Loader loader, Class<?> target) {
087    return Stream.of(Module.augmentationApplications(module, target))
088      .flatMap(this::fullyQualifiedName)
089      .map(augmentName -> new AugmentationApplication(
090              loader.load(augmentName),
091              target, scope, Kind.NAMED));
092  }
093
094  /**
095   * Fully qualify an augmentation name.
096   * <p>
097   * Given an augmentation name, this generate a stream of alternative names by prepending
098   * names of the defining module as well as imported modules.
099   */
100  private Stream<String> fullyQualifiedName(String augmentationName) {
101    Stream.Builder<String> names = Stream.builder();
102    int idx = augmentationName.lastIndexOf(".");
103    if (idx == -1) {
104      names.add(augmentationName);
105    } else {
106      names.add(new StringBuilder(augmentationName).replace(idx, idx + 1, "$").toString());
107    }
108    names.add(module.getName() + "$" + augmentationName.replace(".", "$"));
109    return Stream.concat(
110        names.build(),
111        Stream.of(Module.imports(module))
112        .map(prefix -> prefix + "$" + augmentationName.replace(".", "$")));
113  }
114
115  public Stream<AugmentationApplication> augmentationsFor(Loader loader, Class<?> receiverType) {
116    if (module == null) {
117      return Stream.empty();
118    }
119    return Stream.concat(
120        simpleAugmentationsFor(loader, receiverType),
121        fullyNamedAugmentationsFor(loader, receiverType));
122
123  }
124
125  @Override
126  public String toString() {
127    return "DefiningModule<" + module + "," + scope + ">";
128  }
129}
130
131