blob: ddb6dfb0453f3db4dff1017924437145b3b68c24 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Cloudsmith Inc. - rewrite for smaller memory footprint
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
import org.eclipse.equinox.p2.core.IPool;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.query.CollectionResult;
import org.eclipse.equinox.p2.query.Collector;
import org.eclipse.equinox.p2.query.IQuery;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
/**
* A map that stores {@link IInstallableUnit} instances in a way that is efficient to query
*/
public class IUMap implements Cloneable {
/**
* Iterator over all the {@link IInstallableUnit} instances in the map.
*/
public class MapIterator implements Iterator<IInstallableUnit> {
//iterator over the keys in UIMap
private final Iterator<Object> unitIterator;
private IInstallableUnit[] currentBucket;
private int bucketIndex = 0;
private IInstallableUnit nextElement = null;
MapIterator() {
super();
unitIterator = units.values().iterator();
}
@Override
public boolean hasNext() {
return positionNext();
}
@Override
public IInstallableUnit next() {
if (!positionNext())
throw new NoSuchElementException();
IInstallableUnit nxt = nextElement;
nextElement = null;
return nxt;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private boolean positionNext() {
if (nextElement != null)
return true;
if (currentBucket != null) {
nextElement = currentBucket[bucketIndex];
if (++bucketIndex == currentBucket.length) {
currentBucket = null;
bucketIndex = -1;
}
return true;
}
if (!unitIterator.hasNext())
return false;
Object val = unitIterator.next();
if (val instanceof IInstallableUnit)
nextElement = (IInstallableUnit) val;
else {
currentBucket = (IInstallableUnit[]) val;
nextElement = currentBucket[0];
bucketIndex = 1;
}
return true;
}
}
/**
* Map<String,Object> mapping IU id to either arrays of iu's or a single iu with that id.
*/
final Map<String, Object> units = new HashMap<>();
public IUMap() {
//
}
private IUMap(IUMap cloneSource) {
units.putAll(cloneSource.units);
}
public void add(IInstallableUnit unit) {
String key = unit.getId();
Object matching = units.get(key);
if (matching == null) {
units.put(key, unit);
return;
}
// We already had something at this key position. It must be
// preserved.
if (matching.getClass().isArray()) {
// Entry is an array. Add unique
IInstallableUnit[] iuArr = (IInstallableUnit[]) matching;
int idx = iuArr.length;
while (--idx >= 0)
if (iuArr[idx].equals(unit))
// This unit has already been added
return;
IInstallableUnit[] iuArrPlus = new IInstallableUnit[iuArr.length + 1];
System.arraycopy(iuArr, 0, iuArrPlus, 0, iuArr.length);
iuArrPlus[iuArr.length] = unit;
units.put(unit.getId(), iuArrPlus);
} else {
IInstallableUnit old = (IInstallableUnit) matching;
if (!old.equals(unit))
units.put(key, new IInstallableUnit[] {old, unit});
}
}
public void addAll(IInstallableUnit[] toAdd) {
for (IInstallableUnit toAdd1 : toAdd) {
add(toAdd1);
}
}
public void addAll(Collection<IInstallableUnit> toAdd) {
for (IInstallableUnit unit : toAdd) {
add(unit);
}
}
public void clear() {
units.clear();
}
@Override
public IUMap clone() {
return new IUMap(this);
}
public Iterator<IInstallableUnit> iterator() {
return new MapIterator();
}
public boolean contains(IInstallableUnit unit) {
return !internalGet(unit.getId(), unit.getVersion()).isEmpty();
}
/**
* Returns a collection of units that has the given <code>id</code>.
* @param id The id of the desired units. Must not be <code>null</code>.
* @return The units corresponding to the given <code>id</code>.
*/
public Collection<IInstallableUnit> getUnits(String id) {
Object bucket = units.get(id);
if (bucket == null)
return Collections.emptyList();
return bucket.getClass().isArray() ? CollectionUtils.unmodifiableList((IInstallableUnit[]) bucket) : Collections.singletonList((IInstallableUnit) bucket);
}
public IQueryResult<IInstallableUnit> get(String id) {
return internalGet(id, null);
}
private IQueryResult<IInstallableUnit> internalGet(String id, Version version) {
if (id == null) {
IQuery<IInstallableUnit> query = version == null ? QueryUtil.createIUAnyQuery() : QueryUtil.createIUQuery(null, version);
return query.perform(iterator());
}
Collection<IInstallableUnit> idUnits = getUnits(id);
if (idUnits.isEmpty())
return Collector.emptyCollector();
return version == null ? new CollectionResult<>(idUnits) : QueryUtil.createIUQuery(id, version).perform(idUnits.iterator());
}
public IInstallableUnit get(String id, Version version) {
IQueryResult<IInstallableUnit> result = internalGet(id, version);
return result.isEmpty() ? null : result.iterator().next();
}
public void remove(IInstallableUnit unit) {
String key = unit.getId();
Object matching = units.get(key);
if (matching == null)
return;
if (matching instanceof IInstallableUnit) {
if (matching.equals(unit))
units.remove(key);
return;
}
IInstallableUnit[] array = (IInstallableUnit[]) matching;
int idx = array.length;
while (--idx >= 0) {
if (unit.equals(array[idx])) {
if (array.length == 2) {
// We no longer need this array. Replace it with the
// entry that we keep.
units.put(key, idx == 0 ? array[1] : array[0]);
break;
}
// Shrink the array
IInstallableUnit[] newArray = new IInstallableUnit[array.length - 1];
if (idx > 0)
System.arraycopy(array, 0, newArray, 0, idx);
if (idx + 1 < array.length)
System.arraycopy(array, idx + 1, newArray, idx, array.length - (idx + 1));
units.put(key, newArray);
break;
}
}
}
public void removeAll(Collection<IInstallableUnit> toRemove) {
for (IInstallableUnit iu : toRemove)
remove(iu);
}
/**
* Replace all instances of the IInstallableUnits in the receiver
* with the shared IInstallableUnits from the provided iuPool.
* This operation is a no-op if iuPool is null.
*
* @param iuPool an IPool containing the shared IInstallableUnits
*/
public void compress(IPool<IInstallableUnit> iuPool) {
if (iuPool == null) {
return;
}
Iterator<Entry<String, Object>> entries = units.entrySet().iterator();
while (entries.hasNext()) {
Entry<String, Object> entry = entries.next();
Object value = entry.getValue();
if (value.getClass().isArray()) {
IInstallableUnit[] array = (IInstallableUnit[]) value;
for (int i = 0; i < array.length; i++) {
array[i] = iuPool.add(array[i]);
}
} else {
entry.setValue(iuPool.add((IInstallableUnit) value));
}
}
}
}