Skip to content

Instantly share code, notes, and snippets.

@rhmoller
Last active August 29, 2015 14:07
Show Gist options
  • Save rhmoller/7aebcfa928ebfd190784 to your computer and use it in GitHub Desktop.
Save rhmoller/7aebcfa928ebfd190784 to your computer and use it in GitHub Desktop.
GWT-RPC Linter that checks that serializable interfaces have implementations and serializable classes have default constructors and serializable fields. Should be invoked from a GWT Generator
public class GwtRpcLinter {
public static final String PACKAGE_PREFIX = "com.yourcompany"; // only check classes in this package
private TreeLogger logger;
private GeneratorContext context;
private List<String> errorMessages = new ArrayList<>();
public GwtRpcLinter(TreeLogger logger, GeneratorContext context) {
this.logger = logger;
this.context = context;
}
public void run() throws UnableToCompleteException {
TreeLogger lintLogger = logger.branch(TreeLogger.Type.WARN, "GWT-RPC Linter started");
JClassType type = context.getTypeOracle().findType("com.google.gwt.user.client.rpc.IsSerializable");
checkSubTypes(type);
JClassType type2 = context.getTypeOracle().findType("java.io.Serializable");
checkSubTypes(type2);
if (errorMessages.isEmpty()) {
lintLogger.log(TreeLogger.Type.INFO, "GWT-RPC Linter completed without errors");
} else {
lintLogger.log(TreeLogger.Type.WARN, "GWT-RPC Linter failed with " + errorMessages.size() + " errors");
for (String errorMessage : errorMessages) {
System.err.println(errorMessage);
lintLogger.log(TreeLogger.Type.WARN, errorMessage);
}
// todo: raise level to ERROR and throw this exception when we are sure it does the right thing.
// throw new UnableToCompleteException();
}
}
private void checkSubTypes(JClassType type) {
JClassType[] serializableTypes = type.getSubtypes();
for (JClassType serializableType : serializableTypes) {
if (serializableType.getPackage().getName().startsWith(PACKAGE_PREFIX)) {
if (isSerializableInterfaceWithoutImplementation(serializableType)) {
errorMessages.add("Serializable/IsSerializable interface " + serializableType + " has no implementation.");
}
if (isConcreteClass(serializableType)) {
if (!hasDefaultConstructor(serializableType)) {
errorMessages.add("Serializable/IsSerializable class " + serializableType + " has no default constructor.");
}
JField[] fields = serializableType.getFields();
for (JField field : fields) {
boolean serializableField = isSerializableOrTransientField(field);
if (!serializableField) {
errorMessages.add("Serializable/IsSerializable class " + serializableType + " has non-transient field " + field.getName() + " that is not serializable");
}
}
}
}
}
}
private boolean isSerializableOrTransientField(JField field) {
boolean serializableField = false;
JClassType fieldClass = field.getType().isClassOrInterface();
if (fieldClass != null && !field.isTransient()) {
JClassType[] interfaces = fieldClass.getImplementedInterfaces();
for (JClassType anInterface : interfaces) {
if ("com.google.gwt.user.client.rpc.IsSerializable".equals(anInterface.getName()) ||
"java.io.Serializable".equals(anInterface.getName())) {
serializableField = true;
}
}
} else {
serializableField = true;
}
return serializableField;
}
private boolean hasDefaultConstructor(JClassType serializableType) {
if (serializableType.getConstructors().length == 0) return true;
try {
JConstructor defaultConstructor = serializableType.getConstructor(new JType[]{});
return true;
} catch (NotFoundException e) {
return false;
}
}
private boolean isConcreteClass(JClassType serializableType) {
return !(serializableType.isAbstract() || serializableType.isInterface() == null);
}
private boolean isSerializableInterfaceWithoutImplementation(JClassType serializableType) {
return serializableType.isInterface() != null && serializableType.getSubtypes().length == 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment