Browse Source

code splitting + client initialization

svn changeset:13652/svn branch:6.4
tags/6.7.0.beta1
Matti Tahvonen 14 years ago
parent
commit
2517866aec

+ 71
- 22
src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java View File

@@ -9,15 +9,17 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;
import com.vaadin.terminal.gwt.client.ui.VUnknownComponent;

public class ApplicationConfiguration {
public class ApplicationConfiguration implements EntryPoint {

// can only be inited once, to avoid multiple-entrypoint-problem
private static WidgetSet initedWidgetSet;
private static WidgetSet widgetSet = GWT.create(WidgetSet.class);

private String id;
private String themeUri;
@@ -38,10 +40,10 @@ public class ApplicationConfiguration {

private String windowId;

// TODO consider to make this hashmap per application
static// TODO consider to make this hashmap per application
LinkedList<Command> callbacks = new LinkedList<Command>();

private int widgetsLoading;
private static int widgetsLoading;

private static ArrayList<ApplicationConnection> unstartedApplications = new ArrayList<ApplicationConnection>();
private static ArrayList<ApplicationConnection> runningApplications = new ArrayList<ApplicationConnection>();
@@ -151,28 +153,20 @@ public class ApplicationConfiguration {
* @param widgetset
* the widgetset that is running the apps
*/
public static void initConfigurations(WidgetSet widgetset) {

if (initedWidgetSet != null) {
// Multiple widgetsets inited; can happen with custom WS + entry
// point
String msg = "Ignoring " + widgetset.getClass().getName()
+ ", because " + initedWidgetSet.getClass().getName()
+ " was already inited (if this is wrong, your entry point"
+ " is probably not first your .gwt.xml).";
throw new IllegalStateException(msg);
}
initedWidgetSet = widgetset;
public static void initConfigurations() {

ArrayList<String> appIds = new ArrayList<String>();
loadAppIdListFromDOM(appIds);

for (Iterator<String> it = appIds.iterator(); it.hasNext();) {
String appId = it.next();
ApplicationConfiguration appConf = getConfigFromDOM(appId);
ApplicationConnection a = new ApplicationConnection(widgetset,
ApplicationConnection a = new ApplicationConnection(widgetSet,
appConf);
unstartedApplications.add(a);
}

deferredWidgetLoadLoop.scheduleRepeating(100);
}

/**
@@ -242,7 +236,7 @@ public class ApplicationConfiguration {
for (int i = 0; i < keyArray.length(); i++) {
String key = keyArray.get(i).intern();
int value = valueMap.getInt(key);
classes[value] = widgetSet.getImplementationByClassName(key, this);
classes[value] = widgetSet.getImplementationByClassName(key);
if (classes[value] == VUnknownComponent.class) {
if (unknownComponents == null) {
unknownComponents = new HashMap<String, String>();
@@ -273,7 +267,7 @@ public class ApplicationConfiguration {
*
* @param c
*/
void runWhenWidgetsLoaded(Command c) {
static void runWhenWidgetsLoaded(Command c) {
if (widgetsLoading == 0) {
c.execute();
} else {
@@ -281,11 +275,11 @@ public class ApplicationConfiguration {
}
}

void widgetLoadStart() {
static void startWidgetLoading() {
widgetsLoading++;
}

void widgetLoaded() {
static void endWidgetLoading() {
widgetsLoading--;
if (widgetsLoading == 0 && !callbacks.isEmpty()) {
for (Command cmd : callbacks) {
@@ -295,4 +289,59 @@ public class ApplicationConfiguration {
}

}

/*
* This loop loads widget implementation that should be loaded deferred.
*/
private static final Timer deferredWidgetLoadLoop = new Timer() {
private static final int FREE_LIMIT = 4;

int communicationFree = 0;
int nextWidgetIndex = 0;

@Override
public void run() {
if (!isBusy()) {
Class<? extends Paintable> nextType = getNextType();
if (nextType == null) {
// ensured that all widgets are loaded
cancel();
} else {
widgetSet.loadImplementation(nextType);
}
}
}

private Class<? extends Paintable> getNextType() {
Class<? extends Paintable>[] deferredLoadedWidgets = widgetSet
.getDeferredLoadedWidgets();
if (deferredLoadedWidgets.length <= nextWidgetIndex) {
return null;
} else {
return deferredLoadedWidgets[nextWidgetIndex++];
}
}

private boolean isBusy() {
if (widgetsLoading > 0) {
communicationFree = 0;
return false;
}
for (ApplicationConnection app : runningApplications) {
if (app.hasActiveRequest()) {
// if an UIDL request or widget loading is active, mark as
// busy
communicationFree = 0;
return false;
}
}
communicationFree++;
return communicationFree < FREE_LIMIT;
}
};

public void onModuleLoad() {
initConfigurations();
startNextApplication();
}
}

+ 1
- 1
src/com/vaadin/terminal/gwt/client/ApplicationConnection.java View File

@@ -893,7 +893,7 @@ public class ApplicationConnection {

}
};
configuration.runWhenWidgetsLoaded(c);
ApplicationConfiguration.runWhenWidgetsLoaded(c);
}

/**

+ 0
- 130
src/com/vaadin/terminal/gwt/client/DefaultWidgetSet.java View File

@@ -1,130 +0,0 @@
/*
@ITMillApache2LicenseForJavaFiles@
*/

package com.vaadin.terminal.gwt.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ui.VButton;
import com.vaadin.terminal.gwt.client.ui.VCheckBox;
import com.vaadin.terminal.gwt.client.ui.VDateFieldCalendar;
import com.vaadin.terminal.gwt.client.ui.VFilterSelect;
import com.vaadin.terminal.gwt.client.ui.VListSelect;
import com.vaadin.terminal.gwt.client.ui.VNativeSelect;
import com.vaadin.terminal.gwt.client.ui.VOptionGroup;
import com.vaadin.terminal.gwt.client.ui.VPasswordField;
import com.vaadin.terminal.gwt.client.ui.VPopupCalendar;
import com.vaadin.terminal.gwt.client.ui.VSplitPanelHorizontal;
import com.vaadin.terminal.gwt.client.ui.VSplitPanelVertical;
import com.vaadin.terminal.gwt.client.ui.VTextArea;
import com.vaadin.terminal.gwt.client.ui.VTextField;
import com.vaadin.terminal.gwt.client.ui.VTwinColSelect;
import com.vaadin.terminal.gwt.client.ui.VUnknownComponent;
import com.vaadin.terminal.gwt.client.ui.VView;
import com.vaadin.terminal.gwt.client.ui.VWindow;

public class DefaultWidgetSet implements WidgetSet {

/**
* DefaultWidgetSet (and its extensions) delegate instantiation of widgets
* and client-server mathing to WidgetMap. The actual implementations are
* generated with gwts deferred binding.
*/
private WidgetMap map;

/**
* This is the entry point method. It will start the first
*/
public void onModuleLoad() {
try {
ApplicationConfiguration.initConfigurations(this);
} catch (Exception e) {
// Log & don't continue;
// custom WidgetSets w/ entry points will cause this
ApplicationConnection.getConsole().log(e.getMessage());
return;
}
ApplicationConfiguration.startNextApplication(); // start first app
map = GWT.create(WidgetMap.class);
}

public Paintable createWidget(UIDL uidl, ApplicationConfiguration conf) {
final Class<? extends Paintable> classType = resolveWidgetType(uidl,
conf);
if (classType == null || classType == VUnknownComponent.class) {
String serverSideName = conf
.getUnknownServerClassNameByEncodedTagName(uidl.getTag());
return new VUnknownComponent(serverSideName);
}

return map.instantiate(classType);
}

protected Class<? extends Paintable> resolveWidgetType(UIDL uidl,
ApplicationConfiguration conf) {
final String tag = uidl.getTag();

Class<? extends Paintable> widgetClass = conf
.getWidgetClassByEncodedTag(tag);

// add our historical quirks

if (widgetClass == VButton.class && uidl.hasAttribute("type")) {
return VCheckBox.class;
} else if (widgetClass == VView.class && uidl.hasAttribute("sub")) {
return VWindow.class;
} else if (widgetClass == VFilterSelect.class) {
if (uidl.hasAttribute("type")) {
// TODO check if all type checks are really neede
final String type = uidl.getStringAttribute("type").intern();
if (type == "twincol") {
return VTwinColSelect.class;
} else if (type == "optiongroup") {
return VOptionGroup.class;
} else if (type == "native") {
return VNativeSelect.class;
} else if (type == "list") {
return VListSelect.class;
} else if (uidl.hasAttribute("selectmode")
&& uidl.getStringAttribute("selectmode")
.equals("multi")) {
return VListSelect.class;
}
}
} else if (widgetClass == VTextField.class) {
if (uidl.hasAttribute("multiline")) {
return VTextArea.class;
} else if (uidl.hasAttribute("secret")) {
return VPasswordField.class;
}
} else if (widgetClass == VPopupCalendar.class) {
if (uidl.hasAttribute("type")
&& uidl.getStringAttribute("type").equals("inline")) {
return VDateFieldCalendar.class;
}
} else if (widgetClass == VSplitPanelHorizontal.class
&& uidl.hasAttribute("vertical")) {
return VSplitPanelVertical.class;
}

return widgetClass;

}

public boolean isCorrectImplementation(Widget currentWidget, UIDL uidl,
ApplicationConfiguration conf) {
return currentWidget.getClass() == resolveWidgetType(uidl, conf);
}

public Class<? extends Paintable> getImplementationByClassName(
String fullyqualifiedName,
ApplicationConfiguration applicationConfiguration) {
Class<? extends Paintable> implementationByServerSideClassName = map
.getImplementationByServerSideClassName(fullyqualifiedName,
applicationConfiguration);
return implementationByServerSideClassName;

}

}

+ 8
- 0
src/com/vaadin/terminal/gwt/client/WidgetInstantiator.java View File

@@ -0,0 +1,8 @@
package com.vaadin.terminal.gwt.client;

/**
* A helper class used by WidgetMap implementation. Used by the generated code.
*/
interface WidgetInstantiator {
public Paintable get();
}

+ 18
- 0
src/com/vaadin/terminal/gwt/client/WidgetLoader.java View File

@@ -0,0 +1,18 @@
package com.vaadin.terminal.gwt.client;

import com.google.gwt.core.client.RunAsyncCallback;

/** A helper class used by WidgetMap implementation. Used by the generated code. */
abstract class WidgetLoader implements RunAsyncCallback {

public void onFailure(Throwable reason) {
ApplicationConfiguration.endWidgetLoading();
}

public void onSuccess() {
addInstantiator();
ApplicationConfiguration.endWidgetLoading();
}

abstract void addInstantiator();
}

+ 10
- 30
src/com/vaadin/terminal/gwt/client/WidgetMap.java View File

@@ -3,41 +3,21 @@
*/
package com.vaadin.terminal.gwt.client;

import com.vaadin.terminal.gwt.client.ui.VDateFieldCalendar;
import com.vaadin.terminal.gwt.client.ui.VPasswordField;
import com.vaadin.terminal.gwt.client.ui.VSplitPanelVertical;
import com.vaadin.terminal.gwt.client.ui.VTextArea;
import com.vaadin.terminal.gwt.client.ui.VWindow;
import java.util.HashMap;

public abstract class WidgetMap {
abstract class WidgetMap {

public Paintable instantiate(Class<? extends Paintable> classType) {
protected static HashMap<Class, WidgetInstantiator> instmap = new HashMap<Class, WidgetInstantiator>();

/*
* Yes, this (including the generated) may look very odd code, but due
* the nature of GWT, we cannot do this with reflect. Luckily this is
* mostly written by WidgetSetGenerator, here are just some hacks. Extra
* instantiation code is needed if client side widget has no "native"
* counterpart on client side.
*/
if (VSplitPanelVertical.class == classType) {
return new VSplitPanelVertical();
} else if (VTextArea.class == classType) {
return new VTextArea();

} else if (VDateFieldCalendar.class == classType) {
return new VDateFieldCalendar();
} else if (VPasswordField.class == classType) {
return new VPasswordField();
} else if (VWindow.class == classType) {
return new VWindow();
} else {
return null; // let generated type handle this
}
public Paintable instantiate(Class<? extends Paintable> classType) {
return instmap.get(classType).get();
}

public abstract Class<? extends Paintable> getImplementationByServerSideClassName(
String fullyqualifiedName,
ApplicationConfiguration applicationConfiguration);
String fullyqualifiedName);

public abstract Class<? extends Paintable>[] getDeferredLoadedWidgets();

public abstract void ensureInstantiator(Class<? extends Paintable> classType);

}

+ 129
- 6
src/com/vaadin/terminal/gwt/client/WidgetSet.java View File

@@ -4,10 +4,34 @@

package com.vaadin.terminal.gwt.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ui.VButton;
import com.vaadin.terminal.gwt.client.ui.VCheckBox;
import com.vaadin.terminal.gwt.client.ui.VDateFieldCalendar;
import com.vaadin.terminal.gwt.client.ui.VFilterSelect;
import com.vaadin.terminal.gwt.client.ui.VListSelect;
import com.vaadin.terminal.gwt.client.ui.VNativeSelect;
import com.vaadin.terminal.gwt.client.ui.VOptionGroup;
import com.vaadin.terminal.gwt.client.ui.VPasswordField;
import com.vaadin.terminal.gwt.client.ui.VPopupCalendar;
import com.vaadin.terminal.gwt.client.ui.VSplitPanelHorizontal;
import com.vaadin.terminal.gwt.client.ui.VSplitPanelVertical;
import com.vaadin.terminal.gwt.client.ui.VTextArea;
import com.vaadin.terminal.gwt.client.ui.VTextField;
import com.vaadin.terminal.gwt.client.ui.VTwinColSelect;
import com.vaadin.terminal.gwt.client.ui.VUnknownComponent;
import com.vaadin.terminal.gwt.client.ui.VView;
import com.vaadin.terminal.gwt.client.ui.VWindow;

public interface WidgetSet extends EntryPoint {
public class WidgetSet {

/**
* WidgetSet (and its extensions) delegate instantiation of widgets and
* client-server matching to WidgetMap. The actual implementations are
* generated with gwts generators/deferred binding.
*/
private WidgetMap widgetMap = GWT.create(WidgetMap.class);

/**
* Create an uninitialized component that best matches given UIDL. The
@@ -21,7 +45,92 @@ public interface WidgetSet extends EntryPoint {
* @return New uninitialized and unregistered component that can paint given
* UIDL.
*/
public Paintable createWidget(UIDL uidl, ApplicationConfiguration conf);
public Paintable createWidget(UIDL uidl, ApplicationConfiguration conf) {
/*
* Yes, this (including the generated code in WidgetMap) may look very
* odd code, but due the nature of GWT, we cannot do this any cleaner.
* Luckily this is mostly written by WidgetSetGenerator, here are just
* some hacks. Extra instantiation code is needed if client side widget
* has no "native" counterpart on client side.
*
* TODO should try to get rid of these exceptions here
*/

final Class<? extends Paintable> classType = resolveWidgetType(uidl,
conf);
if (classType == null || classType == VUnknownComponent.class) {
String serverSideName = conf
.getUnknownServerClassNameByEncodedTagName(uidl.getTag());
return new VUnknownComponent(serverSideName);
} else if (VSplitPanelVertical.class == classType) {
return new VSplitPanelVertical();
} else if (VTextArea.class == classType) {
return new VTextArea();
} else if (VDateFieldCalendar.class == classType) {
return new VDateFieldCalendar();
} else if (VPasswordField.class == classType) {
return new VPasswordField();
} else if (VWindow.class == classType) {
return new VWindow();
} else {
/*
* let the auto generated code instantiate this type
*/
return widgetMap.instantiate(classType);
}

}

protected Class<? extends Paintable> resolveWidgetType(UIDL uidl,
ApplicationConfiguration conf) {
final String tag = uidl.getTag();

Class<? extends Paintable> widgetClass = conf
.getWidgetClassByEncodedTag(tag);

// add our historical quirks

if (widgetClass == VButton.class && uidl.hasAttribute("type")) {
return VCheckBox.class;
} else if (widgetClass == VView.class && uidl.hasAttribute("sub")) {
return VWindow.class;
} else if (widgetClass == VFilterSelect.class) {
if (uidl.hasAttribute("type")) {
// TODO check if all type checks are really neede
final String type = uidl.getStringAttribute("type").intern();
if (type == "twincol") {
return VTwinColSelect.class;
} else if (type == "optiongroup") {
return VOptionGroup.class;
} else if (type == "native") {
return VNativeSelect.class;
} else if (type == "list") {
return VListSelect.class;
} else if (uidl.hasAttribute("selectmode")
&& uidl.getStringAttribute("selectmode")
.equals("multi")) {
return VListSelect.class;
}
}
} else if (widgetClass == VTextField.class) {
if (uidl.hasAttribute("multiline")) {
return VTextArea.class;
} else if (uidl.hasAttribute("secret")) {
return VPasswordField.class;
}
} else if (widgetClass == VPopupCalendar.class) {
if (uidl.hasAttribute("type")
&& uidl.getStringAttribute("type").equals("inline")) {
return VDateFieldCalendar.class;
}
} else if (widgetClass == VSplitPanelHorizontal.class
&& uidl.hasAttribute("vertical")) {
return VSplitPanelVertical.class;
}

return widgetClass;

}

/**
* Test if the given component implementation conforms to UIDL.
@@ -34,7 +143,9 @@ public interface WidgetSet extends EntryPoint {
* class than currentWidget
*/
public boolean isCorrectImplementation(Widget currentWidget, UIDL uidl,
ApplicationConfiguration conf);
ApplicationConfiguration conf) {
return currentWidget.getClass() == resolveWidgetType(uidl, conf);
}

/**
* Due its nature, GWT does not support dynamic classloading. To bypass this
@@ -46,7 +157,19 @@ public interface WidgetSet extends EntryPoint {
* @return
*/
public Class<? extends Paintable> getImplementationByClassName(
String fullyQualifiedName,
ApplicationConfiguration applicationConfiguration);
String fullyqualifiedName) {
Class<? extends Paintable> implementationByServerSideClassName = widgetMap
.getImplementationByServerSideClassName(fullyqualifiedName);
return implementationByServerSideClassName;

}

public Class<? extends Paintable>[] getDeferredLoadedWidgets() {
return widgetMap.getDeferredLoadedWidgets();
}

public void loadImplementation(Class<? extends Paintable> nextType) {
widgetMap.ensureInstantiator(nextType);
}

}

+ 44
- 31
src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java View File

@@ -7,6 +7,7 @@ import java.io.PrintWriter;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeSet;

import com.google.gwt.core.ext.Generator;
@@ -21,6 +22,7 @@ import com.google.gwt.user.rebind.SourceWriter;
import com.vaadin.terminal.Paintable;
import com.vaadin.terminal.gwt.client.ui.VView;
import com.vaadin.ui.ClientWidget;
import com.vaadin.ui.ClientWidget.LoadStyle;

/**
* GWT generator to build WidgetMapImpl dynamically based on
@@ -173,28 +175,26 @@ public class WidgetMapGenerator extends Generator {
* @return true iff the widget for given component should be lazy loaded by
* the client side engine
*/
protected boolean isLazyLoaded(Class<? extends Paintable> paintableType) {
protected LoadStyle getLoadStyle(Class<? extends Paintable> paintableType) {
ClientWidget annotation = paintableType
.getAnnotation(ClientWidget.class);
return annotation.lazyLoad();
return annotation.loadStyle();
}

private void generateInstantiatorMethod(
SourceWriter sourceWriter,
Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {

sourceWriter
.println("public interface Instantiator { public Paintable get();};");
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("private HashMap<Class,Instantiator> instmap = new HashMap<Class,Instantiator>();");

sourceWriter
.println("public void ensureInstantiator(Class<? extends Paintable> classType, final ApplicationConfiguration c) {");
.println("public void ensureInstantiator(Class<? extends Paintable> classType) {");
sourceWriter.println("if(!instmap.containsKey(classType)){");
boolean first = true;

for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) {
ClientWidget annotation = class1.getAnnotation(ClientWidget.class);
Class<? extends com.vaadin.terminal.gwt.client.Paintable> clientClass = annotation
@@ -203,27 +203,31 @@ public class WidgetMapGenerator extends Generator {
// VView's are not instantiated by widgetset
continue;
}
if (!first) {
sourceWriter.print(" else ");
} else {
first = false;
}
sourceWriter.print("if( classType == " + clientClass.getName()
+ ".class) {");

String instantiator = "new Instantiator() { public Paintable get(){ return GWT.create("
+ clientClass.getName() + ".class );}}";
String instantiator = "new WidgetInstantiator() {\n public Paintable get() {\n return GWT.create("
+ clientClass.getName() + ".class );\n}\n}\n";

if (isLazyLoaded(class1)) {
sourceWriter
.print("c.widgetLoadStart();GWT.runAsync(new RunAsyncCallback() {\n"
+ " public void onSuccess() {");
LoadStyle loadStyle = getLoadStyle(class1);

sourceWriter.print("instmap.put(");
sourceWriter.print(clientClass.getName());
sourceWriter.print(".class, ");
sourceWriter.print(instantiator);
sourceWriter.println("); c.widgetLoaded();");
if (loadStyle != LoadStyle.EAGER) {
sourceWriter
.print(" }\n"
+ "\n"
+ " public void onFailure(Throwable reason) {c.widgetLoaded();\n"
+ "\n" + " }\n" + " });\n");
.print("ApplicationConfiguration.startWidgetLoading();\n"
+ "GWT.runAsync( \n"
+ "new WidgetLoader() { void addInstantiator() {instmap.put("
+ clientClass.getName()
+ ".class,"
+ instantiator + ");}});\n");

if (loadStyle == LoadStyle.DEFERRED) {
deferredWidgets.add(class1);
}

} else {
// widget implementation in initially loaded js script
@@ -241,13 +245,22 @@ public class WidgetMapGenerator extends Generator {
sourceWriter.println("}");

sourceWriter
.println("public Paintable instantiate(Class<? extends Paintable> classType) {");
sourceWriter.indent();
sourceWriter
.println("Paintable p = super.instantiate(classType); if(p!= null) return p;");
sourceWriter.println("return instmap.get(classType).get();");
.println("public Class<? extends Paintable>[] getDeferredLoadedWidgets() {");

sourceWriter.outdent();
sourceWriter.println("return new Class[] {");
first = true;
for (Class<?> class2 : deferredWidgets) {
if (!first) {
sourceWriter.println(",");
}
first = false;
ClientWidget annotation = class2.getAnnotation(ClientWidget.class);
Class<? extends com.vaadin.terminal.gwt.client.Paintable> value = annotation
.value();
sourceWriter.print(value.getName() + ".class");
}

sourceWriter.println("};");
sourceWriter.println("}");
}

@@ -262,7 +275,7 @@ public class WidgetMapGenerator extends Generator {
Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {
sourceWriter
.println("public Class<? extends Paintable> "
+ "getImplementationByServerSideClassName(String fullyQualifiedName, ApplicationConfiguration c) {");
+ "getImplementationByServerSideClassName(String fullyQualifiedName) {");
sourceWriter.indent();
sourceWriter
.println("fullyQualifiedName = fullyQualifiedName.intern();");
@@ -274,7 +287,7 @@ public class WidgetMapGenerator extends Generator {
sourceWriter.print("if ( fullyQualifiedName == \"");
sourceWriter.print(class1.getName());
sourceWriter.print("\" ) { ensureInstantiator("
+ clientClass.getName() + ".class, c); return ");
+ clientClass.getName() + ".class); return ");
sourceWriter.print(clientClass.getName());
sourceWriter.println(".class;}");
sourceWriter.print("else ");

Loading…
Cancel
Save