/*
* 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 org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Manage a classpath.
*/
public class HaxeClasspath {
Logger LOG = Logger.getInstance("#com.intellij.plugins.haxe.haxelib.HaxeClasspath");
/**
* An immutable empty classpath that can be re-used.
*/
public static final HaxeClasspath EMPTY_CLASSPATH = new HaxeClasspath(true);
// Any access of myOrderedEntries MUST be synchronized if this classpath is
// used in a multi-threaded environment.
protected Set<HaxeClasspathEntry> myOrderedEntries;
/**
* Constructor used solely to create the EMPTY_CLASSPATH;
* @param createEmpty
*/
private HaxeClasspath(boolean createEmpty) {
myOrderedEntries = Collections.emptySet();
}
HaxeClasspath(HaxeClasspath initialEntries) {
this();
synchronized (initialEntries) {
myOrderedEntries.addAll(initialEntries.myOrderedEntries);
}
}
HaxeClasspath(Collection<HaxeClasspathEntry> initialEntries) {
this(initialEntries.size());
myOrderedEntries.addAll(initialEntries);
}
HaxeClasspath() {
this(16);
}
HaxeClasspath(int sizeHint) {
myOrderedEntries = new LinkedHashSet<HaxeClasspathEntry>(2 * sizeHint);
}
/**
* Add an entry to the end of this classpath. If the entry with the same
* URL already exists, the current one is maintained, along with its current
* position, and the new one is ignored.
*
* @param item to add to the end of the classpath.
*/
public void add(HaxeClasspathEntry item) {
synchronized(this) {
if (!contains(item))
myOrderedEntries.add(item);
}
}
/**
* Add a set of entries to this classpath. New entries
* will be added at the end of this classpath. Duplicate entries are ignored
* (current entries with the same URL are maintained, with their current
* ordering).
*
* @param entries to add to the end of this classpath.
*/
public void addAll(Collection<HaxeClasspathEntry> entries) {
synchronized(this) {
myOrderedEntries.addAll(entries);
}
}
/**
* Add another classpath to this one. New entries from the other classpath
* will be added at the end of this classpath. Duplicate entries are ignored
* (current entries with the same URL are maintained, with their current
* ordering).
*
* @param classpath to add to this classpath
*/
public void addAll(HaxeClasspath classpath) {
synchronized(this) {
synchronized(classpath) {
myOrderedEntries.addAll(classpath.myOrderedEntries);
}
}
}
/**
* Remove all entries from this classpath.
*/
public void clear() {
synchronized(this) {
myOrderedEntries.clear();
}
}
/**
* Determine whether this classpath contains an entry that semantically matches
* the given entry. (That is, it represents the same file system path.)
*
* @param item
* @return
*/
public boolean contains(HaxeClasspathEntry item) {
synchronized(this) {
return myOrderedEntries.contains(item);
}
}
/**
* Determine if a given URL is represented by any Entry/Item in the classpath.
* NOTE: This method *does NOT* attempt to normalize the URL. relative paths
* will NOT match.
*
* @param url we are looking for.
* @return true if an entry matches the URL; false otherwise.
*/
public boolean containsUrl(final String url) {
if (null == url || url.isEmpty())
return false;
// This algorithm could be done using:
// myOrderedEntries.contains(new HaxeIdeaItem(null, url));
// but that's not really right, because we'd be creating a sub-class
// of HaxeClasspathEntry here, and we would assume the format of the
// subclass. (Plus, it's slower because of the name parsing and
// management overhead.)
//
// Instead, we let the class figure out how to do it, and assume only that
// a matching hashcode can be generated given an URL.
//
synchronized(this) {
// OK, this works because the hash code for a HaxeClasspathEntry is the
// myUrl hash code. That makes hash lookups the same for urls and entries.
// It also makes equals work because the HaxeClasspathEntry's equals
// method is overridden in the same way, to only check the url string.
class Comparator {
public int hashCode() { return HaxeClasspathEntry.hashUrl(url); }
public boolean equals(Object o) {
HaxeClasspathEntry that = (HaxeClasspathEntry)o;
if (!that.getUrl().equals(url)) return false; // In case of hash collision.
return true;
}
}
return myOrderedEntries.contains(new Comparator());
}
}
/**
* Tell whether this classpath is empty (has no entries). This is a constant
* time operation, whereas size() would not be.
*
* @return true if empty, false if not.
*/
public boolean isEmpty() {
synchronized(this) {
return myOrderedEntries.isEmpty();
}
}
/**
* Iterate over the list and perform a task. The lambda must return a boolean
* value indicating whether to continue iterating through the paths, or stop
* immediately: true to continue, false to stop.
*
* @param lambda functional interface for the action to perform.
* @return what the action returned: true to keep going, or false to stop.
*/
public boolean iterate(Lambda lambda) {
boolean continu = true;
synchronized(this) {
for (HaxeClasspathEntry entry : myOrderedEntries) {
continu = lambda.processEntry(entry);
if (!continu)
break;
}
}
return continu;
}
/**
* Remove an entry from the classpath.
*
* @param item to remove.
*/
public void remove(HaxeClasspathEntry item) {
synchronized(this) {
myOrderedEntries.remove(item);
}
}
/**
* Remove all entries from this classpath that occur in the given classpath.
* It is NOT an error of otherpath contains entries that do not exist in
* this classpath.
*
* @param otherPath with entries to remove
*/
public void removeAll(@NotNull HaxeClasspath otherPath) {
synchronized(this) {
synchronized (otherPath) {
myOrderedEntries.removeAll(otherPath.myOrderedEntries);
}
}
}
/**
* Remove all entries from this classpath that occur in the given list.
* It is NOT an error to specify entries that do not already exist.
*
* @param entries to remove.
*/
public void removeAll(@NotNull Collection<HaxeClasspathEntry> entries) {
synchronized(this) {
if (entries.isEmpty() || myOrderedEntries.isEmpty()) {
return;
}
Iterator iterator = myOrderedEntries.iterator();
while (iterator.hasNext()) {
HaxeClasspathEntry entry = (HaxeClasspathEntry)iterator.next();
if (entries.contains(entry)) {
iterator.remove();
}
}
}
}
/**
* Get the number of entries in this classpath. BEWARE: This is NOT a
* constant time operation. It is dependent upon the number of elements
* in this classpath.
*
* @return the number of entries.
*/
public int size() {
synchronized(this) {
return myOrderedEntries.size();
}
}
/**
* Wrapper to do a discrete bit of work while honoring the synchronized
* aspects of the path.
*/
public interface Lambda {
/**
* Process a single entry in the classpath list. The list is already
* synchronized when this is called.
*
* @param entry A class path element (path).
* @return true if the loop should keep running, false if not.
*/
public boolean processEntry(HaxeClasspathEntry entry);
}
/**
* Dumps this classpath to a single idea.log entry.
* @param header to display at the start of the entry.
*/
public void debugDump(String header) {
class Collector implements Lambda { // XXX: Wish this could be anonymous.
public String myLog;
public Collector(String header) {myLog = header;}
@Override
public boolean processEntry(HaxeClasspathEntry entry) {
myLog += "\n " + entry.getName() + "\n " + entry.getUrl();
return true;
}
};
Collector logCollector = new Collector(null == header ? "HaxeClasspath dump" : header);
iterate(logCollector);
LOG.debug(logCollector.myLog);
}
}