blob: 2b8f2b1b3156e61b0f1a77f20fbd579c3f71d00e [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* 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.google.web.bindery.requestfactory.apt;
import com.google.web.bindery.requestfactory.vm.impl.Deobfuscator;
import com.google.web.bindery.requestfactory.vm.impl.OperationData;
import com.google.web.bindery.requestfactory.vm.impl.OperationKey;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
/**
* Visits a RequestFactory to create its associated DeobfuscatorBuilder, a
* self-configuring subtype of
* {@link com.google.web.bindery.requestfactory.vm.impl.Deobfuscator.Builder}
* which provides the ServiceLayer with type- and method-mapping information.
*/
class DeobfuscatorBuilder extends ScannerBase<Void> {
private TypeElement requestFactoryElement;
private final StringBuilder sb = new StringBuilder();
public String toString() {
return sb.toString();
}
/**
* Examine a method defined within a RequestFactory.
*/
@Override
public Void visitExecutable(ExecutableElement x, State state) {
if (shouldIgnore(x, state)) {
return null;
}
final TypeElement requestContextElement =
(TypeElement) state.types.asElement(x.getReturnType());
new ScannerBase<Void>() {
/**
* Scan a method within a RequestContext.
*/
@Override
public Void visitExecutable(ExecutableElement x, State state) {
if (shouldIgnore(x, state)) {
return null;
}
String requestContextBinaryName =
state.elements.getBinaryName(requestContextElement).toString();
String clientMethodDescriptor = x.asType().accept(new DescriptorBuilder(), state);
ExecutableElement domainElement = (ExecutableElement) state.getClientToDomainMap().get(x);
if (domainElement == null) {
/*
* No mapping from the client to domain type, probably because of an
* unresolved ServiceName annotation. This can be fixed when building
* the server by running ValidationTool.
*/
if (state.mustResolveAllAnnotations()) {
state.poison(requestContextElement, Messages
.deobfuscatorMissingContext(requestContextElement.getSimpleName()));
}
return super.visitExecutable(x, state);
}
String domainMethodDescriptor =
domainElement.asType().accept(new DescriptorBuilder(), state);
String methodName = x.getSimpleName().toString();
OperationKey key =
new OperationKey(requestContextBinaryName, methodName, clientMethodDescriptor);
println("withOperation(new OperationKey(\"%s\"),", key.get());
println(" new OperationData.Builder()");
println(" .withClientMethodDescriptor(\"%s\")", clientMethodDescriptor);
println(" .withDomainMethodDescriptor(\"%s\")", domainMethodDescriptor);
println(" .withMethodName(\"%s\")", methodName);
println(" .withRequestContext(\"%s\")", requestContextBinaryName);
println(" .build());");
return super.visitExecutable(x, state);
}
/**
* Scan a RequestContext.
*/
@Override
public Void visitType(TypeElement x, State state) {
scanAllInheritedMethods(x, state);
return null;
}
}.scan(requestContextElement, state);
return null;
}
/**
* Scan a RequestFactory type.
*/
@Override
public Void visitType(TypeElement x, State state) {
requestFactoryElement = x;
String simpleName = computeSimpleName(x, state);
String packageName = state.elements.getPackageOf(x).getQualifiedName().toString();
println("// Automatically Generated -- DO NOT EDIT");
println("// %s", state.elements.getBinaryName(x));
println("package %s;", packageName);
println("import %s;", Arrays.class.getCanonicalName());
println("import %s;", OperationData.class.getCanonicalName());
println("import %s;", OperationKey.class.getCanonicalName());
println("public final class %s extends %s {", simpleName, Deobfuscator.Builder.class
.getCanonicalName());
println("{");
scanAllInheritedMethods(x, state);
writeTypeAndTokenMap(state);
println("}}");
// Don't write the deobfuscator if something has gone wrong.
if (state.isPoisoned()) {
return null;
}
try {
JavaFileObject obj = state.filer.createSourceFile(packageName + "." + simpleName, x);
Writer w = obj.openWriter();
w.write(sb.toString());
w.close();
} catch (Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
state.poison(x, sw.toString());
}
return null;
}
private String computeSimpleName(TypeElement x, State state) {
// See constants in Deobfuscator
String simpleName = state.elements.getBinaryName(x).toString() + "DeobfuscatorBuilder";
if (state.isClientOnly()) {
simpleName += "Lite";
}
simpleName = simpleName.substring(simpleName.lastIndexOf('.') + 1);
return simpleName;
}
private void println(String line, Object... args) {
sb.append(String.format(line, args)).append("\n");
}
/**
* Write calls to {@code withRawTypeToken} and
* {@code withClientToDomainMappings}.
*/
private void writeTypeAndTokenMap(State state) {
/*
* A map and its comparator to build up the mapping between domain types and
* the client type(s) that it is mapped to.
*/
TypeComparator comparator = new TypeComparator(state);
Map<TypeElement, SortedSet<TypeElement>> domainToClientMappings =
new TreeMap<TypeElement, SortedSet<TypeElement>>(comparator);
// Map accumulated by previous visitors
Map<Element, Element> clientToDomainMap = state.getClientToDomainMap();
// Get all types used by the RequestFactory
Set<TypeElement> referredTypes = ReferredTypesCollector.collect(requestFactoryElement, state);
for (TypeElement clientType : referredTypes) {
// Ignare non-proxy types
if (!state.types.isAssignable(clientType.asType(), state.baseProxyType)) {
continue;
}
String binaryName = state.elements.getBinaryName(clientType).toString();
// withRawTypeToken("1234ABC", "com.example.FooProxy");
println("withRawTypeToken(\"%s\", \"%s\");", OperationKey.hash(binaryName), binaryName);
TypeElement domainType = (TypeElement) clientToDomainMap.get(clientType);
if (domainType == null) {
/*
* Missing proxy mapping, probably due to an unresolved ProxyForName. If
* we're running as part of a build tool or an IDE, the
* mustResolveAllAnnotations() call below will return false. The
* isMappingRequired() check avoid false-positives on proxy supertypes
* that extend BaseProxy but that don't declare a ProxyFor annotation.
*/
if (state.mustResolveAllAnnotations() && state.isMappingRequired(domainType)) {
state.poison(clientType, Messages.deobfuscatorMissingProxy(clientType.getSimpleName()));
}
continue;
}
// Generic get-and-add code
SortedSet<TypeElement> clientTypes = domainToClientMappings.get(domainType);
if (clientTypes == null) {
clientTypes = new TreeSet<TypeElement>(comparator);
domainToClientMappings.put(domainType, clientTypes);
}
clientTypes.add(clientType);
}
for (Map.Entry<TypeElement, SortedSet<TypeElement>> entry : domainToClientMappings.entrySet()) {
// Arrays.asList("com.example.FooView1Proxy", "com.example.FooView2Proxy")
StringBuilder list = new StringBuilder("Arrays.asList(");
boolean needsComma = false;
for (TypeElement elt : entry.getValue()) {
if (needsComma) {
list.append(", ");
} else {
needsComma = true;
}
list.append('"').append(state.elements.getBinaryName(elt)).append('"');
}
list.append(")");
// withClientToDomainMappings("com.example.Domain", Arrays.asList(...))
println("withClientToDomainMappings(\"%s\", %s);", state.elements.getBinaryName(entry
.getKey()), list);
}
}
}