|
|
@@ -1,434 +0,0 @@ |
|
|
|
/* |
|
|
|
* Copyright 2011 Vaadin Ltd. |
|
|
|
* |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not |
|
|
|
* use this file except in compliance with the License. You may obtain a copy of |
|
|
|
* the License at |
|
|
|
* |
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
|
|
* |
|
|
|
* Unless required by applicable law or agreed to in writing, software |
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
|
|
* License for the specific language governing permissions and limitations under |
|
|
|
* the License. |
|
|
|
*/ |
|
|
|
package com.vaadin.server.widgetsetutils; |
|
|
|
|
|
|
|
import java.io.PrintWriter; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.Collection; |
|
|
|
import java.util.Date; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.Iterator; |
|
|
|
import java.util.LinkedList; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.TreeSet; |
|
|
|
|
|
|
|
import com.google.gwt.core.ext.Generator; |
|
|
|
import com.google.gwt.core.ext.GeneratorContext; |
|
|
|
import com.google.gwt.core.ext.TreeLogger; |
|
|
|
import com.google.gwt.core.ext.TreeLogger.Type; |
|
|
|
import com.google.gwt.core.ext.UnableToCompleteException; |
|
|
|
import com.google.gwt.core.ext.typeinfo.JClassType; |
|
|
|
import com.google.gwt.core.ext.typeinfo.TypeOracle; |
|
|
|
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; |
|
|
|
import com.google.gwt.user.rebind.SourceWriter; |
|
|
|
import com.vaadin.client.ServerConnector; |
|
|
|
import com.vaadin.client.ui.UnknownComponentConnector; |
|
|
|
import com.vaadin.client.ui.ui.UIConnector; |
|
|
|
import com.vaadin.server.ClientConnector; |
|
|
|
import com.vaadin.shared.Connector; |
|
|
|
import com.vaadin.shared.ui.Connect; |
|
|
|
import com.vaadin.shared.ui.Connect.LoadStyle; |
|
|
|
|
|
|
|
/** |
|
|
|
* WidgetMapGenerator's are GWT generator to build WidgetMapImpl dynamically |
|
|
|
* based on {@link Connect} annotations available in workspace. By modifying the |
|
|
|
* generator it is possible to do some fine tuning for the generated widgetset |
|
|
|
* (aka client side engine). The components to be included in the client side |
|
|
|
* engine can modified be overriding {@link #getUsedConnectors()}. |
|
|
|
* <p> |
|
|
|
* The generator also decides how the client side component implementations are |
|
|
|
* loaded to the browser. The default generator is |
|
|
|
* {@link EagerWidgetMapGenerator} that builds a monolithic client side engine |
|
|
|
* that loads all widget implementation on application initialization. This has |
|
|
|
* been the only option until Vaadin 6.4. |
|
|
|
* <p> |
|
|
|
* This generator uses the loadStyle hints from the {@link Connect} annotations. |
|
|
|
* Depending on the {@link LoadStyle} used, the widget may be included in the |
|
|
|
* initially loaded JavaScript, loaded when the application has started and |
|
|
|
* there is no communication to server or lazy loaded when the implementation is |
|
|
|
* absolutely needed. |
|
|
|
* <p> |
|
|
|
* The GWT module description file of the widgetset ( |
|
|
|
* <code>...Widgetset.gwt.xml</code>) can be used to define the |
|
|
|
* WidgetMapGenarator. An example that defines this generator to be used: |
|
|
|
* |
|
|
|
* <pre> |
|
|
|
* <code> |
|
|
|
* <generate-with |
|
|
|
* class="com.vaadin.server.widgetsetutils.MyWidgetMapGenerator"> |
|
|
|
* <when-type-is class="com.vaadin.client.WidgetMap" /> |
|
|
|
* </generate-with> |
|
|
|
* |
|
|
|
* </code> |
|
|
|
* </pre> |
|
|
|
* |
|
|
|
* <p> |
|
|
|
* Vaadin package also includes {@link LazyWidgetMapGenerator}, which is a good |
|
|
|
* option if the transferred data should be minimized, and |
|
|
|
* {@link CustomWidgetMapGenerator} for easy overriding of loading strategies. |
|
|
|
* |
|
|
|
*/ |
|
|
|
public class WidgetMapGenerator extends Generator { |
|
|
|
|
|
|
|
private static String serverConnectorClassName = ServerConnector.class |
|
|
|
.getName(); |
|
|
|
|
|
|
|
private String packageName; |
|
|
|
private String className; |
|
|
|
|
|
|
|
@Override |
|
|
|
public String generate(TreeLogger logger, GeneratorContext context, |
|
|
|
String typeName) throws UnableToCompleteException { |
|
|
|
|
|
|
|
try { |
|
|
|
TypeOracle typeOracle = context.getTypeOracle(); |
|
|
|
|
|
|
|
// get classType and save instance variables |
|
|
|
JClassType classType = typeOracle.getType(typeName); |
|
|
|
packageName = classType.getPackage().getName(); |
|
|
|
className = classType.getSimpleSourceName() + "Impl"; |
|
|
|
// Generate class source code |
|
|
|
generateClass(logger, context); |
|
|
|
} catch (Exception e) { |
|
|
|
logger.log(TreeLogger.ERROR, "WidgetMap creation failed", e); |
|
|
|
} |
|
|
|
// return the fully qualifed name of the class generated |
|
|
|
return packageName + "." + className; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Generate source code for WidgetMapImpl |
|
|
|
* |
|
|
|
* @param logger |
|
|
|
* Logger object |
|
|
|
* @param context |
|
|
|
* Generator context |
|
|
|
* @throws UnableToCompleteException |
|
|
|
*/ |
|
|
|
private void generateClass(TreeLogger logger, GeneratorContext context) |
|
|
|
throws UnableToCompleteException { |
|
|
|
// get print writer that receives the source code |
|
|
|
PrintWriter printWriter = null; |
|
|
|
printWriter = context.tryCreate(logger, packageName, className); |
|
|
|
// print writer if null, source code has ALREADY been generated, |
|
|
|
// return (WidgetMap is equal to all permutations atm) |
|
|
|
if (printWriter == null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
logger.log(Type.INFO, |
|
|
|
"Detecting Vaadin connectors in classpath to generate WidgetMapImpl.java ..."); |
|
|
|
Date date = new Date(); |
|
|
|
|
|
|
|
// init composer, set class properties, create source writer |
|
|
|
ClassSourceFileComposerFactory composer = null; |
|
|
|
composer = new ClassSourceFileComposerFactory(packageName, className); |
|
|
|
composer.addImport("com.google.gwt.core.client.GWT"); |
|
|
|
composer.addImport("java.util.HashMap"); |
|
|
|
composer.addImport("com.google.gwt.core.client.RunAsyncCallback"); |
|
|
|
composer.setSuperclass("com.vaadin.client.WidgetMap"); |
|
|
|
SourceWriter sourceWriter = composer.createSourceWriter(context, |
|
|
|
printWriter); |
|
|
|
|
|
|
|
Collection<Class<? extends ServerConnector>> connectors = getUsedConnectors(context |
|
|
|
.getTypeOracle()); |
|
|
|
|
|
|
|
validateConnectors(logger, connectors); |
|
|
|
logConnectors(logger, context, connectors); |
|
|
|
|
|
|
|
// generator constructor source code |
|
|
|
generateImplementationDetector(logger, sourceWriter, connectors); |
|
|
|
generateInstantiatorMethod(sourceWriter, connectors); |
|
|
|
// close generated class |
|
|
|
sourceWriter.outdent(); |
|
|
|
sourceWriter.println("}"); |
|
|
|
// commit generated class |
|
|
|
context.commit(logger, printWriter); |
|
|
|
logger.log(Type.INFO, |
|
|
|
"Done. (" + (new Date().getTime() - date.getTime()) / 1000 |
|
|
|
+ "seconds)"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
private void validateConnectors(TreeLogger logger, |
|
|
|
Collection<Class<? extends ServerConnector>> connectors) { |
|
|
|
|
|
|
|
Iterator<Class<? extends ServerConnector>> iter = connectors.iterator(); |
|
|
|
while (iter.hasNext()) { |
|
|
|
Class<? extends ServerConnector> connectorClass = iter.next(); |
|
|
|
Connect annotation = connectorClass.getAnnotation(Connect.class); |
|
|
|
if (!ClientConnector.class.isAssignableFrom(annotation.value())) { |
|
|
|
logger.log( |
|
|
|
Type.WARN, |
|
|
|
"Connector class " |
|
|
|
+ annotation.value().getName() |
|
|
|
+ " defined in @Connect annotation is not a subclass of " |
|
|
|
+ ClientConnector.class.getName() |
|
|
|
+ ". The component connector " |
|
|
|
+ connectorClass.getName() |
|
|
|
+ " will not be included in the widgetset."); |
|
|
|
iter.remove(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
private void logConnectors(TreeLogger logger, GeneratorContext context, |
|
|
|
Collection<Class<? extends ServerConnector>> connectors) { |
|
|
|
logger.log(Type.INFO, |
|
|
|
"Widget set will contain implementations for following component connectors: "); |
|
|
|
|
|
|
|
TreeSet<String> classNames = new TreeSet<String>(); |
|
|
|
HashMap<String, String> loadStyle = new HashMap<String, String>(); |
|
|
|
for (Class<? extends ServerConnector> connectorClass : connectors) { |
|
|
|
String className = connectorClass.getCanonicalName(); |
|
|
|
classNames.add(className); |
|
|
|
if (getLoadStyle(connectorClass) == LoadStyle.DEFERRED) { |
|
|
|
loadStyle.put(className, "DEFERRED"); |
|
|
|
} else if (getLoadStyle(connectorClass) == LoadStyle.LAZY) { |
|
|
|
loadStyle.put(className, "LAZY"); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
for (String className : classNames) { |
|
|
|
String msg = className; |
|
|
|
if (loadStyle.containsKey(className)) { |
|
|
|
msg += " (load style: " + loadStyle.get(className) + ")"; |
|
|
|
} |
|
|
|
logger.log(Type.INFO, "\t" + msg); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* This method is protected to allow creation of optimized widgetsets. The |
|
|
|
* Widgetset will contain only implementation returned by this function. If |
|
|
|
* one knows which widgets are needed for the application, returning only |
|
|
|
* them here will significantly optimize the size of the produced JS. |
|
|
|
* |
|
|
|
* @return a collections of Vaadin components that will be added to |
|
|
|
* widgetset |
|
|
|
*/ |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
private Collection<Class<? extends ServerConnector>> getUsedConnectors( |
|
|
|
TypeOracle typeOracle) { |
|
|
|
JClassType connectorType = typeOracle.findType(Connector.class |
|
|
|
.getName()); |
|
|
|
Collection<Class<? extends ServerConnector>> connectors = new HashSet<Class<? extends ServerConnector>>(); |
|
|
|
for (JClassType jClassType : connectorType.getSubtypes()) { |
|
|
|
Connect annotation = jClassType.getAnnotation(Connect.class); |
|
|
|
if (annotation != null) { |
|
|
|
try { |
|
|
|
Class<? extends ServerConnector> clazz = (Class<? extends ServerConnector>) Class |
|
|
|
.forName(jClassType.getQualifiedSourceName()); |
|
|
|
connectors.add(clazz); |
|
|
|
} catch (ClassNotFoundException e) { |
|
|
|
throw new RuntimeException(e); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return connectors; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns true if the widget for given component will be lazy loaded by the |
|
|
|
* client. The default implementation reads the information from the |
|
|
|
* {@link Connect} annotation. |
|
|
|
* <p> |
|
|
|
* The method can be overridden to optimize the widget loading mechanism. If |
|
|
|
* the Widgetset is wanted to be optimized for a network with a high latency |
|
|
|
* or for a one with a very fast throughput, it may be good to return false |
|
|
|
* for every component. |
|
|
|
* |
|
|
|
* @param connector |
|
|
|
* @return true iff the widget for given component should be lazy loaded by |
|
|
|
* the client side engine |
|
|
|
*/ |
|
|
|
protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) { |
|
|
|
Connect annotation = connector.getAnnotation(Connect.class); |
|
|
|
return annotation.loadStyle(); |
|
|
|
} |
|
|
|
|
|
|
|
private void generateInstantiatorMethod( |
|
|
|
SourceWriter sourceWriter, |
|
|
|
Collection<Class<? extends ServerConnector>> connectorsHavingComponentAnnotation) { |
|
|
|
|
|
|
|
Collection<Class<?>> deferredWidgets = new LinkedList<Class<?>>(); |
|
|
|
|
|
|
|
// TODO detect if it would be noticably faster to instantiate with a |
|
|
|
// lookup with index than with the hashmap |
|
|
|
|
|
|
|
sourceWriter.println("public void ensureInstantiator(Class<? extends " |
|
|
|
+ serverConnectorClassName + "> classType) {"); |
|
|
|
sourceWriter.println("if(!instmap.containsKey(classType)){"); |
|
|
|
boolean first = true; |
|
|
|
|
|
|
|
ArrayList<Class<? extends ServerConnector>> lazyLoadedConnectors = new ArrayList<Class<? extends ServerConnector>>(); |
|
|
|
|
|
|
|
HashSet<Class<? extends ServerConnector>> connectorsWithInstantiator = new HashSet<Class<? extends ServerConnector>>(); |
|
|
|
|
|
|
|
for (Class<? extends ServerConnector> class1 : connectorsHavingComponentAnnotation) { |
|
|
|
Class<? extends ServerConnector> clientClass = class1; |
|
|
|
if (connectorsWithInstantiator.contains(clientClass)) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
if (clientClass == UIConnector.class) { |
|
|
|
// Roots are not instantiated by widgetset |
|
|
|
continue; |
|
|
|
} |
|
|
|
if (!first) { |
|
|
|
sourceWriter.print(" else "); |
|
|
|
} else { |
|
|
|
first = false; |
|
|
|
} |
|
|
|
sourceWriter.print("if( classType == " + clientClass.getName() |
|
|
|
+ ".class) {"); |
|
|
|
|
|
|
|
String instantiator = "new WidgetInstantiator() {\n public " |
|
|
|
+ serverConnectorClassName |
|
|
|
+ " get() {\n return GWT.create(" + clientClass.getName() |
|
|
|
+ ".class );\n}\n}\n"; |
|
|
|
|
|
|
|
LoadStyle loadStyle = getLoadStyle(class1); |
|
|
|
|
|
|
|
if (loadStyle != LoadStyle.EAGER) { |
|
|
|
sourceWriter |
|
|
|
.print("ApplicationConfiguration.startWidgetLoading();\n" |
|
|
|
+ "GWT.runAsync( \n" |
|
|
|
+ "new WidgetLoader() { void addInstantiator() {instmap.put(" |
|
|
|
+ clientClass.getName() |
|
|
|
+ ".class," |
|
|
|
+ instantiator + ");}});\n"); |
|
|
|
lazyLoadedConnectors.add(class1); |
|
|
|
|
|
|
|
if (loadStyle == LoadStyle.DEFERRED) { |
|
|
|
deferredWidgets.add(class1); |
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
// widget implementation in initially loaded js script |
|
|
|
sourceWriter.print("instmap.put("); |
|
|
|
sourceWriter.print(clientClass.getName()); |
|
|
|
sourceWriter.print(".class, "); |
|
|
|
sourceWriter.print(instantiator); |
|
|
|
sourceWriter.print(");"); |
|
|
|
} |
|
|
|
sourceWriter.print("}"); |
|
|
|
connectorsWithInstantiator.add(clientClass); |
|
|
|
} |
|
|
|
|
|
|
|
sourceWriter.println("}"); |
|
|
|
|
|
|
|
sourceWriter.println("}"); |
|
|
|
|
|
|
|
sourceWriter.println("public Class<? extends " |
|
|
|
+ serverConnectorClassName |
|
|
|
+ ">[] getDeferredLoadedConnectors() {"); |
|
|
|
|
|
|
|
sourceWriter.println("return new Class[] {"); |
|
|
|
first = true; |
|
|
|
for (Class<?> class2 : deferredWidgets) { |
|
|
|
if (!first) { |
|
|
|
sourceWriter.println(","); |
|
|
|
} |
|
|
|
first = false; |
|
|
|
sourceWriter.print(class2.getName() + ".class"); |
|
|
|
} |
|
|
|
|
|
|
|
sourceWriter.println("};"); |
|
|
|
sourceWriter.println("}"); |
|
|
|
|
|
|
|
// in constructor add a "thread" that lazyly loads lazy loaded widgets |
|
|
|
// if communication to server idles |
|
|
|
|
|
|
|
// TODO an array of lazy loaded widgets |
|
|
|
|
|
|
|
// TODO an index of last ensured widget in array |
|
|
|
|
|
|
|
sourceWriter.println("public " + serverConnectorClassName |
|
|
|
+ " instantiate(Class<? extends " + serverConnectorClassName |
|
|
|
+ "> classType) {"); |
|
|
|
sourceWriter.indent(); |
|
|
|
sourceWriter.println(serverConnectorClassName |
|
|
|
+ " p = super.instantiate(classType); if(p!= null) return p;"); |
|
|
|
sourceWriter.println("return instmap.get(classType).get();"); |
|
|
|
|
|
|
|
sourceWriter.outdent(); |
|
|
|
sourceWriter.println("}"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* |
|
|
|
* @param logger |
|
|
|
* logger to print messages to |
|
|
|
* @param sourceWriter |
|
|
|
* Source writer to output source code |
|
|
|
* @param paintablesHavingWidgetAnnotation |
|
|
|
* @throws UnableToCompleteException |
|
|
|
*/ |
|
|
|
private void generateImplementationDetector( |
|
|
|
TreeLogger logger, |
|
|
|
SourceWriter sourceWriter, |
|
|
|
Collection<Class<? extends ServerConnector>> paintablesHavingWidgetAnnotation) |
|
|
|
throws UnableToCompleteException { |
|
|
|
sourceWriter |
|
|
|
.println("public Class<? extends " |
|
|
|
+ serverConnectorClassName |
|
|
|
+ "> " |
|
|
|
+ "getConnectorClassForServerSideClassName(String fullyQualifiedName) {"); |
|
|
|
sourceWriter.indent(); |
|
|
|
sourceWriter |
|
|
|
.println("fullyQualifiedName = fullyQualifiedName.intern();"); |
|
|
|
|
|
|
|
// Keep track of encountered mappings to detect conflicts |
|
|
|
Map<Class<? extends ClientConnector>, Class<? extends ServerConnector>> mappings = new HashMap<Class<? extends ClientConnector>, Class<? extends ServerConnector>>(); |
|
|
|
|
|
|
|
for (Class<? extends ServerConnector> connectorClass : paintablesHavingWidgetAnnotation) { |
|
|
|
Class<? extends ClientConnector> clientConnectorClass = getClientConnectorClass(connectorClass); |
|
|
|
|
|
|
|
// Check for conflicts |
|
|
|
Class<? extends ServerConnector> prevousMapping = mappings.put( |
|
|
|
clientConnectorClass, connectorClass); |
|
|
|
if (prevousMapping != null) { |
|
|
|
logger.log(Type.ERROR, |
|
|
|
"Both " + connectorClass.getName() + " and " |
|
|
|
+ prevousMapping.getName() |
|
|
|
+ " have @Connect referring to " |
|
|
|
+ clientConnectorClass.getName() + "."); |
|
|
|
throw new UnableToCompleteException(); |
|
|
|
} |
|
|
|
|
|
|
|
sourceWriter.print("if ( fullyQualifiedName == \""); |
|
|
|
sourceWriter.print(clientConnectorClass.getName()); |
|
|
|
sourceWriter.print("\" ) { ensureInstantiator(" |
|
|
|
+ connectorClass.getName() + ".class); return "); |
|
|
|
sourceWriter.print(connectorClass.getName()); |
|
|
|
sourceWriter.println(".class;}"); |
|
|
|
sourceWriter.print("else "); |
|
|
|
} |
|
|
|
sourceWriter.println("return " |
|
|
|
+ UnknownComponentConnector.class.getName() + ".class;"); |
|
|
|
sourceWriter.outdent(); |
|
|
|
sourceWriter.println("}"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
private static Class<? extends ClientConnector> getClientConnectorClass( |
|
|
|
Class<? extends ServerConnector> connectorClass) { |
|
|
|
Connect annotation = connectorClass.getAnnotation(Connect.class); |
|
|
|
return (Class<? extends ClientConnector>) annotation.value(); |
|
|
|
} |
|
|
|
} |