/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.ioc.rebind.ioc.bootstrapper;
import static org.jboss.errai.codegen.builder.impl.ClassBuilder.define;
import static org.jboss.errai.codegen.meta.MetaClassFactory.parameterizedAs;
import static org.jboss.errai.codegen.meta.MetaClassFactory.typeParametersOf;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import org.jboss.errai.codegen.builder.ClassStructureBuilder;
import org.jboss.errai.codegen.meta.MetaClassMember;
import org.jboss.errai.codegen.meta.MetaParameter;
import org.jboss.errai.common.metadata.RebindUtils;
import org.jboss.errai.ioc.client.container.Factory;
import org.jboss.errai.ioc.rebind.ioc.graph.api.CustomFactoryInjectable;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraph;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.Dependency;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.InjectableType;
import org.jboss.errai.ioc.rebind.ioc.graph.api.Injectable;
import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.IncrementalGenerator;
import com.google.gwt.core.ext.RebindMode;
import com.google.gwt.core.ext.RebindResult;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
/**
* Generates {@link Factory} subclasses by dispatching to the appropriate
* {@link FactoryBodyGenerator} and writing the output.
*
* @author Max Barkley <mbarkley@redhat.com>
*/
public class FactoryGenerator extends IncrementalGenerator {
private static final Logger log = LoggerFactory.getLogger(FactoryGenerator.class);
private static final String GENERATED_PACKAGE = "org.jboss.errai.ioc.client";
private static DependencyGraph graph;
private static InjectionContext injectionContext;
private static Map<String, String> generatedSourceByFactoryTypeName = new HashMap<String, String>();
private static Map<String, Injectable> injectablesByFactoryTypeName = new HashMap<String, Injectable>();
private static long totalTime;
public static void resetTotalTime() {
totalTime = 0;
}
public static void setDependencyGraph(final DependencyGraph graph) {
log.debug("Dependency graph set.");
FactoryGenerator.graph = graph;
}
public static DependencyGraph getDependencyGraph() {
return graph;
}
public static void setInjectionContext(final InjectionContext injectionContext) {
log.debug("Injection context set.");
FactoryGenerator.injectionContext = injectionContext;
}
public static String getLocalVariableName(final MetaParameter param) {
final MetaClassMember member = param.getDeclaringMember();
return member.getName() + "_" + param.getName() + "_" + param.getIndex();
}
private static DependencyGraph assertGraphSet() {
if (graph == null) {
throw new RuntimeException("Dependency graph must be generated and set before " + FactoryGenerator.class.getSimpleName() + " runs.");
}
return graph;
}
private static InjectionContext assertInjectionContextSet() {
if (injectionContext == null) {
throw new RuntimeException("Injection context must be set before " + FactoryGenerator.class.getSimpleName() + " runs.");
}
return injectionContext;
}
@Override
public RebindResult generateIncrementally(final TreeLogger logger, final GeneratorContext generatorContext, final String typeName)
throws UnableToCompleteException {
final long start = System.currentTimeMillis();
final DependencyGraph graph = assertGraphSet();
final InjectionContext injectionContext = assertInjectionContextSet();
final Injectable injectable = graph.getConcreteInjectable(typeName.substring(typeName.lastIndexOf('.')+1));
final InjectableType factoryType = injectable.getInjectableType();
final ClassStructureBuilder<?> factoryBuilder = define(getFactorySubTypeName(typeName),
parameterizedAs(Factory.class, typeParametersOf(injectable.getInjectedType()))).publicScope().body();
final FactoryBodyGenerator generator = selectBodyGenerator(factoryType, typeName, injectable);
final String factorySimpleClassName = getFactorySubTypeSimpleName(typeName);
final PrintWriter pw = generatorContext.tryCreate(logger, GENERATED_PACKAGE, factorySimpleClassName);
final RebindResult retVal;
if (pw != null) {
final String factorySource;
if (isCacheUsable(typeName, injectable)) {
log.debug("Reusing cached factory for " + typeName);
factorySource = generatedSourceByFactoryTypeName.get(typeName);
} else {
log.debug("Generating factory for " + typeName);
generator.generate(factoryBuilder, injectable, graph, injectionContext, logger, generatorContext);
factorySource = factoryBuilder.toJavaString();
generatedSourceByFactoryTypeName.put(typeName, factorySource);
injectablesByFactoryTypeName.put(typeName, injectable);
writeToDotErraiFolder(factorySimpleClassName, factorySource);
}
pw.write(factorySource);
generatorContext.commit(logger, pw);
retVal = new RebindResult(RebindMode.USE_ALL_NEW, factoryBuilder.getClassDefinition().getFullyQualifiedName());
} else {
log.debug("Reusing factory for " + typeName);
retVal = new RebindResult(RebindMode.USE_EXISTING, getFactorySubTypeName(typeName));
}
final long ellapsed = System.currentTimeMillis() - start;
totalTime += ellapsed;
log.debug("Factory for {} completed in {}ms. Total factory generation time: {}ms", typeName, ellapsed, totalTime);
return retVal;
}
private void writeToDotErraiFolder(final String factorySimpleClassName, final String factorySource) {
RebindUtils.writeStringToJavaSourceFileInErraiCacheDir(GENERATED_PACKAGE, factorySimpleClassName, factorySource);
}
private boolean isCacheUsable(final String typeName, final Injectable givenInjectable) {
if (RebindUtils.NO_CACHE) {
return false;
}
final Injectable cachedInjectable = injectablesByFactoryTypeName.get(typeName);
if (cachedInjectable != null) {
final boolean sameContent = cachedInjectable.hashContent() == givenInjectable.hashContent();
if (log.isTraceEnabled() && !sameContent) {
log.trace("Different hashContent for cached " + typeName);
traceConstituentHashContents(cachedInjectable, "cached " + typeName);
traceConstituentHashContents(givenInjectable, "new " + typeName);
}
return sameContent;
} else {
log.trace("No cached injectable was found for {}", typeName);
return false;
}
}
private static void traceConstituentHashContents(final Injectable injectable, final String name) {
log.trace("Begin trace of hashContent for {}", name);
log.trace("Combined content: {}", injectable.hashContent());
log.trace("HashContent for injectable type: {}", injectable.getInjectedType().hashContent());
for (final Dependency dep : injectable.getDependencies()) {
log.trace("HashContent for {} dep of type {}: {}", dep.getDependencyType().toString(),
dep.getInjectable().getInjectedType(), dep.getInjectable().getInjectedType().hashContent());
}
log.trace("End trace of hashContent for {}", name);
}
private FactoryBodyGenerator selectBodyGenerator(final InjectableType factoryType, final String typeName, final Injectable injectable) {
final FactoryBodyGenerator generator;
switch (factoryType) {
case Type:
generator = new TypeFactoryBodyGenerator();
break;
case Provider:
generator = new ProviderFactoryBodyGenerator();
break;
case JsType:
generator = new JsTypeFactoryBodyGenerator();
break;
case Producer:
generator = new ProducerFactoryBodyGenerator();
break;
case ContextualProvider:
generator = new ContextualFactoryBodyGenerator();
break;
case ExtensionProvided:
if (!(injectable instanceof CustomFactoryInjectable)) {
throw new RuntimeException(String.format("The injectable, %s, for %s is extension provided but is not a %s",
injectable.toString(), typeName, CustomFactoryInjectable.class.getSimpleName()));
}
generator = ((CustomFactoryInjectable) injectable).getGenerator();
break;
default:
throw new RuntimeException(factoryType + " not yet implemented!");
}
return generator;
}
public static String getFactorySubTypeName(final String typeName) {
return GENERATED_PACKAGE + "." + getFactorySubTypeSimpleName(typeName);
}
public static String getFactorySubTypeSimpleName(final String typeName) {
final int simpleNameStart = Math.max(typeName.lastIndexOf('.'), typeName.lastIndexOf('$')) + 1;
return typeName.substring(simpleNameStart);
}
@Override
public long getVersionId() {
return 1;
}
}