com.vaadin.server.widgetsetutils.ConnectorBundleLoaderFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin-client-compiler Show documentation
Show all versions of vaadin-client-compiler Show documentation
Vaadin is a web application framework for Rich Internet Applications (RIA).
Vaadin enables easy development and maintenance of fast and
secure rich web
applications with a stunning look and feel and a wide browser support.
It features a server-side architecture with the majority of the logic
running
on the server. Ajax technology is used at the browser-side to ensure a
rich
and interactive user experience.
/*
* Copyright (C) 2000-2023 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* 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.client.ui.UnknownExtensionConnector;
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 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();
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 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 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 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> entry : bundle.getIdentifiers()
.entrySet()) {
Set 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 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 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> definedProperties = new HashMap>();
for (Property property : bundle.getNeedsProperty()) {
JClassType beanType = property.getBeanType();
Set usedPropertyNames = definedProperties.get(beanType);
if (usedPropertyNames == null) {
usedPropertyNames = new HashSet();
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 printJsBundleData(TreeLogger logger, SourceWriter w,
ConnectorBundle bundle, String methodName) {
SplittingSourceWriter writer = new SplittingSourceWriter(w, methodName,
30000);
Set 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> needsOnStateChangeHandler = bundle
.getNeedsOnStateChangeHandler();
for (Entry> 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 stateProperties = new HashMap();
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 needsSuperclass = new ArrayList(
bundle.getNeedsSuperclass());
// Emit in hierarchy order to ensure superclass is defined when
// referenced
Collections.sort(needsSuperclass, new Comparator() {
@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> needsDelegateToWidget = bundle
.getNeedsDelegateToWidget();
for (Entry> 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 serializers = bundle.getSerializers();
for (Entry 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 presentationTypes = bundle
.getPresentationTypes();
for (Entry 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 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>> typeEntry : bundle
.getMethodAttributes().entrySet()) {
JClassType type = typeEntry.getKey();
for (Entry> methodEntry : typeEntry
.getValue().entrySet()) {
JMethod method = methodEntry.getKey();
Set 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 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> needsParamTypes = bundle
.getNeedsParamTypes();
for (Entry> entry : needsParamTypes
.entrySet()) {
JClassType type = entry.getKey();
Set 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> needsInvoker = bundle.getNeedsInvoker();
for (Entry> entry : needsInvoker.entrySet()) {
JClassType type = entry.getKey();
TreeLogger typeLogger = logger.branch(Type.DEBUG,
"Creating invokers for " + type);
Set 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