/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2014-2014 AS3Boyan
* Copyright 2014-2014 Elias Ku
*
* 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.intellij.plugins.haxe.haxelib;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.plugins.haxe.util.HaxeDebugTimeLog;
import org.apache.log4j.Level;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.ConcurrentSkipListSet;
/**
* Manages library retrieval and caching.
*
* This should be instantiated once for each SDK in the project. (Projects,
* particularly those that keep separate versions of the libraries in
* source control using separate branches, are not necessarily using the
* same haxe installation.)
*/
public final class HaxelibLibraryCache {
static final Logger LOG = Logger.getInstance("#com.intellij.plugins.haxe.haxelib.HaxeLibraryManager");
{
LOG.setLevel(Level.DEBUG);
}
private final InternalCache myCache;
ConcurrentSkipListSet<String> knownLibraries;
final Sdk mySdk;
public HaxelibLibraryCache(@NotNull Sdk sdk) {
myCache = new InternalCache();
knownLibraries = null;
mySdk = sdk;
/* TODO: EMB Note: This block of code belongs in HaxelibUtils.getInstalledLibraries.
* I'm leaving it here for now, to simplify the merge, but really should be moved.
*/
final List<String> listCmdOutput = HaxelibCommandUtils.issueHaxelibCommand(sdk, "list");
if ((listCmdOutput.size() > 0) && (! listCmdOutput.get(0).contains("Unknown command"))) {
final List<String> installedHaxelibs = new ArrayList<String>();
// add haxelib names as args for 'haxelib path' command
final String[] pathCmdline = new String[listCmdOutput.size()+1];
int index = 0;
pathCmdline[index++] = "path";
for (final String line : listCmdOutput) {
final String[] tokens = line.split(":");
pathCmdline[index++] = tokens[0];
installedHaxelibs.add(tokens[0]);
}
// update list of known haxelibs
knownLibraries = new ConcurrentSkipListSet<String>(installedHaxelibs);
// add haxelib classpath to lookup cache
final List<String> pathCmdOutput = HaxelibCommandUtils.issueHaxelibCommand(sdk, pathCmdline);
for (final String s : pathCmdOutput) {
if (s.startsWith("-")) continue; // skip lines that don't contain haxelib path
try {
final int tmpSeparator = s.lastIndexOf('/');
final int endSeparator = s.lastIndexOf('/', tmpSeparator-1);
final int beginSeparator = s.lastIndexOf(File.separatorChar, endSeparator - 1);
final String haxelibName = s.substring(beginSeparator+1, endSeparator);
final HaxeClasspath classpath = new HaxeClasspath();
classpath.add(new HaxelibItem(haxelibName, s));
myCache.add(new HaxelibLibraryEntry(haxelibName, classpath));
}
catch (IndexOutOfBoundsException e) {
// defensive try-catch block to handle possible exceptions when 'haxelib path'
// output does not match above parsing expectations
// e.g. when haxelib path output order or format is changed (happened once),
// or when platform specific (Windows OS) format errors occur
}
}
}
/* END of code that should be moved to HaxelibUtils.getInstalledLibraries. */
}
/**
* Get a union of all of the classpaths for the given libraries.
*
* @param libraryNames a set of libraries of current interest.
* @return a (possibly empty) collection of classpaths.
*/
@NotNull
public HaxeClasspath getClasspathForHaxelibs(@Nullable List<String> libraryNames) {
if (null == libraryNames || libraryNames.isEmpty())
return HaxeClasspath.EMPTY_CLASSPATH;
HaxeClasspath paths = new HaxeClasspath(libraryNames.size());
for (String libName : libraryNames) {
HaxeClasspath libPath = getClasspathForHaxelib(libName);
paths.addAll(libPath);
}
return paths;
}
/**
* Get the classpath for a specific library. If it does not reside in
* the cache, it will be looked up and cached for future use.
*
* @param libraryName name of the library of interest.
* @return a (possibly empty) list of classpaths known for that library.
*/
@NotNull
public HaxeClasspath getClasspathForHaxelib(String libraryName) {
HaxeDebugTimeLog timeLog = HaxeDebugTimeLog.startNew("getClasspathForLibrary",
HaxeDebugTimeLog.Since.Start);
try {
if (libraryIsKnown(libraryName)) {
timeLog.stamp("Loading library classpath:" + libraryName);
// Try the cache first.
HaxelibLibraryEntry lib = myCache.get(libraryName);
if (null != lib) {
timeLog.stamp("Returning cached results");
return lib.getClasspathEntries();
}
timeLog.stamp("Cache miss");
// It's not in the cache, so go get it and cache the results.
HaxeClasspath itemList = findHaxelibPath(libraryName);
myCache.add(new HaxelibLibraryEntry(libraryName, itemList));
timeLog.stamp("haxelib finished with " + itemList.size() + " entries");
return itemList;
}
timeLog.stamp("Unknown library !!! " + libraryName + " !!! ");
return HaxeClasspath.EMPTY_CLASSPATH;
}
finally {
timeLog.printIfTimeExceeds(2); // Short-timed logs just clutter up the ouput.
}
}
/**
* Find a library on the haxelib path and return its complete class path.
*
* @param libraryName file to find.
* @return a list of path names in the requested library, if any.
*/
@NotNull
public HaxeClasspath findHaxelibPath(@NotNull String libraryName) {
if (! libraryIsKnown(libraryName)) {
return HaxeClasspath.EMPTY_CLASSPATH;
}
HaxelibLibraryEntry cacheEntry = myCache.get(libraryName);
if (cacheEntry != null) {
return cacheEntry.getClasspathEntries();
}
return HaxelibClasspathUtils.getHaxelibLibraryPath(mySdk, libraryName);
}
/**
* Retrieve the known libraries, first from the cache, then, if missing,
* from haxelib.
*
* @return a collection of known libraries.
*/
@NotNull
private Collection<String> retrieveKnownLibraries() {
// If we don't have the list, then load it.
if (null == knownLibraries) {
List<String> libs = HaxelibClasspathUtils.getInstalledLibraries(mySdk);
knownLibraries = new ConcurrentSkipListSet<String>(libs);
}
return knownLibraries;
}
/**
* Tell if a given library is known to haxelib.
*
* @param libraryName the library of interest. Case sensitive!
* @return true if the library is found, false otherwise.
*/
public boolean libraryIsKnown(String libraryName) {
return retrieveKnownLibraries().contains(libraryName);
}
/**
* Get a list of all of the libraries known to this library manager.
* @return a (possibly empty) list of all known libraries.
*/
public List<String> getKnownLibraries() {
Collection<String> knownLibs = retrieveKnownLibraries();
ArrayList<String> aryLibs = new ArrayList<String>(knownLibs.size());
aryLibs.addAll(knownLibs);
return aryLibs;
}
/**
* Encapsulate haxelib output so that it can be cached.
*/
private final class HaxelibLibraryEntry {
final String myName;
final HaxeClasspath myClasspathEntries;
public HaxelibLibraryEntry(String name, HaxeClasspath classpathEntries) {
myName = name;
myClasspathEntries = new HaxeClasspath(classpathEntries);
}
public String getName() {
return myName;
}
public HaxeClasspath getClasspathEntries() {
return myClasspathEntries;
}
}
/**
* A simple cache of entries. This is used to cache the return values
* from the haxelib command. It should be checked before running
* haxelib.
*/
private final class InternalCache {
final Hashtable<String, HaxelibLibraryEntry> myCache;
public InternalCache() {
myCache = new Hashtable<String, HaxelibLibraryEntry>();
}
public void add(HaxelibLibraryEntry entry) {
HaxelibLibraryEntry oldEntry = myCache.put(entry.getName(), entry);
if (null != oldEntry) {
LOG.warn("Duplicating cached data for entry " + entry.getName());
}
}
public void clear() {
myCache.clear();
}
@Nullable
public HaxelibLibraryEntry get(@NotNull String name) {
return myCache.get(name);
}
}
}