/* * Copyright (C) 2013 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.config.rebind; import java.io.PrintWriter; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import org.jboss.errai.codegen.meta.MetaClass; import org.jboss.errai.codegen.meta.MetaClassFactory; import org.jboss.errai.common.metadata.RebindUtils; import org.jboss.errai.config.util.ThreadUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gwt.core.ext.Generator; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; /** * Base class of all asynchronous code generators. * * @author Christian Sadilek <csadilek@redhat.com> */ public abstract class AbstractAsyncGenerator extends Generator implements AsyncCodeGenerator { private static final Map<Class<? extends AbstractAsyncGenerator>, String> cacheMap = new ConcurrentHashMap<Class<? extends AbstractAsyncGenerator>, String>(); private static final Map<Class<? extends AbstractAsyncGenerator>, Set<String>> cacheRelevantClasses = new ConcurrentHashMap<Class<? extends AbstractAsyncGenerator>, Set<String>>(); private static Logger log = LoggerFactory.getLogger(AbstractAsyncGenerator.class); @Override public Future<String> generateAsync(final TreeLogger logger, final GeneratorContext context) { return ThreadUtil.submit(new Callable<String>() { @Override public String call() throws Exception { final String generatedCode; if (isCacheEnabled() && isCacheValid()) { log.info("Using cached output from " + AbstractAsyncGenerator.this.getClass().getName()); generatedCode = getGeneratedCache(); } else { log.info("Running generator " + AbstractAsyncGenerator.this.getClass().getName()); clearCacheRelevantClasses(); generatedCode = generate(logger, context); setGeneratedCache(generatedCode); } return generatedCode; } }); } private boolean isCacheEnabled() { return !RebindUtils.NO_CACHE; } /** * @return True iff there is a cached output for this generator. Useful for subclasses that must override {@link #isCacheValid()}. */ protected final boolean hasGenerationCache() { final boolean hasCache = cacheMap.containsKey(getClass()); if (log.isDebugEnabled()) { log.debug("{} {} a generated cache.", getClass().getSimpleName(), (hasCache ? "has" : "does not have")); } return hasCache; } protected final String getGeneratedCache() { return cacheMap.get(getClass()); } private final void setGeneratedCache(final String generated) { cacheMap.put(getClass(), generated); } /** * @return True iff this generator does not need to be run again this refresh. */ protected boolean isCacheValid() { return hasGenerationCache() && !(MetaClassFactory.hasAnyChanges() && hasRelevantChanges()); } private boolean hasRelevantChanges() { final String generatorName = this.getClass().getSimpleName(); final Set<String> relevantClasses = cacheRelevantClasses.get(this.getClass()); if (relevantClasses == null) { log.debug("No classes marked as relevant for {}. Assuming there are relevant changes.", generatorName); return true; } for (final MetaClass clazz : MetaClassFactory.getAllNewOrUpdatedClasses()) { final boolean previouslyMarkedRelevant = relevantClasses.contains(clazz.getFullyQualifiedName()); if (previouslyMarkedRelevant || isRelevantClass(clazz)) { log.debug("New or updated class {} is {} cache relevant for {}.", clazz.getFullyQualifiedName(), (previouslyMarkedRelevant ? "previously marked as" : "now marked as"), generatorName); return true; } else { log.trace("New or updated class {} is not cache relevant for {}.", clazz.getFullyQualifiedName(), generatorName); } } for (final String deleted : MetaClassFactory.getAllDeletedClasses()) { if (relevantClasses.contains(deleted)) { log.debug("Deleted class {} was cache relevant for {}.", deleted, generatorName); return true; } else { log.trace("Deleted class {} is not cache relevant for {}.", deleted, generatorName); } } return false; } /** * Checks if the provided class is relevant to this generator. This method is * invoked to determine whether or not the introduction of the provided class * should trigger a rerun of this generator. * * @param clazz * a newly introduced clazz since the last refresh. * * @return true if newly introduced class is relevant to this generator, * otherwise false. */ protected boolean isRelevantClass(final MetaClass clazz) { return true; } /** * Called by {@link #generateAsync(TreeLogger, GeneratorContext)} to carry out the actual code * generation. * * @param context * the generator context to use. * * @return the generated code. */ protected abstract String generate(final TreeLogger logger, final GeneratorContext context); /** * Starts all asynchronous generators if they haven't been started yet and waits for the * completion of the generator responsible for the provided interface type. * * @param interfaceType * the interface for which an implementation should be generated. * @param context * the generation context to use. * @param logger * the tree logger to use. * @param packageName * the package name of the generated class. * @param className * the name of the generated class. * * @return the fully qualified name of the generated class. */ protected String startAsyncGeneratorsAndWaitFor( final Class<?> interfaceType, final GeneratorContext context, final TreeLogger logger, final String packageName, final String className) { try { final PrintWriter printWriter = context.tryCreate(logger, packageName, className); if (printWriter != null) { final Future<String> future = AsyncGenerationJob.createBuilder() .treeLogger(logger) .generatorContext(context) .interfaceType(interfaceType) .runIfStarting(new Runnable() { @Override public void run() { final long start = System.currentTimeMillis(); MetaClassBridgeUtil.populateMetaClassFactoryFromTypeOracle(context, logger); log.debug("MetaClassFactory populated in {}ms", System.currentTimeMillis() - start); } }) .build() // this causes all asynchronous code generators to run if this is the first one that executes. .submit(); final String gen = future.get(); printWriter.append(gen); RebindUtils.writeStringToJavaSourceFileInErraiCacheDir(packageName, className, gen); context.commit(logger, printWriter); } } catch (final Throwable e) { e.printStackTrace(); logger.log(TreeLogger.ERROR, "Error generating " + className, e); } return packageName + "." + className; } /** * Marks the provided class as cache relevant. This means that the provided * class will be considered when checking for changes to determine whether or * not the generator needs to rerun after a refresh. If no cache relevant * classes are added any change to reloadable classes will cause the generator * to rerun. * * @param clazz * the class to consider when checking for changes. */ protected void addCacheRelevantClass(final MetaClass clazz) { Set<String> classes = cacheRelevantClasses.get(this.getClass()); if (classes == null) { classes = new HashSet<String>(); cacheRelevantClasses.put(this.getClass(), classes); } classes.add(clazz.getFullyQualifiedName()); } /** * Marks the provided classes as cache relevant. This means that the provided * classes will be considered when checking for changes to determine whether * or not the generator needs to rerun after a refresh. If no cache relevant * classes are added any change to reloadable classes will cause the generator * to rerun. * * @param classes * the classes to consider when checking for changes. */ protected void addCacheRelevantClasses(final Collection<MetaClass> classes) { for (final MetaClass clazz : classes) { addCacheRelevantClass(clazz); } } private void clearCacheRelevantClasses() { cacheRelevantClasses.remove(this.getClass()); } }