aboutsummaryrefslogtreecommitdiffstats
path: root/client-compiler/src/main/java/com
diff options
context:
space:
mode:
authorelmot <elmot@vaadin.com>2016-03-18 17:29:27 +0200
committerelmot <elmot@vaadin.com>2016-03-22 11:27:53 +0200
commit521ec24c62a1ba476795369a42114fede7043f44 (patch)
tree0a8991b4c2a1bb8c2a6738701d74d6d43439bb59 /client-compiler/src/main/java/com
parent646cbaecbb5ef0e1cbefc7570e0340d926b7c85a (diff)
downloadvaadin-framework-521ec24c62a1ba476795369a42114fede7043f44.tar.gz
vaadin-framework-521ec24c62a1ba476795369a42114fede7043f44.zip
Build client-compiler with maven
Change-Id: Ic86a95ce44cc37c8d34d607b39f2aec4cc922ce2
Diffstat (limited to 'client-compiler/src/main/java/com')
-rw-r--r--client-compiler/src/main/java/com/vaadin/sass/linker/SassLinker.java233
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java143
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java1306
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ArraySerializer.java95
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java84
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java737
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java40
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java43
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java58
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java93
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java26
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java88
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java144
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/OnStateChangeVisitor.java54
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/Property.java127
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/RendererVisitor.java119
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java70
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java37
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java44
-rw-r--r--client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java105
-rw-r--r--client-compiler/src/main/java/com/vaadin/tools/CvalAddonsChecker.java193
-rw-r--r--client-compiler/src/main/java/com/vaadin/tools/CvalChecker.java510
-rwxr-xr-xclient-compiler/src/main/java/com/vaadin/tools/WidgetsetCompiler.java99
23 files changed, 4448 insertions, 0 deletions
diff --git a/client-compiler/src/main/java/com/vaadin/sass/linker/SassLinker.java b/client-compiler/src/main/java/com/vaadin/sass/linker/SassLinker.java
new file mode 100644
index 0000000000..dda6733384
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/sass/linker/SassLinker.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2000-2014 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.sass.linker;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.w3c.css.sac.CSSException;
+
+import com.google.gwt.core.ext.LinkerContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.AbstractLinker;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+import com.google.gwt.core.ext.linker.Shardable;
+import com.vaadin.sass.internal.ScssStylesheet;
+
+/**
+ * Pre-linker that checks for the existence of SASS files in public folders,
+ * compiles them to CSS files with the SassCompiler from Vaadin and adds the CSS
+ * back into the artifact.
+ *
+ */
+@LinkerOrder(Order.PRE)
+@Shardable
+public class SassLinker extends AbstractLinker {
+
+ @Override
+ public String getDescription() {
+ return "Compiling SCSS files in public folders to standard CSS";
+ }
+
+ @Override
+ public ArtifactSet link(TreeLogger logger, LinkerContext context,
+ ArtifactSet artifacts, boolean onePermutation)
+ throws UnableToCompleteException {
+
+ if (!onePermutation) {
+ // The artifact to return
+ ArtifactSet toReturn = new ArtifactSet(artifacts);
+
+ // The temporary scss files provided from the artefacts
+ List<FileInfo> scssFiles = new ArrayList<FileInfo>();
+
+ // The public files are provided as inputstream, but the compiler
+ // needs real files, as they can contain references to other
+ // files. They will be stored here, with their relative paths intact
+ String tempFolderName = new Date().getTime() + File.separator;
+ File tempFolder = createTempDir(tempFolderName);
+
+ // Can't search here specifically for public resources, as the type
+ // is different during compilation. This means we have to loop
+ // through all the artifacts
+ for (EmittedArtifact resource : artifacts
+ .find(EmittedArtifact.class)) {
+
+ // Create the temporary files.
+ String partialPath = resource.getPartialPath();
+ if (partialPath.endsWith(".scss")) {
+ // In my opinion, the SCSS file does not need to be
+ // output to the web content folder, as they can't
+ // be used there
+ toReturn.remove(resource);
+
+ String fileName = partialPath;
+ File path = tempFolder;
+
+ int separatorIndex = fileName.lastIndexOf(File.separator);
+ if (-1 != separatorIndex) {
+ fileName = fileName.substring(separatorIndex + 1);
+
+ String filePath = partialPath.substring(0,
+ separatorIndex);
+ path = createTempDir(tempFolderName + filePath);
+ }
+
+ File tempfile = new File(path, fileName);
+ try {
+ boolean fileCreated = tempfile.createNewFile();
+ if (fileCreated) {
+
+ // write the received inputstream to the temp file
+ writeFromInputStream(resource.getContents(logger),
+ tempfile);
+
+ // Store the file info for the compilation
+ scssFiles.add(new FileInfo(tempfile, partialPath));
+ } else {
+ logger.log(TreeLogger.WARN, "Duplicate file "
+ + tempfile.getPath());
+ }
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR,
+ "Could not write temporary file " + fileName, e);
+ }
+ }
+ }
+
+ // Compile the files and store them in the artifact
+ logger.log(TreeLogger.INFO, "Processing " + scssFiles.size()
+ + " Sass file(s)");
+ for (FileInfo fileInfo : scssFiles) {
+ logger.log(TreeLogger.INFO, " " + fileInfo.originalScssPath
+ + " -> " + fileInfo.getOriginalCssPath());
+
+ try {
+ ScssStylesheet scss = ScssStylesheet.get(fileInfo
+ .getAbsolutePath());
+ if (!fileInfo.isMixin()) {
+ scss.compile();
+ InputStream is = new ByteArrayInputStream(scss
+ .printState().getBytes());
+
+ toReturn.add(this.emitInputStream(logger, is,
+ fileInfo.getOriginalCssPath()));
+ }
+
+ fileInfo.getFile().delete();
+ } catch (CSSException e) {
+ logger.log(TreeLogger.ERROR, "SCSS compilation failed for "
+ + fileInfo.getOriginalCssPath(), e);
+ } catch (IOException e) {
+ logger.log(
+ TreeLogger.ERROR,
+ "Could not write CSS file for "
+ + fileInfo.getOriginalCssPath(), e);
+ } catch (Exception e) {
+ logger.log(TreeLogger.ERROR, "SCSS compilation failed for "
+ + fileInfo.getOriginalCssPath(), e);
+ }
+ }
+
+ return toReturn;
+ }
+
+ return artifacts;
+ }
+
+ /**
+ * Writes the contents of an InputStream out to a file.
+ *
+ * @param contents
+ * @param tempfile
+ * @throws IOException
+ */
+ private void writeFromInputStream(InputStream contents, File tempfile)
+ throws IOException {
+ // write the inputStream to a FileOutputStream
+ OutputStream out = new FileOutputStream(tempfile);
+
+ int read = 0;
+ byte[] bytes = new byte[1024];
+
+ while ((read = contents.read(bytes)) != -1) {
+ out.write(bytes, 0, read);
+ }
+
+ contents.close();
+ out.flush();
+ out.close();
+ }
+
+ /**
+ * Create folder in temporary space on disk.
+ *
+ * @param partialPath
+ * @return
+ */
+ private File createTempDir(String partialPath) {
+ String baseTempPath = System.getProperty("java.io.tmpdir");
+
+ File tempDir = new File(baseTempPath + File.separator + partialPath);
+ if (!tempDir.exists()) {
+ tempDir.mkdirs();
+ }
+
+ tempDir.deleteOnExit();
+ return tempDir;
+ }
+
+ /**
+ * Temporal storage for file info from Artifact.
+ */
+ private class FileInfo {
+ private String originalScssPath;
+ private File file;
+
+ public FileInfo(File file, String originalScssPath) {
+ this.file = file;
+ this.originalScssPath = originalScssPath;
+ }
+
+ public boolean isMixin() {
+ return file.getName().startsWith("_");
+ }
+
+ public String getAbsolutePath() {
+ return file.getAbsolutePath();
+ }
+
+ public String getOriginalCssPath() {
+ return originalScssPath.substring(0, originalScssPath.length() - 5)
+ + ".css";
+ }
+
+ public File getFile() {
+ return file;
+ }
+ }
+}
diff --git a/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java
new file mode 100644
index 0000000000..2d08329e9a
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2000-2014 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.Arrays;
+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.server.widgetsetutils.metadata.ConnectorBundle;
+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());
+ JClassType[] subtypes = criteriaType.getSubtypes();
+ Arrays.sort(subtypes, ConnectorBundle.jClassComparator);
+ for (JClassType clientClass : subtypes) {
+ 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/main/java/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java
new file mode 100644
index 0000000000..2b8ccc87d0
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java
@@ -0,0 +1,1306 @@
+/*
+ * Copyright 2000-2014 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.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+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 java.util.TreeMap;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.RunAsyncCallback;
+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.JPrimitiveType;
+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.JsArrayObject;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.communication.JsonDecoder;
+import com.vaadin.client.metadata.ConnectorBundleLoader;
+import com.vaadin.client.metadata.ConnectorBundleLoader.CValUiInfo;
+import com.vaadin.client.metadata.InvokationHandler;
+import com.vaadin.client.metadata.OnStateChangeMethod;
+import com.vaadin.client.metadata.ProxyHandler;
+import com.vaadin.client.metadata.TypeData;
+import com.vaadin.client.metadata.TypeDataStore;
+import com.vaadin.client.metadata.TypeDataStore.MethodAttribute;
+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.OnStateChangeVisitor;
+import com.vaadin.server.widgetsetutils.metadata.Property;
+import com.vaadin.server.widgetsetutils.metadata.RendererVisitor;
+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.DelegateToWidget;
+import com.vaadin.shared.annotations.NoLayout;
+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;
+import com.vaadin.tools.CvalAddonsChecker;
+import com.vaadin.tools.CvalChecker;
+import com.vaadin.tools.CvalChecker.InvalidCvalException;
+
+public class ConnectorBundleLoaderFactory extends Generator {
+ /**
+ * Special SourceWriter that approximates the number of written bytes to
+ * support splitting long methods into shorter chunks to avoid hitting the
+ * 65535 byte limit.
+ */
+ private class SplittingSourceWriter implements SourceWriter {
+ private final SourceWriter target;
+ private final String baseName;
+ private final int splitSize;
+ private final List<String> methodNames;
+
+ // Seems to be undercounted by about 15%
+ private int approximateChars = 0;
+ private int wrapCount = 0;
+
+ public SplittingSourceWriter(SourceWriter target, String baseName,
+ int splitSize) {
+ this.target = target;
+ this.baseName = baseName;
+ this.splitSize = splitSize;
+ methodNames = new ArrayList<String>();
+ methodNames.add(baseName);
+ }
+
+ @Override
+ public void beginJavaDocComment() {
+ target.beginJavaDocComment();
+ addChars(10);
+ }
+
+ private void addChars(int i) {
+ approximateChars += i;
+ }
+
+ private void addChars(String s) {
+ addChars(s.length());
+ }
+
+ private void addChars(String s, Object[] args) {
+ addChars(String.format(s, args));
+ }
+
+ @Override
+ public void commit(TreeLogger logger) {
+ target.commit(logger);
+ }
+
+ @Override
+ public void endJavaDocComment() {
+ target.endJavaDocComment();
+ addChars(10);
+ }
+
+ @Override
+ public void indent() {
+ target.indent();
+ addChars(10);
+ }
+
+ @Override
+ public void indentln(String s) {
+ target.indentln(s);
+ addChars(s);
+ }
+
+ @Override
+ public void indentln(String s, Object... args) {
+ target.indentln(s, args);
+ addChars(s, args);
+ }
+
+ @Override
+ public void outdent() {
+ target.outdent();
+ }
+
+ @Override
+ public void print(String s) {
+ target.print(s);
+ addChars(s);
+ }
+
+ @Override
+ public void print(String s, Object... args) {
+ target.print(s, args);
+ addChars(s, args);
+ }
+
+ @Override
+ public void println() {
+ target.println();
+ addChars(5);
+ }
+
+ @Override
+ public void println(String s) {
+ target.println(s);
+ addChars(s);
+ }
+
+ @Override
+ public void println(String s, Object... args) {
+ target.println(s, args);
+ addChars(s, args);
+ }
+
+ public void splitIfNeeded() {
+ splitIfNeeded(false, null);
+ }
+
+ public void splitIfNeeded(boolean isNative, String params) {
+ if (approximateChars > splitSize) {
+ String newMethod = baseName + wrapCount++;
+ String args = params == null ? "" : params;
+ if (isNative) {
+ outdent();
+ println("}-*/;");
+ // To support fields of type long (#13692)
+ println("@com.google.gwt.core.client.UnsafeNativeLong");
+ println("private native void %s(%s) /*-{", newMethod, args);
+ } else {
+ println("%s();", newMethod);
+ outdent();
+ println("}");
+ println("private void %s(%s) {", newMethod, args);
+ }
+ methodNames.add(newMethod);
+ indent();
+
+ approximateChars = 0;
+ }
+ }
+
+ public List<String> getMethodNames() {
+ return Collections.unmodifiableList(methodNames);
+ }
+
+ }
+
+ private CvalAddonsChecker cvalChecker = new CvalAddonsChecker();
+
+ @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<CValUiInfo> cvalInfos = null;
+ try {
+ if (cvalChecker != null) {
+ cvalInfos = cvalChecker.run();
+ // Don't run twice
+ cvalChecker = null;
+ }
+ } catch (InvalidCvalException e) {
+ System.err.println("\n\n\n\n" + CvalChecker.LINE);
+ for (String line : e.getMessage().split("\n")) {
+ System.err.println(line);
+ }
+ System.err.println(CvalChecker.LINE + "\n\n\n\n");
+ System.exit(1);
+ throw new UnableToCompleteException();
+ }
+
+ 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) {
+ detectBadProperties(bundle, logger);
+
+ 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.println("new %s() {", RunAsyncCallback.class.getName());
+ w.indent();
+
+ w.println("public void onSuccess() {");
+ w.indent();
+
+ w.println("load();");
+ w.println("%s.get().setLoaded(getName());",
+ ConnectorBundleLoader.class.getName());
+
+ // Close onSuccess method
+ w.outdent();
+ w.println("}");
+
+ w.println("private void load() {");
+ w.indent();
+
+ String loadNativeJsBundle = "loadJsBundle";
+ printBundleData(logger, w, bundle, loadNativeJsBundle);
+
+ // Close load method
+ w.outdent();
+ w.println("}");
+
+ // Separate method for loading native JS stuff (e.g. callbacks)
+ String loadNativeJsMethodName = "loadNativeJs";
+ // To support fields of type long (#13692)
+ w.println("@com.google.gwt.core.client.UnsafeNativeLong");
+ w.println("private native void %s(%s store) /*-{",
+ loadNativeJsMethodName, TypeDataStore.class.getName());
+ w.indent();
+ List<String> jsMethodNames = printJsBundleData(logger, w, bundle,
+ loadNativeJsMethodName);
+
+ w.outdent();
+ w.println("}-*/;");
+
+ // Call all generated native method inside one Java method to avoid
+ // refercences inside native methods to each other
+ w.println("private void %s(%s store) {", loadNativeJsBundle,
+ TypeDataStore.class.getName());
+ w.indent();
+ printLoadJsBundleData(w, loadNativeJsBundle, jsMethodNames);
+ w.outdent();
+ w.println("}");
+
+ // onFailure method declaration starts
+ w.println("public void onFailure(Throwable reason) {");
+ w.indent();
+
+ w.println("%s.get().setLoadFailure(getName(), reason);",
+ ConnectorBundleLoader.class.getName());
+
+ w.outdent();
+ w.println("}");
+
+ // Close new RunAsyncCallback() {}
+ 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("});");
+ }
+
+ if (cvalInfos != null && !cvalInfos.isEmpty()) {
+ w.println("{");
+ for (CValUiInfo c : cvalInfos) {
+ if ("evaluation".equals(c.type)) {
+ w.println("cvals.add(new CValUiInfo(\"" + c.product
+ + "\", \"" + c.version + "\", \"" + c.widgetset
+ + "\", null));");
+ }
+ }
+ w.println("}");
+ }
+
+ w.outdent();
+ w.println("}");
+
+ w.commit(logger);
+ }
+
+ private void printLoadJsBundleData(SourceWriter w, String methodName,
+ List<String> methods) {
+ SplittingSourceWriter writer = new SplittingSourceWriter(w, methodName,
+ 30000);
+
+ for (String method : methods) {
+ writer.println("%s(store);", method);
+ writer.splitIfNeeded();
+ }
+ }
+
+ private void detectBadProperties(ConnectorBundle bundle, TreeLogger logger)
+ throws UnableToCompleteException {
+ Map<JClassType, Set<String>> definedProperties = new HashMap<JClassType, Set<String>>();
+
+ for (Property property : bundle.getNeedsProperty()) {
+ JClassType beanType = property.getBeanType();
+ Set<String> usedPropertyNames = definedProperties.get(beanType);
+ if (usedPropertyNames == null) {
+ usedPropertyNames = new HashSet<String>();
+ definedProperties.put(beanType, usedPropertyNames);
+ }
+
+ String name = property.getName();
+ if (!usedPropertyNames.add(name)) {
+ logger.log(Type.ERROR, beanType.getQualifiedSourceName()
+ + " has multiple properties with the name " + name
+ + ". This can happen if there are multiple "
+ + "setters with identical names ignoring case.");
+ throw new UnableToCompleteException();
+ }
+ if (!property.hasAccessorMethods()) {
+ logger.log(Type.ERROR, beanType.getQualifiedSourceName()
+ + " has the property '" + name
+ + "' without getter defined.");
+ throw new UnableToCompleteException();
+ }
+ }
+ }
+
+ private List<String> printJsBundleData(TreeLogger logger, SourceWriter w,
+ ConnectorBundle bundle, String methodName) {
+ SplittingSourceWriter writer = new SplittingSourceWriter(w, methodName,
+ 30000);
+ Set<Property> needsProperty = bundle.getNeedsProperty();
+ for (Property property : needsProperty) {
+ writer.println("var data = {");
+ writer.indent();
+
+ if (property.getAnnotation(NoLayout.class) != null) {
+ writer.println("noLayout: 1, ");
+ }
+
+ writer.println("setter: function(bean, value) {");
+ writer.indent();
+ property.writeSetterBody(logger, writer, "bean", "value");
+ writer.outdent();
+ writer.println("},");
+
+ writer.println("getter: function(bean) {");
+ writer.indent();
+ property.writeGetterBody(logger, writer, "bean");
+ writer.outdent();
+ writer.println("}");
+
+ writer.outdent();
+ writer.println("};");
+
+ // Method declaration
+ writer.print(
+ "store.@%s::setPropertyData(Ljava/lang/Class;Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)",
+ TypeDataStore.class.getName());
+ writer.println("(@%s::class, '%s', data);", property.getBeanType()
+ .getQualifiedSourceName(), property.getName());
+ writer.println();
+ writer.splitIfNeeded(true,
+ String.format("%s store", TypeDataStore.class.getName()));
+ }
+ return writer.getMethodNames();
+ }
+
+ private void printBundleData(TreeLogger logger, SourceWriter sourceWriter,
+ ConnectorBundle bundle, String loadNativeJsMethodName)
+ throws UnableToCompleteException {
+ // Split into new load method when reaching approximately 30000 bytes
+ SplittingSourceWriter w = new SplittingSourceWriter(sourceWriter,
+ "load", 30000);
+
+ writeSuperClasses(w, bundle);
+ writeIdentifiers(w, bundle);
+ writeGwtConstructors(w, bundle);
+ writeReturnTypes(w, bundle);
+ writeInvokers(logger, w, bundle);
+ writeParamTypes(w, bundle);
+ writeProxys(w, bundle);
+ writeMethodAttributes(logger, w, bundle);
+
+ w.println("%s(store);", loadNativeJsMethodName);
+
+ // Must use Java code to generate Type data (because of Type[]), doing
+ // this after the JS property data has been initialized
+ writePropertyTypes(logger, w, bundle);
+ writeSerializers(logger, w, bundle);
+ writePresentationTypes(w, bundle);
+ writeDelegateToWidget(logger, w, bundle);
+ writeOnStateChangeHandlers(logger, w, bundle);
+ }
+
+ private void writeOnStateChangeHandlers(TreeLogger logger,
+ SplittingSourceWriter w, ConnectorBundle bundle)
+ throws UnableToCompleteException {
+ Map<JClassType, Set<JMethod>> needsOnStateChangeHandler = bundle
+ .getNeedsOnStateChangeHandler();
+ for (Entry<JClassType, Set<JMethod>> entry : needsOnStateChangeHandler
+ .entrySet()) {
+ JClassType connector = entry.getKey();
+
+ TreeLogger typeLogger = logger.branch(
+ Type.DEBUG,
+ "Generating @OnStateChange support for "
+ + connector.getName());
+
+ // Build map to speed up error checking
+ HashMap<String, Property> stateProperties = new HashMap<String, Property>();
+ JClassType stateType = ConnectorBundle
+ .findInheritedMethod(connector, "getState").getReturnType()
+ .isClassOrInterface();
+ for (Property property : bundle.getProperties(stateType)) {
+ stateProperties.put(property.getName(), property);
+ }
+
+ for (JMethod method : entry.getValue()) {
+ TreeLogger methodLogger = typeLogger.branch(Type.DEBUG,
+ "Processing method " + method.getName());
+
+ if (method.isPublic() || method.isProtected()) {
+ methodLogger
+ .log(Type.ERROR,
+ "@OnStateChange is only supported for methods with private or default visibility.");
+ throw new UnableToCompleteException();
+ }
+
+ OnStateChange onStateChange = method
+ .getAnnotation(OnStateChange.class);
+
+ String[] properties = onStateChange.value();
+
+ if (properties.length == 0) {
+ methodLogger.log(Type.ERROR,
+ "There are no properties to listen to");
+ throw new UnableToCompleteException();
+ }
+
+ // Verify that all properties do exist
+ for (String propertyName : properties) {
+ if (!stateProperties.containsKey(propertyName)) {
+ methodLogger.log(Type.ERROR,
+ "State class has no property named "
+ + propertyName);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ if (method.getParameters().length != 0) {
+ methodLogger.log(Type.ERROR,
+ "Method should accept zero parameters");
+ throw new UnableToCompleteException();
+ }
+
+ // new OnStateChangeMethod(Class declaringClass, String
+ // methodName, String[], properties)
+ w.print("store.addOnStateChangeMethod(%s, new %s(",
+ getClassLiteralString(connector),
+ OnStateChangeMethod.class.getName());
+ if (!connector.equals(method.getEnclosingType())) {
+ w.print("%s, ",
+ getClassLiteralString(method.getEnclosingType()));
+ }
+ w.print("\"%s\", ", method.getName());
+
+ w.print("new String[] {");
+ for (String propertyName : properties) {
+ w.print("\"%s\", ", propertyName);
+ }
+ w.print("}");
+
+ w.println("));");
+
+ w.splitIfNeeded();
+ }
+ }
+ }
+
+ private void writeSuperClasses(SplittingSourceWriter w,
+ ConnectorBundle bundle) {
+ List<JClassType> needsSuperclass = new ArrayList<JClassType>(
+ bundle.getNeedsSuperclass());
+ // Emit in hierarchy order to ensure superclass is defined when
+ // referenced
+ Collections.sort(needsSuperclass, new Comparator<JClassType>() {
+
+ @Override
+ public int compare(JClassType type1, JClassType type2) {
+ int depthDiff = getDepth(type1) - getDepth(type2);
+ if (depthDiff != 0) {
+ return depthDiff;
+ } else {
+ // Just something to get a stable compare
+ return type1.getName().compareTo(type2.getName());
+ }
+ }
+
+ private int getDepth(JClassType type) {
+ int depth = 0;
+ while (type != null) {
+ depth++;
+ type = type.getSuperclass();
+ }
+ return depth;
+ }
+ });
+
+ for (JClassType jClassType : needsSuperclass) {
+ JClassType superclass = jClassType.getSuperclass();
+ while (superclass != null && !superclass.isPublic()) {
+ superclass = superclass.getSuperclass();
+ }
+ String classLiteralString;
+ if (superclass == null) {
+ classLiteralString = "null";
+ } else {
+ classLiteralString = getClassLiteralString(superclass);
+ }
+ w.println("store.setSuperClass(%s, %s);",
+ getClassLiteralString(jClassType), classLiteralString);
+ }
+ }
+
+ private void writeDelegateToWidget(TreeLogger logger,
+ SplittingSourceWriter w, ConnectorBundle bundle) {
+ Map<JClassType, Set<Property>> needsDelegateToWidget = bundle
+ .getNeedsDelegateToWidget();
+ for (Entry<JClassType, Set<Property>> entry : needsDelegateToWidget
+ .entrySet()) {
+ JClassType beanType = entry.getKey();
+ for (Property property : entry.getValue()) {
+ w.println(
+ "store.setDelegateToWidget(%s, \"%s\", \"%s\");",
+ getClassLiteralString(beanType),// property.getBeanType()),
+ property.getName(),
+ property.getAnnotation(DelegateToWidget.class).value());
+ }
+ w.splitIfNeeded();
+ }
+ }
+
+ private void writeSerializers(TreeLogger logger, SplittingSourceWriter 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(");");
+
+ w.splitIfNeeded();
+ }
+ }
+
+ private void writePresentationTypes(SplittingSourceWriter w,
+ ConnectorBundle bundle) {
+ Map<JClassType, JType> presentationTypes = bundle
+ .getPresentationTypes();
+ for (Entry<JClassType, JType> entry : presentationTypes.entrySet()) {
+
+ w.print("store.setPresentationType(");
+ writeClassLiteral(w, entry.getKey());
+ w.print(", ");
+ writeClassLiteral(w, entry.getValue());
+ w.println(");");
+ w.splitIfNeeded();
+ }
+ }
+
+ private void writePropertyTypes(TreeLogger logger, SplittingSourceWriter w,
+ ConnectorBundle bundle) {
+ Set<Property> properties = bundle.getNeedsProperty();
+ 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(");");
+
+ w.splitIfNeeded();
+ }
+ }
+
+ private void writeMethodAttributes(TreeLogger logger,
+ SplittingSourceWriter w, ConnectorBundle bundle) {
+ for (Entry<JClassType, Map<JMethod, Set<MethodAttribute>>> typeEntry : bundle
+ .getMethodAttributes().entrySet()) {
+ JClassType type = typeEntry.getKey();
+ for (Entry<JMethod, Set<MethodAttribute>> methodEntry : typeEntry
+ .getValue().entrySet()) {
+ JMethod method = methodEntry.getKey();
+ Set<MethodAttribute> attributes = methodEntry.getValue();
+ for (MethodAttribute attribute : attributes) {
+ w.println("store.setMethodAttribute(%s, \"%s\", %s.%s);",
+ getClassLiteralString(type), method.getName(),
+ MethodAttribute.class.getCanonicalName(),
+ attribute.name());
+ w.splitIfNeeded();
+ }
+ }
+ }
+ }
+
+ private void writeProxys(SplittingSourceWriter 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("});");
+
+ w.splitIfNeeded();
+ }
+ }
+
+ private void writeParamTypes(SplittingSourceWriter 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("});");
+
+ w.splitIfNeeded();
+ }
+ }
+ }
+
+ private void writeInvokers(TreeLogger logger, SplittingSourceWriter w,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ Map<JClassType, Set<JMethod>> needsInvoker = bundle.getNeedsInvoker();
+ for (Entry<JClassType, Set<JMethod>> entry : needsInvoker.entrySet()) {
+ JClassType type = entry.getKey();
+
+ TreeLogger typeLogger = logger.branch(Type.DEBUG,
+ "Creating invokers for " + type);
+
+ Set<JMethod> methods = entry.getValue();
+ for (JMethod method : methods) {
+ w.print("store.setInvoker(");
+ writeClassLiteral(w, type);
+ w.print(", \"");
+ w.print(escape(method.getName()));
+ w.print("\",");
+
+ if (method.isPublic()) {
+ typeLogger.log(Type.DEBUG, "Invoking " + method.getName()
+ + " using java");
+
+ writeJavaInvoker(w, type, method);
+ } else {
+ TreeLogger methodLogger = typeLogger.branch(Type.DEBUG,
+ "Invoking " + method.getName() + " using jsni");
+ // Must use JSNI to access non-public methods
+ writeJsniInvoker(methodLogger, w, type, method);
+ }
+
+ w.println(");");
+
+ w.splitIfNeeded();
+ }
+ }
+ }
+
+ private void writeJsniInvoker(TreeLogger logger, SplittingSourceWriter w,
+ JClassType type, JMethod method) throws UnableToCompleteException {
+ w.println("new JsniInvoker() {");
+ w.indent();
+
+ w.println(
+ "protected native Object jsniInvoke(Object target, %s<Object> params) /*-{ ",
+ JsArrayObject.class.getName());
+ w.indent();
+
+ JType returnType = method.getReturnType();
+ boolean hasReturnType = !"void".equals(returnType
+ .getQualifiedSourceName());
+
+ // Note that void is also a primitive type
+ boolean hasPrimitiveReturnType = hasReturnType
+ && returnType.isPrimitive() != null;
+
+ if (hasReturnType) {
+ w.print("return ");
+
+ if (hasPrimitiveReturnType) {
+ // Integer.valueOf(expression);
+ w.print("@%s::valueOf(%s)(", returnType.isPrimitive()
+ .getQualifiedBoxedSourceName(), returnType
+ .getJNISignature());
+
+ // Implementation tested briefly, but I don't dare leave it
+ // enabled since we are not using it in the framework and we
+ // have not tests for it.
+ logger.log(Type.ERROR,
+ "JSNI invocation is not yet supported for methods with "
+ + "primitive return types. Change your method "
+ + "to public to be able to use conventional"
+ + " Java invoking instead.");
+ throw new UnableToCompleteException();
+ }
+ }
+
+ JType[] parameterTypes = method.getParameterTypes();
+
+ w.print("target.@%s::" + method.getName() + "(*)(", method
+ .getEnclosingType().getQualifiedSourceName());
+ for (int i = 0; i < parameterTypes.length; i++) {
+ if (i != 0) {
+ w.print(", ");
+ }
+
+ w.print("params[" + i + "]");
+
+ JPrimitiveType primitive = parameterTypes[i].isPrimitive();
+ if (primitive != null) {
+ // param.intValue();
+ w.print(".@%s::%sValue()()",
+ primitive.getQualifiedBoxedSourceName(),
+ primitive.getQualifiedSourceName());
+ }
+ }
+
+ if (hasPrimitiveReturnType) {
+ assert hasReturnType;
+ w.print(")");
+ }
+
+ w.println(");");
+
+ if (!hasReturnType) {
+ w.println("return null;");
+ }
+
+ w.outdent();
+ w.println("}-*/;");
+
+ w.outdent();
+ w.print("}");
+ }
+
+ private void writeJavaInvoker(SplittingSourceWriter w, JClassType type,
+ JMethod method) {
+ 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);
+
+ if (parameterTypeName.startsWith("elemental.json.Json")) {
+ // Need to pass through native method to allow casting Object to
+ // JSO if the value is a string
+ w.print("%s.<%s>obj2jso(params[%d])",
+ JsonDecoder.class.getCanonicalName(),
+ parameterTypeName, i);
+ } else {
+ w.print("(" + parameterTypeName + ") params[" + i + "]");
+ }
+ }
+ w.println(");");
+
+ if (!hasReturnType) {
+ w.println("return null;");
+ }
+
+ w.outdent();
+ w.println("}");
+
+ w.outdent();
+ w.print("}");
+ }
+
+ private void writeReturnTypes(SplittingSourceWriter 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(");");
+
+ w.splitIfNeeded();
+ }
+ }
+ }
+
+ private void writeGwtConstructors(SplittingSourceWriter 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("});");
+
+ w.splitIfNeeded();
+ }
+ }
+
+ 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(SplittingSourceWriter 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(");");
+
+ w.splitIfNeeded();
+ }
+ }
+ }
+
+ 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>());
+ }
+
+ // Find all types with a valid mapping
+ Collection<JClassType> selectedTypes = getConnectorsForWidgetset(
+ logger, typeOracle);
+
+ // Group by load style
+ for (JClassType connectorSubtype : selectedTypes) {
+ 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;
+ }
+
+ /**
+ * Returns the connector types that should be included in the widgetset.
+ * This method can be overridden to create a widgetset only containing
+ * selected connectors.
+ * <p>
+ * The default implementation finds all type implementing
+ * {@link ServerConnector} that have a @{@link Connect} annotation. It also
+ * checks that multiple connectors aren't connected to the same server-side
+ * class.
+ *
+ * @param logger
+ * the logger to which information can be logged
+ * @param typeOracle
+ * the type oracle that can be used for finding types
+ * @return a collection of all the connector types that should be included
+ * in the widgetset
+ * @throws UnableToCompleteException
+ * if the operation fails
+ */
+ protected Collection<JClassType> getConnectorsForWidgetset(
+ TreeLogger logger, TypeOracle typeOracle)
+ throws UnableToCompleteException {
+ JClassType serverConnectorType;
+ try {
+ serverConnectorType = typeOracle.getType(ServerConnector.class
+ .getName());
+ } catch (NotFoundException e) {
+ logger.log(Type.ERROR,
+ "Can't find " + ServerConnector.class.getName());
+ throw new UnableToCompleteException();
+ }
+
+ JClassType[] types = serverConnectorType.getSubtypes();
+
+ Map<String, JClassType> mappings = new TreeMap<String, JClassType>();
+
+ // Keep track of what has happened to avoid logging intermediate state
+ Map<JClassType, List<JClassType>> replaced = new TreeMap<JClassType, List<JClassType>>(
+ ConnectorBundle.jClassComparator);
+
+ for (JClassType type : types) {
+ Connect connectAnnotation = type.getAnnotation(Connect.class);
+ if (connectAnnotation == null) {
+ continue;
+ }
+
+ String identifier = connectAnnotation.value().getCanonicalName();
+
+ JClassType previousMapping = mappings.put(identifier, type);
+ if (previousMapping != null) {
+ // There are multiple mappings, pick the subclass
+ JClassType subclass;
+ JClassType superclass;
+ if (previousMapping.isAssignableFrom(type)) {
+ subclass = type;
+ superclass = previousMapping;
+ } else if (type.isAssignableFrom(previousMapping)) {
+ subclass = previousMapping;
+ superclass = type;
+ } else {
+ // Neither inherits from the other - this is a conflict
+ logger.log(
+ Type.ERROR,
+ "Conflicting @Connect mappings detected for "
+ + identifier
+ + ": "
+ + type.getQualifiedSourceName()
+ + " and "
+ + previousMapping.getQualifiedSourceName()
+ + ". There can only be multiple @Connect mappings for the same server-side type if one is the subclass of the other.");
+ throw new UnableToCompleteException();
+ }
+
+ mappings.put(identifier, subclass);
+
+ // Inherit any previous replacements
+ List<JClassType> previousReplacements = replaced
+ .remove(superclass);
+ if (previousReplacements == null) {
+ previousReplacements = new ArrayList<JClassType>();
+ }
+
+ previousReplacements.add(superclass);
+ replaced.put(subclass, previousReplacements);
+ }
+ }
+
+ // Log the final set of replacements
+ for (Entry<JClassType, List<JClassType>> entry : replaced.entrySet()) {
+ String msg = entry.getKey().getQualifiedSourceName() + " replaces ";
+
+ List<JClassType> list = entry.getValue();
+ for (int i = 0; i < list.size(); i++) {
+ if (i != 0) {
+ msg += ", ";
+ }
+ msg += list.get(i).getQualifiedSourceName();
+ }
+
+ logger.log(Type.INFO, msg);
+ }
+
+ // Return the types of the final mapping
+ return mappings.values();
+ }
+
+ private Collection<TypeVisitor> getVisitors(TypeOracle oracle)
+ throws NotFoundException {
+ List<TypeVisitor> visitors = Arrays.<TypeVisitor> asList(
+ new ConnectorInitVisitor(), new StateInitVisitor(),
+ new WidgetInitVisitor(), new RendererVisitor(),
+ new ClientRpcVisitor(), new ServerRpcVisitor(),
+ new OnStateChangeVisitor());
+ 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/main/java/com/vaadin/server/widgetsetutils/metadata/ArraySerializer.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ArraySerializer.java
new file mode 100644
index 0000000000..0049ae9b50
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ArraySerializer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2000-2014 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.user.rebind.SourceWriter;
+import com.vaadin.client.communication.JsonDecoder;
+import com.vaadin.client.communication.JsonEncoder;
+import com.vaadin.server.widgetsetutils.ConnectorBundleLoaderFactory;
+
+import elemental.json.Json;
+import elemental.json.JsonArray;
+
+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 = ("
+ + JsonArray.class.getName() + ")" + jsonValue + ";");
+
+ // Type value = new Type[jsonArray.size()][][];
+ w.print(arrayType.getQualifiedSourceName() + " value = new "
+ + leafType.getQualifiedSourceName() + "[jsonArray.length()]");
+ 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) {
+ JType componentType = arrayType.getComponentType();
+
+ w.println(JsonArray.class.getName() + " values = "
+ + Json.class.getName() + ".createArray();");
+ // 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],");
+ ConnectorBundleLoaderFactory.writeTypeCreator(w, componentType);
+ w.print(", " + applicationConnection + ")");
+ w.println(");");
+ w.outdent();
+ w.println("}");
+ w.println("return values;");
+ }
+
+}
diff --git a/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java
new file mode 100644
index 0000000000..992a012005
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2000-2014 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.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.JType;
+import com.vaadin.client.metadata.TypeDataStore.MethodAttribute;
+import com.vaadin.shared.annotations.NoLayout;
+
+public class ClientRpcVisitor extends TypeVisitor {
+ @Override
+ public void visitClientRpc(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ checkGenericType(logger, type);
+ Set<? extends JClassType> hierarchy = type
+ .getFlattenedSupertypeHierarchy();
+ for (JClassType subType : hierarchy) {
+ JMethod[] methods = subType.getMethods();
+ for (JMethod method : methods) {
+ checkReturnType(logger, method);
+
+ bundle.setNeedsInvoker(type, method);
+ bundle.setNeedsParamTypes(type, method);
+ if (method.getAnnotation(NoLayout.class) != null) {
+ bundle.setMethodAttribute(type, method,
+ MethodAttribute.NO_LAYOUT);
+ }
+
+ JType[] parameterTypes = method.getParameterTypes();
+ for (JType paramType : parameterTypes) {
+ bundle.setNeedsSerialize(paramType);
+ }
+ }
+ }
+ }
+
+ public static void checkGenericType(TreeLogger logger, JClassType type)
+ throws UnableToCompleteException {
+ if (type.isGenericType() != null) {
+ logger.log(Type.ERROR,
+ "Type " + type.getParameterizedQualifiedSourceName()
+ + "is parameterizied generic. RPC proxy "
+ + "for parameterizied types is not supported.");
+ throw new UnableToCompleteException();
+ }
+ }
+
+ public static void checkReturnType(TreeLogger logger, JMethod method)
+ throws UnableToCompleteException {
+ if (!method.getReturnType().getQualifiedSourceName().equals("void")) {
+ logger.log(
+ Type.ERROR,
+ "The method "
+ + method.getEnclosingType()
+ .getQualifiedSourceName()
+ + "."
+ + method.getName()
+ + " returns "
+ + method.getReturnType().getQualifiedSourceName()
+ + " but only void is supported for methods in RPC interfaces.");
+ throw new UnableToCompleteException();
+ }
+ }
+}
diff --git a/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java
new file mode 100644
index 0000000000..b4531eb08e
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java
@@ -0,0 +1,737 @@
+/*
+ * Copyright 2000-2014 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.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+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.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.communication.JSONSerializer;
+import com.vaadin.client.connectors.AbstractRendererConnector;
+import com.vaadin.client.metadata.TypeDataStore.MethodAttribute;
+import com.vaadin.client.ui.UnknownComponentConnector;
+import com.vaadin.shared.communication.ClientRpc;
+import com.vaadin.shared.communication.ServerRpc;
+import com.vaadin.shared.ui.Connect;
+
+import elemental.json.JsonValue;
+
+public class ConnectorBundle {
+ private static final String FAIL_IF_NOT_SERIALIZABLE = "vFailIfNotSerializable";
+
+ public static final Comparator<JClassType> jClassComparator = new Comparator<JClassType>() {
+ @Override
+ public int compare(JClassType o1, JClassType o2) {
+ return o1.getQualifiedSourceName().compareTo(
+ o2.getQualifiedSourceName());
+ }
+ };
+
+ public static final Comparator<JMethod> jMethodComparator = new Comparator<JMethod>() {
+ @Override
+ public int compare(JMethod o1, JMethod o2) {
+ return o1.getReadableDeclaration().compareTo(
+ o2.getReadableDeclaration());
+ }
+ };
+
+ 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 TreeMap<JType, GeneratedSerializer>(
+ new Comparator<JType>() {
+ @Override
+ public int compare(JType o1, JType o2) {
+ return o1.toString().compareTo(o2.toString());
+ }
+ });
+
+ private final Map<JClassType, Map<JMethod, Set<MethodAttribute>>> methodAttributes = new TreeMap<JClassType, Map<JMethod, Set<MethodAttribute>>>(
+ jClassComparator);
+ private final Set<JClassType> needsSuperClass = new TreeSet<JClassType>(
+ jClassComparator);
+ private final Set<JClassType> needsGwtConstructor = new TreeSet<JClassType>(
+ jClassComparator);
+ private final Set<JClassType> visitedTypes = new HashSet<JClassType>();
+
+ private final Set<JClassType> needsProxySupport = new TreeSet<JClassType>(
+ jClassComparator);
+
+ private final Map<JClassType, JType> presentationTypes = new TreeMap<JClassType, JType>(
+ jClassComparator);
+ private final Map<JClassType, Set<String>> identifiers = new TreeMap<JClassType, Set<String>>(
+ jClassComparator);
+ private final Map<JClassType, Set<JMethod>> needsReturnType = new TreeMap<JClassType, Set<JMethod>>(
+ jClassComparator);
+ private final Map<JClassType, Set<JMethod>> needsInvoker = new TreeMap<JClassType, Set<JMethod>>(
+ jClassComparator);
+ private final Map<JClassType, Set<JMethod>> needsParamTypes = new TreeMap<JClassType, Set<JMethod>>(
+ jClassComparator);
+ private final Map<JClassType, Set<JMethod>> needsOnStateChange = new TreeMap<JClassType, Set<JMethod>>(
+ jClassComparator);
+
+ private final Set<Property> needsProperty = new TreeSet<Property>();
+ private final Map<JClassType, Set<Property>> needsDelegateToWidget = new TreeMap<JClassType, Set<Property>>(
+ jClassComparator);
+
+ 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 (hasSerializeSupport(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");
+
+ JClassType needsSuperClass = typeAsClass;
+ while (needsSuperClass != null) {
+ if (needsSuperClass.isPublic()) {
+ setNeedsSuperclass(needsSuperClass);
+ }
+ needsSuperClass = needsSuperClass.getSuperclass();
+ }
+
+ setNeedsGwtConstructor(typeAsClass);
+
+ for (Property property : getProperties(typeAsClass)) {
+ setNeedsProperty(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);
+ }
+
+ public void setPresentationType(JClassType type, JType presentationType) {
+ if (!hasPresentationType(type)) {
+ presentationTypes.put(type, presentationType);
+ }
+ }
+
+ private boolean hasPresentationType(JClassType type) {
+ if (presentationTypes.containsKey(type)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.hasPresentationType(type);
+ }
+ }
+
+ public Map<JClassType, JType> getPresentationTypes() {
+ return Collections.unmodifiableMap(presentationTypes);
+ }
+
+ private void setNeedsSuperclass(JClassType typeAsClass) {
+ if (!isNeedsSuperClass(typeAsClass)) {
+ needsSuperClass.add(typeAsClass);
+ }
+ }
+
+ private boolean isNeedsSuperClass(JClassType typeAsClass) {
+ if (needsSuperClass.contains(typeAsClass)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsSuperClass(typeAsClass);
+ }
+ }
+
+ public Set<JClassType> getNeedsSuperclass() {
+ return Collections.unmodifiableSet(needsSuperClass);
+ }
+
+ private void setNeedsProperty(Property property) {
+ if (!isNeedsProperty(property)) {
+ needsProperty.add(property);
+ }
+ }
+
+ private boolean isNeedsProperty(Property property) {
+ if (needsProperty.contains(property)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsProperty(property);
+ }
+ }
+
+ public Set<Property> getNeedsProperty() {
+ return Collections.unmodifiableSet(needsProperty);
+ }
+
+ public Collection<Property> getProperties(JClassType type) {
+ Set<Property> properties = new TreeSet<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 isInterfaceType(type, ClientRpc.class);
+ }
+
+ private static boolean isServerRpc(JClassType type) {
+ return isInterfaceType(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);
+ }
+
+ public static boolean isConnectedRendererConnector(JClassType type) {
+ return isConnected(type)
+ && isType(type, AbstractRendererConnector.class);
+ }
+
+ private static boolean isInterfaceType(JClassType type, Class<?> class1) {
+ return type.isInterface() != null && isType(type, class1);
+ }
+
+ 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> void addMapping(Map<K, Set<String>> map, K key, String value) {
+ Set<String> set = map.get(key);
+ if (set == null) {
+ set = new TreeSet<String>();
+ map.put(key, set);
+ }
+ set.add(value);
+ }
+
+ private <K> void addMapping(Map<K, Set<JMethod>> map, K key, JMethod value) {
+ Set<JMethod> set = map.get(key);
+ if (set == null) {
+ set = new TreeSet<JMethod>(jMethodComparator);
+ 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 setMethodAttribute(JClassType type, JMethod method,
+ MethodAttribute methodAttribute) {
+ if (!hasMethodAttribute(type, method, methodAttribute)) {
+ Map<JMethod, Set<MethodAttribute>> typeData = methodAttributes
+ .get(type);
+ if (typeData == null) {
+ typeData = new TreeMap<JMethod, Set<MethodAttribute>>(
+ jMethodComparator);
+ methodAttributes.put(type, typeData);
+ }
+
+ Map<JMethod, Set<MethodAttribute>> methods = methodAttributes
+ .get(type);
+ if (methods == null) {
+ methods = new TreeMap<JMethod, Set<MethodAttribute>>(
+ jMethodComparator);
+ methodAttributes.put(type, methods);
+ }
+
+ Set<MethodAttribute> attributes = methods.get(method);
+ if (attributes == null) {
+ attributes = new TreeSet<MethodAttribute>();
+ methods.put(method, attributes);
+ }
+
+ attributes.add(methodAttribute);
+ }
+ }
+
+ private boolean hasMethodAttribute(JClassType type, JMethod method,
+ MethodAttribute methodAttribute) {
+ Map<JMethod, Set<MethodAttribute>> typeData = methodAttributes
+ .get(type);
+ if (typeData != null && hasMapping(typeData, method, methodAttribute)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.hasMethodAttribute(type, method,
+ methodAttribute);
+ }
+ }
+
+ public Map<JClassType, Map<JMethod, Set<MethodAttribute>>> getMethodAttributes() {
+ return Collections.unmodifiableMap(methodAttributes);
+ }
+
+ public void setNeedsSerialize(JType type) {
+ if (!hasSerializeSupport(type)) {
+ needsSerializeSupport.add(type);
+ }
+ }
+
+ private static Set<Class<?>> frameworkHandledTypes = new LinkedHashSet<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);
+ frameworkHandledTypes.add(Void.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 hasSerializeSupport(JType type) {
+ if (hasSerializeSupport.contains(type)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.hasSerializeSupport(type);
+ }
+ }
+
+ public void setNeedsDelegateToWidget(Property property, JClassType type) {
+ if (!isNeedsDelegateToWidget(type)) {
+ TreeSet<Property> set = new TreeSet<Property>();
+ set.add(property);
+ needsDelegateToWidget.put(type, set);
+ } else if (!needsDelegateToWidget.get(type).contains(property)) {
+ needsDelegateToWidget.get(type).add(property);
+ }
+ }
+
+ private boolean isNeedsDelegateToWidget(JClassType type) {
+ if (needsDelegateToWidget.containsKey(type)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsDelegateToWidget(type);
+ }
+ }
+
+ public Map<JClassType, Set<Property>> getNeedsDelegateToWidget() {
+ return Collections.unmodifiableMap(needsDelegateToWidget);
+ }
+
+ public void setNeedsOnStateChangeHandler(JClassType type, JMethod method) {
+ if (!isNeedsOnStateChangeHandler(type, method)) {
+ addMapping(needsOnStateChange, type, method);
+ }
+ }
+
+ private boolean isNeedsOnStateChangeHandler(JClassType type, JMethod method) {
+ if (hasMapping(needsOnStateChange, type, method)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsOnStateChangeHandler(type, method);
+ }
+ }
+
+ public Map<JClassType, Set<JMethod>> getNeedsOnStateChangeHandler() {
+ return Collections.unmodifiableMap(needsOnStateChange);
+ }
+
+ public static 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/main/java/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java
new file mode 100644
index 0000000000..ea3b097fa2
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2000-2014 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.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+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) throws UnableToCompleteException {
+ Connect connectAnnotation = type.getAnnotation(Connect.class);
+ if (connectAnnotation != null) {
+ logger.log(Type.INFO, type.getName() + " will be in the "
+ + bundle.getName().replaceAll("^_*", "") + " bundle");
+ String identifier = connectAnnotation.value().getCanonicalName();
+
+ bundle.setIdentifier(type, identifier);
+ bundle.setNeedsGwtConstructor(type);
+ }
+ }
+
+}
diff --git a/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java
new file mode 100644
index 0000000000..bb3dd4f61d
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2014 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/main/java/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java
new file mode 100644
index 0000000000..9876baf946
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2014 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.user.rebind.SourceWriter;
+
+import elemental.json.Json;
+
+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 = " + jsonValue + ".asString();");
+ 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 Json.create(castedValue.name());
+ w.println("return " + Json.class.getName() + ".create(" + value
+ + ".name());");
+ }
+
+}
diff --git a/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java
new file mode 100644
index 0000000000..a31dafe05c
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2000-2014 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 boolean hasAccessorMethods() {
+ return true;
+ }
+
+ @Override
+ public void writeSetterBody(TreeLogger logger, SourceWriter w,
+ String beanVariable, String valueVariable) {
+ w.println("%s.@%s::%s = %s;", beanVariable, getBeanType()
+ .getQualifiedSourceName(), getName(), unboxValue(valueVariable));
+ }
+
+ @Override
+ public void writeGetterBody(TreeLogger logger, SourceWriter w,
+ String beanVariable) {
+ String value = String.format("%s.@%s::%s", beanVariable, getBeanType()
+ .getQualifiedSourceName(), getName());
+ w.print("return ");
+ w.print(boxValue(value));
+ w.println(";");
+ }
+
+ 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(field.getEnclosingType(), 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/main/java/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java
new file mode 100644
index 0000000000..6afb172ea2
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2014 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/main/java/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java
new file mode 100644
index 0000000000..a7a6c568da
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2000-2014 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.user.rebind.SourceWriter;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.communication.JSONSerializer;
+import elemental.json.JsonValue;
+
+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/main/java/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java
new file mode 100644
index 0000000000..32aad92774
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2000-2014 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.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 final String getter;
+
+ private MethodProperty(JClassType beanType, JMethod setter, String getter) {
+ super(getTransportFieldName(setter), beanType, setter
+ .getParameterTypes()[0]);
+ this.setter = setter;
+ this.getter = getter;
+ }
+
+ @Override
+ public boolean hasAccessorMethods() {
+ return getter != null;
+ }
+
+ public static Collection<MethodProperty> findProperties(JClassType type) {
+ Collection<MethodProperty> properties = new ArrayList<MethodProperty>();
+
+ Set<String> getters = new HashSet<String>();
+ List<JMethod> setters = getSetters(type, getters);
+ for (JMethod setter : setters) {
+ String getter = findGetter(type, setter);
+ properties.add(new MethodProperty(setter.getEnclosingType(),
+ setter, getters.contains(getter) ? getter : null));
+ }
+
+ return properties;
+ }
+
+ /**
+ * Returns a list of all setters found in the beanType or its parent class
+ *
+ * @param beanType
+ * The type to check
+ * @param getters
+ * Set that will be filled with names of getters.
+ * @return A list of setter methods from the class and its parents
+ */
+ private static List<JMethod> getSetters(JClassType beanType,
+ Set<String> getters) {
+ 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()) {
+ // Not getter/setter, skip to next method
+ continue;
+ }
+ String methodName = method.getName();
+ if (methodName.startsWith("set")
+ && method.getParameterTypes().length == 1) {
+ setterMethods.add(method);
+ } else if (method.getParameterTypes().length == 0
+ && methodName.startsWith("is")
+ || methodName.startsWith("get")) {
+ getters.add(methodName);
+ }
+ }
+ beanType = beanType.getSuperclass();
+ }
+
+ return setterMethods;
+ }
+
+ @Override
+ public void writeGetterBody(TreeLogger logger, SourceWriter w,
+ String beanVariable) {
+ String value = String.format("%s.@%s::%s()()", beanVariable,
+ getBeanType().getQualifiedSourceName(), getter);
+ w.print("return ");
+ w.print(boxValue(value));
+ w.println(";");
+ }
+
+ @Override
+ public void writeSetterBody(TreeLogger logger, SourceWriter w,
+ String beanVariable, String valueVariable) {
+ w.println("%s.@%s::%s(%s)(%s);", beanVariable, getBeanType()
+ .getQualifiedSourceName(), setter.getName(), setter
+ .getParameterTypes()[0].getJNISignature(),
+ unboxValue(valueVariable));
+
+ }
+
+ private static 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/main/java/com/vaadin/server/widgetsetutils/metadata/OnStateChangeVisitor.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/OnStateChangeVisitor.java
new file mode 100644
index 0000000000..1c0da9d9e8
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/OnStateChangeVisitor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2014 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.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.shared.ui.Connect;
+
+/**
+ * Visits Connector classes and check for methods with @OnStateChange
+ * annotations.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class OnStateChangeVisitor extends TypeVisitor {
+
+ @Override
+ public void visitConnector(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ Connect connectAnnotation = type.getAnnotation(Connect.class);
+ if (connectAnnotation != null) {
+ // Find all the annotated methods in all the superclasses
+ JClassType connector = type;
+ while (connector != null) {
+ for (JMethod method : connector.getMethods()) {
+ if (method.getAnnotation(OnStateChange.class) != null) {
+ bundle.setNeedsInvoker(connector, method);
+ bundle.setNeedsOnStateChangeHandler(type, method);
+ }
+ }
+
+ connector = connector.getSuperclass();
+ }
+ }
+ }
+
+}
diff --git a/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/Property.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/Property.java
new file mode 100644
index 0000000000..0c849bead5
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/Property.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2000-2014 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 implements Comparable<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 String boxValue(String codeSnippet) {
+ JPrimitiveType primitive = propertyType.isPrimitive();
+ if (primitive == null) {
+ return codeSnippet;
+ } else {
+ return String.format("@%s::valueOf(%s)(%s)",
+ primitive.getQualifiedBoxedSourceName(),
+ propertyType.getJNISignature(), codeSnippet);
+ }
+ }
+
+ public String unboxValue(String codeSnippet) {
+ JPrimitiveType primitive = propertyType.isPrimitive();
+ if (primitive == null) {
+ return codeSnippet;
+ } else {
+ return String.format("%s.@%s::%sValue()()", codeSnippet,
+ primitive.getQualifiedBoxedSourceName(),
+ primitive.getSimpleSourceName());
+ }
+ }
+
+ 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);
+
+ public abstract boolean hasAccessorMethods();
+
+ @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();
+ }
+
+ @Override
+ public int compareTo(Property o) {
+ int comp = getName().compareTo(o.getName());
+ if (comp == 0) {
+ comp = getBeanType().getQualifiedSourceName().compareTo(
+ o.getBeanType().getQualifiedSourceName());
+ }
+ if (comp == 0) {
+ comp = getClass().getCanonicalName().compareTo(
+ o.getClass().getCanonicalName());
+ }
+ return comp;
+ }
+
+ public abstract <T extends Annotation> T getAnnotation(
+ Class<T> annotationClass);
+
+}
diff --git a/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/RendererVisitor.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/RendererVisitor.java
new file mode 100644
index 0000000000..2e54d00aab
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/RendererVisitor.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2000-2014 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.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.vaadin.client.connectors.AbstractRendererConnector;
+
+/**
+ * Generates type data for renderer connectors.
+ * <ul>
+ * <li>Stores the return type of the overridden
+ * {@link AbstractRendererConnector#getRenderer() getRenderer} method to enable
+ * automatic creation of an instance of the proper renderer type.
+ * <li>Stores the presentation type of the connector to enable the
+ * {@link AbstractRendererConnector#decode(elemental.json.JsonValue) decode}
+ * method to work without having to implement a "getPresentationType" method.
+ * </ul>
+ *
+ * @see WidgetInitVisitor
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class RendererVisitor extends TypeVisitor {
+
+ @Override
+ public void visitConnector(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ if (ConnectorBundle.isConnectedRendererConnector(type)) {
+ doRendererType(logger, type, bundle);
+ doPresentationType(logger, type, bundle);
+ }
+ }
+
+ private static void doRendererType(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ // The class in which createRenderer is implemented
+ JClassType createRendererClass = ConnectorBundle.findInheritedMethod(
+ type, "createRenderer").getEnclosingType();
+
+ // Needs GWT constructor if createRenderer is not overridden
+ if (createRendererClass.getQualifiedSourceName().equals(
+ AbstractRendererConnector.class.getCanonicalName())) {
+
+ JMethod getRenderer = ConnectorBundle.findInheritedMethod(type,
+ "getRenderer");
+ if (getRenderer.getEnclosingType().getQualifiedSourceName()
+ .equals(AbstractRendererConnector.class.getCanonicalName())) {
+ logger.log(Type.ERROR, type.getQualifiedSourceName()
+ + " must override either createRenderer or getRenderer");
+ throw new UnableToCompleteException();
+ }
+ JClassType rendererType = getRenderer.getReturnType().isClass();
+
+ bundle.setNeedsGwtConstructor(rendererType);
+
+ // Also needs renderer type to find the right GWT constructor
+ bundle.setNeedsReturnType(type, getRenderer);
+
+ logger.log(Type.DEBUG, "Renderer type of " + type + " is "
+ + rendererType);
+ }
+ }
+
+ private void doPresentationType(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ JType presentationType = getPresentationType(type, logger);
+ bundle.setPresentationType(type, presentationType);
+
+ bundle.setNeedsSerialize(presentationType);
+
+ logger.log(Type.DEBUG, "Presentation type of " + type + " is "
+ + presentationType);
+ }
+
+ private static JType getPresentationType(JClassType type, TreeLogger logger)
+ throws UnableToCompleteException {
+ JClassType originalType = type;
+ while (type != null) {
+ if (type.getQualifiedBinaryName().equals(
+ AbstractRendererConnector.class.getName())) {
+ JParameterizedType parameterized = type.isParameterized();
+ if (parameterized == null) {
+ logger.log(
+ Type.ERROR,
+ type.getQualifiedSourceName()
+ + " must define the generic parameter of the inherited "
+ + AbstractRendererConnector.class
+ .getSimpleName());
+ throw new UnableToCompleteException();
+ }
+ return parameterized.getTypeArgs()[0];
+ }
+ type = type.getSuperclass();
+ }
+ throw new IllegalArgumentException("The type "
+ + originalType.getQualifiedSourceName() + " does not extend "
+ + AbstractRendererConnector.class.getName());
+ }
+}
diff --git a/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java
new file mode 100644
index 0000000000..86ece28041
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2000-2014 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.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.vaadin.client.metadata.TypeDataStore.MethodAttribute;
+import com.vaadin.shared.annotations.NoLoadingIndicator;
+import com.vaadin.shared.annotations.Delayed;
+
+public class ServerRpcVisitor extends TypeVisitor {
+ @Override
+ public void visitServerRpc(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ ClientRpcVisitor.checkGenericType(logger, type);
+ 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) {
+ ClientRpcVisitor.checkReturnType(logger, method);
+
+ Delayed delayed = method.getAnnotation(Delayed.class);
+ if (delayed != null) {
+ bundle.setMethodAttribute(type, method,
+ MethodAttribute.DELAYED);
+ if (delayed.lastOnly()) {
+ bundle.setMethodAttribute(type, method,
+ MethodAttribute.LAST_ONLY);
+ }
+ }
+
+ if (method.getAnnotation(NoLoadingIndicator.class) != null) {
+ bundle.setMethodAttribute(type, method,
+ MethodAttribute.NO_LOADING_INDICATOR);
+ }
+
+ bundle.setNeedsParamTypes(type, method);
+
+ JType[] parameterTypes = method.getParameterTypes();
+ for (JType paramType : parameterTypes) {
+ bundle.setNeedsSerialize(paramType);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java
new file mode 100644
index 0000000000..046c5c4611
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2014 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.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 = ConnectorBundle
+ .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/main/java/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java
new file mode 100644
index 0000000000..028e4cc44d
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2014 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.JClassType;
+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) throws UnableToCompleteException {
+ // Default does nothing
+ }
+
+ public void visitServerRpc(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ // Default does nothing
+ }
+
+}
diff --git a/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java
new file mode 100644
index 0000000000..9a9cac18ba
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2000-2014 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.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)) {
+ // The class in which createWidget is implemented
+ JClassType createWidgetClass = ConnectorBundle.findInheritedMethod(
+ type, "createWidget").getEnclosingType();
+
+ JMethod getWidget = ConnectorBundle.findInheritedMethod(type,
+ "getWidget");
+ JClassType widgetType = getWidget.getReturnType().isClass();
+
+ // Needs GWT constructor if createWidget is not overridden
+ if (createWidgetClass.getQualifiedSourceName().equals(
+ AbstractComponentConnector.class.getCanonicalName())) {
+ if (getWidget
+ .getEnclosingType()
+ .getQualifiedSourceName()
+ .equals(AbstractComponentConnector.class
+ .getCanonicalName())) {
+ logger.log(Type.ERROR, type.getQualifiedSourceName()
+ + " must override either createWidget or getWidget");
+ throw new UnableToCompleteException();
+ }
+
+ bundle.setNeedsGwtConstructor(widgetType);
+
+ // Also needs widget type to find the right GWT constructor
+ bundle.setNeedsReturnType(type, getWidget);
+ }
+
+ // Check state properties for @DelegateToWidget
+ JMethod getState = ConnectorBundle.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) {
+ // Generate meta data required for @DelegateToWidget
+ bundle.setNeedsDelegateToWidget(property, stateType);
+
+ // Find the delegate target method
+ String methodName = DelegateToWidget.Helper
+ .getDelegateTarget(property.getName(),
+ delegateToWidget.value());
+ JMethod delegatedSetter = ConnectorBundle
+ .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);
+
+ // GWT code needs widget type to find the target method
+ bundle.setNeedsReturnType(type, getWidget);
+ }
+ }
+
+ }
+ }
+}
diff --git a/client-compiler/src/main/java/com/vaadin/tools/CvalAddonsChecker.java b/client-compiler/src/main/java/com/vaadin/tools/CvalAddonsChecker.java
new file mode 100644
index 0000000000..aab7231258
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/tools/CvalAddonsChecker.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2000-2014 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.tools;
+
+import static com.vaadin.tools.CvalChecker.LINE;
+import static com.vaadin.tools.CvalChecker.computeMajorVersion;
+import static com.vaadin.tools.CvalChecker.getErrorMessage;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import com.vaadin.client.metadata.ConnectorBundleLoader.CValUiInfo;
+import com.vaadin.tools.CvalChecker.CvalInfo;
+import com.vaadin.tools.CvalChecker.CvalServer;
+import com.vaadin.tools.CvalChecker.InvalidCvalException;
+import com.vaadin.tools.CvalChecker.UnreachableCvalServerException;
+
+/**
+ * This class is able to visit all MANIFEST.MF files present in the classpath,
+ * filter by name, and check if the user has a valid license.
+ *
+ * Manifest files should have a few attributes indicating the license type of
+ * the addon:
+ * <ul>
+ * <li>Implementation-Version: 4.x.x
+ * <li>AdVaaName: addon_name
+ * <li>AdVaaLicen: cval, agpl, empty
+ * <li>AdVaaPkg: package of the widgets in this addon
+ * </ul>
+ *
+ * The class also have a method to check just one product.
+ *
+ * @since 7.3
+ */
+public final class CvalAddonsChecker {
+
+ // Manifest attributes
+ public static final String VAADIN_ADDON_LICENSE = "AdVaaLicen";
+ public static final String VAADIN_ADDON_NAME = "AdVaaName";
+ public static final String VAADIN_ADDON_WIDGETSET = "Vaadin-Widgetsets";
+ public static final String VAADIN_ADDON_VERSION = "Implementation-Version";
+ public static final String VAADIN_ADDON_TITLE = "Implementation-Title";
+
+ // License types
+ public static final String VAADIN_AGPL = "agpl";
+ public static final String VAADIN_CVAL = "cval";
+
+ private CvalChecker cvalChecker = new CvalChecker();
+ private String filterPattern;
+
+ /**
+ * The constructor.
+ */
+ public CvalAddonsChecker() {
+ setLicenseProvider(new CvalServer());
+ setFilter(".*vaadin.*");
+ }
+
+ /**
+ * Visit all MANIFEST.MF files in the classpath validating licenses.
+ *
+ * Return a list of Cval licensed products in order to have enough info to
+ * generate nag messages in the UI.
+ */
+ public List<CValUiInfo> run() throws InvalidCvalException {
+ List<CValUiInfo> ret = new ArrayList<CValUiInfo>();
+ try {
+ // Visit all MANIFEST in our classpath
+ Enumeration<URL> manifests = Thread.currentThread()
+ .getContextClassLoader()
+ .getResources(JarFile.MANIFEST_NAME);
+ while (manifests.hasMoreElements()) {
+ try {
+ URL url = manifests.nextElement();
+ // Discard manifests whose name does not match the filter
+ // pattern
+ if (!url.getPath().matches(filterPattern)) {
+ continue;
+ }
+ InputStream is = url.openStream();
+ // Should never happen, but we don't want a NPE here
+ if (is == null) {
+ continue;
+ }
+ // Read manifest attributes
+ Manifest manifest = new Manifest(is);
+ Attributes attribs = manifest.getMainAttributes();
+ String license = attribs.getValue(VAADIN_ADDON_LICENSE);
+ String name = attribs.getValue(VAADIN_ADDON_NAME);
+ String vers = attribs.getValue(VAADIN_ADDON_VERSION) == null ? ""
+ : attribs.getValue(VAADIN_ADDON_VERSION);
+ String title = attribs.getValue(VAADIN_ADDON_TITLE) == null ? name
+ : attribs.getValue(VAADIN_ADDON_TITLE);
+
+ String widgetsets = attribs
+ .getValue(VAADIN_ADDON_WIDGETSET) == null ? name
+ : attribs.getValue(VAADIN_ADDON_WIDGETSET);
+
+ if (name == null || license == null) {
+ continue;
+ }
+ if (VAADIN_AGPL.equals(license)) {
+ // For agpl version we print an info message
+ printAgplLicense(title, vers);
+ } else if (VAADIN_CVAL.equals(license)) {
+ // We only check cval licensed products
+ CvalInfo info;
+ try {
+ info = cvalChecker.validateProduct(name, vers,
+ title);
+ printValidLicense(info, title, vers);
+ } catch (UnreachableCvalServerException e) {
+ info = CvalChecker.parseJson("{'product':{'name':'"
+ + name + "'}}");
+ printServerUnreachable(title, vers);
+ }
+ for (String w : widgetsets.split("[, ]+")) {
+ ret.add(new CValUiInfo(title, String
+ .valueOf(computeMajorVersion(vers)), w,
+ info.getType()));
+ }
+ }
+ } catch (IOException ignored) {
+ }
+ }
+ } catch (IOException ignored) {
+ }
+ return ret;
+ }
+
+ /**
+ * Set the filter regexp of .jar names which we have to consider.
+ *
+ * default is '.*touchkit.*'
+ */
+ public CvalAddonsChecker setFilter(String regexp) {
+ filterPattern = regexp;
+ return this;
+ }
+
+ /*
+ * Change the license provider, only used in tests.
+ */
+ protected CvalAddonsChecker setLicenseProvider(CvalServer p) {
+ cvalChecker.setLicenseProvider(p);
+ return this;
+ }
+
+ private void printAgplLicense(String name, String version) {
+ System.out.println(LINE + "\n"
+ + getErrorMessage("agpl", name, computeMajorVersion(version))
+ + "\n" + LINE);
+ }
+
+ private void printServerUnreachable(String name, String version) {
+ System.out.println(LINE
+ + "\n"
+ + getErrorMessage("unreachable", name,
+ computeMajorVersion(version)) + "\n" + LINE);
+ }
+
+ private void printValidLicense(CvalInfo info, String title, String version) {
+ String msg = info.getMessage();
+ if (msg == null) {
+ String key = "evaluation".equals(info.getType()) ? "evaluation"
+ : "valid";
+ msg = getErrorMessage(key, title, computeMajorVersion(version),
+ info.getLicensee());
+ }
+ System.out.println("\n" + LINE + "\n" + msg + "\n" + LINE + "\n");
+ }
+}
diff --git a/client-compiler/src/main/java/com/vaadin/tools/CvalChecker.java b/client-compiler/src/main/java/com/vaadin/tools/CvalChecker.java
new file mode 100644
index 0000000000..9217781695
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/tools/CvalChecker.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright 2000-2014 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.tools;
+
+import static java.lang.Integer.parseInt;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.ResourceBundle;
+import java.util.prefs.Preferences;
+
+import org.apache.commons.io.IOUtils;
+
+import elemental.json.JsonException;
+import elemental.json.JsonNull;
+import elemental.json.JsonObject;
+import elemental.json.impl.JsonUtil;
+
+/**
+ * This class is able to validate the vaadin CVAL license.
+ *
+ * It reads the developer license file and asks the server to validate the
+ * licenseKey. If the license is invalid it throws an exception with the
+ * information about the problem and the server response.
+ *
+ * @since 7.3
+ */
+public final class CvalChecker {
+
+ /*
+ * Class used for binding the JSON gotten from server.
+ *
+ * It is not in a separate f le, so as it is easier to copy into any product
+ * which does not depend on vaadin core.
+ *
+ * We are using elemental.json in order not to use additional dependency
+ * like auto-beans, gson, etc.
+ */
+ public static class CvalInfo {
+
+ public static class Product {
+ private JsonObject o;
+
+ public Product(JsonObject o) {
+ this.o = o;
+ }
+
+ public String getName() {
+ return get(o, "name", String.class);
+ }
+
+ public Integer getVersion() {
+ return get(o, "version", Integer.class);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T get(JsonObject o, String k, Class<T> clz) {
+ Object ret = null;
+ try {
+ if (o == null || o.get(k) == null
+ || o.get(k) instanceof JsonNull) {
+ return null;
+ }
+ if (clz == String.class) {
+ ret = o.getString(k);
+ } else if (clz == JsonObject.class) {
+ ret = o.getObject(k);
+ } else if (clz == Integer.class) {
+ ret = Integer.valueOf((int) o.getNumber(k));
+ } else if (clz == Date.class) {
+ ret = new Date((long) o.getNumber(k));
+ } else if (clz == Boolean.class) {
+ ret = o.getBoolean(k);
+ }
+ } catch (JsonException e) {
+ }
+ return (T) ret;
+ }
+
+ private JsonObject o;
+
+ private Product product;
+
+ public CvalInfo(JsonObject o) {
+ this.o = o;
+ product = new Product(get(o, "product", JsonObject.class));
+ }
+
+ public Boolean getExpired() {
+ return get(o, "expired", Boolean.class);
+ }
+
+ public Date getExpiredEpoch() {
+ return get(o, "expiredEpoch", Date.class);
+ }
+
+ public String getLicensee() {
+ return get(o, "licensee", String.class);
+ }
+
+ public String getLicenseKey() {
+ return get(o, "licenseKey", String.class);
+ }
+
+ public String getMessage() {
+ return get(o, "message", String.class);
+ }
+
+ public Product getProduct() {
+ return product;
+ }
+
+ public String getType() {
+ return get(o, "type", String.class);
+ }
+
+ public void setExpiredEpoch(Date expiredEpoch) {
+ o.put("expiredEpoch", expiredEpoch.getTime());
+ }
+
+ public void setMessage(String msg) {
+ o.put("message", msg);
+ }
+
+ @Override
+ public String toString() {
+ return o.toString();
+ }
+
+ public boolean isLicenseExpired() {
+ return (getExpired() != null && getExpired())
+ || (getExpiredEpoch() != null && getExpiredEpoch().before(
+ new Date()));
+ }
+
+ public boolean isValidVersion(int majorVersion) {
+ return getProduct().getVersion() == null
+ || getProduct().getVersion() >= majorVersion;
+
+ }
+
+ private boolean isValidInfo(String name, String key) {
+ return getProduct() != null && getProduct().getName() != null
+ && getLicenseKey() != null
+ && getProduct().getName().equals(name)
+ && getLicenseKey().equals(key);
+ }
+ }
+
+ /*
+ * The class with the method for getting json from server side. It is here
+ * and protected just for replacing it in tests.
+ */
+ public static class CvalServer {
+ protected String licenseUrl = LICENSE_URL_PROD;
+
+ String askServer(String productName, String productKey, int timeoutMs)
+ throws IOException {
+ String url = licenseUrl + productKey;
+ URLConnection con;
+ try {
+ // Send some additional info in the User-Agent string.
+ String ua = "Cval " + productName + " " + productKey + " "
+ + getFirstLaunch();
+ for (String prop : Arrays.asList("java.vendor.url",
+ "java.version", "os.name", "os.version", "os.arch")) {
+ ua += " " + System.getProperty(prop, "-").replace(" ", "_");
+ }
+ con = new URL(url).openConnection();
+ con.setRequestProperty("User-Agent", ua);
+ con.setConnectTimeout(timeoutMs);
+ con.setReadTimeout(timeoutMs);
+ String r = IOUtils.toString(con.getInputStream());
+ return r;
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /*
+ * Get the GWT firstLaunch timestamp.
+ */
+ String getFirstLaunch() {
+ try {
+ Class<?> clz = Class
+ .forName("com.google.gwt.dev.shell.CheckForUpdates");
+ return Preferences.userNodeForPackage(clz).get("firstLaunch",
+ "-");
+ } catch (ClassNotFoundException e) {
+ return "-";
+ }
+ }
+ }
+
+ /**
+ * Exception thrown when the user does not have a valid cval license.
+ */
+ public static class InvalidCvalException extends Exception {
+ private static final long serialVersionUID = 1L;
+ public final CvalInfo info;
+ public final String name;
+ public final String key;
+ public final String version;
+ public final String title;
+
+ public InvalidCvalException(String name, String version, String title,
+ String key, CvalInfo info) {
+ super(composeMessage(title, version, key, info));
+ this.info = info;
+ this.name = name;
+ this.key = key;
+ this.version = version;
+ this.title = title;
+ }
+
+ static String composeMessage(String title, String version, String key,
+ CvalInfo info) {
+ String msg = "";
+ int majorVers = computeMajorVersion(version);
+
+ if (info != null && !info.isValidVersion(majorVers)) {
+ msg = getErrorMessage("invalid", title, majorVers);
+ } else if (info != null && info.getMessage() != null) {
+ msg = info.getMessage().replace("\\n", "\n");
+ } else if (info != null && info.isLicenseExpired()) {
+ String type = "evaluation".equals(info.getType()) ? "Evaluation license"
+ : "License";
+ msg = getErrorMessage("expired", title, majorVers, type);
+ } else if (key == null) {
+ msg = getErrorMessage("none", title, majorVers);
+ } else {
+ msg = getErrorMessage("invalid", title, majorVers);
+ }
+ return msg;
+ }
+ }
+
+ /**
+ * Exception thrown when the license server is unreachable
+ */
+ public static class UnreachableCvalServerException extends Exception {
+ private static final long serialVersionUID = 1L;
+ public final String name;
+
+ public UnreachableCvalServerException(String name, Exception e) {
+ super(e);
+ this.name = name;
+ }
+ }
+
+ public static final String LINE = "----------------------------------------------------------------------------------------------------------------------";
+
+ static final int GRACE_DAYS_MSECS = 2 * 24 * 60 * 60 * 1000;
+
+ private static final String LICENSE_URL_PROD = "https://tools.vaadin.com/vaadin-license-server/licenses/";
+
+ /*
+ * used in tests
+ */
+ static void cacheLicenseInfo(CvalInfo info) {
+ if (info != null) {
+ Preferences p = Preferences.userNodeForPackage(CvalInfo.class);
+ if (info.toString().length() > Preferences.MAX_VALUE_LENGTH) {
+ // This should never happen since MAX_VALUE_LENGTH is big
+ // enough.
+ // But server could eventually send a very big message, so we
+ // discard it in cache and would use hard-coded messages.
+ info.setMessage(null);
+ }
+ p.put(info.getProduct().getName(), info.toString());
+ }
+ }
+
+ /*
+ * used in tests
+ */
+ static void deleteCache(String productName) {
+ Preferences p = Preferences.userNodeForPackage(CvalInfo.class);
+ p.remove(productName);
+ }
+
+ /**
+ * Given a product name returns the name of the file with the license key.
+ *
+ * Traditionally we have delivered license keys with a name like
+ * 'vaadin.touchkit.developer.license' but our database product name is
+ * 'vaadin-touchkit' so we have to replace '-' by '.' to maintain
+ * compatibility.
+ */
+ static final String computeLicenseName(String productName) {
+ return productName.replace("-", ".") + ".developer.license";
+ }
+
+ static final int computeMajorVersion(String productVersion) {
+ return productVersion == null || productVersion.isEmpty() ? 0
+ : parseInt(productVersion.replaceFirst("[^\\d]+.*$", ""));
+ }
+
+ /*
+ * used in tests
+ */
+ static CvalInfo parseJson(String json) {
+ if (json == null) {
+ return null;
+ }
+ try {
+ JsonObject o = JsonUtil.parse(json);
+ return new CvalInfo(o);
+ } catch (JsonException e) {
+ return null;
+ }
+ }
+
+ private CvalServer provider;
+
+ /**
+ * The constructor.
+ */
+ public CvalChecker() {
+ setLicenseProvider(new CvalServer());
+ }
+
+ /**
+ * Validate whether there is a valid license key for a product.
+ *
+ * @param productName
+ * for example vaadin-touchkit
+ * @param productVersion
+ * for instance 4.0.1
+ * @return CvalInfo Server response or cache response if server is offline
+ * @throws InvalidCvalException
+ * when there is no a valid license for the product
+ * @throws UnreachableCvalServerException
+ * when we have license key but server is unreachable
+ */
+ public CvalInfo validateProduct(String productName, String productVersion,
+ String productTitle) throws InvalidCvalException,
+ UnreachableCvalServerException {
+ String key = getDeveloperLicenseKey(productName, productVersion,
+ productTitle);
+
+ CvalInfo info = null;
+ if (key != null && !key.isEmpty()) {
+ info = getCachedLicenseInfo(productName);
+ if (info != null && !info.isValidInfo(productName, key)) {
+ deleteCache(productName);
+ info = null;
+ }
+ info = askLicenseServer(productName, key, productVersion, info);
+ if (info != null && info.isValidInfo(productName, key)
+ && info.isValidVersion(computeMajorVersion(productVersion))
+ && !info.isLicenseExpired()) {
+ return info;
+ }
+ }
+
+ throw new InvalidCvalException(productName, productVersion,
+ productTitle, key, info);
+ }
+
+ /*
+ * Change the license provider, only used in tests.
+ */
+ final CvalChecker setLicenseProvider(CvalServer p) {
+ provider = p;
+ return this;
+ }
+
+ private CvalInfo askLicenseServer(String productName, String productKey,
+ String productVersion, CvalInfo info)
+ throws UnreachableCvalServerException {
+
+ int majorVersion = computeMajorVersion(productVersion);
+
+ // If we have a valid license info here, it means that we got it from
+ // cache.
+ // We add a grace time when so as if the server is unreachable
+ // we allow the user to use the product.
+ if (info != null && info.getExpiredEpoch() != null
+ && !"evaluation".equals(info.getType())) {
+ long ts = info.getExpiredEpoch().getTime() + GRACE_DAYS_MSECS;
+ info.setExpiredEpoch(new Date(ts));
+ }
+
+ boolean validCache = info != null
+ && info.isValidInfo(productName, productKey)
+ && info.isValidVersion(majorVersion)
+ && !info.isLicenseExpired();
+
+ // if we have a validCache we set the timeout smaller
+ int timeout = validCache ? 2000 : 10000;
+
+ try {
+ CvalInfo srvinfo = parseJson(provider.askServer(productName + "-"
+ + productVersion, productKey, timeout));
+ if (srvinfo != null && srvinfo.isValidInfo(productName, productKey)
+ && srvinfo.isValidVersion(majorVersion)) {
+ // We always cache the info if it is valid although it is
+ // expired
+ cacheLicenseInfo(srvinfo);
+ info = srvinfo;
+ }
+ } catch (FileNotFoundException e) {
+ // 404
+ return null;
+ } catch (Exception e) {
+ if (info == null) {
+ throw new UnreachableCvalServerException(productName, e);
+ }
+ }
+ return info;
+ }
+
+ private CvalInfo getCachedLicenseInfo(String productName) {
+ Preferences p = Preferences.userNodeForPackage(CvalInfo.class);
+ String json = p.get(productName, "");
+ if (!json.isEmpty()) {
+ CvalInfo info = parseJson(json);
+ if (info != null) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ private String getDeveloperLicenseKey(String productName,
+ String productVersion, String productTitle)
+ throws InvalidCvalException {
+ String licenseName = computeLicenseName(productName);
+
+ String key = System.getProperty(licenseName);
+ if (key != null && !key.isEmpty()) {
+ return key;
+ }
+
+ try {
+ String dotLicenseName = "." + licenseName;
+ String userHome = System.getProperty("user.home");
+ for (URL url : new URL[] {
+ new File(userHome, dotLicenseName).toURI().toURL(),
+ new File(userHome, licenseName).toURI().toURL(),
+ URL.class.getResource("/" + dotLicenseName),
+ URL.class.getResource("/" + licenseName) }) {
+ if (url != null) {
+ try {
+ key = readKeyFromFile(url,
+ computeMajorVersion(productVersion));
+ if (key != null && !(key = key.trim()).isEmpty()) {
+ return key;
+ }
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ throw new InvalidCvalException(productName, productVersion,
+ productTitle, null, null);
+ }
+
+ String readKeyFromFile(URL url, int majorVersion) throws IOException {
+ String majorVersionStr = String.valueOf(majorVersion);
+ List<String> lines = IOUtils.readLines(url.openStream());
+ String defaultKey = null;
+ for (String line : lines) {
+ String[] parts = line.split("\\s*=\\s*");
+ if (parts.length < 2) {
+ defaultKey = parts[0].trim();
+ }
+ if (parts[0].equals(majorVersionStr)) {
+ return parts[1].trim();
+ }
+ }
+ return defaultKey;
+ }
+
+ static String getErrorMessage(String key, Object... pars) {
+ Locale loc = Locale.getDefault();
+ ResourceBundle res = ResourceBundle.getBundle(
+ CvalChecker.class.getName(), loc);
+ String msg = res.getString(key);
+ return new MessageFormat(msg, loc).format(pars);
+ }
+}
diff --git a/client-compiler/src/main/java/com/vaadin/tools/WidgetsetCompiler.java b/client-compiler/src/main/java/com/vaadin/tools/WidgetsetCompiler.java
new file mode 100755
index 0000000000..7c06e9d7af
--- /dev/null
+++ b/client-compiler/src/main/java/com/vaadin/tools/WidgetsetCompiler.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2000-2014 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.tools;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.server.widgetsetutils.WidgetSetBuilder;
+
+/**
+ * A wrapper for the GWT compiler that runs the compiler in a new thread after
+ * updating the widgetset file.
+ *
+ * This class originally existed to allow circumventing a J2SE 5.0 bug (6316197)
+ * that prevents setting the stack size for the main thread.
+ *
+ * This class takes the same command line arguments as the
+ * com.google.gwt.dev.Compiler class.
+ *
+ * A typical invocation would use e.g. the following arguments
+ *
+ * "-war WebContent/VAADIN/widgetsets com.vaadin.DefaultWidgetSet"
+ *
+ * In addition, larger memory usage settings for the VM should be used, e.g.
+ *
+ * "-Xms256M -Xmx512M -Xss8M"
+ *
+ * The source directory containing widgetset and related classes must be
+ * included in the classpath, as well as other relevant JARs.
+ *
+ * @deprecated with Java 6, can use com.google.gwt.dev.Compiler directly (also
+ * in Eclipse plug-in etc.)
+ */
+@Deprecated
+public class WidgetsetCompiler {
+
+ /**
+ * @param args
+ * same arguments as for com.google.gwt.dev.Compiler
+ */
+ public static void main(final String[] args) {
+ try {
+ // run the compiler in a different thread to enable using the
+ // user-set stack size
+
+ // on Windows, the default stack size is too small for the main
+ // thread and cannot be changed in JRE 1.5 (see
+ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6316197)
+
+ Runnable runCompiler = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // GWTCompiler.main(args);
+ // avoid warnings
+
+ String wsname = args[args.length - 1];
+
+ // TODO expecting this is launched via eclipse WTP
+ // project
+ System.out
+ .println("Updating GWT module description file...");
+ WidgetSetBuilder.updateWidgetSet(wsname);
+ System.out.println("Done.");
+
+ System.out.println("Starting GWT compiler");
+ com.google.gwt.dev.Compiler.main(args);
+ } catch (Throwable thr) {
+ getLogger().log(Level.SEVERE,
+ "Widgetset compilation failed", thr);
+ }
+ }
+ };
+ Thread runThread = new Thread(runCompiler);
+ runThread.start();
+ runThread.join();
+ System.out.println("Widgetset compilation finished");
+ } catch (Throwable thr) {
+ getLogger().log(Level.SEVERE, "Widgetset compilation failed", thr);
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(WidgetsetCompiler.class.getName());
+ }
+}