diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndexerConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndexerConfig.java index b481b9292..0a6f8b398 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndexerConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndexerConfig.java @@ -12,12 +12,12 @@ package org.springframework.ide.vscode.boot.app; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.boot.index.cache.IndexCache; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider; import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider; +import org.springframework.ide.vscode.boot.java.beans.FeignClientSymbolProvider; import org.springframework.ide.vscode.boot.java.data.DataRepositorySymbolProvider; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingSymbolProvider; @@ -71,6 +71,8 @@ public class SpringSymbolIndexerConfig { providers.put(Annotations.REPOSITORY, dataRepositorySymbolProvider); providers.put("", webfluxRouterSymbolProvider); + + providers.put(Annotations.FEIGN_CLIENT, new FeignClientSymbolProvider()); return providers; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java index 504f09b0a..a330e41ab 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java @@ -59,6 +59,8 @@ public class Annotations { public static final String CONDITIONAL_ON_JNDI = "org.springframework.boot.autoconfigure.condition.ConditionalOnJndi"; public static final String CONDITIONAL_ON_SINGLE_CANDIDATE = "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate"; + public static final String FEIGN_CLIENT = "org.springframework.cloud.openfeign.FeignClient"; + public static final String VALUE = "org.springframework.beans.factory.annotation.Value"; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientBeanSymbolAddOnInformation.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientBeanSymbolAddOnInformation.java new file mode 100644 index 000000000..1030dc724 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientBeanSymbolAddOnInformation.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2023 VMware, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.beans; + +public class FeignClientBeanSymbolAddOnInformation extends BeansSymbolAddOnInformation { + + final public String[] configClasses; + + public FeignClientBeanSymbolAddOnInformation(String beanID, String beanType, String... configClasses) { + super(beanID, beanType); + this.configClasses = configClasses; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java new file mode 100644 index 000000000..c80f42839 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java @@ -0,0 +1,181 @@ +/******************************************************************************* + * Copyright (c) 2023 VMware, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.beans; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.SymbolKind; +import org.eclipse.lsp4j.WorkspaceSymbol; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.Tuple; +import org.eclipse.lsp4j.jsonrpc.messages.Tuple.Two; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; +import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; +import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; +import org.springframework.ide.vscode.commons.util.BadLocationException; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +public class FeignClientSymbolProvider extends AbstractSymbolProvider { + + private static final Logger log = LoggerFactory.getLogger(FeignClientSymbolProvider.class); + + @Override + protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, + SpringIndexerJavaContext context, TextDocument doc) { + try { + if (node != null && node.getParent() != null && node.getParent() instanceof TypeDeclaration) { + Two result = createSymbol(node, annotationType, metaAnnotations, doc); + + EnhancedSymbolInformation enhancedSymbol = result.getFirst(); + Bean beanDefinition = result.getSecond(); + context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol)); + context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition)); + } + } + catch (Exception e) { + log.error("", e); + } + } + + private Two createSymbol(Annotation node, ITypeBinding annotationType, + Collection metaAnnotations, TextDocument doc) throws BadLocationException { + String annotationTypeName = annotationType.getName(); + Collection metaAnnotationNames = metaAnnotations.stream() + .map(ITypeBinding::getName) + .collect(Collectors.toList()); + + TypeDeclaration type = (TypeDeclaration) node.getParent(); + + String beanName = getBeanName(node, type); + ITypeBinding beanType = type.resolveBinding(); + + Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())); + + WorkspaceSymbol symbol = new WorkspaceSymbol( + beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, beanType == null ? "" : beanType.getName()), SymbolKind.Interface, + Either.forLeft(location)); + + SymbolAddOnInformation[] addon = new SymbolAddOnInformation[] {new FeignClientBeanSymbolAddOnInformation(beanName, beanType == null ? "" : beanType.getQualifiedName(), getConfigClass(node))}; + + InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(type, doc); + + Set supertypes = new HashSet<>(); + ASTUtils.findSupertypes(beanType, supertypes); + + String[] annotations = Stream.concat(Stream.of(annotationType), metaAnnotations.stream()).map(t -> t.getQualifiedName()).toArray(String[]::new); + + Bean beanDefinition = new Bean(beanName, beanType == null ? "" : beanType.getQualifiedName(), location, injectionPoints, (String[]) supertypes.toArray(new String[supertypes.size()]), annotations); + + return Tuple.two(new EnhancedSymbolInformation(symbol, addon), beanDefinition); + } + + protected String beanLabel(String searchPrefix, String annotationTypeName, Collection metaAnnotationNames, String beanName, String beanType) { + StringBuilder symbolLabel = new StringBuilder(); + symbolLabel.append("@"); + symbolLabel.append(searchPrefix); + symbolLabel.append(' '); + symbolLabel.append('\''); + symbolLabel.append(beanName); + symbolLabel.append('\''); + symbolLabel.append(" (@"); + symbolLabel.append(annotationTypeName); + if (!metaAnnotationNames.isEmpty()) { + symbolLabel.append(" <: "); + boolean first = true; + for (String ma : metaAnnotationNames) { + if (!first) { + symbolLabel.append(", "); + } + symbolLabel.append("@"); + symbolLabel.append(ma); + first = false; + } + } + symbolLabel.append(") "); + symbolLabel.append(beanType); + return symbolLabel.toString(); + } + + private String getBeanName(Annotation node, TypeDeclaration typeDecl) { + if (node.isSingleMemberAnnotation()) { + Object o = ((SingleMemberAnnotation)node).getValue().resolveConstantExpressionValue(); + if (o instanceof String) { + return (String) o; + } + } else if (node.isNormalAnnotation()) { + NormalAnnotation normalAnnotation = (NormalAnnotation) node; + for (Object o : normalAnnotation.values()) { + if (o instanceof MemberValuePair) { + MemberValuePair pair = (MemberValuePair) o; + switch (pair.getName().getIdentifier()) { + case "name": + case "value": + Object obj = pair.getValue().resolveConstantExpressionValue(); + if (obj instanceof String) { + return (String) obj; + } + } + } + } + } + return BeanUtils.getBeanNameFromType(typeDecl.getName().getIdentifier()); + } + + private String[] getConfigClass(Annotation node) { + if (node.isNormalAnnotation()) { + NormalAnnotation normalAnnotation = (NormalAnnotation) node; + for (Object o : normalAnnotation.values()) { + if (o instanceof MemberValuePair) { + MemberValuePair pair = (MemberValuePair) o; + if ("configuration".equals(pair.getName().getIdentifier())) { + if (pair.getValue() instanceof TypeLiteral) { + ITypeBinding b = ((TypeLiteral) pair.getValue()).getType().resolveBinding(); + return new String[] { b.getQualifiedName() }; + } else if (pair.getValue() instanceof ArrayInitializer){ + List expressions = (List) ((ArrayInitializer) pair.getValue()).expressions(); + return expressions.stream() + .filter(TypeLiteral.class::isInstance) + .map(TypeLiteral.class::cast) + .map(tl -> tl.getType().resolveBinding()) + .filter(Objects::nonNull) + .map(b -> b.getQualifiedName()) + .toArray(String[]::new); + } + } + } + } + } + return new String[0]; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java index 863697d35..33dd5e5d6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java @@ -24,9 +24,17 @@ import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.openrewrite.java.spring.boot2.AddConfigurationAnnotationIfBeansPresent; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; +import org.springframework.ide.vscode.boot.java.beans.BeansSymbolAddOnInformation; +import org.springframework.ide.vscode.boot.java.beans.FeignClientBeanSymbolAddOnInformation; +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; +import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; import org.springframework.ide.vscode.commons.java.Version; @@ -37,7 +45,7 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.Reconcile import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; -public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconciler { +public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconciler, ApplicationContextAware { private static final String ID = AddConfigurationAnnotationIfBeansPresent.class.getName(); @@ -47,6 +55,8 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile private QuickfixRegistry quickfixRegistry; + private ApplicationContext applicationContext; + public AddConfigurationIfBeansPresentReconciler(QuickfixRegistry quickfixRegistry) { this.quickfixRegistry = quickfixRegistry; } @@ -58,7 +68,7 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile @Override public boolean visit(TypeDeclaration classDecl) { - if (isApplicableClass(cu, classDecl)) { + if (isApplicableClass(project, cu, classDecl)) { SimpleName nameAst = classDecl.getName(); ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL, nameAst.getStartPosition(), nameAst.getLength()); @@ -79,7 +89,7 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile }); } - private static boolean isApplicableClass(CompilationUnit cu, TypeDeclaration classDecl) { + private boolean isApplicableClass(IJavaProject project, CompilationUnit cu, TypeDeclaration classDecl) { if (classDecl.isInterface()) { return false; } @@ -112,16 +122,47 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile } } } - + // No '@Configuration' present. Check if any methods have '@Bean' annotation for (MethodDeclaration m : classDecl.getMethods()) { - if (isBeanMethod(m)) { + if (isBeanMethod(m) && !isException(project, classDecl)) { return true; } } return false; } + + private boolean isException(IJavaProject project, TypeDeclaration classDecl) { + if (applicationContext != null) { + SpringSymbolIndex index = applicationContext.getBean(SpringSymbolIndex.class); + if (index != null) { + final String beanClassName = RewriteQuickFixUtils.getDeepErasureType(classDecl.resolveBinding()).getQualifiedName(); + for (EnhancedSymbolInformation s : index.getEnhancedSymbols(project)) { + SymbolAddOnInformation[] additionalInformation = s.getAdditionalInformation(); + if (additionalInformation != null) { + for (SymbolAddOnInformation info : additionalInformation) { + if (info instanceof BeansSymbolAddOnInformation) { + BeansSymbolAddOnInformation info2 = (BeansSymbolAddOnInformation) info; + if (beanClassName.equals(info2.getBeanType())) { + return true; + } + if (info instanceof FeignClientBeanSymbolAddOnInformation) { + FeignClientBeanSymbolAddOnInformation feign = (FeignClientBeanSymbolAddOnInformation) info; + for (String configBean : feign.configClasses) { + if (beanClassName.equals(configBean)) { + return true; + } + } + } + } + } + } + } + } + } + return false; + } private static boolean isBeanMethod(MethodDeclaration m) { for (Iterator itr = m.modifiers().iterator(); itr.hasNext();) { @@ -149,4 +190,9 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile return Boot2JavaProblemType.MISSING_CONFIGURATION_ANNOTATION; } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentReconcilerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentReconcilerTest.java new file mode 100644 index 000000000..70975c9c3 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentReconcilerTest.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2023 VMware, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.reconcilers.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.SymbolKind; +import org.eclipse.lsp4j.WorkspaceSymbol; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; +import org.springframework.ide.vscode.boot.java.beans.ConfigBeanSymbolAddOnInformation; +import org.springframework.ide.vscode.boot.java.beans.FeignClientBeanSymbolAddOnInformation; +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; +import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; +import org.springframework.ide.vscode.boot.java.reconcilers.AddConfigurationIfBeansPresentReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; + +public class AddConfigurationIfBeansPresentReconcilerTest extends BaseReconcilerTest { + + @Override + protected String getFolder() { + return "addconfiguration"; + } + + @Override + protected String getProjectName() { + return "test-spring-validations"; + } + + @Override + protected JdtAstReconciler getReconciler() { + return new AddConfigurationIfBeansPresentReconciler(new QuickfixRegistry()); + } + + @BeforeEach + void setup() throws Exception { + super.setup(); + } + + @AfterEach + void tearDown() throws Exception { + super.tearDown(); + } + + @Test + void basicCase() throws Exception { + String source = """ + package example.demo; + + import org.springframework.context.annotation.Bean; + + class A { + + @Bean + String myBean() { + return "my-bean"; + } + + } + """; + List problems = reconcile("A.java", source, false); + + assertEquals(1, problems.size()); + + ReconcileProblem problem = problems.get(0); + + assertEquals(Boot2JavaProblemType.MISSING_CONFIGURATION_ANNOTATION, problem.getType()); + + String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength()); + assertEquals("A", markedStr); + + assertEquals(2, problem.getQuickfixes().size()); + + } + + @Test + void beanCase() throws Exception { + String source = """ + package example.demo; + + import org.springframework.context.annotation.Bean; + + class A { + + @Bean + String myBean() { + return "my-bean"; + } + + } + """; + List problems = reconcile(() -> { + AddConfigurationIfBeansPresentReconciler r = new AddConfigurationIfBeansPresentReconciler(new QuickfixRegistry()); + + WorkspaceSymbol workspaceSymbol = new WorkspaceSymbol("testConfig", SymbolKind.Class, Either.forLeft(new Location("file:///someUri", new Range()))); + ConfigBeanSymbolAddOnInformation beanSymbolAddOn = new ConfigBeanSymbolAddOnInformation("a", "example.demo.A"); + EnhancedSymbolInformation beanSymbol = new EnhancedSymbolInformation(workspaceSymbol, new SymbolAddOnInformation[] { beanSymbolAddOn }); + + SpringSymbolIndex mockSymbolIndex = mock(SpringSymbolIndex.class); + + when(mockSymbolIndex.getEnhancedSymbols(any(IJavaProject.class))).thenReturn(List.of(beanSymbol)); + + ApplicationContext context = mock(ApplicationContext.class); + when(context.getBean(SpringSymbolIndex.class)).thenReturn(mockSymbolIndex); + + r.setApplicationContext(context); + + return r; + }, "A.java", source, false); + + assertEquals(0, problems.size()); + + } + + @Test + void feignClientConfigCase() throws Exception { + String source = """ + package example.demo; + + import org.springframework.context.annotation.Bean; + + class A { + + @Bean + String myBean() { + return "my-bean"; + } + + } + """; + List problems = reconcile(() -> { + AddConfigurationIfBeansPresentReconciler r = new AddConfigurationIfBeansPresentReconciler(new QuickfixRegistry()); + + WorkspaceSymbol workspaceSymbol = new WorkspaceSymbol("testConfig", SymbolKind.Class, Either.forLeft(new Location("file:///someUri", new Range()))); + FeignClientBeanSymbolAddOnInformation beanSymbolAddOn = new FeignClientBeanSymbolAddOnInformation("b", "example.demo.B", "example.demo.A"); + EnhancedSymbolInformation beanSymbol = new EnhancedSymbolInformation(workspaceSymbol, new SymbolAddOnInformation[] { beanSymbolAddOn }); + + SpringSymbolIndex mockSymbolIndex = mock(SpringSymbolIndex.class); + + when(mockSymbolIndex.getEnhancedSymbols(any(IJavaProject.class))).thenReturn(List.of(beanSymbol)); + + ApplicationContext context = mock(ApplicationContext.class); + when(context.getBean(SpringSymbolIndex.class)).thenReturn(mockSymbolIndex); + + r.setApplicationContext(context); + + return r; + }, "A.java", source, false); + + assertEquals(0, problems.size()); + + } +}