/* * 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); } } }