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