/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2012 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.extension;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Caches meta-data & contents of an IExtensionProvider for fast reuse.
* Some ExtensionProviders will acquire data through network connections, some will parse it from disk, but this class
* helps to avoid repeating resource & time consuming operations.
*
* @author acostescu
*/
public abstract class CachingExtensionProvider implements IExtensionProvider
{
// keeps track of the version intervals already cached per extension id; the list must be a sorted List
protected final Map<String, List<VersionInterval>> cachedDepencencyMetadataVersions = new HashMap<String, List<VersionInterval>>();
// all cached versions of DependencyMetadata info per extension id
protected final Map<String, List<DependencyMetadata>> cachedDependencyMetadata = new HashMap<String, List<DependencyMetadata>>();
public DependencyMetadata[] getDependencyMetadata(ExtensionDependencyDeclaration extensionDependency)
{
boolean cached = false;
List<VersionInterval> cachedIntervals = cachedDepencencyMetadataVersions.get(extensionDependency.id);
if (cachedIntervals != null)
{
for (int i = cachedIntervals.size() - 1; !cached && i >= 0; i--)
{
VersionInterval cachedInterval = cachedIntervals.get(i);
if (compareVersions(cachedInterval.max, extensionDependency.minVersion, false, true) < 0)
{
i = -1; // no use searching further (backwards) in the sorted list
}
else
{
// so cached.max >= interval.min
if (compareVersions(cachedInterval.min, extensionDependency.maxVersion, true, false) <= 0)
{
// so cached.min <= interval.max && cached.max >= interval.min
VersionInterval intersection = intersection(cachedInterval, new VersionInterval(extensionDependency.minVersion,
extensionDependency.maxVersion));
if (intersection == null || intersection.min != extensionDependency.minVersion || intersection.max != extensionDependency.maxVersion)
{
i = -1;
}
else
{
cached = true;
}
} // else cached.min > interval.max; unrelated intervals
}
}
}
DependencyMetadata[] result;
if (cached)
{
List<DependencyMetadata> resultList = new ArrayList<DependencyMetadata>();
List<DependencyMetadata> cachedVersions = cachedDependencyMetadata.get(extensionDependency.id);
if (cachedVersions != null)
{
for (DependencyMetadata version : cachedVersions)
{
if (VersionStringUtils.belongsToInterval(version.version, extensionDependency.minVersion, extensionDependency.maxVersion))
{
resultList.add(version);
}
}
}
result = resultList.size() == 0 ? null : resultList.toArray(new DependencyMetadata[resultList.size()]);
}
else
{
// TODO can be improved to break down this interval into cached/non-cached intervals and merge the results
result = getDependencyMetadataImpl(extensionDependency);
// update cache
addCachedDependencyMetadataVersionInterval(extensionDependency.id, new VersionInterval(extensionDependency.minVersion,
extensionDependency.maxVersion));
if (result != null)
{
for (DependencyMetadata version : result)
{
cacheDependencyMetadataVersion(version);
}
}
}
return result;
}
protected abstract DependencyMetadata[] getDependencyMetadataImpl(ExtensionDependencyDeclaration extensionDependency);
public void flushCache()
{
cachedDepencencyMetadataVersions.clear();
cachedDependencyMetadata.clear();
}
/**
* @return true if the the given extension version was cached, false if it was not (maybe it was already cached?)
*/
protected boolean cacheDependencyMetadataVersion(DependencyMetadata version)
{
List<DependencyMetadata> cachedVersions = cachedDependencyMetadata.get(version.id);
if (cachedVersions == null)
{
cachedVersions = new ArrayList<DependencyMetadata>();
cachedDependencyMetadata.put(version.id, cachedVersions);
}
// do not allow duplicates
boolean found = false;
for (DependencyMetadata dmd : cachedVersions)
{
if (dmd.id.equals(version.id) && dmd.version.equals(version.version))
{
found = true;
break;
}
}
if (!found) cachedVersions.add(version);
return !found;
}
protected void addCachedDependencyMetadataVersionInterval(String extensionId, VersionInterval interval)
{
List<VersionInterval> cachedVersionIntervals = cachedDepencencyMetadataVersions.get(extensionId);
if (cachedVersionIntervals == null)
{
cachedVersionIntervals = new ArrayList<CachingExtensionProvider.VersionInterval>();
cachedDepencencyMetadataVersions.put(extensionId, cachedVersionIntervals);
cachedVersionIntervals.add(interval);
}
else
{
VersionInterval intervalToAdd = interval;
// merge this new interval with existing intervals
int cacheSize = cachedVersionIntervals.size();
int insertIndex = 0;
for (int i = 0; insertIndex == cacheSize && i < cacheSize; i++)
{
if (compareVersions(intervalToAdd.min, cachedVersionIntervals.get(i).min, true, true) >= 0)
{
insertIndex = i;
}
}
// we found the place where this needs to be added based on minVer; we must check the previous interval and
// the following intervals to see if any of the intervals need merging/removing
if (insertIndex - 1 > 0)
{
VersionInterval previousInterval = cachedVersionIntervals.get(insertIndex - 1);
if (compareVersions(intervalToAdd.min, previousInterval.max, true, false) <= 0)
{
intervalToAdd = reunion(previousInterval, intervalToAdd);
cachedVersionIntervals.remove(previousInterval);
insertIndex--;
}
}
int nextIntervalIndex = insertIndex;
while (nextIntervalIndex < cachedVersionIntervals.size())
{
VersionInterval nextInterval = cachedVersionIntervals.get(nextIntervalIndex);
if (compareVersions(nextInterval.min, intervalToAdd.max, true, false) <= 0)
{
intervalToAdd = reunion(nextInterval, intervalToAdd);
cachedVersionIntervals.remove(nextIntervalIndex);
}
else
{
// no more overlapping intervals
nextIntervalIndex = cachedVersionIntervals.size();
}
}
cachedVersionIntervals.add(insertIndex, intervalToAdd);
}
}
private VersionInterval reunion(VersionInterval interval1, VersionInterval interval2)
{
// needs merging
int compMin = compareVersions(interval1.min, interval2.min, true, true);
int compMax = compareVersions(interval1.max, interval2.max, false, false);
String min = compMin < 0 ? interval1.min : (compMin == 0 ? (VersionStringUtils.isExclusive(interval1.min) ? interval2.min : interval1.min)
: interval2.min); // use inclusive if possible and equal
String max = compMax > 0 ? interval1.max : (compMax == 0 ? (VersionStringUtils.isExclusive(interval1.max) ? interval2.max : interval1.max)
: interval2.max); // use inclusive if possible and equal
return new VersionInterval(min, max);
}
private VersionInterval intersection(VersionInterval interval1, VersionInterval interval2)
{
// needs merging
int compMin = compareVersions(interval1.min, interval2.min, true, true);
int compMax = compareVersions(interval1.max, interval2.max, false, false);
String min = compMin < 0 ? interval2.min : (compMin == 0 ? (VersionStringUtils.isExclusive(interval1.min) ? interval1.min : interval2.min)
: interval1.min); // use exclusive if possible and equal
String max = compMax > 0 ? interval2.max : (compMax == 0 ? (VersionStringUtils.isExclusive(interval1.max) ? interval1.max : interval2.max)
: interval1.max); // use exclusive if possible and equal
int validCompare = compareVersions(min, max, true, false);
if ((validCompare > 0) || (validCompare == 0 && (VersionStringUtils.isExclusive(min) || VersionStringUtils.isExclusive(max))))
{
return null;
}
return new VersionInterval(min, max);
}
// compares two min/max versions; does not differentiate between inclusive and exclusive versions
private int compareVersions(String ver1, String ver2, boolean ver1Min, boolean ver2Min)
{
int result;
if (ver1 == VersionStringUtils.UNBOUNDED && ver2 == VersionStringUtils.UNBOUNDED)
{
result = (ver1Min ^ ver2Min) ? (ver1Min ? -1 : 1) : 0;
}
else if (ver1 == VersionStringUtils.UNBOUNDED) // and the other is not
{
result = (ver1Min ? -1 : 1);
}
else if (ver2 == VersionStringUtils.UNBOUNDED) // and the other is not
{
result = (ver2Min ? 1 : -1);
}
else
{
// neither is unbounded
result = VersionStringUtils.compareVersions(ver1, ver2);
}
return result;
}
protected class VersionInterval
{
public final String min;
public final String max;
public VersionInterval(String min, String max)
{
this.min = min;
this.max = max;
}
@Override
@SuppressWarnings("nls")
public String toString()
{
return "{" + min + "," + max + "}";
}
}
}