public ReachableTypes getCache(final GeneratorContext context) { final String moduleName = RebindUtils.getModuleName(context); if (isCacheValid(context, moduleName)) { return moduleToReachableTypes.get(moduleName); } else { return null; } }
/** * Returns all bindable types on the classpath. * * @param context the current generator context * @return a set of meta classes representing the all bindable types (both annotated and * configured in ErraiApp.properties). */ public static Set<MetaClass> getAllBindableTypes(final GeneratorContext context) { Collection<MetaClass> annotatedBindableTypes = ClassScanner.getTypesAnnotatedWith( Bindable.class, RebindUtils.findTranslatablePackages(context), context); Set<MetaClass> bindableTypes = new HashSet<MetaClass>(annotatedBindableTypes); bindableTypes.addAll(DataBindingUtil.getConfiguredBindableTypes()); return bindableTypes; }
public void putCache(final GeneratorContext context, final ReachableTypes reachableTypes) { if (_lastContext == null) { _lastContext = context; } else if (_lastContext != context) { _lastContext = context; moduleToReachableTypes.clear(); } moduleToReachableTypes.put(RebindUtils.getModuleName(context), reachableTypes); }
/** * Erases the {@link MetaClassFactory} cache, then populates it with types discovered via GWT's * TypeOracle. The reason for the initial flush of the MetaClassFactory is to support hot redeploy * in Dev Mode. The reason for doing this operation at all is so that the overridden class * definitions (super-source classes) are used in preference to the Java reflection based class * definitions. * * @param context The GeneratorContext supplied by the GWT compiler. Not null. * @param logger The TreeLogger supplied by the GWT compiler. Not null. */ public static void populateMetaClassFactoryFromTypeOracle( final GeneratorContext context, final TreeLogger logger) { // if we're in production mode -- it means we're compiling, and we do not need to accommodate // dynamically // changing classes. Therefore, do a NOOP after the first successful call. if (typeOraclePopulated && (context.equals(populatedFrom.get()) || EnvUtil.isProdMode())) { return; } final TypeOracle typeOracle = context.getTypeOracle(); MetaClassFactory.emptyCache(); if (typeOracle != null) { final Set<String> translatable = new HashSet<String>(RebindUtils.findTranslatablePackages(context)); translatable.remove("java.lang"); translatable.remove("java.lang.annotation"); for (final JClassType type : typeOracle.getTypes()) { if (!translatable.contains(type.getPackage().getName())) { logger.log( com.google.gwt.core.ext.TreeLogger.Type.DEBUG, "Skipping non-translatable " + type.getQualifiedSourceName()); continue; } if (type.isAnnotation() != null || type.getQualifiedSourceName().equals("java.lang.annotation.Annotation")) { logger.log( com.google.gwt.core.ext.TreeLogger.Type.DEBUG, "Caching annotation type " + type.getQualifiedSourceName()); if (!MetaClassFactory.canLoadClass(type.getQualifiedBinaryName())) { throw new RuntimeException( "a new annotation has been introduced (" + type.getQualifiedSourceName() + "); " + "you cannot currently introduce new annotations in devmode. Please restart."); } MetaClassFactory.pushCache( JavaReflectionClass.newUncachedInstance( MetaClassFactory.loadClass(type.getQualifiedBinaryName()))); } else { logger.log( com.google.gwt.core.ext.TreeLogger.Type.DEBUG, "Caching translatable type " + type.getQualifiedSourceName()); MetaClassFactory.pushCache(GWTClass.newInstance(typeOracle, type)); } } } typeOraclePopulated = true; populatedFrom = new SoftReference<GeneratorContext>(context); }
/** * Renders the page-to-page navigation graph into the file {@code navgraph.gv} in the {@code * .errai} cache directory. */ private void renderNavigationToDotFile(BiMap<String, MetaClass> pages) { final File dotFile = new File(RebindUtils.getErraiCacheDir().getAbsolutePath(), "navgraph.gv"); PrintWriter out = null; try { out = new PrintWriter(dotFile); out.println("digraph Navigation {"); final MetaClass transitionToType = MetaClassFactory.get(TransitionTo.class); final MetaClass transitionAnchorType = MetaClassFactory.get(TransitionAnchor.class); final MetaClass transitionAnchorFactoryType = MetaClassFactory.get(TransitionAnchorFactory.class); for (Map.Entry<String, MetaClass> entry : pages.entrySet()) { String pageName = entry.getKey(); MetaClass pageClass = entry.getValue(); // entry for the node itself out.print("\"" + pageName + "\""); Page pageAnnotation = pageClass.getAnnotation(Page.class); List<Class<? extends PageRole>> roles = Arrays.asList(pageAnnotation.role()); if (roles.contains(DefaultPage.class)) { out.print(" [penwidth=3]"); } out.println(); for (MetaField field : getAllFields(pageClass)) { if (field.getType().getErased().equals(transitionToType) || field.getType().getErased().equals(transitionAnchorType) || field.getType().getErased().equals(transitionAnchorFactoryType)) { MetaType targetPageType = field.getType().getParameterizedType().getTypeParameters()[0]; String targetPageName = pages.inverse().get(targetPageType); // entry for the link between nodes out.println( "\"" + pageName + "\" -> \"" + targetPageName + "\" [label=\"" + field.getName() + "\"]"); } } } out.println("}"); } catch (FileNotFoundException e) { throw new RuntimeException(e); } finally { if (out != null) { out.close(); } } }
public String generate(String packageName, String clazzName) { File fileCacheDir = RebindUtils.getErraiCacheDir(); File cacheFile = new File(fileCacheDir.getAbsolutePath() + "/" + clazzName + ".java"); final Set<Class<? extends Annotation>> annos = new HashSet<Class<? extends Annotation>>(); annos.add(Portable.class); annos.add(ExposeEntity.class); String gen; if (RebindUtils.hasClasspathChangedForAnnotatedWith(annos) || !cacheFile.exists()) { log.info("generating marshalling class..."); long st = System.currentTimeMillis(); gen = _generate(packageName, clazzName); log.info("generated marshalling class in " + (System.currentTimeMillis() - st) + "ms"); RebindUtils.writeStringToFile(cacheFile, gen); } else { gen = RebindUtils.readFileToString(cacheFile); log.info("nothing has changed. using cached marshaller factory class."); } return gen; }
public static Class<? extends MarshallerFactory> getGeneratedMarshallerFactoryForServer() { final String packageName = MarshallersGenerator.SERVER_MARSHALLER_PACKAGE_NAME; final String className = MarshallersGenerator.SERVER_MARSHALLER_CLASS_NAME; try { log.debug("searching for marshaller class: " + packageName + "." + className); final String classResource = packageName.replaceAll("\\.", "/") + "/" + className + ".class"; final Set<String> locations = new HashSet<String>(); // look for the class in every classloader we can think of. For example, current thread // classloading works in Jetty but not JBoss AS 7. locations.addAll( urlToFile(Thread.currentThread().getContextClassLoader().getResources(classResource))); locations.addAll( urlToFile(ServerMarshallUtil.class.getClassLoader().getResources(classResource))); locations.addAll(urlToFile(ClassLoader.getSystemResources(classResource))); File newest = null; for (String url : locations) { final File file = ClassChangeUtil.getFileIfExists(url); if (file != null && (newest == null || file.lastModified() > newest.lastModified())) { newest = file; } } if (locations.size() > 1) { log.warn( "*** MULTIPLE VERSIONS OF " + packageName + "." + className + " FOUND IN CLASSPATH: " + "Attempted to guess the newest one based on file dates. But you should clean your output directories"); for (String loc : locations) { log.warn(" Ambiguous version -> " + loc); } } if (newest == null) { try { // maybe we're in an appserver with a VFS, so try to load anyways. return Thread.currentThread() .getContextClassLoader() .loadClass(packageName + "." + className) .asSubclass(MarshallerFactory.class); } catch (ClassNotFoundException e) { log.warn("could not locate marshaller class."); if (!MarshallingGenUtil.isForceStaticMarshallers()) { return null; } else { log.info("couldn't find marshaller class, attempting to generate ..."); } } } else { return ClassChangeUtil.loadClassDefinition( newest.getAbsolutePath(), packageName, className); } } catch (IOException e) { e.printStackTrace(); log.warn("could not read marshaller classes: " + e); } final String classStr = MarshallerGeneratorFactory.getFor(MarshallerOutputTarget.Java) .generate(packageName, className); final File directory = new File( RebindUtils.getTempDirectory() + "/errai.gen/classes/" + packageName.replaceAll("\\.", "/")); final File sourceFile = new File(directory.getAbsolutePath() + File.separator + className + ".java"); try { if (directory.exists()) { for (File file : directory.listFiles()) { file.delete(); } directory.delete(); } directory.mkdirs(); final FileOutputStream outputStream = new FileOutputStream(sourceFile); outputStream.write(classStr.getBytes("UTF-8")); outputStream.flush(); outputStream.close(); String compiledClassPath = ClassChangeUtil.compileClass( directory.getAbsolutePath(), packageName, className, directory.getAbsolutePath()); return ClassChangeUtil.loadClassDefinition(compiledClassPath, packageName, className); } catch (IOException e) { throw new RuntimeException("failed to generate class ", e); } }
public static ReachableTypes getAllReachableClasses(final GeneratorContext context) { if (System.getProperty(SYSPROP_USE_REACHABILITY_ANALYSIS) == null || !Boolean.getBoolean(SYSPROP_USE_REACHABILITY_ANALYSIS)) { log.warn("reachability analysis disabled. errai may generate unnecessary code."); log.warn( "enable reachability analysis with -D" + SYSPROP_USE_REACHABILITY_ANALYSIS + "=true"); return ReachableTypes.EVERYTHING_REACHABLE_INSTANCE; } ReachabilityCache cache; if (reachabilityCache == null || (cache = reachabilityCache.get()) == null) { reachabilityCache = new SoftReference<ReachabilityCache>(cache = new ReachabilityCache()); } if (cache.isCacheValid(context)) { return cache.getCache(context); } final EnvironmentConfig config = getEnvironmentConfig(); long time = System.currentTimeMillis(); final Set<String> packages = new HashSet<String>(); if (isJUnitTest()) { packages.addAll(RebindUtils.findTranslatablePackagesInModule(context)); } else { packages.addAll(RebindUtils.getOuterTranslatablePackages(context)); } class Reachability { private final Set<String> packages; private final Set<String> negativeHits = new HashSet<String>(); Reachability(final Set<String> packages) { this.packages = new HashSet<String>(packages); } public boolean isReachablePackage(final String pkg) { if (pkg == null || packages.contains(pkg)) { return true; } if (negativeHits.contains(pkg)) { return false; } for (final String p : packages) { if (pkg.startsWith(p)) { packages.add(pkg); return true; } } negativeHits.add(pkg); return false; } } final Set<String> allDependencies = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(100)); final Collection<MetaClass> allCachedClasses = MetaClassFactory.getAllCachedClasses(); final ClassLoader classLoader = EnvUtil.class.getClassLoader(); final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); final Reachability reachability = new Reachability(packages); try { for (final MetaClass mc : allCachedClasses) { String fullyQualifiedName = mc.getFullyQualifiedName(); int splitPoint; while ((splitPoint = fullyQualifiedName.lastIndexOf('$')) != -1) { fullyQualifiedName = fullyQualifiedName.substring(0, splitPoint); } if (mc.isPrimitive() || mc.isArray()) { continue; } else if (isReachabilityExcluded(mc.getPackageName())) { continue; } else if (!config.getExplicitTypes().contains(fullyQualifiedName) && !reachability.isReachablePackage(mc.getPackageName())) { continue; } final URL resource = classLoader.getResource(fullyQualifiedName.replaceAll("\\.", "/") + ".java"); if (resource != null) { InputStream stream = null; try { stream = new BufferedInputStream(resource.openStream()); final byte[] readBuffer = new byte[stream.available()]; stream.read(readBuffer); if (log.isDebugEnabled()) { log.debug("scanning " + fullyQualifiedName + " for reachable types ..."); } executor.execute(new ReachabilityRunnable(readBuffer, allDependencies)); } catch (IOException e) { log.warn("could not open resource: " + resource.getFile()); } finally { if (stream != null) { stream.close(); } } } else { log.warn("source for " + fullyQualifiedName + " is missing."); } } } catch (Throwable e) { e.printStackTrace(); } try { executor.shutdown(); executor.awaitTermination(60, TimeUnit.MINUTES); } catch (InterruptedException e) { log.warn("the reachability analysis was interrupted", e); cache.putCache(context, ReachableTypes.EVERYTHING_REACHABLE_INSTANCE); return ReachableTypes.EVERYTHING_REACHABLE_INSTANCE; } if (log.isDebugEnabled()) { log.debug("*** REACHABILITY ANALYSIS (production mode: " + EnvUtil.isProdMode() + ") ***"); for (final String s : allDependencies) { log.debug(" -> " + s); } time = System.currentTimeMillis() - time; log.debug("*** END OF REACHABILITY ANALYSIS (" + time + "ms) *** "); } final ReachableTypes reachableTypes = new ReachableTypes(allDependencies, true); cache.putCache(context, reachableTypes); return reachableTypes; }
public boolean isCacheValid(final GeneratorContext context) { return isCacheValid(context, RebindUtils.getModuleName(context)); }