Java tutorial
/* * Copyright 2016 The Bazel Authors. All rights reserved. * * 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.idea.blaze.android.rendering; import static com.android.SdkConstants.ANDROID_MANIFEST_XML; import com.android.tools.idea.rendering.HtmlLinkManager; import com.android.tools.idea.rendering.RenderErrorContributor; import com.android.tools.idea.rendering.RenderLogger; import com.android.tools.idea.rendering.RenderResult; import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; import com.android.utils.HtmlBuilder; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.Maps; import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.TreeMultimap; import com.google.idea.blaze.android.sync.model.AndroidResourceModule; import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; import com.google.idea.blaze.base.ideinfo.TargetKey; import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager; import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.search.GlobalSearchScope; import java.io.File; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.SortedMap; import java.util.function.Function; import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; /** Contribute blaze specific render errors. */ public class BlazeRenderErrorContributor extends RenderErrorContributor { private RenderLogger logger; private Module module; private Project project; public BlazeRenderErrorContributor(RenderResult result, @Nullable DataContext dataContext) { super(result, dataContext); logger = result.getLogger(); module = result.getModule(); project = module.getProject(); } @Override public Collection<RenderErrorModel.Issue> reportIssues() { BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); if (blazeProjectData == null || !logger.hasErrors()) { return getIssues(); } TargetMap targetMap = blazeProjectData.targetMap; ArtifactLocationDecoder decoder = blazeProjectData.artifactLocationDecoder; AndroidResourceModule resourceModule = AndroidResourceModuleRegistry.getInstance(project).get(module); if (resourceModule == null) { return getIssues(); } TargetIdeInfo target = targetMap.get(resourceModule.targetKey); if (target == null) { return getIssues(); } reportGeneratedResources(resourceModule, targetMap, decoder); reportNonStandardAndroidManifestName(target, decoder); reportResourceTargetShouldDependOnClassTarget(target, targetMap, decoder); return getIssues(); } /** * We can't find generated resources. If a layout uses them, the layout won't render correctly. */ private void reportGeneratedResources(AndroidResourceModule resourceModule, TargetMap targetMap, ArtifactLocationDecoder decoder) { Map<String, Throwable> brokenClasses = logger.getBrokenClasses(); if (brokenClasses == null || brokenClasses.isEmpty()) { return; } // Sorted entries for deterministic error message. SortedMap<ArtifactLocation, TargetIdeInfo> generatedResources = Maps .newTreeMap(getGeneratedResources(targetMap.get(resourceModule.targetKey))); for (TargetKey dependency : resourceModule.transitiveResourceDependencies) { generatedResources.putAll(getGeneratedResources(targetMap.get(dependency))); } if (generatedResources.isEmpty()) { return; } HtmlBuilder builder = new HtmlBuilder(); builder.add("Generated resources will not be discovered by the IDE:"); builder.beginList(); for (Map.Entry<ArtifactLocation, TargetIdeInfo> entry : generatedResources.entrySet()) { ArtifactLocation resource = entry.getKey(); TargetIdeInfo target = entry.getValue(); builder.listItem().add(resource.getRelativePath()).add(" from "); addTargetLink(builder, target, decoder); } builder.endList().add("Please avoid using generated resources, ") .addLink("then ", "sync the project", " ", getLinkManager().createSyncProjectUrl()) .addLink("and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()); addIssue().setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes .setSummary("Generated resources").setHtmlContent(builder).build(); } private static SortedMap<ArtifactLocation, TargetIdeInfo> getGeneratedResources(TargetIdeInfo target) { if (target == null || target.androidIdeInfo == null) { return Collections.emptySortedMap(); } SortedMap<ArtifactLocation, TargetIdeInfo> generatedResources = Maps.newTreeMap(); generatedResources.putAll(target.androidIdeInfo.resources.stream().filter(ArtifactLocation::isGenerated) .collect(Collectors.toMap(Function.identity(), resource -> target))); return generatedResources; } /** * When the Android manifest isn't AndroidManifest.xml, resolving resource IDs would fail. This * doesn't seem to be an issue if the manifest belongs to one of the target's dependencies. */ private void reportNonStandardAndroidManifestName(TargetIdeInfo target, ArtifactLocationDecoder decoder) { if (target.androidIdeInfo == null || target.androidIdeInfo.manifest == null) { return; } Map<String, Throwable> brokenClasses = logger.getBrokenClasses(); if (brokenClasses == null || brokenClasses.isEmpty()) { return; } File manifest = decoder.decode(target.androidIdeInfo.manifest); if (manifest.getName().equals(ANDROID_MANIFEST_XML)) { return; } HtmlBuilder builder = new HtmlBuilder(); addTargetLink(builder, target, decoder).add(" uses a non-standard name for the Android manifest: "); String linkToManifest = HtmlLinkManager.createFilePositionUrl(manifest, -1, 0); if (linkToManifest != null) { builder.addLink(manifest.getName(), linkToManifest); } else { builder.newline().add(manifest.getPath()); } // TODO: add a link to automatically rename the file and refactor all references. builder.newline().add("Please rename it to ").add(ANDROID_MANIFEST_XML) .addLink(", then ", "sync the project", "", getLinkManager().createSyncProjectUrl()) .addLink(" and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()); addIssue().setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes. .setSummary("Non-standard manifest name").setHtmlContent(builder).build(); } /** * Blaze doesn't resolve class dependencies from resources until building the final * android_binary, so we could end up with resources that ultimately build correctly, but fail to * find their class dependencies during rendering in the layout editor. */ private void reportResourceTargetShouldDependOnClassTarget(TargetIdeInfo target, TargetMap targetMap, ArtifactLocationDecoder decoder) { Collection<String> missingClasses = logger.getMissingClasses(); if (missingClasses == null || missingClasses.isEmpty()) { return; } // Sorted entries for deterministic error message. SortedSetMultimap<String, TargetKey> missingClassToTargetMap = TreeMultimap.create(); SourceToTargetMap sourceToTargetMap = SourceToTargetMap.getInstance(project); ImmutableCollection transitiveDependencies = TransitiveDependencyMap.getInstance(project) .getTransitiveDependencies(target.key); for (String missingClass : missingClasses) { File sourceFile = getSourceFileForClass(missingClass); if (sourceFile == null) { continue; } ImmutableCollection<TargetKey> sourceTargets = sourceToTargetMap.getRulesForSourceFile(sourceFile); if (sourceTargets.stream().noneMatch(sourceTarget -> sourceTarget.equals(target.key) || transitiveDependencies.contains(sourceTarget))) { missingClassToTargetMap.putAll(missingClass, sourceTargets); } } if (missingClassToTargetMap.isEmpty()) { return; } HtmlBuilder builder = new HtmlBuilder(); addTargetLink(builder, target, decoder).add(" contains resource files that reference these classes:") .beginList(); for (String missingClass : missingClassToTargetMap.keySet()) { builder.listItem().addLink(missingClass, getLinkManager().createOpenClassUrl(missingClass)) .add(" from "); for (TargetKey targetKey : missingClassToTargetMap.get(missingClass)) { addTargetLink(builder, targetMap.get(targetKey), decoder).add(" "); } } builder.endList().add("Please fix your dependencies so that "); addTargetLink(builder, target, decoder).add(" correctly depends on these classes, ") .addLink("then ", "sync the project", " ", getLinkManager().createSyncProjectUrl()) .addLink("and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()).newline() .newline() .addBold("NOTE: blaze can still build with the incorrect dependencies " + "due to the way it handles resources, " + "but the layout editor needs them to be correct."); addIssue().setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above missing classes. .setSummary("Missing class dependencies").setHtmlContent(builder).build(); } private File getSourceFileForClass(String className) { return ApplicationManager.getApplication().runReadAction((Computable<File>) () -> { try { PsiClass psiClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.projectScope(project)); if (psiClass == null) { return null; } return VfsUtilCore.virtualToIoFile(psiClass.getContainingFile().getVirtualFile()); } catch (IndexNotReadyException ignored) { // We're in dumb mode. Abort! Abort! return null; } }); } private HtmlBuilder addTargetLink(HtmlBuilder builder, TargetIdeInfo target, ArtifactLocationDecoder decoder) { File buildFile = decoder.decode(target.buildFile); int line = ApplicationManager.getApplication().runReadAction((Computable<Integer>) () -> { PsiElement buildTargetPsi = BuildReferenceManager.getInstance(project).resolveLabel(target.key.label); if (buildTargetPsi == null) { return -1; } return StringUtil.offsetToLineNumber(buildTargetPsi.getContainingFile().getText(), buildTargetPsi.getTextOffset()); }); String url = HtmlLinkManager.createFilePositionUrl(buildFile, line, 0); if (url != null) { return builder.addLink(target.toString(), url); } return builder.add(target.toString()); } /** Extension to provide {@link BlazeRenderErrorContributor}. */ public static class BlazeProvider extends Provider { @Override public boolean isApplicable(Project project) { return Blaze.isBlazeProject(project); } @Override public RenderErrorContributor getContributor(RenderResult result, @Nullable DataContext dataContext) { return new BlazeRenderErrorContributor(result, dataContext); } } }