package org.jetbrains.android.uipreview; import com.android.builder.model.AndroidArtifact; import com.android.builder.model.AndroidArtifactOutput; import com.android.builder.model.Variant; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.rendering.RenderSecurityManager; import com.android.sdklib.IAndroidTarget; import com.android.tools.idea.gradle.IdeaAndroidProject; import com.android.tools.idea.gradle.util.GradleUtil; import com.android.tools.idea.rendering.*; import com.android.utils.HtmlBuilder; import com.android.utils.SdkUtils; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.CompilerModuleExtension; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.containers.HashSet; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidRootUtil; import org.jetbrains.android.sdk.AndroidPlatform; import org.jetbrains.android.sdk.AndroidTargetData; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Set; import static com.android.SdkConstants.DOT_AAR; import static com.android.SdkConstants.EXT_JAR; import static com.android.SdkConstants.FN_RESOURCE_CLASS; import static com.intellij.lang.annotation.HighlightSeverity.WARNING; import static org.jetbrains.android.facet.ResourceFolderManager.EXPLODED_AAR; /** * @author Eugene.Kudelevsky */ public final class ProjectClassLoader extends RenderClassLoader { private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.uipreview.ProjectClassLoader"); private final Module myModule; private final RenderLogger myLogger; private final Object myCredential; public ProjectClassLoader(@Nullable ClassLoader parentClassLoader, @NotNull Module module, @Nullable RenderLogger logger, @Nullable Object credential) { super(parentClassLoader); myModule = module; myLogger = logger; myCredential = credential; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { return super.findClass(name); } catch (ClassNotFoundException e) { if (!myInsideJarClassLoader) { int index = name.lastIndexOf('.'); if (index != -1 && name.charAt(index + 1) == 'R' && (index == name.length() - 2 || name.charAt(index + 2) == '$') && index > 1) { byte[] data = AarResourceClassRegistry.get().findClassDefinition(name); if (data != null) { return defineClass(null, data, 0, data.length); } } } throw e; } } @Nullable public static ClassLoader create(IAndroidTarget target, Module module) throws Exception { AndroidPlatform androidPlatform = AndroidPlatform.getInstance(module); if (androidPlatform == null) { return null; } AndroidTargetData targetData = androidPlatform.getSdkData().getTargetData(target); LayoutLibrary library = targetData.getLayoutLibrary(module.getProject()); if (library == null) { return null; } return new ProjectClassLoader(library.getClassLoader(), module, null, null); } @NotNull @Override protected Class<?> load(String name) throws ClassNotFoundException { final Class<?> aClass = loadClassFromModuleOrDependency(myModule, name, new HashSet<Module>()); if (aClass != null) { return aClass; } throw new ClassNotFoundException(name); } @Nullable private Class<?> loadClassFromModuleOrDependency(Module module, String name, Set<Module> visited) { if (!visited.add(module)) { return null; } Class<?> aClass = loadClassFromModule(module, name); if (aClass != null) { return aClass; } aClass = loadClassFromJar(name); if (aClass != null) { return aClass; } for (Module depModule : ModuleRootManager.getInstance(module).getDependencies(false)) { aClass = loadClassFromModuleOrDependency(depModule, name, visited); if (aClass != null) { return aClass; } } return null; } @Nullable private Class<?> loadClassFromModule(Module module, String name) { final CompilerModuleExtension extension = CompilerModuleExtension.getInstance(module); if (extension == null) { return null; } VirtualFile vOutFolder = extension.getCompilerOutputPath(); if (vOutFolder == null) { AndroidFacet facet = AndroidFacet.getInstance(module); if (facet != null && facet.isGradleProject()) { // Try a bit harder; we don't have a compiler module extension or mechanism // to query this yet, so just hardcode it (ugh!) IdeaAndroidProject gradleProject = facet.getIdeaAndroidProject(); if (gradleProject != null) { Variant variant = gradleProject.getSelectedVariant(); String variantName = variant.getName(); AndroidArtifact mainArtifactInfo = variant.getMainArtifact(); File classesFolder = mainArtifactInfo.getClassesFolder(); // Older models may not supply it; in that case, we rely on looking relative // to the .APK file location: //noinspection ConstantConditions if (classesFolder == null) { AndroidArtifactOutput output = GradleUtil.getOutput(mainArtifactInfo); File file = output.getOutputFile(); File buildFolder = file.getParentFile().getParentFile(); classesFolder = new File(buildFolder, "classes"); // See AndroidContentRoot } File outFolder = new File(classesFolder, // Change variant name variant-release into variant/release directories variantName.replace('-', File.separatorChar)); if (outFolder.exists()) { vOutFolder = LocalFileSystem.getInstance().findFileByIoFile(outFolder); if (vOutFolder != null) { Class<?> localClass = loadClassFromClassPath(name, VfsUtilCore.virtualToIoFile(vOutFolder)); if (localClass != null) { return localClass; } } } } } return null; } return loadClassFromClassPath(name, VfsUtilCore.virtualToIoFile(vOutFolder)); } @Override @Nullable protected Class<?> loadClassFile(final String fqcn, File classFile) { // Make sure the class file is up to date and if not, log an error if (myLogger != null) { // Allow creating class loaders during rendering; may be prevented by the RenderSecurityManager boolean token = RenderSecurityManager.enterSafeRegion(myCredential); try { long classFileModified = classFile.lastModified(); if (classFileModified > 0L) { VirtualFile virtualFile = ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() { @Nullable @Override public VirtualFile compute() { Project project = myModule.getProject(); GlobalSearchScope scope = myModule.getModuleScope(); PsiManager psiManager = PsiManager.getInstance(project); JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(psiManager.getProject()); PsiClass source = psiFacade.findClass(fqcn, scope); if (source != null) { PsiFile containingFile = source.getContainingFile(); if (containingFile != null) { return containingFile.getVirtualFile(); } } return null; } }); if (virtualFile != null && !FN_RESOURCE_CLASS.equals(virtualFile.getName())) { // Don't flag R.java edits; not done by user // Edited but not yet saved? boolean modified = FileDocumentManager.getInstance().isFileModified(virtualFile); if (!modified) { // Check timestamp File sourceFile = VfsUtilCore.virtualToIoFile(virtualFile); long sourceFileModified = sourceFile.lastModified(); if (sourceFileModified > classFileModified) { modified = true; } } if (modified) { RenderProblem.Html problem = RenderProblem.create(WARNING); HtmlBuilder builder = problem.getHtmlBuilder(); String className = fqcn.substring(fqcn.lastIndexOf('.') + 1); builder.addLink("The " + className + " custom view has been edited more recently than the last build: ", "Build", " the project.", myLogger.getLinkManager().createCompileModuleUrl()); myLogger.addMessage(problem); } } } } finally { RenderSecurityManager.exitSafeRegion(token); } } return super.loadClassFile(fqcn, classFile); } @Override protected URL[] getExternalJars() { final List<URL> result = new ArrayList<URL>(); for (VirtualFile libFile : AndroidRootUtil.getExternalLibraries(myModule)) { if (EXT_JAR.equals(libFile.getExtension())) { final File file = new File(libFile.getPath()); if (file.exists()) { try { result.add(SdkUtils.fileToUrl(file)); File parentFile = file.getParentFile(); if (parentFile != null && (parentFile.getPath().endsWith(DOT_AAR) || parentFile.getPath().contains(EXPLODED_AAR))) { AppResourceRepository appResources = AppResourceRepository.getAppResources(myModule, true); if (appResources != null) { AarResourceClassRegistry.get().addLibrary(appResources, parentFile); } } } catch (MalformedURLException e) { LOG.error(e); } } } } return result.toArray(new URL[result.size()]); } }