/*
Copyright 2013-2016 Jason Leyba
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.github.jsdossier.testing;
import static com.google.common.base.Preconditions.checkArgument;
import com.github.jsdossier.CompilerModule;
import com.github.jsdossier.MarkdownPage;
import com.github.jsdossier.ModuleNamingConvention;
import com.github.jsdossier.annotations.DocumentationScoped;
import com.github.jsdossier.annotations.Input;
import com.github.jsdossier.annotations.ModuleExterns;
import com.github.jsdossier.annotations.ModuleFilter;
import com.github.jsdossier.annotations.ModulePrefix;
import com.github.jsdossier.annotations.Modules;
import com.github.jsdossier.annotations.Output;
import com.github.jsdossier.annotations.SourcePrefix;
import com.github.jsdossier.annotations.SourceUrlTemplate;
import com.github.jsdossier.annotations.Stderr;
import com.github.jsdossier.annotations.TypeFilter;
import com.github.jsdossier.soy.DossierSoyModule;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.jimfs.Jimfs;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.Predicate;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* A simple rule for injecting necessary values into an object prior to evaluating a statement.
*/
@AutoValue
public abstract class GuiceRule implements TestRule {
public static Builder builder(Object target, Module... modules) {
Builder builder = new AutoValue_GuiceRule.Builder();
return builder
.setTarget(target)
.setLanguageIn(CompilerOptions.LanguageMode.ECMASCRIPT5)
.setModuleNamingConvention(ModuleNamingConvention.ES6)
.setNewTypeInference(false)
.setGuiceModules(ImmutableList.copyOf(modules))
.setInputFs(Jimfs.newFileSystem())
.setModulePrefix(Optional.empty())
.setSourcePrefix(Optional.empty())
.setModuleExterns(ImmutableSet.of())
.setModules(ImmutableSet.of())
.setModulePathFilter(path -> false)
.setTypeNameFilter(path -> false)
.setSourceUrlTemplate(Optional.empty())
.setOutputFs(Jimfs.newFileSystem())
.setOutputDir(Optional.empty())
;
}
abstract Object getTarget();
abstract ImmutableList<Module> getGuiceModules();
abstract FileSystem getInputFs();
abstract Optional<Path> getModulePrefix();
abstract Optional<Path> getSourcePrefix();
abstract ImmutableSet<Path> getModules();
abstract ImmutableSet<Path> getModuleExterns();
abstract Predicate<Path> getModulePathFilter();
abstract Predicate<String> getTypeNameFilter();
abstract Optional<String> getSourceUrlTemplate();
abstract ModuleNamingConvention getModuleNamingConvention();
abstract CompilerOptions.LanguageMode getLanguageIn();
abstract boolean getNewTypeInference();
abstract FileSystem getOutputFs();
abstract Optional<Path> getOutputDir();
public abstract Builder toBuilder();
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
Injector injector = createInjector();
injector.injectMembers(getTarget());
base.evaluate();
}
};
}
public Injector createInjector() {
ImmutableList<Module> modules = ImmutableList.<Module>builder()
.addAll(getGuiceModules())
.add(new CompilerModule())
.add(new DossierSoyModule())
.add(new AbstractModule() {
@Override
protected void configure() {
bind(Path.class, ModulePrefix.class, getModulePrefix());
bind(Path.class, SourcePrefix.class, getSourcePrefix());
bind(Path.class, Output.class, getOutputDir());
bindScope(DocumentationScoped.class, Scopes.NO_SCOPE);
bind(ModuleNamingConvention.class).toInstance(getModuleNamingConvention());
bind(new Key<ImmutableSet<MarkdownPage>>() {})
.toInstance(ImmutableSet.<MarkdownPage>of());
}
@Provides
@Input
LanguageMode provideInputLanguage() {
return getLanguageIn();
}
@Provides
@Stderr
PrintStream provideStderr() {
return System.err;
}
@Provides
@Input
FileSystem provideInputFs() {
return getInputFs();
}
@Provides
@Output
FileSystem provideOutputFs() {
return getOutputFs();
}
@Provides
@ModuleFilter
Predicate<Path> provideModulePathFilter() {
return getModulePathFilter();
}
@Provides
@TypeFilter
Predicate<String> provideTypeNameFilter() {
return getTypeNameFilter();
}
@Provides
@SourceUrlTemplate
Optional<String> provideSourceUrlTemplate() {
return getSourceUrlTemplate();
}
@Provides
@Modules
ImmutableSet<Path> provideModules() {
return getModules();
}
@Provides
@ModuleExterns
ImmutableSet<Path> provideModuleExterns() {
return getModuleExterns();
}
private <T> void bind(
Class<T> clazz, Class<? extends Annotation> ann, Optional<T> opt) {
if (opt.isPresent()) {
bind(Key.get(clazz, ann)).toInstance(opt.get());
}
}
})
.build();
return Guice.createInjector(modules);
}
@AutoValue.Builder
public static abstract class Builder {
abstract Builder setTarget(Object target);
abstract Builder setGuiceModules(ImmutableList<Module> modules);
abstract Builder setModulePrefix(Optional<Path> path);
abstract Optional<Path> getModulePrefix();
abstract Builder setSourcePrefix(Optional<Path> path);
abstract Builder setOutputDir(Optional<Path> out);
public abstract Builder setNewTypeInference(boolean set);
public abstract Builder setModuleNamingConvention(ModuleNamingConvention convention);
public abstract Builder setLanguageIn(CompilerOptions.LanguageMode languageIn);
public abstract Builder setModulePathFilter(Predicate<Path> filter);
public abstract Builder setTypeNameFilter(Predicate<String> filter);
public abstract Builder setInputFs(FileSystem fs);
public abstract FileSystem getInputFs();
abstract Builder setSourceUrlTemplate(Optional<String> pattern);
public Builder setSourceUrlTemplate(String pattern) {
return setSourceUrlTemplate(Optional.of(pattern));
}
public Builder setModulePrefix(String prefix) {
return setModulePrefix(Optional.of(getInputFs().getPath(prefix)));
}
public Builder setSourcePrefix(String prefix) {
return setSourcePrefix(Optional.of(getInputFs().getPath(prefix)));
}
public abstract Builder setModuleExterns(ImmutableSet<Path> externs);
public Builder setModuleExterns(String... paths) {
ImmutableSet.Builder<Path> externs = ImmutableSet.builder();
for (String path : paths) {
externs.add(getInputFs().getPath(path));
}
return setModuleExterns(externs.build());
}
public abstract Builder setModules(ImmutableSet<Path> modules);
public Builder setModules(String... paths) {
Optional<Path> opt = getModulePrefix();
checkArgument(opt.isPresent(), "module prefix not set");
Path prefix = opt.get();
ImmutableSet.Builder<Path> modules = ImmutableSet.builder();
for (String path : paths) {
modules.add(prefix.resolve(path));
}
return setModules(modules.build());
}
public abstract Builder setOutputFs(FileSystem fs);
public abstract FileSystem getOutputFs();
public Builder setOutputDir(String path) {
return setOutputDir(Optional.of(getOutputFs().getPath(path)));
}
public abstract GuiceRule build();
}
}