package org.jetbrains.jps.android; import com.android.sdklib.SdkManager; import com.android.sdklib.repository.local.LocalSdk; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.HashSet; import org.jetbrains.android.util.ResourceEntry; import org.jetbrains.android.util.ValueResourcesFileParser; import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.android.model.JpsAndroidModuleExtension; import org.jetbrains.jps.incremental.java.FormsParsing; import org.jetbrains.jps.model.java.JpsJavaClasspathKind; import org.jetbrains.jps.model.java.JpsJavaExtensionService; import org.jetbrains.jps.model.module.JpsDependencyElement; import org.jetbrains.jps.model.module.JpsModule; import org.jetbrains.jps.model.module.JpsModuleDependency; import java.io.*; import java.util.*; /** * @author Eugene.Kudelevsky */ public class AndroidBuildDataCache { private static AndroidBuildDataCache ourInstance; private final List<LocalSdk> myLocalSdks = Lists.newArrayList(); private final Map<JpsModule, MyAndroidDeps> myModule2AndroidDeps = new HashMap<JpsModule, MyAndroidDeps>(); private final Map<String, List<ResourceEntry>> myParsedValueResourceFiles = new HashMap<String, List<ResourceEntry>>(); @NotNull public static AndroidBuildDataCache getInstance() { if (ourInstance == null) { ourInstance = new AndroidBuildDataCache(); } return ourInstance; } public static void clean() { ourInstance = null; } // If parsing throws IOException, the result it is not cached, so invoker should catch it and stop the build public List<ResourceEntry> getParsedValueResourceFile(@NotNull File file) throws IOException { final String path = FileUtil.toCanonicalPath(file.getPath()); List<ResourceEntry> entries = myParsedValueResourceFiles.get(path); if (entries == null) { entries = parseValueResourceFile(file); myParsedValueResourceFiles.put(path, entries); } return entries; } @NotNull private static List<ResourceEntry> parseValueResourceFile(@NotNull File valueResXmlFile) throws IOException { final ArrayList<ResourceEntry> result = new ArrayList<ResourceEntry>(); final InputStream inputStream = new BufferedInputStream(new FileInputStream(valueResXmlFile)); try { FormsParsing.parse(inputStream, new ValueResourcesFileParser() { @Override protected void stop() { throw new FormsParsing.ParserStoppedException(); } @Override protected void process(@NotNull ResourceEntry resourceEntry) { result.add(resourceEntry); } }); } finally { inputStream.close(); } return result; } @NotNull public List<JpsAndroidModuleExtension> getAllAndroidDependencies(@NotNull JpsModule module, boolean librariesOnly) { MyAndroidDeps deps = myModule2AndroidDeps.get(module); if (deps == null) { deps = computeAndroidDependencies(module); myModule2AndroidDeps.put(module, deps); } return librariesOnly ? deps.myLibAndroidDeps : deps.myAndroidDeps; } @NotNull private static MyAndroidDeps computeAndroidDependencies(@NotNull JpsModule module) { final MyAndroidDeps result = new MyAndroidDeps(); final boolean recursively = AndroidJpsUtil.shouldProcessDependenciesRecursively(module); collectAndroidDependencies(module, result, new HashSet<String>(), true, recursively); Collections.reverse(result.myAndroidDeps); Collections.reverse(result.myLibAndroidDeps); return result; } private static void collectAndroidDependencies(@NotNull JpsModule module, @NotNull MyAndroidDeps result, @NotNull Set<String> visitedSet, boolean fillLibs, boolean recursively) { final List<JpsDependencyElement> dependencies = new ArrayList<JpsDependencyElement>(JpsJavaExtensionService.getInstance().getDependencies( module, JpsJavaClasspathKind.PRODUCTION_RUNTIME, false)); for (int i = dependencies.size() - 1; i >= 0; i--) { final JpsDependencyElement item = dependencies.get(i); if (item instanceof JpsModuleDependency) { final JpsModule depModule = ((JpsModuleDependency)item).getModule(); if (depModule != null) { final JpsAndroidModuleExtension depExtension = AndroidJpsUtil.getExtension(depModule); if (depExtension != null && visitedSet.add(depModule.getName())) { if (recursively) { final boolean newRecursively = AndroidJpsUtil.shouldProcessDependenciesRecursively(depModule); collectAndroidDependencies(depModule, result, visitedSet, fillLibs && depExtension.isLibrary(), newRecursively); } result.myAndroidDeps.add(depExtension); if (fillLibs && depExtension.isLibrary()) { result.myLibAndroidDeps.add(depExtension); } } } } } } @NotNull public LocalSdk getSdk(@NotNull File androidSdkHomePath) { for (LocalSdk sdk : myLocalSdks) { if (FileUtil.filesEqual(sdk.getLocation(), androidSdkHomePath)) { return sdk; } } LocalSdk newSdk = new LocalSdk(androidSdkHomePath); myLocalSdks.add(0, newSdk); return newSdk; } private abstract static class MyComputedValue<T> { @NotNull abstract T getValue() throws ComputationException; } private static class SuccessComputedValue<T> extends MyComputedValue<T> { @NotNull private final T myValue; private SuccessComputedValue(@NotNull T value) { myValue = value; } @NotNull @Override T getValue() throws ComputationException { return myValue; } } private static class ErrorComputedValue<T> extends MyComputedValue<T> { @NotNull private final String myMessage; private ErrorComputedValue(@NotNull String message) { myMessage = message; } @NotNull @Override T getValue() throws ComputationException { throw new ComputationException(myMessage); } } public static class ComputationException extends Exception { public ComputationException(@NotNull String message) { super(message); } } private static class MyAndroidDeps { final List<JpsAndroidModuleExtension> myAndroidDeps = new ArrayList<JpsAndroidModuleExtension>(); final List<JpsAndroidModuleExtension> myLibAndroidDeps = new ArrayList<JpsAndroidModuleExtension>(); } }