diff options
author | Artur Signell <artur@vaadin.com> | 2012-08-29 12:29:14 +0300 |
---|---|---|
committer | Artur Signell <artur@vaadin.com> | 2012-08-29 12:29:20 +0300 |
commit | f5f73df7937d6fc8008ae34cc5a7d39e60de266b (patch) | |
tree | 028a4ff386f0fdc8b166274138f376957ef0f0f2 /client-compiler/src/com/vaadin/server | |
parent | 63595217a224b9fbc6de4b8bdc2e6b317e0558d5 (diff) | |
download | vaadin-framework-f5f73df7937d6fc8008ae34cc5a7d39e60de266b.tar.gz vaadin-framework-f5f73df7937d6fc8008ae34cc5a7d39e60de266b.zip |
Renamed com.vaadin.terminal.gwt.widgetsetutils -> com.vaadin.server.widgetsetutils (#9431)
Diffstat (limited to 'client-compiler/src/com/vaadin/server')
23 files changed, 3613 insertions, 0 deletions
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java new file mode 100644 index 0000000000..5b9cd399c7 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java @@ -0,0 +1,139 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils; + +import java.io.PrintWriter; +import java.util.Date; + +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.client.ui.dd.VAcceptCriterion; +import com.vaadin.client.ui.dd.VAcceptCriterionFactory; +import com.vaadin.shared.ui.dd.AcceptCriterion; + +/** + * GWT generator to build {@link VAcceptCriterionFactory} implementation + * dynamically based on {@link AcceptCriterion} annotations available in + * classpath. + * + */ +public class AcceptCriteriaFactoryGenerator extends Generator { + + private String packageName; + private String className; + + @Override + public String generate(TreeLogger logger, GeneratorContext context, + String typeName) throws UnableToCompleteException { + + try { + TypeOracle typeOracle = context.getTypeOracle(); + + // get classType and save instance variables + JClassType classType = typeOracle.getType(typeName); + packageName = classType.getPackage().getName(); + className = classType.getSimpleSourceName() + "Impl"; + // Generate class source code + generateClass(logger, context); + } catch (Exception e) { + logger.log(TreeLogger.ERROR, + "Accept criterion factory creation failed", e); + } + // return the fully qualifed name of the class generated + return packageName + "." + className; + } + + /** + * Generate source code for WidgetMapImpl + * + * @param logger + * Logger object + * @param context + * Generator context + */ + private void generateClass(TreeLogger logger, GeneratorContext context) { + // get print writer that receives the source code + PrintWriter printWriter = null; + printWriter = context.tryCreate(logger, packageName, className); + // print writer if null, source code has ALREADY been generated, + // return (WidgetMap is equal to all permutations atm) + if (printWriter == null) { + return; + } + logger.log(Type.INFO, "Detecting available criteria ..."); + Date date = new Date(); + + // init composer, set class properties, create source writer + ClassSourceFileComposerFactory composer = null; + composer = new ClassSourceFileComposerFactory(packageName, className); + composer.addImport("com.google.gwt.core.client.GWT"); + composer.setSuperclass("com.vaadin.client.ui.dd.VAcceptCriterionFactory"); + SourceWriter sourceWriter = composer.createSourceWriter(context, + printWriter); + + // generator constructor source code + generateInstantiatorMethod(sourceWriter, context, logger); + // close generated class + sourceWriter.outdent(); + sourceWriter.println("}"); + // commit generated class + context.commit(logger, printWriter); + logger.log(Type.INFO, + "Done. (" + (new Date().getTime() - date.getTime()) / 1000 + + "seconds)"); + + } + + private void generateInstantiatorMethod(SourceWriter sourceWriter, + GeneratorContext context, TreeLogger logger) { + + sourceWriter.println("public VAcceptCriterion get(String name) {"); + sourceWriter.indent(); + + sourceWriter.println("name = name.intern();"); + + JClassType criteriaType = context.getTypeOracle().findType( + VAcceptCriterion.class.getName()); + for (JClassType clientClass : criteriaType.getSubtypes()) { + AcceptCriterion annotation = clientClass + .getAnnotation(AcceptCriterion.class); + if (annotation != null) { + String clientClassName = clientClass.getQualifiedSourceName(); + Class<?> serverClass = clientClass.getAnnotation( + AcceptCriterion.class).value(); + String serverClassName = serverClass.getCanonicalName(); + logger.log(Type.INFO, "creating mapping for " + serverClassName); + sourceWriter.print("if (\""); + sourceWriter.print(serverClassName); + sourceWriter.print("\" == name) return GWT.create("); + sourceWriter.print(clientClassName); + sourceWriter.println(".class );"); + sourceWriter.print("else "); + } + } + + sourceWriter.println("return null;"); + sourceWriter.outdent(); + sourceWriter.println("}"); + } +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java new file mode 100644 index 0000000000..70120b944d --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java @@ -0,0 +1,474 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Utility class to collect widgetset related information from classpath. + * Utility will seek all directories from classpaths, and jar files having + * "Vaadin-Widgetsets" key in their manifest file. + * <p> + * Used by WidgetMapGenerator and ide tools to implement some monkey coding for + * you. + * <p> + * Developer notice: If you end up reading this comment, I guess you have faced + * a sluggish performance of widget compilation or unreliable detection of + * components in your classpaths. The thing you might be able to do is to use + * annotation processing tool like apt to generate the needed information. Then + * either use that information in {@link WidgetMapGenerator} or create the + * appropriate monkey code for gwt directly in annotation processor and get rid + * of {@link WidgetMapGenerator}. Using annotation processor might be a good + * idea when dropping Java 1.5 support (integrated to javac in 6). + * + */ +public class ClassPathExplorer { + + private static final String VAADIN_ADDON_VERSION_ATTRIBUTE = "Vaadin-Package-Version"; + + /** + * File filter that only accepts directories. + */ + private final static FileFilter DIRECTORIES_ONLY = new FileFilter() { + @Override + public boolean accept(File f) { + if (f.exists() && f.isDirectory()) { + return true; + } else { + return false; + } + } + }; + + /** + * Raw class path entries as given in the java class path string. Only + * entries that could include widgets/widgetsets are listed (primarily + * directories, Vaadin JARs and add-on JARs). + */ + private static List<String> rawClasspathEntries = getRawClasspathEntries(); + + /** + * Map from identifiers (either a package name preceded by the path and a + * slash, or a URL for a JAR file) to the corresponding URLs. This is + * constructed from the class path. + */ + private static Map<String, URL> classpathLocations = getClasspathLocations(rawClasspathEntries); + + /** + * No instantiation from outside, callable methods are static. + */ + private ClassPathExplorer() { + } + + /** + * Finds the names and locations of widgetsets available on the class path. + * + * @return map from widgetset classname to widgetset location URL + */ + public static Map<String, URL> getAvailableWidgetSets() { + long start = System.currentTimeMillis(); + Map<String, URL> widgetsets = new HashMap<String, URL>(); + Set<String> keySet = classpathLocations.keySet(); + for (String location : keySet) { + searchForWidgetSets(location, widgetsets); + } + long end = System.currentTimeMillis(); + + StringBuilder sb = new StringBuilder(); + sb.append("Widgetsets found from classpath:\n"); + for (String ws : widgetsets.keySet()) { + sb.append("\t"); + sb.append(ws); + sb.append(" in "); + sb.append(widgetsets.get(ws)); + sb.append("\n"); + } + final Logger logger = getLogger(); + logger.info(sb.toString()); + logger.info("Search took " + (end - start) + "ms"); + return widgetsets; + } + + /** + * Finds all GWT modules / Vaadin widgetsets in a valid location. + * + * If the location is a directory, all GWT modules (files with the + * ".gwt.xml" extension) are added to widgetsets. + * + * If the location is a JAR file, the comma-separated values of the + * "Vaadin-Widgetsets" attribute in its manifest are added to widgetsets. + * + * @param locationString + * an entry in {@link #classpathLocations} + * @param widgetsets + * a map from widgetset name (including package, with dots as + * separators) to a URL (see {@link #classpathLocations}) - new + * entries are added to this map + */ + private static void searchForWidgetSets(String locationString, + Map<String, URL> widgetsets) { + + URL location = classpathLocations.get(locationString); + File directory = new File(location.getFile()); + + if (directory.exists() && !directory.isHidden()) { + // Get the list of the files contained in the directory + String[] files = directory.list(); + for (int i = 0; i < files.length; i++) { + // we are only interested in .gwt.xml files + if (!files[i].endsWith(".gwt.xml")) { + continue; + } + + // remove the .gwt.xml extension + String classname = files[i].substring(0, files[i].length() - 8); + String packageName = locationString.substring(locationString + .lastIndexOf("/") + 1); + classname = packageName + "." + classname; + + if (!WidgetSetBuilder.isWidgetset(classname)) { + // Only return widgetsets and not GWT modules to avoid + // comparing modules and widgetsets + continue; + } + + if (!widgetsets.containsKey(classname)) { + String packagePath = packageName.replaceAll("\\.", "/"); + String basePath = location.getFile().replaceAll( + "/" + packagePath + "$", ""); + try { + URL url = new URL(location.getProtocol(), + location.getHost(), location.getPort(), + basePath); + widgetsets.put(classname, url); + } catch (MalformedURLException e) { + // should never happen as based on an existing URL, + // only changing end of file name/path part + getLogger().log(Level.SEVERE, + "Error locating the widgetset " + classname, e); + } + } + } + } else { + + try { + // check files in jar file, entries will list all directories + // and files in jar + + URLConnection openConnection = location.openConnection(); + if (openConnection instanceof JarURLConnection) { + JarURLConnection conn = (JarURLConnection) openConnection; + + JarFile jarFile = conn.getJarFile(); + + Manifest manifest = jarFile.getManifest(); + if (manifest == null) { + // No manifest so this is not a Vaadin Add-on + return; + } + String value = manifest.getMainAttributes().getValue( + "Vaadin-Widgetsets"); + if (value != null) { + String[] widgetsetNames = value.split(","); + for (int i = 0; i < widgetsetNames.length; i++) { + String widgetsetname = widgetsetNames[i].trim() + .intern(); + if (!widgetsetname.equals("")) { + widgetsets.put(widgetsetname, location); + } + } + } + } + } catch (IOException e) { + getLogger().log(Level.WARNING, "Error parsing jar file", e); + } + + } + } + + /** + * Splits the current class path into entries, and filters them accepting + * directories, Vaadin add-on JARs with widgetsets and Vaadin JARs. + * + * Some other non-JAR entries may also be included in the result. + * + * @return filtered list of class path entries + */ + private final static List<String> getRawClasspathEntries() { + // try to keep the order of the classpath + List<String> locations = new ArrayList<String>(); + + String pathSep = System.getProperty("path.separator"); + String classpath = System.getProperty("java.class.path"); + + if (classpath.startsWith("\"")) { + classpath = classpath.substring(1); + } + if (classpath.endsWith("\"")) { + classpath = classpath.substring(0, classpath.length() - 1); + } + + getLogger().fine("Classpath: " + classpath); + + String[] split = classpath.split(pathSep); + for (int i = 0; i < split.length; i++) { + String classpathEntry = split[i]; + if (acceptClassPathEntry(classpathEntry)) { + locations.add(classpathEntry); + } + } + + return locations; + } + + /** + * Determine every URL location defined by the current classpath, and it's + * associated package name. + * + * See {@link #classpathLocations} for information on output format. + * + * @param rawClasspathEntries + * raw class path entries as split from the Java class path + * string + * @return map of classpath locations, see {@link #classpathLocations} + */ + private final static Map<String, URL> getClasspathLocations( + List<String> rawClasspathEntries) { + long start = System.currentTimeMillis(); + // try to keep the order of the classpath + Map<String, URL> locations = new LinkedHashMap<String, URL>(); + for (String classpathEntry : rawClasspathEntries) { + File file = new File(classpathEntry); + include(null, file, locations); + } + long end = System.currentTimeMillis(); + Logger logger = getLogger(); + if (logger.isLoggable(Level.FINE)) { + logger.fine("getClassPathLocations took " + (end - start) + "ms"); + } + return locations; + } + + /** + * Checks a class path entry to see whether it can contain widgets and + * widgetsets. + * + * All directories are automatically accepted. JARs are accepted if they + * have the "Vaadin-Widgetsets" attribute in their manifest or the JAR file + * name contains "vaadin-" or ".vaadin.". + * + * Also other non-JAR entries may be accepted, the caller should be prepared + * to handle them. + * + * @param classpathEntry + * class path entry string as given in the Java class path + * @return true if the entry should be considered when looking for widgets + * or widgetsets + */ + private static boolean acceptClassPathEntry(String classpathEntry) { + if (!classpathEntry.endsWith(".jar")) { + // accept all non jars (practically directories) + return true; + } else { + // accepts jars that comply with vaadin-component packaging + // convention (.vaadin. or vaadin- as distribution packages), + if (classpathEntry.contains("vaadin-") + || classpathEntry.contains(".vaadin.")) { + return true; + } else { + URL url; + try { + url = new URL("file:" + + new File(classpathEntry).getCanonicalPath()); + url = new URL("jar:" + url.toExternalForm() + "!/"); + JarURLConnection conn = (JarURLConnection) url + .openConnection(); + getLogger().fine(url.toString()); + JarFile jarFile = conn.getJarFile(); + Manifest manifest = jarFile.getManifest(); + if (manifest != null) { + Attributes mainAttributes = manifest + .getMainAttributes(); + if (mainAttributes.getValue("Vaadin-Widgetsets") != null) { + return true; + } + } + } catch (MalformedURLException e) { + getLogger().log(Level.FINEST, "Failed to inspect JAR file", + e); + } catch (IOException e) { + getLogger().log(Level.FINEST, "Failed to inspect JAR file", + e); + } + + return false; + } + } + } + + /** + * Recursively add subdirectories and jar files to locations - see + * {@link #classpathLocations}. + * + * @param name + * @param file + * @param locations + */ + private final static void include(String name, File file, + Map<String, URL> locations) { + if (!file.exists()) { + return; + } + if (!file.isDirectory()) { + // could be a JAR file + includeJar(file, locations); + return; + } + + if (file.isHidden() || file.getPath().contains(File.separator + ".")) { + return; + } + + if (name == null) { + name = ""; + } else { + name += "."; + } + + // add all directories recursively + File[] dirs = file.listFiles(DIRECTORIES_ONLY); + for (int i = 0; i < dirs.length; i++) { + try { + // add the present directory + if (!dirs[i].isHidden() + && !dirs[i].getPath().contains(File.separator + ".")) { + String key = dirs[i].getCanonicalPath() + "/" + name + + dirs[i].getName(); + locations.put(key, + new URL("file://" + dirs[i].getCanonicalPath())); + } + } catch (Exception ioe) { + return; + } + include(name + dirs[i].getName(), dirs[i], locations); + } + } + + /** + * Add a jar file to locations - see {@link #classpathLocations}. + * + * @param name + * @param locations + */ + private static void includeJar(File file, Map<String, URL> locations) { + try { + URL url = new URL("file:" + file.getCanonicalPath()); + url = new URL("jar:" + url.toExternalForm() + "!/"); + JarURLConnection conn = (JarURLConnection) url.openConnection(); + JarFile jarFile = conn.getJarFile(); + if (jarFile != null) { + // the key does not matter here as long as it is unique + locations.put(url.toString(), url); + } + } catch (Exception e) { + // e.printStackTrace(); + return; + } + + } + + /** + * Find and return the default source directory where to create new + * widgetsets. + * + * Return the first directory (not a JAR file etc.) on the classpath by + * default. + * + * TODO this could be done better... + * + * @return URL + */ + public static URL getDefaultSourceDirectory() { + + final Logger logger = getLogger(); + + if (logger.isLoggable(Level.FINE)) { + logger.fine("classpathLocations values:"); + ArrayList<String> locations = new ArrayList<String>( + classpathLocations.keySet()); + for (String location : locations) { + logger.fine(String.valueOf(classpathLocations.get(location))); + } + } + + Iterator<String> it = rawClasspathEntries.iterator(); + while (it.hasNext()) { + String entry = it.next(); + + File directory = new File(entry); + if (directory.exists() && !directory.isHidden() + && directory.isDirectory()) { + try { + return new URL("file://" + directory.getCanonicalPath()); + } catch (MalformedURLException e) { + logger.log(Level.FINEST, "Ignoring exception", e); + // ignore: continue to the next classpath entry + } catch (IOException e) { + logger.log(Level.FINEST, "Ignoring exception", e); + // ignore: continue to the next classpath entry + } + } + } + return null; + } + + /** + * Test method for helper tool + */ + public static void main(String[] args) { + getLogger().info("Searching available widgetsets..."); + + Map<String, URL> availableWidgetSets = ClassPathExplorer + .getAvailableWidgetSets(); + for (String string : availableWidgetSets.keySet()) { + + getLogger().info(string + " in " + availableWidgetSets.get(string)); + } + } + + private static final Logger getLogger() { + return Logger.getLogger(ClassPathExplorer.class.getName()); + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java b/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java new file mode 100644 index 0000000000..aa220225c9 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java @@ -0,0 +1,684 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.server.widgetsetutils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JParameterizedType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.NotFoundException; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.metadata.ConnectorBundleLoader; +import com.vaadin.client.metadata.InvokationHandler; +import com.vaadin.client.metadata.ProxyHandler; +import com.vaadin.client.metadata.TypeData; +import com.vaadin.client.metadata.TypeDataBundle; +import com.vaadin.client.metadata.TypeDataStore; +import com.vaadin.client.ui.UnknownComponentConnector; +import com.vaadin.server.widgetsetutils.metadata.ClientRpcVisitor; +import com.vaadin.server.widgetsetutils.metadata.ConnectorBundle; +import com.vaadin.server.widgetsetutils.metadata.ConnectorInitVisitor; +import com.vaadin.server.widgetsetutils.metadata.GeneratedSerializer; +import com.vaadin.server.widgetsetutils.metadata.Property; +import com.vaadin.server.widgetsetutils.metadata.ServerRpcVisitor; +import com.vaadin.server.widgetsetutils.metadata.StateInitVisitor; +import com.vaadin.server.widgetsetutils.metadata.TypeVisitor; +import com.vaadin.server.widgetsetutils.metadata.WidgetInitVisitor; +import com.vaadin.shared.annotations.Delayed; +import com.vaadin.shared.annotations.DelegateToWidget; +import com.vaadin.shared.communication.ClientRpc; +import com.vaadin.shared.communication.ServerRpc; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; + +public class ConnectorBundleLoaderFactory extends Generator { + + @Override + public String generate(TreeLogger logger, GeneratorContext context, + String typeName) throws UnableToCompleteException { + TypeOracle typeOracle = context.getTypeOracle(); + + try { + JClassType classType = typeOracle.getType(typeName); + String packageName = classType.getPackage().getName(); + String className = classType.getSimpleSourceName() + "Impl"; + + generateClass(logger, context, packageName, className, typeName); + + return packageName + "." + className; + } catch (UnableToCompleteException e) { + // Just rethrow + throw e; + } catch (Exception e) { + logger.log(Type.ERROR, getClass() + " failed", e); + throw new UnableToCompleteException(); + } + + } + + private void generateClass(TreeLogger logger, GeneratorContext context, + String packageName, String className, String requestedType) + throws Exception { + PrintWriter printWriter = context.tryCreate(logger, packageName, + className); + if (printWriter == null) { + return; + } + + List<ConnectorBundle> bundles = buildBundles(logger, + context.getTypeOracle()); + + ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory( + packageName, className); + composer.setSuperclass(requestedType); + + SourceWriter w = composer.createSourceWriter(context, printWriter); + + w.println("public void init() {"); + w.indent(); + + for (ConnectorBundle bundle : bundles) { + String name = bundle.getName(); + boolean isEager = name + .equals(ConnectorBundleLoader.EAGER_BUNDLE_NAME); + + w.print("addAsyncBlockLoader(new AsyncBundleLoader(\""); + w.print(escape(name)); + w.print("\", "); + + w.print("new String[] {"); + for (Entry<JClassType, Set<String>> entry : bundle.getIdentifiers() + .entrySet()) { + Set<String> identifiers = entry.getValue(); + for (String id : identifiers) { + w.print("\""); + w.print(escape(id)); + w.print("\","); + } + } + w.println("}) {"); + w.indent(); + + w.print("protected void load(final "); + w.print(TypeDataStore.class.getName()); + w.println(" store) {"); + w.indent(); + + if (!isEager) { + w.print(GWT.class.getName()); + w.print(".runAsync("); + } + + w.print("new "); + w.print(TypeDataBundle.class.getName()); + w.println("(getName()) {"); + w.indent(); + + w.println("public void load() {"); + w.indent(); + + printBundleData(logger, w, bundle); + + // Close load method + w.outdent(); + w.println("}"); + + // Close new TypeDataBundle() {} + w.outdent(); + w.print("}"); + + if (isEager) { + w.println(".onSuccess();"); + } else { + w.println(");"); + } + + // Close load method + w.outdent(); + w.println("}"); + + // Close add(new ... + w.outdent(); + w.println("});"); + } + + w.outdent(); + w.println("}"); + + w.commit(logger); + } + + private void printBundleData(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) throws UnableToCompleteException { + writeIdentifiers(w, bundle); + writeGwtConstructors(w, bundle); + writeReturnTypes(w, bundle); + writeInvokers(w, bundle); + writeParamTypes(w, bundle); + writeProxys(w, bundle); + wirteDelayedInfo(w, bundle); + writeProperites(logger, w, bundle); + writePropertyTypes(w, bundle); + writeSetters(logger, w, bundle); + writeGetters(logger, w, bundle); + writeSerializers(logger, w, bundle); + writeDelegateToWidget(logger, w, bundle); + } + + private void writeDelegateToWidget(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) { + Set<Property> needsDelegateToWidget = bundle.getNeedsDelegateToWidget(); + for (Property property : needsDelegateToWidget) { + w.println("store.setDelegateToWidget(%s, \"%s\", \"%s\");", + getClassLiteralString(property.getBeanType()), + property.getName(), + property.getAnnotation(DelegateToWidget.class).value()); + } + } + + private void writeSerializers(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) throws UnableToCompleteException { + Map<JType, GeneratedSerializer> serializers = bundle.getSerializers(); + for (Entry<JType, GeneratedSerializer> entry : serializers.entrySet()) { + JType type = entry.getKey(); + GeneratedSerializer serializer = entry.getValue(); + + w.print("store.setSerializerFactory("); + writeClassLiteral(w, type); + w.print(", "); + w.println("new Invoker() {"); + w.indent(); + + w.println("public Object invoke(Object target, Object[] params) {"); + w.indent(); + + serializer.writeSerializerInstantiator(logger, w); + + w.outdent(); + w.println("}"); + + w.outdent(); + w.print("}"); + w.println(");"); + } + } + + private void writeGetters(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) { + Set<Property> properties = bundle.getNeedsSetter(); + for (Property property : properties) { + w.print("store.setGetter("); + writeClassLiteral(w, property.getBeanType()); + w.print(", \""); + w.print(escape(property.getName())); + w.println("\", new Invoker() {"); + w.indent(); + + w.println("public Object invoke(Object bean, Object[] params) {"); + w.indent(); + + property.writeGetterBody(logger, w, "bean"); + w.println(); + + w.outdent(); + w.println("}"); + + w.outdent(); + w.println("});"); + } + } + + private void writeSetters(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) { + Set<Property> properties = bundle.getNeedsSetter(); + for (Property property : properties) { + w.print("store.setSetter("); + writeClassLiteral(w, property.getBeanType()); + w.print(", \""); + w.print(escape(property.getName())); + w.println("\", new Invoker() {"); + w.indent(); + + w.println("public Object invoke(Object bean, Object[] params) {"); + w.indent(); + + property.writeSetterBody(logger, w, "bean", "params[0]"); + + w.println("return null;"); + + w.outdent(); + w.println("}"); + + w.outdent(); + w.println("});"); + } + } + + private void writePropertyTypes(SourceWriter w, ConnectorBundle bundle) { + Set<Property> properties = bundle.getNeedsType(); + for (Property property : properties) { + w.print("store.setPropertyType("); + writeClassLiteral(w, property.getBeanType()); + w.print(", \""); + w.print(escape(property.getName())); + w.print("\", "); + writeTypeCreator(w, property.getPropertyType()); + w.println(");"); + } + } + + private void writeProperites(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) throws UnableToCompleteException { + Set<JClassType> needsPropertyListing = bundle.getNeedsPropertyListing(); + for (JClassType type : needsPropertyListing) { + w.print("store.setProperties("); + writeClassLiteral(w, type); + w.print(", new String[] {"); + + Set<String> usedPropertyNames = new HashSet<String>(); + Collection<Property> properties = bundle.getProperties(type); + for (Property property : properties) { + String name = property.getName(); + if (!usedPropertyNames.add(name)) { + logger.log( + Type.ERROR, + type.getQualifiedSourceName() + + " has multiple properties with the name " + + name + + ". This can happen if there are multiple setters with identical names exect casing."); + throw new UnableToCompleteException(); + } + + w.print("\""); + w.print(name); + w.print("\", "); + } + + w.println("});"); + } + } + + private void wirteDelayedInfo(SourceWriter w, ConnectorBundle bundle) { + Map<JClassType, Set<JMethod>> needsDelayedInfo = bundle + .getNeedsDelayedInfo(); + Set<Entry<JClassType, Set<JMethod>>> entrySet = needsDelayedInfo + .entrySet(); + for (Entry<JClassType, Set<JMethod>> entry : entrySet) { + JClassType type = entry.getKey(); + Set<JMethod> methods = entry.getValue(); + for (JMethod method : methods) { + Delayed annotation = method.getAnnotation(Delayed.class); + if (annotation != null) { + w.print("store.setDelayed("); + writeClassLiteral(w, type); + w.print(", \""); + w.print(escape(method.getName())); + w.println("\");"); + + if (annotation.lastonly()) { + w.print("store.setLastonly("); + writeClassLiteral(w, type); + w.print(", \""); + w.print(escape(method.getName())); + w.println("\");"); + } + } + } + } + } + + private void writeProxys(SourceWriter w, ConnectorBundle bundle) { + Set<JClassType> needsProxySupport = bundle.getNeedsProxySupport(); + for (JClassType type : needsProxySupport) { + w.print("store.setProxyHandler("); + writeClassLiteral(w, type); + w.print(", new "); + w.print(ProxyHandler.class.getCanonicalName()); + w.println("() {"); + w.indent(); + + w.println("public Object createProxy(final " + + InvokationHandler.class.getName() + " handler) {"); + w.indent(); + + w.print("return new "); + w.print(type.getQualifiedSourceName()); + w.println("() {"); + w.indent(); + + JMethod[] methods = type.getOverridableMethods(); + for (JMethod method : methods) { + if (method.isAbstract()) { + w.print("public "); + w.print(method.getReturnType().getQualifiedSourceName()); + w.print(" "); + w.print(method.getName()); + w.print("("); + + JType[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (i != 0) { + w.print(", "); + } + w.print(types[i].getQualifiedSourceName()); + w.print(" p"); + w.print(Integer.toString(i)); + } + + w.println(") {"); + w.indent(); + + if (!method.getReturnType().getQualifiedSourceName() + .equals("void")) { + w.print("return "); + } + + w.print("handler.invoke(this, "); + w.print(TypeData.class.getCanonicalName()); + w.print(".getType("); + writeClassLiteral(w, type); + w.print(").getMethod(\""); + w.print(escape(method.getName())); + w.print("\"), new Object [] {"); + for (int i = 0; i < types.length; i++) { + w.print("p" + i + ", "); + } + w.println("});"); + + w.outdent(); + w.println("}"); + } + } + + w.outdent(); + w.println("};"); + + w.outdent(); + w.println("}"); + + w.outdent(); + w.println("});"); + + } + } + + private void writeParamTypes(SourceWriter w, ConnectorBundle bundle) { + Map<JClassType, Set<JMethod>> needsParamTypes = bundle + .getNeedsParamTypes(); + for (Entry<JClassType, Set<JMethod>> entry : needsParamTypes.entrySet()) { + JClassType type = entry.getKey(); + + Set<JMethod> methods = entry.getValue(); + for (JMethod method : methods) { + w.print("store.setParamTypes("); + writeClassLiteral(w, type); + w.print(", \""); + w.print(escape(method.getName())); + w.print("\", new Type[] {"); + + for (JType parameter : method.getParameterTypes()) { + ConnectorBundleLoaderFactory.writeTypeCreator(w, parameter); + w.print(", "); + } + + w.println("});"); + + } + } + } + + private void writeInvokers(SourceWriter w, ConnectorBundle bundle) { + Map<JClassType, Set<JMethod>> needsInvoker = bundle.getNeedsInvoker(); + for (Entry<JClassType, Set<JMethod>> entry : needsInvoker.entrySet()) { + JClassType type = entry.getKey(); + + Set<JMethod> methods = entry.getValue(); + for (JMethod method : methods) { + w.print("store.setInvoker("); + writeClassLiteral(w, type); + w.print(", \""); + w.print(escape(method.getName())); + w.println("\", new Invoker() {"); + w.indent(); + + w.println("public Object invoke(Object target, Object[] params) {"); + w.indent(); + + JType returnType = method.getReturnType(); + boolean hasReturnType = !"void".equals(returnType + .getQualifiedSourceName()); + if (hasReturnType) { + w.print("return "); + } + + JType[] parameterTypes = method.getParameterTypes(); + + w.print("((" + type.getQualifiedSourceName() + ") target)." + + method.getName() + "("); + for (int i = 0; i < parameterTypes.length; i++) { + JType parameterType = parameterTypes[i]; + if (i != 0) { + w.print(", "); + } + String parameterTypeName = getBoxedTypeName(parameterType); + w.print("(" + parameterTypeName + ") params[" + i + "]"); + } + w.println(");"); + + if (!hasReturnType) { + w.println("return null;"); + } + + w.outdent(); + w.println("}"); + + w.outdent(); + w.println("});"); + + } + } + } + + private void writeReturnTypes(SourceWriter w, ConnectorBundle bundle) { + Map<JClassType, Set<JMethod>> methodReturnTypes = bundle + .getMethodReturnTypes(); + for (Entry<JClassType, Set<JMethod>> entry : methodReturnTypes + .entrySet()) { + JClassType type = entry.getKey(); + + Set<JMethod> methods = entry.getValue(); + for (JMethod method : methods) { + // setReturnType(Class<?> type, String methodName, Type + // returnType) + w.print("store.setReturnType("); + writeClassLiteral(w, type); + w.print(", \""); + w.print(escape(method.getName())); + w.print("\", "); + writeTypeCreator(w, method.getReturnType()); + w.println(");"); + } + } + } + + private void writeGwtConstructors(SourceWriter w, ConnectorBundle bundle) { + Set<JClassType> constructors = bundle.getGwtConstructors(); + for (JClassType type : constructors) { + w.print("store.setConstructor("); + writeClassLiteral(w, type); + w.println(", new Invoker() {"); + w.indent(); + + w.println("public Object invoke(Object target, Object[] params) {"); + w.indent(); + + w.print("return "); + w.print(GWT.class.getName()); + w.print(".create("); + writeClassLiteral(w, type); + w.println(");"); + + w.outdent(); + w.println("}"); + + w.outdent(); + w.println("});"); + } + } + + public static void writeClassLiteral(SourceWriter w, JType type) { + w.print(getClassLiteralString(type)); + } + + public static String getClassLiteralString(JType type) { + return type.getQualifiedSourceName() + ".class"; + } + + private void writeIdentifiers(SourceWriter w, ConnectorBundle bundle) { + Map<JClassType, Set<String>> identifiers = bundle.getIdentifiers(); + for (Entry<JClassType, Set<String>> entry : identifiers.entrySet()) { + Set<String> ids = entry.getValue(); + JClassType type = entry.getKey(); + for (String id : ids) { + w.print("store.setClass(\""); + w.print(escape(id)); + w.print("\", "); + writeClassLiteral(w, type); + w.println(");"); + } + } + } + + private List<ConnectorBundle> buildBundles(TreeLogger logger, + TypeOracle typeOracle) throws NotFoundException, + UnableToCompleteException { + + Map<LoadStyle, Collection<JClassType>> connectorsByLoadStyle = new HashMap<LoadStyle, Collection<JClassType>>(); + for (LoadStyle loadStyle : LoadStyle.values()) { + connectorsByLoadStyle.put(loadStyle, new ArrayList<JClassType>()); + } + + JClassType connectorType = typeOracle.getType(ServerConnector.class + .getName()); + JClassType[] subtypes = connectorType.getSubtypes(); + for (JClassType connectorSubtype : subtypes) { + if (!connectorSubtype.isAnnotationPresent(Connect.class)) { + continue; + } + LoadStyle loadStyle = getLoadStyle(connectorSubtype); + if (loadStyle != null) { + connectorsByLoadStyle.get(loadStyle).add(connectorSubtype); + } + } + + List<ConnectorBundle> bundles = new ArrayList<ConnectorBundle>(); + + Collection<TypeVisitor> visitors = getVisitors(typeOracle); + + ConnectorBundle eagerBundle = new ConnectorBundle( + ConnectorBundleLoader.EAGER_BUNDLE_NAME, visitors, typeOracle); + TreeLogger eagerLogger = logger.branch(Type.TRACE, + "Populating eager bundle"); + + // Eager connectors and all RPC interfaces are loaded by default + eagerBundle.processTypes(eagerLogger, + connectorsByLoadStyle.get(LoadStyle.EAGER)); + eagerBundle.processType(eagerLogger, typeOracle + .findType(UnknownComponentConnector.class.getCanonicalName())); + eagerBundle.processSubTypes(eagerLogger, + typeOracle.getType(ClientRpc.class.getName())); + eagerBundle.processSubTypes(eagerLogger, + typeOracle.getType(ServerRpc.class.getName())); + + bundles.add(eagerBundle); + + ConnectorBundle deferredBundle = new ConnectorBundle( + ConnectorBundleLoader.DEFERRED_BUNDLE_NAME, eagerBundle); + TreeLogger deferredLogger = logger.branch(Type.TRACE, + "Populating deferred bundle"); + deferredBundle.processTypes(deferredLogger, + connectorsByLoadStyle.get(LoadStyle.DEFERRED)); + + bundles.add(deferredBundle); + + Collection<JClassType> lazy = connectorsByLoadStyle.get(LoadStyle.LAZY); + for (JClassType type : lazy) { + ConnectorBundle bundle = new ConnectorBundle(type.getName(), + eagerBundle); + TreeLogger subLogger = logger.branch(Type.TRACE, "Populating " + + type.getName() + " bundle"); + bundle.processType(subLogger, type); + + bundles.add(bundle); + } + + return bundles; + } + + private Collection<TypeVisitor> getVisitors(TypeOracle oracle) + throws NotFoundException { + List<TypeVisitor> visitors = Arrays.<TypeVisitor> asList( + new ConnectorInitVisitor(), new StateInitVisitor(), + new WidgetInitVisitor(), new ClientRpcVisitor(), + new ServerRpcVisitor()); + for (TypeVisitor typeVisitor : visitors) { + typeVisitor.init(oracle); + } + return visitors; + } + + protected LoadStyle getLoadStyle(JClassType connectorType) { + Connect annotation = connectorType.getAnnotation(Connect.class); + return annotation.loadStyle(); + } + + public static String getBoxedTypeName(JType type) { + if (type.isPrimitive() != null) { + // Used boxed types for primitives + return type.isPrimitive().getQualifiedBoxedSourceName(); + } else { + return type.getErasedType().getQualifiedSourceName(); + } + } + + public static void writeTypeCreator(SourceWriter sourceWriter, JType type) { + String typeName = ConnectorBundleLoaderFactory.getBoxedTypeName(type); + JParameterizedType parameterized = type.isParameterized(); + if (parameterized != null) { + sourceWriter.print("new Type(\"" + typeName + "\", "); + sourceWriter.print("new Type[] {"); + JClassType[] typeArgs = parameterized.getTypeArgs(); + for (JClassType jClassType : typeArgs) { + writeTypeCreator(sourceWriter, jClassType); + sourceWriter.print(", "); + } + sourceWriter.print("}"); + } else { + sourceWriter.print("new Type(" + typeName + ".class"); + } + sourceWriter.print(")"); + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/CustomWidgetMapGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/CustomWidgetMapGenerator.java new file mode 100644 index 0000000000..250bfbb4a1 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/CustomWidgetMapGenerator.java @@ -0,0 +1,96 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils; + +import java.util.Collection; +import java.util.HashSet; + +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ServerConnector; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; + +/** + * An abstract helper class that can be used to easily build a widgetset with + * customized load styles for each components. In three abstract methods one can + * override the default values given in {@link Connect} annotations. + * + * @see WidgetMapGenerator + * + */ +public abstract class CustomWidgetMapGenerator extends WidgetMapGenerator { + + private Collection<Class<? extends ComponentConnector>> eagerPaintables = new HashSet<Class<? extends ComponentConnector>>(); + private Collection<Class<? extends ComponentConnector>> lazyPaintables = new HashSet<Class<? extends ComponentConnector>>(); + private Collection<Class<? extends ComponentConnector>> deferredPaintables = new HashSet<Class<? extends ComponentConnector>>(); + + @Override + protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) { + if (eagerPaintables == null) { + init(); + } + if (eagerPaintables.contains(connector)) { + return LoadStyle.EAGER; + } + if (lazyPaintables.contains(connector)) { + return LoadStyle.LAZY; + } + if (deferredPaintables.contains(connector)) { + return LoadStyle.DEFERRED; + } + return super.getLoadStyle(connector); + } + + private void init() { + Class<? extends ComponentConnector>[] eagerComponents = getEagerComponents(); + if (eagerComponents != null) { + for (Class<? extends ComponentConnector> class1 : eagerComponents) { + eagerPaintables.add(class1); + } + } + Class<? extends ComponentConnector>[] lazyComponents = getEagerComponents(); + if (lazyComponents != null) { + for (Class<? extends ComponentConnector> class1 : lazyComponents) { + lazyPaintables.add(class1); + } + } + Class<? extends ComponentConnector>[] deferredComponents = getEagerComponents(); + if (deferredComponents != null) { + for (Class<? extends ComponentConnector> class1 : deferredComponents) { + deferredPaintables.add(class1); + } + } + } + + /** + * @return an array of components whose load style should be overridden to + * {@link LoadStyle#EAGER} + */ + protected abstract Class<? extends ComponentConnector>[] getEagerComponents(); + + /** + * @return an array of components whose load style should be overridden to + * {@link LoadStyle#LAZY} + */ + protected abstract Class<? extends ComponentConnector>[] getLazyComponents(); + + /** + * @return an array of components whose load style should be overridden to + * {@link LoadStyle#DEFERRED} + */ + protected abstract Class<? extends ComponentConnector>[] getDeferredComponents(); + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/EagerWidgetMapGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/EagerWidgetMapGenerator.java new file mode 100644 index 0000000000..568dc939e7 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/EagerWidgetMapGenerator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils; + +import com.vaadin.client.ServerConnector; +import com.vaadin.shared.ui.Connect.LoadStyle; + +/** + * WidgetMap generator that builds a widgetset that packs all included widgets + * into a single JavaScript file loaded at application initialization. Initially + * loaded data will be relatively large, but minimal amount of server requests + * will be done. + * <p> + * This is the default generator in version 6.4 and produces similar type of + * widgetset as in previous versions of Vaadin. To activate "code splitting", + * use the {@link WidgetMapGenerator} instead, that loads most components + * deferred. + * + * @see WidgetMapGenerator + * + */ +public class EagerWidgetMapGenerator extends WidgetMapGenerator { + + @Override + protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) { + return LoadStyle.EAGER; + } +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/LazyWidgetMapGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/LazyWidgetMapGenerator.java new file mode 100644 index 0000000000..9e46b6d9dd --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/LazyWidgetMapGenerator.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils; + +import com.vaadin.client.ServerConnector; +import com.vaadin.shared.ui.Connect.LoadStyle; + +/** + * WidgetMap generator that builds a widgetset that optimizes the transferred + * data. Widgets are loaded only when used if the widgetset is built with this + * generator. + * + * @see WidgetMapGenerator + * + */ +public class LazyWidgetMapGenerator extends WidgetMapGenerator { + @Override + protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) { + return LoadStyle.LAZY; + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/WidgetMapGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/WidgetMapGenerator.java new file mode 100644 index 0000000000..b90791d61b --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/WidgetMapGenerator.java @@ -0,0 +1,434 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.TreeSet; + +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.ui.UnknownComponentConnector; +import com.vaadin.client.ui.UI.UIConnector; +import com.vaadin.server.ClientConnector; +import com.vaadin.shared.Connector; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; + +/** + * WidgetMapGenerator's are GWT generator to build WidgetMapImpl dynamically + * based on {@link Connect} annotations available in workspace. By modifying the + * generator it is possible to do some fine tuning for the generated widgetset + * (aka client side engine). The components to be included in the client side + * engine can modified be overriding {@link #getUsedConnectors()}. + * <p> + * The generator also decides how the client side component implementations are + * loaded to the browser. The default generator is + * {@link EagerWidgetMapGenerator} that builds a monolithic client side engine + * that loads all widget implementation on application initialization. This has + * been the only option until Vaadin 6.4. + * <p> + * This generator uses the loadStyle hints from the {@link Connect} annotations. + * Depending on the {@link LoadStyle} used, the widget may be included in the + * initially loaded JavaScript, loaded when the application has started and + * there is no communication to server or lazy loaded when the implementation is + * absolutely needed. + * <p> + * The GWT module description file of the widgetset ( + * <code>...Widgetset.gwt.xml</code>) can be used to define the + * WidgetMapGenarator. An example that defines this generator to be used: + * + * <pre> + * <code> + * <generate-with + * class="com.vaadin.server.widgetsetutils.MyWidgetMapGenerator"> + * <when-type-is class="com.vaadin.client.WidgetMap" /> + * </generate-with> + * + * </code> + * </pre> + * + * <p> + * Vaadin package also includes {@link LazyWidgetMapGenerator}, which is a good + * option if the transferred data should be minimized, and + * {@link CustomWidgetMapGenerator} for easy overriding of loading strategies. + * + */ +public class WidgetMapGenerator extends Generator { + + private static String serverConnectorClassName = ServerConnector.class + .getName(); + + private String packageName; + private String className; + + @Override + public String generate(TreeLogger logger, GeneratorContext context, + String typeName) throws UnableToCompleteException { + + try { + TypeOracle typeOracle = context.getTypeOracle(); + + // get classType and save instance variables + JClassType classType = typeOracle.getType(typeName); + packageName = classType.getPackage().getName(); + className = classType.getSimpleSourceName() + "Impl"; + // Generate class source code + generateClass(logger, context); + } catch (Exception e) { + logger.log(TreeLogger.ERROR, "WidgetMap creation failed", e); + } + // return the fully qualifed name of the class generated + return packageName + "." + className; + } + + /** + * Generate source code for WidgetMapImpl + * + * @param logger + * Logger object + * @param context + * Generator context + * @throws UnableToCompleteException + */ + private void generateClass(TreeLogger logger, GeneratorContext context) + throws UnableToCompleteException { + // get print writer that receives the source code + PrintWriter printWriter = null; + printWriter = context.tryCreate(logger, packageName, className); + // print writer if null, source code has ALREADY been generated, + // return (WidgetMap is equal to all permutations atm) + if (printWriter == null) { + return; + } + logger.log(Type.INFO, + "Detecting Vaadin connectors in classpath to generate WidgetMapImpl.java ..."); + Date date = new Date(); + + // init composer, set class properties, create source writer + ClassSourceFileComposerFactory composer = null; + composer = new ClassSourceFileComposerFactory(packageName, className); + composer.addImport("com.google.gwt.core.client.GWT"); + composer.addImport("java.util.HashMap"); + composer.addImport("com.google.gwt.core.client.RunAsyncCallback"); + composer.setSuperclass("com.vaadin.client.WidgetMap"); + SourceWriter sourceWriter = composer.createSourceWriter(context, + printWriter); + + Collection<Class<? extends ServerConnector>> connectors = getUsedConnectors(context + .getTypeOracle()); + + validateConnectors(logger, connectors); + logConnectors(logger, context, connectors); + + // generator constructor source code + generateImplementationDetector(logger, sourceWriter, connectors); + generateInstantiatorMethod(sourceWriter, connectors); + // close generated class + sourceWriter.outdent(); + sourceWriter.println("}"); + // commit generated class + context.commit(logger, printWriter); + logger.log(Type.INFO, + "Done. (" + (new Date().getTime() - date.getTime()) / 1000 + + "seconds)"); + + } + + private void validateConnectors(TreeLogger logger, + Collection<Class<? extends ServerConnector>> connectors) { + + Iterator<Class<? extends ServerConnector>> iter = connectors.iterator(); + while (iter.hasNext()) { + Class<? extends ServerConnector> connectorClass = iter.next(); + Connect annotation = connectorClass.getAnnotation(Connect.class); + if (!ClientConnector.class.isAssignableFrom(annotation.value())) { + logger.log( + Type.WARN, + "Connector class " + + annotation.value().getName() + + " defined in @Connect annotation is not a subclass of " + + ClientConnector.class.getName() + + ". The component connector " + + connectorClass.getName() + + " will not be included in the widgetset."); + iter.remove(); + } + } + + } + + private void logConnectors(TreeLogger logger, GeneratorContext context, + Collection<Class<? extends ServerConnector>> connectors) { + logger.log(Type.INFO, + "Widget set will contain implementations for following component connectors: "); + + TreeSet<String> classNames = new TreeSet<String>(); + HashMap<String, String> loadStyle = new HashMap<String, String>(); + for (Class<? extends ServerConnector> connectorClass : connectors) { + String className = connectorClass.getCanonicalName(); + classNames.add(className); + if (getLoadStyle(connectorClass) == LoadStyle.DEFERRED) { + loadStyle.put(className, "DEFERRED"); + } else if (getLoadStyle(connectorClass) == LoadStyle.LAZY) { + loadStyle.put(className, "LAZY"); + } + + } + for (String className : classNames) { + String msg = className; + if (loadStyle.containsKey(className)) { + msg += " (load style: " + loadStyle.get(className) + ")"; + } + logger.log(Type.INFO, "\t" + msg); + } + } + + /** + * This method is protected to allow creation of optimized widgetsets. The + * Widgetset will contain only implementation returned by this function. If + * one knows which widgets are needed for the application, returning only + * them here will significantly optimize the size of the produced JS. + * + * @return a collections of Vaadin components that will be added to + * widgetset + */ + @SuppressWarnings("unchecked") + private Collection<Class<? extends ServerConnector>> getUsedConnectors( + TypeOracle typeOracle) { + JClassType connectorType = typeOracle.findType(Connector.class + .getName()); + Collection<Class<? extends ServerConnector>> connectors = new HashSet<Class<? extends ServerConnector>>(); + for (JClassType jClassType : connectorType.getSubtypes()) { + Connect annotation = jClassType.getAnnotation(Connect.class); + if (annotation != null) { + try { + Class<? extends ServerConnector> clazz = (Class<? extends ServerConnector>) Class + .forName(jClassType.getQualifiedSourceName()); + connectors.add(clazz); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + } + return connectors; + } + + /** + * Returns true if the widget for given component will be lazy loaded by the + * client. The default implementation reads the information from the + * {@link Connect} annotation. + * <p> + * The method can be overridden to optimize the widget loading mechanism. If + * the Widgetset is wanted to be optimized for a network with a high latency + * or for a one with a very fast throughput, it may be good to return false + * for every component. + * + * @param connector + * @return true iff the widget for given component should be lazy loaded by + * the client side engine + */ + protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) { + Connect annotation = connector.getAnnotation(Connect.class); + return annotation.loadStyle(); + } + + private void generateInstantiatorMethod( + SourceWriter sourceWriter, + Collection<Class<? extends ServerConnector>> connectorsHavingComponentAnnotation) { + + Collection<Class<?>> deferredWidgets = new LinkedList<Class<?>>(); + + // TODO detect if it would be noticably faster to instantiate with a + // lookup with index than with the hashmap + + sourceWriter.println("public void ensureInstantiator(Class<? extends " + + serverConnectorClassName + "> classType) {"); + sourceWriter.println("if(!instmap.containsKey(classType)){"); + boolean first = true; + + ArrayList<Class<? extends ServerConnector>> lazyLoadedConnectors = new ArrayList<Class<? extends ServerConnector>>(); + + HashSet<Class<? extends ServerConnector>> connectorsWithInstantiator = new HashSet<Class<? extends ServerConnector>>(); + + for (Class<? extends ServerConnector> class1 : connectorsHavingComponentAnnotation) { + Class<? extends ServerConnector> clientClass = class1; + if (connectorsWithInstantiator.contains(clientClass)) { + continue; + } + if (clientClass == UIConnector.class) { + // Roots are not instantiated by widgetset + continue; + } + if (!first) { + sourceWriter.print(" else "); + } else { + first = false; + } + sourceWriter.print("if( classType == " + clientClass.getName() + + ".class) {"); + + String instantiator = "new WidgetInstantiator() {\n public " + + serverConnectorClassName + + " get() {\n return GWT.create(" + clientClass.getName() + + ".class );\n}\n}\n"; + + LoadStyle loadStyle = getLoadStyle(class1); + + if (loadStyle != LoadStyle.EAGER) { + sourceWriter + .print("ApplicationConfiguration.startWidgetLoading();\n" + + "GWT.runAsync( \n" + + "new WidgetLoader() { void addInstantiator() {instmap.put(" + + clientClass.getName() + + ".class," + + instantiator + ");}});\n"); + lazyLoadedConnectors.add(class1); + + if (loadStyle == LoadStyle.DEFERRED) { + deferredWidgets.add(class1); + } + + } else { + // widget implementation in initially loaded js script + sourceWriter.print("instmap.put("); + sourceWriter.print(clientClass.getName()); + sourceWriter.print(".class, "); + sourceWriter.print(instantiator); + sourceWriter.print(");"); + } + sourceWriter.print("}"); + connectorsWithInstantiator.add(clientClass); + } + + sourceWriter.println("}"); + + sourceWriter.println("}"); + + sourceWriter.println("public Class<? extends " + + serverConnectorClassName + + ">[] getDeferredLoadedConnectors() {"); + + sourceWriter.println("return new Class[] {"); + first = true; + for (Class<?> class2 : deferredWidgets) { + if (!first) { + sourceWriter.println(","); + } + first = false; + sourceWriter.print(class2.getName() + ".class"); + } + + sourceWriter.println("};"); + sourceWriter.println("}"); + + // in constructor add a "thread" that lazyly loads lazy loaded widgets + // if communication to server idles + + // TODO an array of lazy loaded widgets + + // TODO an index of last ensured widget in array + + sourceWriter.println("public " + serverConnectorClassName + + " instantiate(Class<? extends " + serverConnectorClassName + + "> classType) {"); + sourceWriter.indent(); + sourceWriter.println(serverConnectorClassName + + " p = super.instantiate(classType); if(p!= null) return p;"); + sourceWriter.println("return instmap.get(classType).get();"); + + sourceWriter.outdent(); + sourceWriter.println("}"); + + } + + /** + * + * @param logger + * logger to print messages to + * @param sourceWriter + * Source writer to output source code + * @param paintablesHavingWidgetAnnotation + * @throws UnableToCompleteException + */ + private void generateImplementationDetector( + TreeLogger logger, + SourceWriter sourceWriter, + Collection<Class<? extends ServerConnector>> paintablesHavingWidgetAnnotation) + throws UnableToCompleteException { + sourceWriter + .println("public Class<? extends " + + serverConnectorClassName + + "> " + + "getConnectorClassForServerSideClassName(String fullyQualifiedName) {"); + sourceWriter.indent(); + sourceWriter + .println("fullyQualifiedName = fullyQualifiedName.intern();"); + + // Keep track of encountered mappings to detect conflicts + Map<Class<? extends ClientConnector>, Class<? extends ServerConnector>> mappings = new HashMap<Class<? extends ClientConnector>, Class<? extends ServerConnector>>(); + + for (Class<? extends ServerConnector> connectorClass : paintablesHavingWidgetAnnotation) { + Class<? extends ClientConnector> clientConnectorClass = getClientConnectorClass(connectorClass); + + // Check for conflicts + Class<? extends ServerConnector> prevousMapping = mappings.put( + clientConnectorClass, connectorClass); + if (prevousMapping != null) { + logger.log(Type.ERROR, + "Both " + connectorClass.getName() + " and " + + prevousMapping.getName() + + " have @Connect referring to " + + clientConnectorClass.getName() + "."); + throw new UnableToCompleteException(); + } + + sourceWriter.print("if ( fullyQualifiedName == \""); + sourceWriter.print(clientConnectorClass.getName()); + sourceWriter.print("\" ) { ensureInstantiator(" + + connectorClass.getName() + ".class); return "); + sourceWriter.print(connectorClass.getName()); + sourceWriter.println(".class;}"); + sourceWriter.print("else "); + } + sourceWriter.println("return " + + UnknownComponentConnector.class.getName() + ".class;"); + sourceWriter.outdent(); + sourceWriter.println("}"); + + } + + private static Class<? extends ClientConnector> getClientConnectorClass( + Class<? extends ServerConnector> connectorClass) { + Connect annotation = connectorClass.getAnnotation(Connect.class); + return (Class<? extends ClientConnector>) annotation.value(); + } +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java b/client-compiler/src/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java new file mode 100644 index 0000000000..4137662b35 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java @@ -0,0 +1,213 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.Reader; +import java.net.URL; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Helper class to update widgetsets GWT module configuration file. Can be used + * command line or via IDE tools. + * + * <p> + * If module definition file contains text "WS Compiler: manually edited", tool + * will skip editing file. + * + */ +public class WidgetSetBuilder { + + public static void main(String[] args) throws IOException { + if (args.length == 0) { + printUsage(); + } else { + String widgetsetname = args[0]; + updateWidgetSet(widgetsetname); + + } + } + + public static void updateWidgetSet(final String widgetset) + throws IOException, FileNotFoundException { + boolean changed = false; + + Map<String, URL> availableWidgetSets = ClassPathExplorer + .getAvailableWidgetSets(); + + URL sourceUrl = availableWidgetSets.get(widgetset); + if (sourceUrl == null) { + // find first/default source directory + sourceUrl = ClassPathExplorer.getDefaultSourceDirectory(); + } + + String widgetsetfilename = sourceUrl.getFile() + "/" + + widgetset.replace(".", "/") + ".gwt.xml"; + + File widgetsetFile = new File(widgetsetfilename); + if (!widgetsetFile.exists()) { + // create empty gwt module file + File parent = widgetsetFile.getParentFile(); + if (parent != null && !parent.exists()) { + if (!parent.mkdirs()) { + throw new IOException( + "Could not create directory for the widgetset: " + + parent.getPath()); + } + } + widgetsetFile.createNewFile(); + PrintStream printStream = new PrintStream(new FileOutputStream( + widgetsetFile)); + printStream.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + + "<!DOCTYPE module PUBLIC \"-//Google Inc.//DTD " + + "Google Web Toolkit 1.7.0//EN\" \"http://google" + + "-web-toolkit.googlecode.com/svn/tags/1.7.0/dis" + + "tro-source/core/src/gwt-module.dtd\">\n"); + printStream.print("<module>\n"); + printStream + .print(" <!--\n" + + " Uncomment the following to compile the widgetset for one browser only.\n" + + " This can reduce the GWT compilation time significantly when debugging.\n" + + " The line should be commented out before deployment to production\n" + + " environments.\n\n" + + " Multiple browsers can be specified for GWT 1.7 as a comma separated\n" + + " list. The supported user agents at the moment of writing were:\n" + + " ie6,ie8,gecko,gecko1_8,safari,opera\n\n" + + " The value gecko1_8 is used for Firefox 3 and later and safari is used for\n" + + " webkit based browsers including Google Chrome.\n" + + " -->\n" + + " <!-- <set-property name=\"user.agent\" value=\"gecko1_8\"/> -->\n"); + printStream.print("\n</module>\n"); + printStream.close(); + changed = true; + } + + String content = readFile(widgetsetFile); + if (isEditable(content)) { + String originalContent = content; + + Collection<String> oldInheritedWidgetsets = getCurrentInheritedWidgetsets(content); + + // add widgetsets that do not exist + Iterator<String> i = availableWidgetSets.keySet().iterator(); + while (i.hasNext()) { + String ws = i.next(); + if (ws.equals(widgetset)) { + // do not inherit the module itself + continue; + } + if (!oldInheritedWidgetsets.contains(ws)) { + content = addWidgetSet(ws, content); + } + } + + for (String ws : oldInheritedWidgetsets) { + if (!availableWidgetSets.containsKey(ws)) { + // widgetset not available in classpath + content = removeWidgetSet(ws, content); + } + } + + changed = changed || !content.equals(originalContent); + if (changed) { + commitChanges(widgetsetfilename, content); + } + } else { + System.out + .println("Widgetset is manually edited. Skipping updates."); + } + } + + private static boolean isEditable(String content) { + return !content.contains("WS Compiler: manually edited"); + } + + private static String removeWidgetSet(String ws, String content) { + return content.replaceFirst("<inherits name=\"" + ws + "\"[^/]*/>", ""); + } + + private static void commitChanges(String widgetsetfilename, String content) + throws IOException { + BufferedWriter bufferedWriter = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(widgetsetfilename))); + bufferedWriter.write(content); + bufferedWriter.close(); + } + + private static String addWidgetSet(String ws, String content) { + return content.replace("</module>", "\n <inherits name=\"" + ws + + "\" />" + "\n</module>"); + } + + private static Collection<String> getCurrentInheritedWidgetsets( + String content) { + HashSet<String> hashSet = new HashSet<String>(); + Pattern inheritsPattern = Pattern.compile(" name=\"([^\"]*)\""); + + Matcher matcher = inheritsPattern.matcher(content); + + while (matcher.find()) { + String gwtModule = matcher.group(1); + if (isWidgetset(gwtModule)) { + hashSet.add(gwtModule); + } + } + return hashSet; + } + + static boolean isWidgetset(String gwtModuleName) { + return gwtModuleName.toLowerCase().contains("widgetset"); + } + + private static String readFile(File widgetsetFile) throws IOException { + Reader fi = new FileReader(widgetsetFile); + BufferedReader bufferedReader = new BufferedReader(fi); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + sb.append(line); + sb.append("\n"); + } + fi.close(); + return sb.toString(); + } + + private static void printUsage() { + PrintStream o = System.out; + o.println(WidgetSetBuilder.class.getSimpleName() + " usage:"); + o.println(" 1. Set the same classpath as you will " + + "have for the GWT compiler."); + o.println(" 2. Give the widgetsetname (to be created or updated)" + + " as first parameter"); + o.println(); + o.println("All found vaadin widgetsets will be inherited in given widgetset"); + + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ArraySerializer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ArraySerializer.java new file mode 100644 index 0000000000..4ab5cccb2e --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ArraySerializer.java @@ -0,0 +1,90 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JArrayType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.client.communication.JsonDecoder; +import com.vaadin.client.communication.JsonEncoder; +import com.vaadin.server.widgetsetutils.ConnectorBundleLoaderFactory; + +public class ArraySerializer extends JsonSerializer { + + private final JArrayType arrayType; + + public ArraySerializer(JArrayType arrayType) { + super(arrayType); + this.arrayType = arrayType; + } + + @Override + protected void printDeserializerBody(TreeLogger logger, SourceWriter w, + String type, String jsonValue, String connection) { + JType leafType = arrayType.getLeafType(); + int rank = arrayType.getRank(); + + w.println(JSONArray.class.getName() + " jsonArray = " + jsonValue + + ".isArray();"); + + // Type value = new Type[jsonArray.size()][][]; + w.print(arrayType.getQualifiedSourceName() + " value = new " + + leafType.getQualifiedSourceName() + "[jsonArray.size()]"); + for (int i = 1; i < rank; i++) { + w.print("[]"); + } + w.println(";"); + + w.println("for(int i = 0 ; i < value.length; i++) {"); + w.indent(); + + JType componentType = arrayType.getComponentType(); + + w.print("value[i] = (" + + ConnectorBundleLoaderFactory.getBoxedTypeName(componentType) + + ") " + JsonDecoder.class.getName() + ".decodeValue("); + ConnectorBundleLoaderFactory.writeTypeCreator(w, componentType); + w.print(", jsonArray.get(i), null, " + connection + ")"); + + w.println(";"); + + w.outdent(); + w.println("}"); + + w.println("return value;"); + } + + @Override + protected void printSerializerBody(TreeLogger logger, SourceWriter w, + String value, String applicationConnection) { + w.println(JSONArray.class.getName() + " values = new " + + JSONArray.class.getName() + "();"); + // JPrimitiveType primitive = componentType.isPrimitive(); + w.println("for (int i = 0; i < " + value + ".length; i++) {"); + w.indent(); + w.print("values.set(i, "); + w.print(JsonEncoder.class.getName() + ".encode(" + value + + "[i], false, " + applicationConnection + ")"); + w.println(");"); + w.outdent(); + w.println("}"); + w.println("return values;"); + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java new file mode 100644 index 0000000000..3b78519a0d --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils.metadata; + +import java.util.Set; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JType; + +public class ClientRpcVisitor extends TypeVisitor { + @Override + public void visitClientRpc(TreeLogger logger, JClassType type, + ConnectorBundle bundle) { + Set<? extends JClassType> hierarchy = type + .getFlattenedSupertypeHierarchy(); + for (JClassType subType : hierarchy) { + JMethod[] methods = subType.getMethods(); + for (JMethod method : methods) { + bundle.setNeedsInvoker(type, method); + bundle.setNeedsParamTypes(type, method); + + JType[] parameterTypes = method.getParameterTypes(); + for (JType paramType : parameterTypes) { + bundle.setNeedsSerialize(paramType); + } + } + } + } +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java new file mode 100644 index 0000000000..1d37d12a45 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java @@ -0,0 +1,611 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.server.widgetsetutils.metadata; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JArrayType; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JEnumType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JParameterizedType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.NotFoundException; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.json.client.JSONValue; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.communication.JSONSerializer; +import com.vaadin.client.ui.UnknownComponentConnector; +import com.vaadin.shared.communication.ClientRpc; +import com.vaadin.shared.communication.ServerRpc; +import com.vaadin.shared.ui.Connect; + +public class ConnectorBundle { + private static final String FAIL_IF_NOT_SERIALIZABLE = "vFailIfNotSerializable"; + + private final String name; + private final ConnectorBundle previousBundle; + private final Collection<TypeVisitor> visitors; + private final Map<JType, JClassType> customSerializers; + + private final Set<JType> hasSerializeSupport = new HashSet<JType>(); + private final Set<JType> needsSerializeSupport = new HashSet<JType>(); + private final Map<JType, GeneratedSerializer> serializers = new HashMap<JType, GeneratedSerializer>(); + + private final Set<JClassType> needsPropertyList = new HashSet<JClassType>(); + private final Set<JClassType> needsGwtConstructor = new HashSet<JClassType>(); + private final Set<JClassType> visitedTypes = new HashSet<JClassType>(); + private final Set<JClassType> needsProxySupport = new HashSet<JClassType>(); + + private final Map<JClassType, Set<String>> identifiers = new HashMap<JClassType, Set<String>>(); + private final Map<JClassType, Set<JMethod>> needsReturnType = new HashMap<JClassType, Set<JMethod>>(); + private final Map<JClassType, Set<JMethod>> needsInvoker = new HashMap<JClassType, Set<JMethod>>(); + private final Map<JClassType, Set<JMethod>> needsParamTypes = new HashMap<JClassType, Set<JMethod>>(); + private final Map<JClassType, Set<JMethod>> needsDelayedInfo = new HashMap<JClassType, Set<JMethod>>(); + + private final Set<Property> needsSetter = new HashSet<Property>(); + private final Set<Property> needsType = new HashSet<Property>(); + private final Set<Property> needsGetter = new HashSet<Property>(); + private final Set<Property> needsDelegateToWidget = new HashSet<Property>(); + + private ConnectorBundle(String name, ConnectorBundle previousBundle, + Collection<TypeVisitor> visitors, + Map<JType, JClassType> customSerializers) { + this.name = name; + this.previousBundle = previousBundle; + this.visitors = visitors; + this.customSerializers = customSerializers; + } + + public ConnectorBundle(String name, ConnectorBundle previousBundle) { + this(name, previousBundle, previousBundle.visitors, + previousBundle.customSerializers); + } + + public ConnectorBundle(String name, Collection<TypeVisitor> visitors, + TypeOracle oracle) throws NotFoundException { + this(name, null, visitors, findCustomSerializers(oracle)); + } + + private static Map<JType, JClassType> findCustomSerializers( + TypeOracle oracle) throws NotFoundException { + Map<JType, JClassType> serializers = new HashMap<JType, JClassType>(); + + JClassType serializerInterface = oracle.findType(JSONSerializer.class + .getName()); + JType[] deserializeParamTypes = new JType[] { + oracle.findType(com.vaadin.client.metadata.Type.class + .getName()), + oracle.findType(JSONValue.class.getName()), + oracle.findType(ApplicationConnection.class.getName()) }; + String deserializeMethodName = "deserialize"; + // Just test that the method exists + serializerInterface.getMethod(deserializeMethodName, + deserializeParamTypes); + + for (JClassType serializer : serializerInterface.getSubtypes()) { + JMethod deserializeMethod = serializer.findMethod( + deserializeMethodName, deserializeParamTypes); + if (deserializeMethod == null) { + continue; + } + JType returnType = deserializeMethod.getReturnType(); + + serializers.put(returnType, serializer); + } + return serializers; + } + + public void setNeedsGwtConstructor(JClassType type) { + if (!needsGwtConstructor(type)) { + needsGwtConstructor.add(type); + } + } + + private boolean needsGwtConstructor(JClassType type) { + if (needsGwtConstructor.contains(type)) { + return true; + } else { + return previousBundle != null + && previousBundle.needsGwtConstructor(type); + } + } + + public void setIdentifier(JClassType type, String identifier) { + if (!hasIdentifier(type, identifier)) { + addMapping(identifiers, type, identifier); + } + } + + private boolean hasIdentifier(JClassType type, String identifier) { + if (hasMapping(identifiers, type, identifier)) { + return true; + } else { + return previousBundle != null + && previousBundle.hasIdentifier(type, identifier); + } + } + + public ConnectorBundle getPreviousBundle() { + return previousBundle; + } + + public String getName() { + return name; + } + + public Map<JClassType, Set<String>> getIdentifiers() { + return Collections.unmodifiableMap(identifiers); + } + + public Set<JClassType> getGwtConstructors() { + return Collections.unmodifiableSet(needsGwtConstructor); + } + + public void processTypes(TreeLogger logger, Collection<JClassType> types) + throws UnableToCompleteException { + for (JClassType type : types) { + processType(logger, type); + } + } + + public void processType(TreeLogger logger, JClassType type) + throws UnableToCompleteException { + if (!isTypeVisited(type)) { + for (TypeVisitor typeVisitor : visitors) { + invokeVisitor(logger, type, typeVisitor); + } + visitedTypes.add(type); + purgeSerializeSupportQueue(logger); + } + } + + private boolean isTypeVisited(JClassType type) { + if (visitedTypes.contains(type)) { + return true; + } else { + return previousBundle != null && previousBundle.isTypeVisited(type); + } + } + + private void purgeSerializeSupportQueue(TreeLogger logger) + throws UnableToCompleteException { + while (!needsSerializeSupport.isEmpty()) { + Iterator<JType> iterator = needsSerializeSupport.iterator(); + JType type = iterator.next(); + iterator.remove(); + + if (hasSserializeSupport(type)) { + continue; + } + + addSerializeSupport(logger, type); + } + } + + private void addSerializeSupport(TreeLogger logger, JType type) + throws UnableToCompleteException { + hasSerializeSupport.add(type); + + JParameterizedType parametrized = type.isParameterized(); + if (parametrized != null) { + for (JClassType parameterType : parametrized.getTypeArgs()) { + setNeedsSerialize(parameterType); + } + } + + if (serializationHandledByFramework(type)) { + return; + } + + JClassType customSerializer = customSerializers.get(type); + JClassType typeAsClass = type.isClass(); + JEnumType enumType = type.isEnum(); + JArrayType arrayType = type.isArray(); + + if (customSerializer != null) { + logger.log(Type.INFO, "Will serialize " + type + " using " + + customSerializer.getName()); + setSerializer(type, new CustomSerializer(customSerializer)); + } else if (arrayType != null) { + logger.log(Type.INFO, "Will serialize " + type + " as an array"); + setSerializer(type, new ArraySerializer(arrayType)); + setNeedsSerialize(arrayType.getComponentType()); + } else if (enumType != null) { + logger.log(Type.INFO, "Will serialize " + type + " as an enum"); + setSerializer(type, new EnumSerializer(enumType)); + } else if (typeAsClass != null) { + // Bean + checkSerializable(logger, typeAsClass); + + logger.log(Type.INFO, "Will serialize " + type + " as a bean"); + + setNeedsPropertyList(typeAsClass); + + for (Property property : getProperties(typeAsClass)) { + setNeedsGwtConstructor(property.getBeanType()); + setNeedsSetter(property); + + // Getters needed for reading previous value that should be + // passed to sub encoder + setNeedsGetter(property); + setNeedsType(property); + + JType propertyType = property.getPropertyType(); + setNeedsSerialize(propertyType); + } + } + } + + private void checkSerializable(TreeLogger logger, JClassType type) + throws UnableToCompleteException { + JClassType javaSerializable = type.getOracle().findType( + Serializable.class.getName()); + boolean serializable = type.isAssignableTo(javaSerializable); + if (!serializable) { + boolean abortCompile = "true".equals(System + .getProperty(FAIL_IF_NOT_SERIALIZABLE)); + logger.log( + abortCompile ? Type.ERROR : Type.WARN, + type + + " is used in RPC or shared state but does not implement " + + Serializable.class.getName() + + ". Communication will work but the Application on server side cannot be serialized if it refers to objects of this type. " + + "If the system property " + + FAIL_IF_NOT_SERIALIZABLE + + " is set to \"true\", this causes the compilation to fail instead of just emitting a warning."); + if (abortCompile) { + throw new UnableToCompleteException(); + } + } + } + + private void setSerializer(JType type, GeneratedSerializer serializer) { + if (!hasSerializer(type)) { + serializers.put(type, serializer); + } + } + + private boolean hasSerializer(JType type) { + if (serializers.containsKey(type)) { + return true; + } else { + return previousBundle != null && previousBundle.hasSerializer(type); + } + } + + public Map<JType, GeneratedSerializer> getSerializers() { + return Collections.unmodifiableMap(serializers); + } + + private void setNeedsGetter(Property property) { + if (!isNeedsGetter(property)) { + needsGetter.add(property); + } + } + + private boolean isNeedsGetter(Property property) { + if (needsGetter.contains(property)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsGetter(property); + } + } + + public Set<Property> getNeedsGetter() { + return Collections.unmodifiableSet(needsGetter); + } + + private void setNeedsType(Property property) { + if (!isNeedsType(property)) { + needsType.add(property); + } + } + + public Set<Property> getNeedsType() { + return Collections.unmodifiableSet(needsType); + } + + private boolean isNeedsType(Property property) { + if (needsType.contains(property)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsType(property); + } + } + + public void setNeedsSetter(Property property) { + if (!isNeedsSetter(property)) { + needsSetter.add(property); + } + } + + private boolean isNeedsSetter(Property property) { + if (needsSetter.contains(property)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsSetter(property); + } + } + + public Set<Property> getNeedsSetter() { + return Collections.unmodifiableSet(needsSetter); + } + + private void setNeedsPropertyList(JClassType type) { + if (!isNeedsPropertyList(type)) { + needsPropertyList.add(type); + } + } + + private boolean isNeedsPropertyList(JClassType type) { + if (needsPropertyList.contains(type)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsPropertyList(type); + } + } + + public Set<JClassType> getNeedsPropertyListing() { + return Collections.unmodifiableSet(needsPropertyList); + } + + public Collection<Property> getProperties(JClassType type) { + HashSet<Property> properties = new HashSet<Property>(); + + properties.addAll(MethodProperty.findProperties(type)); + properties.addAll(FieldProperty.findProperties(type)); + + return properties; + } + + private void invokeVisitor(TreeLogger logger, JClassType type, + TypeVisitor typeVisitor) throws UnableToCompleteException { + TreeLogger subLogger = logger.branch(Type.TRACE, + "Visiting " + type.getName() + " with " + + typeVisitor.getClass().getSimpleName()); + if (isConnectedConnector(type)) { + typeVisitor.visitConnector(subLogger, type, this); + } + if (isClientRpc(type)) { + typeVisitor.visitClientRpc(subLogger, type, this); + } + if (isServerRpc(type)) { + typeVisitor.visitServerRpc(subLogger, type, this); + } + } + + public void processSubTypes(TreeLogger logger, JClassType type) + throws UnableToCompleteException { + processTypes(logger, Arrays.asList(type.getSubtypes())); + } + + public void setNeedsReturnType(JClassType type, JMethod method) { + if (!isNeedsReturnType(type, method)) { + addMapping(needsReturnType, type, method); + } + } + + private boolean isNeedsReturnType(JClassType type, JMethod method) { + if (hasMapping(needsReturnType, type, method)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsReturnType(type, method); + } + } + + public Map<JClassType, Set<JMethod>> getMethodReturnTypes() { + return Collections.unmodifiableMap(needsReturnType); + } + + private static boolean isClientRpc(JClassType type) { + return isType(type, ClientRpc.class); + } + + private static boolean isServerRpc(JClassType type) { + return isType(type, ServerRpc.class); + } + + public static boolean isConnectedConnector(JClassType type) { + return isConnected(type) && isType(type, ServerConnector.class); + } + + private static boolean isConnected(JClassType type) { + return type.isAnnotationPresent(Connect.class) + || type.getQualifiedSourceName().equals( + UnknownComponentConnector.class.getCanonicalName()); + } + + public static boolean isConnectedComponentConnector(JClassType type) { + return isConnected(type) && isType(type, ComponentConnector.class); + } + + private static boolean isType(JClassType type, Class<?> class1) { + try { + return type.getOracle().getType(class1.getName()) + .isAssignableFrom(type); + } catch (NotFoundException e) { + throw new RuntimeException("Could not find " + class1.getName(), e); + } + } + + public void setNeedsInvoker(JClassType type, JMethod method) { + if (!isNeedsInvoker(type, method)) { + addMapping(needsInvoker, type, method); + } + } + + private <K, V> void addMapping(Map<K, Set<V>> map, K key, V value) { + Set<V> set = map.get(key); + if (set == null) { + set = new HashSet<V>(); + map.put(key, set); + } + set.add(value); + } + + private <K, V> boolean hasMapping(Map<K, Set<V>> map, K key, V value) { + return map.containsKey(key) && map.get(key).contains(value); + } + + private boolean isNeedsInvoker(JClassType type, JMethod method) { + if (hasMapping(needsInvoker, type, method)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsInvoker(type, method); + } + } + + public Map<JClassType, Set<JMethod>> getNeedsInvoker() { + return Collections.unmodifiableMap(needsInvoker); + } + + public void setNeedsParamTypes(JClassType type, JMethod method) { + if (!isNeedsParamTypes(type, method)) { + addMapping(needsParamTypes, type, method); + } + } + + private boolean isNeedsParamTypes(JClassType type, JMethod method) { + if (hasMapping(needsParamTypes, type, method)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsParamTypes(type, method); + } + } + + public Map<JClassType, Set<JMethod>> getNeedsParamTypes() { + return Collections.unmodifiableMap(needsParamTypes); + } + + public void setNeedsProxySupport(JClassType type) { + if (!isNeedsProxySupport(type)) { + needsProxySupport.add(type); + } + } + + private boolean isNeedsProxySupport(JClassType type) { + if (needsProxySupport.contains(type)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsProxySupport(type); + } + } + + public Set<JClassType> getNeedsProxySupport() { + return Collections.unmodifiableSet(needsProxySupport); + } + + public void setNeedsDelayedInfo(JClassType type, JMethod method) { + if (!isNeedsDelayedInfo(type, method)) { + addMapping(needsDelayedInfo, type, method); + } + } + + private boolean isNeedsDelayedInfo(JClassType type, JMethod method) { + if (hasMapping(needsDelayedInfo, type, method)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsDelayedInfo(type, method); + } + } + + public Map<JClassType, Set<JMethod>> getNeedsDelayedInfo() { + return Collections.unmodifiableMap(needsDelayedInfo); + } + + public void setNeedsSerialize(JType type) { + if (!hasSserializeSupport(type)) { + needsSerializeSupport.add(type); + } + } + + private static Set<Class<?>> frameworkHandledTypes = new HashSet<Class<?>>(); + { + frameworkHandledTypes.add(String.class); + frameworkHandledTypes.add(Boolean.class); + frameworkHandledTypes.add(Integer.class); + frameworkHandledTypes.add(Float.class); + frameworkHandledTypes.add(Double.class); + frameworkHandledTypes.add(Long.class); + frameworkHandledTypes.add(Enum.class); + frameworkHandledTypes.add(String[].class); + frameworkHandledTypes.add(Object[].class); + frameworkHandledTypes.add(Map.class); + frameworkHandledTypes.add(List.class); + frameworkHandledTypes.add(Set.class); + frameworkHandledTypes.add(Byte.class); + frameworkHandledTypes.add(Character.class); + + } + + private boolean serializationHandledByFramework(JType setterType) { + // Some types are handled by the framework at the moment. See #8449 + // This method should be removed at some point. + if (setterType.isPrimitive() != null) { + return true; + } + + String qualifiedName = setterType.getQualifiedSourceName(); + for (Class<?> cls : frameworkHandledTypes) { + if (qualifiedName.equals(cls.getName())) { + return true; + } + } + + return false; + } + + private boolean hasSserializeSupport(JType type) { + if (hasSerializeSupport.contains(type)) { + return true; + } else { + return previousBundle != null + && previousBundle.hasSserializeSupport(type); + } + } + + public void setNeedsDelegateToWidget(Property property) { + if (!isNeedsDelegateToWidget(property)) { + needsDelegateToWidget.add(property); + } + } + + private boolean isNeedsDelegateToWidget(Property property) { + if (needsDelegateToWidget.contains(property)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsDelegateToWidget(property); + } + } + + public Set<Property> getNeedsDelegateToWidget() { + return Collections.unmodifiableSet(needsDelegateToWidget); + } +}
\ No newline at end of file diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java new file mode 100644 index 0000000000..3e863516a5 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java @@ -0,0 +1,27 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.server.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.vaadin.shared.ui.Connect; + +public class ConnectorInitVisitor extends TypeVisitor { + + @Override + public void visitConnector(TreeLogger logger, JClassType type, + ConnectorBundle bundle) { + Connect connectAnnotation = type.getAnnotation(Connect.class); + if (connectAnnotation != null) { + logger.log(Type.INFO, type.getName() + " will be in the " + + bundle.getName().replaceAll("^_*", "") + " bundle"); + bundle.setIdentifier(type, connectAnnotation.value() + .getCanonicalName()); + bundle.setNeedsGwtConstructor(type); + } + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java new file mode 100644 index 0000000000..a0105b4bf3 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java @@ -0,0 +1,43 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils.metadata; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.server.widgetsetutils.ConnectorBundleLoaderFactory; + +public class CustomSerializer implements GeneratedSerializer { + + private final JClassType serializerType; + + public CustomSerializer(JClassType serializerType) { + this.serializerType = serializerType; + } + + @Override + public void writeSerializerInstantiator(TreeLogger logger, SourceWriter w) + throws UnableToCompleteException { + w.print("return "); + w.print(GWT.class.getCanonicalName()); + w.print(".create("); + ConnectorBundleLoaderFactory.writeClassLiteral(w, serializerType); + w.println(");"); + } +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java new file mode 100644 index 0000000000..5590e6cd7a --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JEnumConstant; +import com.google.gwt.core.ext.typeinfo.JEnumType; +import com.google.gwt.json.client.JSONString; +import com.google.gwt.user.rebind.SourceWriter; + +public class EnumSerializer extends JsonSerializer { + + private final JEnumType enumType; + + public EnumSerializer(JEnumType type) { + super(type); + enumType = type; + } + + @Override + protected void printDeserializerBody(TreeLogger logger, SourceWriter w, + String type, String jsonValue, String connection) { + w.println("String enumIdentifier = ((" + JSONString.class.getName() + + ")" + jsonValue + ").stringValue();"); + for (JEnumConstant e : enumType.getEnumConstants()) { + w.println("if (\"" + e.getName() + "\".equals(enumIdentifier)) {"); + w.indent(); + w.println("return " + enumType.getQualifiedSourceName() + "." + + e.getName() + ";"); + w.outdent(); + w.println("}"); + } + w.println("return null;"); + } + + @Override + protected void printSerializerBody(TreeLogger logger, SourceWriter w, + String value, String applicationConnection) { + // return new JSONString(castedValue.name()); + w.println("return new " + JSONString.class.getName() + "(" + value + + ".name());"); + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java new file mode 100644 index 0000000000..a7896a5bf6 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java @@ -0,0 +1,86 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils.metadata; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JField; +import com.google.gwt.user.rebind.SourceWriter; + +public class FieldProperty extends Property { + + private final JField field; + + private FieldProperty(JClassType beanType, JField field) { + super(field.getName(), beanType, field.getType()); + this.field = field; + } + + @Override + public void writeSetterBody(TreeLogger logger, SourceWriter w, + String beanVariable, String valueVariable) { + w.print("((%s) %s).%s = (%s)%s;", getBeanType() + .getQualifiedSourceName(), beanVariable, getName(), + getUnboxedPropertyTypeName(), valueVariable); + } + + @Override + public void writeGetterBody(TreeLogger logger, SourceWriter w, + String beanVariable) { + w.print("return ((%s) %s).%s;", getBeanType().getQualifiedSourceName(), + beanVariable, getName()); + } + + public static Collection<FieldProperty> findProperties(JClassType type) { + Collection<FieldProperty> properties = new ArrayList<FieldProperty>(); + + List<JField> fields = getPublicFields(type); + for (JField field : fields) { + properties.add(new FieldProperty(type, field)); + } + + return properties; + } + + private static List<JField> getPublicFields(JClassType type) { + Set<String> names = new HashSet<String>(); + ArrayList<JField> fields = new ArrayList<JField>(); + for (JClassType subType : type.getFlattenedSupertypeHierarchy()) { + JField[] subFields = subType.getFields(); + for (JField field : subFields) { + if (field.isPublic() && !field.isStatic() + && names.add(field.getName())) { + fields.add(field); + } + } + } + return fields; + } + + @Override + public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { + return field.getAnnotation(annotationClass); + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java new file mode 100644 index 0000000000..8f6c8696fd --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java @@ -0,0 +1,26 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.user.rebind.SourceWriter; + +public interface GeneratedSerializer { + public void writeSerializerInstantiator(TreeLogger logger, SourceWriter w) + throws UnableToCompleteException; +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java new file mode 100644 index 0000000000..6e521dc763 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java @@ -0,0 +1,88 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.json.client.JSONValue; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.communication.JSONSerializer; + +public abstract class JsonSerializer implements GeneratedSerializer { + + private final JType type; + + public JsonSerializer(JType type) { + this.type = type; + } + + @Override + public void writeSerializerInstantiator(TreeLogger logger, SourceWriter w) + throws UnableToCompleteException { + + w.print("return new "); + w.print(JSONSerializer.class.getCanonicalName()); + w.print("<"); + w.print(type.getQualifiedSourceName()); + w.println(">() {"); + w.indent(); + + writeSerializerBody(logger, w); + + w.outdent(); + w.println("};"); + } + + protected void writeSerializerBody(TreeLogger logger, SourceWriter w) { + String qualifiedSourceName = type.getQualifiedSourceName(); + w.println("public " + JSONValue.class.getName() + " serialize(" + + qualifiedSourceName + " value, " + + ApplicationConnection.class.getName() + " connection) {"); + w.indent(); + // MouseEventDetails castedValue = (MouseEventDetails) value; + w.println(qualifiedSourceName + " castedValue = (" + + qualifiedSourceName + ") value;"); + + printSerializerBody(logger, w, "castedValue", "connection"); + + // End of serializer method + w.outdent(); + w.println("}"); + + // Deserializer + // T deserialize(Type type, JSONValue jsonValue, ApplicationConnection + // connection); + w.println("public " + qualifiedSourceName + " deserialize(Type type, " + + JSONValue.class.getName() + " jsonValue, " + + ApplicationConnection.class.getName() + " connection) {"); + w.indent(); + + printDeserializerBody(logger, w, "type", "jsonValue", "connection"); + + w.outdent(); + w.println("}"); + } + + protected abstract void printDeserializerBody(TreeLogger logger, + SourceWriter w, String type, String jsonValue, String connection); + + protected abstract void printSerializerBody(TreeLogger logger, + SourceWriter w, String value, String applicationConnection); + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java new file mode 100644 index 0000000000..318519f37d --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java @@ -0,0 +1,130 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils.metadata; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.user.rebind.SourceWriter; + +public class MethodProperty extends Property { + + private final JMethod setter; + + private MethodProperty(JClassType beanType, JMethod setter) { + super(getTransportFieldName(setter), beanType, setter + .getParameterTypes()[0]); + this.setter = setter; + } + + public static Collection<MethodProperty> findProperties(JClassType type) { + Collection<MethodProperty> properties = new ArrayList<MethodProperty>(); + + List<JMethod> setters = getSetters(type); + for (JMethod setter : setters) { + properties.add(new MethodProperty(type, setter)); + } + + return properties; + } + + /** + * Returns a list of all setters found in the beanType or its parent class + * + * @param beanType + * The type to check + * @return A list of setter methods from the class and its parents + */ + private static List<JMethod> getSetters(JClassType beanType) { + List<JMethod> setterMethods = new ArrayList<JMethod>(); + + while (beanType != null + && !beanType.getQualifiedSourceName().equals( + Object.class.getName())) { + for (JMethod method : beanType.getMethods()) { + // Process all setters that have corresponding fields + if (!method.isPublic() || method.isStatic() + || !method.getName().startsWith("set") + || method.getParameterTypes().length != 1) { + // Not setter, skip to next method + continue; + } + setterMethods.add(method); + } + beanType = beanType.getSuperclass(); + } + + return setterMethods; + } + + @Override + public void writeSetterBody(TreeLogger logger, SourceWriter w, + String beanVariable, String valueVariable) { + w.print("(("); + w.print(getBeanType().getQualifiedSourceName()); + w.print(") "); + w.print(beanVariable); + w.print(")."); + w.print(setter.getName()); + w.print("(("); + w.print(getUnboxedPropertyTypeName()); + w.print(") "); + w.print(valueVariable); + w.println(");"); + } + + @Override + public void writeGetterBody(TreeLogger logger, SourceWriter w, + String beanVariable) { + w.print("return (("); + w.print(getBeanType().getQualifiedSourceName()); + w.print(") "); + w.print(beanVariable); + w.print(")."); + w.print(findGetter(getBeanType(), setter)); + w.print("();"); + } + + private String findGetter(JClassType beanType, JMethod setterMethod) { + JType setterParameterType = setterMethod.getParameterTypes()[0]; + String fieldName = setterMethod.getName().substring(3); + if (setterParameterType.getQualifiedSourceName().equals( + boolean.class.getName())) { + return "is" + fieldName; + } else { + return "get" + fieldName; + } + } + + private static String getTransportFieldName(JMethod setter) { + String baseName = setter.getName().substring(3); + return Character.toLowerCase(baseName.charAt(0)) + + baseName.substring(1); + } + + @Override + public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { + return setter.getAnnotation(annotationClass); + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java new file mode 100644 index 0000000000..b55b70784a --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java @@ -0,0 +1,89 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils.metadata; + +import java.lang.annotation.Annotation; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JPrimitiveType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.user.rebind.SourceWriter; + +public abstract class Property { + private final String name; + private final JClassType beanType; + private final JType propertyType; + + protected Property(String name, JClassType beanType, JType propertyType) { + this.name = name; + this.beanType = beanType; + this.propertyType = propertyType; + } + + public String getName() { + return name; + } + + public JType getPropertyType() { + return propertyType; + } + + public String getUnboxedPropertyTypeName() { + JType propertyType = getPropertyType(); + JPrimitiveType primitive = propertyType.isPrimitive(); + if (primitive != null) { + return primitive.getQualifiedBoxedSourceName(); + } else { + return propertyType.getQualifiedSourceName(); + } + } + + public JClassType getBeanType() { + return beanType; + } + + public abstract void writeSetterBody(TreeLogger logger, SourceWriter w, + String beanVariable, String valueVariable); + + public abstract void writeGetterBody(TreeLogger logger, SourceWriter w, + String beanVariable); + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof Property) { + Property other = (Property) obj; + return other.getClass() == getClass() + && other.getBeanType().equals(getBeanType()) + && other.getName().equals(getName()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return getClass().hashCode() * 31 ^ 2 + getBeanType().hashCode() * 31 + + getName().hashCode(); + } + + public abstract <T extends Annotation> T getAnnotation( + Class<T> annotationClass); + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java new file mode 100644 index 0000000000..cbcfc3075b --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java @@ -0,0 +1,48 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * 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.vaadin.server.widgetsetutils.metadata; + +import java.util.Set; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JType; + +public class ServerRpcVisitor extends TypeVisitor { + @Override + public void visitServerRpc(TreeLogger logger, JClassType type, + ConnectorBundle bundle) { + bundle.setNeedsProxySupport(type); + + Set<? extends JClassType> superTypes = type + .getFlattenedSupertypeHierarchy(); + for (JClassType subType : superTypes) { + if (subType.isInterface() != null) { + JMethod[] methods = subType.getMethods(); + for (JMethod method : methods) { + bundle.setNeedsDelayedInfo(type, method); + + JType[] parameterTypes = method.getParameterTypes(); + for (JType paramType : parameterTypes) { + bundle.setNeedsSerialize(paramType); + } + } + } + } + } +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java new file mode 100644 index 0000000000..17ea62b249 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.server.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JType; + +public class StateInitVisitor extends TypeVisitor { + @Override + public void visitConnector(TreeLogger logger, JClassType type, + ConnectorBundle bundle) { + JMethod getState = findInheritedMethod(type, "getState"); + bundle.setNeedsReturnType(type, getState); + + bundle.setNeedsSerialize(getState.getReturnType()); + + JType stateType = getState.getReturnType(); + bundle.setNeedsGwtConstructor(stateType.isClass()); + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java new file mode 100644 index 0000000000..7191093755 --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java @@ -0,0 +1,58 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.server.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.NotFoundException; +import com.google.gwt.core.ext.typeinfo.TypeOracle; + +public abstract class TypeVisitor { + public void init(TypeOracle oracle) throws NotFoundException { + // Default does nothing + } + + public void visitConnector(TreeLogger logger, JClassType type, + ConnectorBundle bundle) throws UnableToCompleteException { + // Default does nothing + } + + public void visitClientRpc(TreeLogger logger, JClassType type, + ConnectorBundle bundle) { + // Default does nothing + } + + public void visitServerRpc(TreeLogger logger, JClassType type, + ConnectorBundle bundle) { + // Default does nothing + } + + protected JMethod findInheritedMethod(JClassType type, String methodName, + JType... params) { + + JClassType currentType = type; + while (currentType != null) { + JMethod method = currentType.findMethod(methodName, params); + if (method != null) { + return method; + } + currentType = currentType.getSuperclass(); + } + + JClassType[] interfaces = type.getImplementedInterfaces(); + for (JClassType iface : interfaces) { + JMethod method = iface.findMethod(methodName, params); + if (method != null) { + return method; + } + } + + return null; + } + +} diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java new file mode 100644 index 0000000000..fbbabe084d --- /dev/null +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java @@ -0,0 +1,73 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.server.widgetsetutils.metadata; + +import java.util.Collection; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.TreeLogger.Type; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.shared.annotations.DelegateToWidget; + +public class WidgetInitVisitor extends TypeVisitor { + + @Override + public void visitConnector(TreeLogger logger, JClassType type, + ConnectorBundle bundle) throws UnableToCompleteException { + if (ConnectorBundle.isConnectedComponentConnector(type)) { + JClassType createWidgetClass = findInheritedMethod(type, + "createWidget").getEnclosingType(); + boolean createWidgetOverridden = !createWidgetClass + .getQualifiedSourceName() + .equals(AbstractComponentConnector.class.getCanonicalName()); + if (createWidgetOverridden) { + // Don't generate if createWidget is already overridden + return; + } + + JMethod getWidget = findInheritedMethod(type, "getWidget"); + bundle.setNeedsReturnType(type, getWidget); + + JClassType widgetType = getWidget.getReturnType().isClass(); + bundle.setNeedsGwtConstructor(widgetType); + + JMethod getState = findInheritedMethod(type, "getState"); + JClassType stateType = getState.getReturnType().isClass(); + + Collection<Property> properties = bundle.getProperties(stateType); + for (Property property : properties) { + DelegateToWidget delegateToWidget = property + .getAnnotation(DelegateToWidget.class); + if (delegateToWidget != null) { + bundle.setNeedsDelegateToWidget(property); + String methodName = DelegateToWidget.Helper + .getDelegateTarget(property.getName(), + delegateToWidget.value()); + JMethod delegatedSetter = findInheritedMethod(widgetType, + methodName, property.getPropertyType()); + if (delegatedSetter == null) { + logger.log( + Type.ERROR, + widgetType.getName() + + "." + + methodName + + "(" + + property.getPropertyType() + .getSimpleSourceName() + + ") required by @DelegateToWidget for " + + stateType.getName() + "." + + property.getName() + + " can not be found."); + throw new UnableToCompleteException(); + } + bundle.setNeedsInvoker(widgetType, delegatedSetter); + } + } + } + } +} |