blob: d057e070295f52520bdeb848ad4c20dd61a20080 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.tools.internal.versioning;
import java.io.File;
import java.net.URL;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.tools.versioning.IVersionCompare;
import org.eclipse.update.core.IIncludedFeatureReference;
import org.eclipse.update.core.model.FeatureModel;
import org.eclipse.update.core.model.PluginEntryModel;
import org.osgi.framework.Version;
/**
* VersionCompare
*
* @see org.eclipse.pde.tools.versioning.IVersionCompare
*/
public class FeatureVersionCompare implements VersionCompareConstants {
// MultiStatus instance used to store error or warning messages of features and plugin entries
private MultiStatus finalResult;
// Map used to store ICompreResult instances of verified features
private Map verifiedFeatureTable;
// Map used to store ICompreResult instances of verified plugins
private Map verifiedPluginTable;
// FeatureModelTables used to store FeatureModels
private FeatureModelTable featureModelTable1;
private FeatureModelTable featureModelTable2;
//
private PluginVersionCompare pluginVersionCompare;
//
private CompareOptionFileHelper compareOptionFileHelper;
// monitor related variables
private VersioningProgressMonitorWrapper monitorWrapper;
//
private boolean needPluginCompare;
//
private long startTime;
private boolean DEBUG = false;
private static final String DEBUG_OPTION = VersionCompareConstants.PLUGIN_ID + "/debug/features"; //$NON-NLS-1$
/**
* Constructor
*/
public FeatureVersionCompare() {
pluginVersionCompare = new PluginVersionCompare();
DEBUG = Activator.getBooleanDebugOption(DEBUG_OPTION);
}
/* (non-Javadoc)
* @see org.eclipse.pde.tools.versioning.IVersionCompare#checkFeatureVersions(URL, URL, boolean, Map)
*/
public IStatus checkFeatureVersions(URL configURL1, URL configURL2, boolean comparePlugins, File optionFile, IProgressMonitor monitor) throws CoreException {
return compareMain(configURL1, configURL2, comparePlugins, optionFile, monitor);
}
/* (non-Javadoc)
* @see org.eclipse.pde.tools.versioning.IVersionCompare#checkFeatureVersions(java.lang.String, java.lang.String, boolean, Map)
*/
public IStatus checkFeatureVersions(String path1, String path2, boolean comparePlugins, File optionFile, IProgressMonitor monitor) throws CoreException {
return checkFeatureVersions(new File(path1), new File(path2), comparePlugins, optionFile, monitor);
}
/* (non-Javadoc)
* @see org.eclipse.pde.tools.versioning.IVersionCompare#checkFeatureVersions(File, File, boolean, Map)
*/
public IStatus checkFeatureVersions(File file1, File file2, boolean comparePlugins, File optionFile, IProgressMonitor monitor) throws CoreException {
// check to see if we are pointing to two configuration files or two directories
if ((isConfiguration(file1) && isConfiguration(file2)) || (isFeaturesDirectory(file1) && isFeaturesDirectory(file2))) {
return compareMain(file1, file2, comparePlugins, optionFile, monitor);
}
return resultStatusHandler(IStatus.WARNING, IVersionCompare.PROCESS_ERROR_STATUS, NLS.bind(Messages.FeatureVersionCompare_inputErrorMsg, file1, file2), null);
}
/**
* compare main method
* @param object1 URL, String, File instance which denotes a directory or a configuration file
* @param object2 URL, String, File instance which denotes a directory or a configuration file
* @param comparePlugins <code>true</code>plugins need to be compared as object, <code>false</code> just to flat comparison
* @param optionFile File instance which denotes a compare option file
* @param monitor IProgressMonitor instance
* @return IStatus instance which contains message generated during comparing
* @throws CoreException <p>if any nested CoreException has been thrown</p>
*/
private IStatus compareMain(Object object1, Object object2, boolean comparePlugins, File optionFile, IProgressMonitor monitor) throws CoreException {
try {
monitor = VersioningProgressMonitorWrapper.monitorFor(monitor);
monitorWrapper = new VersioningProgressMonitorWrapper(monitor);
if (optionFile != null)
// generate CompareOptionFileHelper instance
compareOptionFileHelper = new CompareOptionFileHelper(optionFile);
// generate a MultiStatus instance to store compare result
finalResult = new MultiStatus(PLUGIN_ID, IStatus.OK, Messages.FeatureVersionCompare_errorReasonMsg, null);
// generate verified features and plugins maps
verifiedFeatureTable = new Hashtable(0);
verifiedPluginTable = new Hashtable(0);
this.needPluginCompare = comparePlugins;
// begin task
monitorWrapper.beginTask(Messages.FeatureVersionCompare_verifyingFeatureMsg, 100);
// generate feature model maps
featureModelTable1 = new FeatureModelTable(object1, compareOptionFileHelper);
monitorWrapper.worked(comparePlugins ? 5 : 30);
featureModelTable2 = new FeatureModelTable(object2, compareOptionFileHelper);
monitorWrapper.worked(comparePlugins ? 5 : 30);
if (!featureModelTable1.isOK()) {
finalResult.merge(featureModelTable1.getStatus());
}
if (!featureModelTable2.isOK()) {
finalResult.merge(featureModelTable2.getStatus());
}
// if featureModelTable1 or featureModelTable2 is empty, we return here
if (featureModelTable1.size() == 0 || featureModelTable2.size() == 0) {
return finalResult;
}
compareFeatures();
monitorWrapper.worked(comparePlugins ? 90 : 40);
return finalResult;
} finally {
monitorWrapper.done();
}
}
/**
* check whether or not the given file potentially represents a platform configuration file on the file-system.
*
* @return <code>true</code> if <code>file</code> is a configuration file
* <code>false</code> otherwise
*/
private boolean isConfiguration(File file) {
return file.isFile() && CONFIGURATION_FILE_NAME.equalsIgnoreCase(new Path(file.getAbsolutePath()).lastSegment());
}
/**
* check whether or not the given file represents a features directory.
*
* @return <code>true</code> if <code>file</code> is a directory
* <code>false</code> otherwise
*/
private boolean isFeaturesDirectory(File file) {
return file.isDirectory();
}
/**
* Add the given compare result to the list of already processed features.
*
* @param result the compare result
*/
private void featureProcessed(CheckedItem result) {
verifiedFeatureTable.put(result.getSourceKey(), result);
}
/**
* Add the given compare result to the list of already processed plugins.
*
* @param result the compare result
*/
private void pluginProcessed(CheckedItem result) {
verifiedPluginTable.put(result.getSourceKey()+KEY_SEPARATOR+ result.getDestinationKey(), result);
}
/**
* Verifies every version of feature included in featureModelTable1 based on the feature which has the same feature
* id in featureModelTable2, and generates an overall result as an VersioningMultiStatus instance
*
* @throws CoreException <p>if any nested CoreException has been thrown</p>
*/
private void compareFeatures() throws CoreException {
startTime = System.currentTimeMillis();
// compare features
for (Iterator keys = featureModelTable1.getKeySet().iterator(); keys.hasNext();) {
if (monitorWrapper.isCanceled())
throw new OperationCanceledException();
// get a key (feature ID) from the new features map
Object key = keys.next();
FeatureModel newFeatureModel = (FeatureModel) (featureModelTable1.getFeatureModel(key));
// check if the feature has been checked or not
if (searchFeatureResult(newFeatureModel.getFeatureIdentifier() + KEY_SEPARATOR + newFeatureModel.getFeatureVersion()) == null) {
// if the feature has not been checked, we check it and add the result to status list
featureProcessed(verifyNewFeatureVersion(newFeatureModel, (FeatureModel) featureModelTable2.getFeatureModel(key), monitorWrapper.getSubMonitor((needPluginCompare ? 90 : 40) / featureModelTable1.size())));
}
}
debug("Feature Compare Time: " + (System.currentTimeMillis() - startTime) + " milliseconds"); //$NON-NLS-1$//$NON-NLS-2$
debug("Total message number: " + finalResult.getChildren().length); //$NON-NLS-1$
}
/*
* Log the given debug message.
*/
private void debug(String message) {
if (DEBUG)
Activator.debug(message);
}
/**
* Compares <code>featureModel1</code> to <code>featureModel2</code> to check if the version of <code>featureModel1</code> is correct or not;
* <code>featureModel1</code> and <code>featureModel2</code> have the same feature ID
*
* @param featureModel1 the FeatureModel instance
* @param featureModel2 the FeatureModel instance
* @return result of verification encapsulated in an IStatus instance
* @throws CoreException <p>if any nested CoreException has been thrown</p>
*/
private CheckedItem verifyNewFeatureVersion(FeatureModel featureModel1, FeatureModel featureModel2, IProgressMonitor monitor) throws CoreException {
// get feature id
String id = featureModel1.getFeatureIdentifier();
// get versions of featureModel1
Version version1 = new Version(featureModel1.getFeatureVersion());
// check if featureModel2 is null, if it's null, we consider it as new added
if (featureModel2 == null) {
Object[] msg = {id, version1, featureModelTable1.getLocation(id)};
finalResult.merge(resultStatusHandler(IStatus.INFO, IVersionCompare.FEATURE_OVERALL_STATUS, NLS.bind(Messages.FeatureVersionCompare_newIntroducedFeatureMsg, msg), null));
return new CheckedItem(id + KEY_SEPARATOR + featureModel1.getFeatureVersion(), null, new Version(featureModel1.getFeatureVersion()), IVersionCompare.NEW_ADDED);
}
// get versions of featureModel2
Version version2 = new Version(featureModel2.getFeatureVersion());
// compare featureModel1 to featureModel2
int result = checkFeatureChange(featureModel1, featureModel2, monitor);
// some versions of included features or plugins are wrong
if (result == IVersionCompare.ERROR_OCCURRED) {
Object[] msg = {id, version1, featureModelTable1.getLocation(id)};
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_OVERALL_STATUS, NLS.bind(Messages.FeatureVersionCompare_nestedErrorOccurredMsg, msg), null));
return new CheckedItem(id + KEY_SEPARATOR + featureModel1.getFeatureVersion(), null, null, result);
}
// get the recommended version
Version recommendedVersion = recommendVersion(version2, result);
// check if version1 is correct or not
if (!isNewVersionValid(version1, recommendedVersion, result)) {
Object[] msg = new Object[] {id, version1, featureModelTable1.getLocation(id), recommendedVersion};
if (result == IVersionCompare.QUALIFIER_CHANGE) {
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_OVERALL_STATUS, NLS.bind(Messages.FeatureVersionCompare_incorrectNewVersionMsg2, msg), null));
} else {
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_OVERALL_STATUS, NLS.bind(Messages.FeatureVersionCompare_incorrectNewVersionMsg, msg), null));
}
return new CheckedItem(id + KEY_SEPARATOR + featureModel1.getFeatureVersion(), null, recommendedVersion, result);
}else{
// check if version has been increased properly
int change = checkVersionChange(version1, version2);
if (change < result) {
Object[] msg = new Object[] {id, version1, featureModelTable1.getLocation(id), getVersionName(result), getVersionName(change)};
finalResult.merge(resultStatusHandler(IStatus.WARNING, IVersionCompare.FEATURE_OVERALL_STATUS, NLS.bind(Messages.FeatureVersionCompare_versionChangeIncorrectMsg, msg), null));
return new CheckedItem(id + KEY_SEPARATOR + version1.toString(), null, version1, change);
}
return new CheckedItem(id + KEY_SEPARATOR + version1.toString(), null, version1, result);
}
}
/**
* gets String which represent type of version denoted by <code>versionType</code>
* @param versionType int number
* @return String
*/
private String getVersionName(int versionType){
switch (versionType){
case IVersionCompare.MAJOR_CHANGE: return "Major"; //$NON-NLS-1$
case IVersionCompare.MINOR_CHANGE: return "Minor"; //$NON-NLS-1$
case IVersionCompare.MICRO_CHANGE: return "Micro"; //$NON-NLS-1$
case IVersionCompare.QUALIFIER_CHANGE: return "Qualify"; //$NON-NLS-1$
case IVersionCompare.NO_CHANGE: return "NoChange"; //$NON-NLS-1$
default: return EMPTY_STRING;
}
}
/**
* Compares included features and plugins in <code>featureModel1</code> to those in <code>featureModel2</code>.
* Check if there is any change from <code>featureModel2</code> to <code>feaureModel1</code>
*
* @param featureModel1 FeatureModel instance
* @param featureModel2 FeatureModel instance
* @return change with the highest priority from <code>featureModel2</code> to <code>featureModel1</code>
* @throws CoreException <p>if any nested CoreException has been thrown</p>
*/
private int checkFeatureChange(FeatureModel featureModel1, FeatureModel featureModel2, IProgressMonitor monitor) throws CoreException {
int overallChange = IVersionCompare.NO_CHANGE;
String id = featureModel1.getFeatureIdentifier();
Version version1 = new Version(featureModel1.getFeatureVersion());
Version version2 = new Version(featureModel2.getFeatureVersion());
// compare feature version first
if (version1.compareTo(version2) < 0) {
Object[] msg = {id, version1, featureModelTable1.getLocation(id), version2, featureModelTable2.getLocation(id)};
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_OVERALL_STATUS, NLS.bind(Messages.FeatureVersionCompare_newVersionLowerThanOldMsg, msg), null));
}
// get IncludedFeatureReference, PluginEntries of featureModel1
IIncludedFeatureReference[] includedFeatures1 = featureModel1.getFeatureIncluded();
PluginEntryModel[] pluginEntries1 = featureModel1.getPluginEntryModels();
// get IncludedFeatureReference, PluginEntries of featureModel2
IIncludedFeatureReference[] includedFeatures2 = featureModel2.getFeatureIncluded();
PluginEntryModel[] pluginEntries2 = featureModel2.getPluginEntryModels();
try {
monitor.beginTask(NLS.bind(Messages.FeatureVersionCompare_comparingFeatureMsg, id + UNDERSCORE_MARK + version1.toString()), 10);
// compare IncludedFeatureReferences in featureModel1 with those in featureModel2
overallChange = Math.min(overallChange, compareReferences(id, includedFeatures1, includedFeatures2, monitorWrapper.getSubMonitor(5)));
// compare PluginEntries in featureModel1 with those in featureModel2
overallChange = Math.min(overallChange, compareReferences(id, pluginEntries1, pluginEntries2, monitorWrapper.getSubMonitor(5)));
// return the version change with the highest priority
return overallChange;
} finally {
monitor.done();
}
}
/**
* Generates and returns a recommended version based on the given version and change.
*
* @param version the current version
* @param change the degree of change
* @return the recommended new version or <code>null</code> if the change is IVersionCompare.ERROR_OCCURRED
* or the change is not an expected value
*/
private Version recommendVersion(Version version, int change) {
// get major, minor, service of version
int major = version.getMajor();
int minor = version.getMinor();
int micro = version.getMicro();
String qualifier = version.getQualifier();
// update major, minor, service based on the compare result
switch (change) {
case IVersionCompare.ERROR_OCCURRED :
return null;
case IVersionCompare.MAJOR_CHANGE : {
major++;
minor = 0;
micro = 0;
qualifier = EMPTY_STRING;
break;
}
// NOT_EXIST, NEW_ADDED are currently treated as minor change
case IVersionCompare.NO_LONGER_EXIST :
case IVersionCompare.NEW_ADDED :
case IVersionCompare.MINOR_CHANGE : {
minor++;
micro = 0;
qualifier = EMPTY_STRING;
break;
}
case IVersionCompare.MICRO_CHANGE : {
micro++;
qualifier = EMPTY_STRING;
break;
}
case IVersionCompare.QUALIFIER_CHANGE : {
// TODO increase qualifier
break;
}
case IVersionCompare.NO_CHANGE : {
// if there is no change, just return the version
return version;
}
default : {
return null;
}
}
// generate recommended version
return new Version(major, minor, micro, qualifier);
}
/**
* Checks if the <code>version1</code> is valid, based on <code>version2</code> and <code>change</code>
* from <code>version2</code> to <code>version1</code>
*
* @param version1 feature version
* @param version2 feature version
* @param change change from <code>version2</code> to <code>version1</code>
* @return <code>true</code> if version1 is correct and <code>false</code> otherwise
*/
private boolean isNewVersionValid(Version version1, Version version2, int change) {
if (change == IVersionCompare.QUALIFIER_CHANGE) {
// compare qualifier version. We currently don't increase qualifier version if there is
// QUALIFIER_CHANGE,we need the version1 be big than version2
return version1.compareTo(version2) > 0;
}
// compare qualifier version
return version1.compareTo(version2) >= 0;
}
/**
* Compares feature version(or plugin entry versions) in <code>references1</code> to those in <code>references2</code>.
* Check if there is any version change from <code>references2</code> to <code>references1</code>. The change with
* the highest priority will be returned
*
* @param parentID id of the feature which these features(or plugin entries) belong to
* @param references1 array of included plugins or features
* @param references2 array of included plugins or features
* @return change with the highest priority from <code>references2</code> to <code>references1</code>
* @throws CoreException <p>if any nested CoreException has been thrown</p>
*/
private int compareReferences(String parentID, Object[] references1, Object[] references2, IProgressMonitor monitor) throws CoreException {
try {
// generate Maps for references1 and references2
Map referenceMap1 = generateTable(references1);
Map referenceMap2 = generateTable(references2);
//
if (referenceMap1 == null && referenceMap2 == null) {
return IVersionCompare.NO_CHANGE;
}
monitor.beginTask(Messages.FeatureVersionCompare_comparingReferenceMsg, referenceMap1 == null? 1 : referenceMap1.size() + 1);
// initialization before compare
int overallChange = IVersionCompare.NO_CHANGE;
if (referenceMap1 != null) {
// compares features(or plugin entries) in referenceMap1 to those in referenceMap2
for (Iterator keys = referenceMap1.keySet().iterator(); keys.hasNext();) {
if (monitorWrapper.isCanceled())
throw new OperationCanceledException();
// get the next key (feature id)
Object id = keys.next();
// get corresponding version from referenceMap1
Version version1 = referenceMap1 == null ? null : (Version) (referenceMap1.get(id));
// get the version of corresponding reference from referenceMap2
Version version2 = referenceMap2 == null ? null : (Version) referenceMap2.get(id);
int currentChange = IVersionCompare.NO_CHANGE;
if (references1[0] instanceof IIncludedFeatureReference) {
currentChange = compareIncludedFeatureReference(parentID, (String) id, version1, version2, new SubProgressMonitor(monitor, 1));
} else {
currentChange = compareIncludedPluginReference(parentID, (String) id, version1, version2, new SubProgressMonitor(monitor, 1));
}
// we save the version change with higher priority
overallChange = Math.min(overallChange, currentChange);
// delete the reference from referenceMap2
if (referenceMap2 != null)
referenceMap2.remove(id);
}
}
// new features or plugins added into the feature
if (referenceMap2 != null) {
for (Iterator keys = referenceMap2.keySet().iterator(); keys.hasNext();) {
Object id = keys.next();
Version version = (Version) referenceMap2.get(id);
if (references2[0] instanceof IIncludedFeatureReference) {
Object[] msg = {FEATURE_TITLE, (String) id + UNDERSCORE_MARK + version.toString(), parentID, featureModelTable1.getVersion(parentID), featureModelTable1.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.INFO, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_deletedFeaturePluginMsg, msg), null));
} else {
Object[] msg = {PLUGIN_TITLE, (String) id + UNDERSCORE_MARK + version.toString(), parentID, featureModelTable1.getVersion(parentID), featureModelTable1.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.INFO, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_deletedFeaturePluginMsg, msg), null));
}
}
if (!referenceMap2.isEmpty()) {
overallChange = Math.min(overallChange, IVersionCompare.NO_LONGER_EXIST);
}
}
monitor.worked(1);
return overallChange;
} finally {
monitor.done();
}
}
/**
* compares two versions of an included plugins
* @param parentID ID of the feature to which the plugin denoted by <code>pluginID</code> belongs
* @param pluginID plugin id
* @param version1 new version of plugin denoted by <code>pluginID</code>
* @param version2 old version of plugin denoted by <code>pluginID</code>
* @return change happened on plugin denoted by <code>pluginID</code>
*/
private int compareIncludedPluginReference(String parentID, String pluginID, Version version1, Version version2, IProgressMonitor monitor) {
try {
monitor.beginTask("", 10); //$NON-NLS-1$
int pluginChange = IVersionCompare.NO_CHANGE;
if (version2 == null) {
// if version2 is null, the plugin is a new added one
Object[] msg = {PLUGIN_TITLE, pluginID + UNDERSCORE_MARK + version1.toString(), parentID, featureModelTable1.getVersion(parentID), featureModelTable1.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.INFO, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_newAddedFeaturePlingMsg, msg), null));
//pluginChange = IVersionCompare.NEW_ADDED;
// the return was changed to a MINOR_CHANGE because adding a new plugin is considering a minor change
// and the value associated with NEW_ADDED indicates that it is a greater than minor change
pluginChange = IVersionCompare.MINOR_CHANGE;
} else {
if (version1.compareTo(version2) < 0) {
// if version1 is lower than version2, it is wrong
Object[] msg = new Object[] {version1, PLUGIN_TITLE, pluginID, parentID, featureModelTable1.getVersion(parentID), featureModelTable1.getLocation(parentID), version2, featureModelTable2.getVersion(parentID), featureModelTable1.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_lowerNewVersion, msg), null));
if (!needPluginCompare)
return IVersionCompare.ERROR_OCCURRED;
}
if (needPluginCompare) {
// Object level compare
String pluginKey1 = pluginID + KEY_SEPARATOR + version1.toString();
String pluginKey2 = pluginID + KEY_SEPARATOR + version2.toString();
// check if the plugin has been compared
CheckedItem pluginResult = searchPluginResult(pluginKey1 + pluginKey2);
monitor.worked(1);
if (pluginResult != null) {
pluginChange = pluginResult.getChange();
} else {
// compare plugins as objects
IPath pluginDirPath1 = getPluginDirPath(featureModelTable1.getLocation(parentID));
IPath pluginDirPath2 = getPluginDirPath(featureModelTable2.getLocation(parentID));
Map pluginInfo1 = searchPlugin(pluginDirPath1, pluginID, version1.toString());
Map pluginInfo2 = searchPlugin(pluginDirPath2, pluginID, version2.toString());
monitor.worked(2);
// do plugin compare if both pluginPath1 and pluginPath2 are not null
if (pluginInfo1 != null && pluginInfo2 != null) {
try {
// compare two plugins
pluginChange = Math.min(pluginChange, pluginVersionCompare.checkPluginVersions(finalResult, ((IPath) pluginInfo1.get(BUNDLE_LOCATION)).toFile(), (ManifestElement[]) pluginInfo1.get(BUNDLE_CLASSPATH), ((IPath) pluginInfo2.get(BUNDLE_LOCATION)).toFile(), (ManifestElement[]) pluginInfo2.get(BUNDLE_CLASSPATH), new SubProgressMonitor(monitor, 7)));
} catch (CoreException ce) {
pluginChange = IVersionCompare.ERROR_OCCURRED;
}
if (pluginChange == IVersionCompare.ERROR_OCCURRED) {
// put the compare result into table, we don't need to compare it again if it is also included in another feature
pluginProcessed(new CheckedItem(pluginKey1, pluginKey2, null, pluginChange));
// set message
Object[] msg = {pluginID + UNDERSCORE_MARK + version1.toString(), parentID, featureModelTable1.getVersion(parentID), featureModelTable1.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_errorInVerifyPluginMsg, msg), null));
// if result is error, set overallChange as error and continue to check next plugin
pluginChange = IVersionCompare.ERROR_OCCURRED;
} else {
// get recommended version based on compare result
Version recommendedVersion = recommendVersion(version2, pluginChange);
if (!isNewVersionValid(version1, recommendedVersion, pluginChange)) {
// if version1 is not correct, set message
Object[] msg = {pluginID, version1.toString(), ((IPath) pluginInfo1.get(BUNDLE_LOCATION)).toOSString(), recommendedVersion};
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_incorrectPluginVersionMsg, msg), null));
}else{
// check if version has been increased properly
int actualChange = checkVersionChange(version1, version2);
if (actualChange < pluginChange){
Object[] msg = new Object[] {pluginID, version1, getVersionName(pluginChange), getVersionName(actualChange)};
finalResult.merge(resultStatusHandler(IStatus.WARNING, IVersionCompare.FEATURE_OVERALL_STATUS, NLS.bind(Messages.FeatureVersionCompare_incorrectPluginVersionChange, msg), null));
pluginChange = actualChange;
}
}
// put the compare result into table, we don't need to compare it again if it is also included in another feature
pluginProcessed(new CheckedItem(pluginKey1, pluginKey2,recommendedVersion, pluginChange));
}
} else {
// if pluginPath1 or pluginPath2 is null, set messages
if (pluginInfo1 == null) {
Object[] msg = {pluginID + UNDERSCORE_MARK + version1.toString(), parentID, featureModelTable1.getVersion(parentID), featureModelTable1.getLocation(parentID), pluginDirPath1};
finalResult.merge(resultStatusHandler(IStatus.WARNING, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_pluginNotFoundMsg, msg), null));
}
if (pluginInfo2 == null) {
Object[] msg = {pluginID + UNDERSCORE_MARK + version2.toString(), parentID, featureModelTable2.getVersion(parentID), featureModelTable2.getLocation(parentID), pluginDirPath2};
finalResult.merge(resultStatusHandler(IStatus.WARNING, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_pluginNotFoundMsg, msg), null));
}
pluginChange = IVersionCompare.ERROR_OCCURRED;
// put the compare into table, we don't need to compare it again if it is also included in another feature
pluginProcessed(new CheckedItem(pluginKey1, pluginKey2, null, pluginChange));
}
}
} else {
// String level compare
pluginChange = checkVersionChange(version1, version2);
}
}
return pluginChange;
} finally {
monitor.done();
}
}
/**
* compares two versions of an included features
* @param parentID ID of the feature to which the feature denoted by <code>featureID</code> belongs
* @param featureID feature id
* @param version1 new version of feature denoted by <code>featureID</code>
* @param version2 old version of feature denoted by <code>featureID</code>
* @return change happened on feature denoted by <code>featureID</code>
* @throws CoreException <p>if any nested CoreException has been thrown</p>
*/
private int compareIncludedFeatureReference(String parentID, String featureID, Version version1, Version version2, IProgressMonitor monitor) throws CoreException {
String key1 = featureID + KEY_SEPARATOR + version1.toString();
int change = IVersionCompare.NO_CHANGE;
// if version2 is null, the feature is a new added one
if (version2 == null) {
Object[] msg = {FEATURE_TITLE, featureID + UNDERSCORE_MARK + version1.toString(), parentID, featureModelTable1.getVersion(parentID), featureModelTable1.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.INFO, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_newAddedFeaturePlingMsg, msg), null));
change = IVersionCompare.NEW_ADDED;
}
// get the corresponding FeatureModel from featureModelTable1
FeatureModel featureModel1 = (FeatureModel) featureModelTable1.getFeatureModel(featureID);
FeatureModel featureModel2 = (FeatureModel) featureModelTable2.getFeatureModel(featureID);
if (featureModel1 == null) {
// if the new FeatureModel has not been found, it's an error
Object[] msg = {featureID + UNDERSCORE_MARK + version1.toString(), parentID, featureModelTable1.getVersion(parentID), featureModelTable1.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_sourceIncludedFeatureNotFoundMsg, msg), null));
change = IVersionCompare.ERROR_OCCURRED;
} else if (!featureModel1.getFeatureVersion().equals(version1.toString())) {
//if the version of the FeatureModel we got does not match the version of the reference, it is an error
// e.g. feature f1 includes feature f2-1.0.0.v2006, but the corresponding FeatureModel we found is feature f2-1.0.1, it is wrong
Object[] msg = {featureID + UNDERSCORE_MARK + version1.toString(), parentID, featureModelTable1.getVersion(parentID), featureModelTable1.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_sourceIncludedFeatureNotFoundMsg, msg), null));
change = IVersionCompare.ERROR_OCCURRED;
}
// if the feature is a new added one, we don't do further check
if (version2 != null) {
if (featureModel2 == null) {
// if featureModel2 is null, it is an error
Object[] msg = {featureID + UNDERSCORE_MARK + version2.toString(), parentID, featureModelTable2.getVersion(parentID), featureModelTable2.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_destIncludedFeatureNotFoundMsg, msg), null));
change = IVersionCompare.ERROR_OCCURRED;
} else if (!featureModel2.getFeatureVersion().equals(version2.toString())) {
//if the version of the FeatureModel we got does not match the version of the reference, it is an error
// e.g. feature f1 includes feature f2-1.0.0.v2006, but the corresponding FeatureModel we found is feature f2-1.0.1, it is wrong
Object[] msg = {featureID + UNDERSCORE_MARK + version2.toString(), parentID, featureModelTable2.getVersion(parentID), featureModelTable2.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_destIncludedFeatureNotFoundMsg, msg), null));
change = IVersionCompare.ERROR_OCCURRED;
}
// String level compare
if (version1.compareTo(version2) < 0) {
Object[] msg = new Object[] {version1, FEATURE_TITLE, featureID, parentID, featureModelTable1.getVersion(parentID), featureModelTable1.getLocation(parentID), version2, featureModelTable2.getVersion(parentID), featureModelTable1.getLocation(parentID)};
finalResult.merge(resultStatusHandler(IStatus.ERROR, IVersionCompare.FEATURE_DETAIL_STATUS, NLS.bind(Messages.FeatureVersionCompare_lowerNewVersion, msg), null));
}
if (change == IVersionCompare.ERROR_OCCURRED) {
featureProcessed(new CheckedItem(key1, null, null, IVersionCompare.ERROR_OCCURRED));
return IVersionCompare.ERROR_OCCURRED;
}
// check if the current feature version has been checked
CheckedItem compareResult = searchFeatureResult(key1);
// Object level compare
if (compareResult == null) {
// check the included feature models and get result status
compareResult = verifyNewFeatureVersion(featureModel1, featureModel2, monitorWrapper.getSubMonitor(needPluginCompare ? 90 : 40 / featureModelTable1.size()));
// add the result status into result list
featureProcessed(compareResult);
}
change = Math.min(change, compareResult.getChange());
}
return change;
}
/*
* Return a Map contains bundle name, bundle version, bundle classpath, and bundle location
* if the location of the specific plug-in version that we are looking for. Otherwise return
* <code>null</code>
*/
private Map matchesPlugin(File file, String pluginName, String pluginVersion) throws CoreException {
if (!file.exists())
return null;
Map manifest = ManifestHelper.getElementsFromManifest(file, new String[] {BUNDLE_SYMBOLICNAME, BUNDLE_VERSION, BUNDLE_CLASSPATH});
ManifestElement[] elements = (ManifestElement[]) manifest.get(BUNDLE_SYMBOLICNAME);
if (elements == null)
return null;
String name = elements[0].getValue();
if (name == null || !name.equals(pluginName))
return null;
elements = (ManifestElement[]) manifest.get(BUNDLE_VERSION);
if (elements == null)
return null;
String version = elements[0].getValue();
if (version == null || !version.equals(pluginVersion))
return null;
manifest.put(BUNDLE_LOCATION, new Path(file.getAbsolutePath()));
return manifest;
}
/**
* searches for plugin denoted by <code>pluginName</code> and <code>pluginVersion</code> under
* plugin directory indicated by <code>plugDir</code>, and return a Map contains bundle name,
* bundle version, bundle classpath, and bundle location if we find, return <code>null</code>
* if we don't find
*
* @param plugDir plugin directory
* @param pluginName plugin name
* @param pluginVersion plugin version
* @return full path of plugin
*/
private Map searchPlugin(IPath plugDir, String pluginName, String pluginVersion) {
// as an optimization first check to see if the plug-in directory name is the Eclipse-format
try {
// check to see if we can guess the JAR name
File jar = plugDir.append(pluginName + UNDERSCORE_MARK + pluginVersion + DOT_MARK + JAR_FILE_EXTENSION).toFile();
Map manifest = matchesPlugin(jar, pluginName, pluginVersion);
if (manifest != null)
return manifest;
// didn't match the JAR so try and match the directory name
File dir = plugDir.append(pluginName + UNDERSCORE_MARK + pluginVersion).toFile();
manifest = matchesPlugin(dir, pluginName, pluginVersion);
if (manifest != null)
return manifest;
} catch (CoreException e) {
// ignore and catch below if re-thrown
}
File[] plugins = plugDir.toFile().listFiles();
if (plugins == null)
return null;
for (int i = 0; i < plugins.length; i++) {
try {
Map manifest = matchesPlugin(plugins[i], pluginName, pluginVersion);
if (manifest != null)
return manifest;
} catch (CoreException ce) {
finalResult.merge(resultStatusHandler(IStatus.WARNING, IVersionCompare.PROCESS_ERROR_STATUS, NLS.bind(Messages.PluginVersionCompare_couldNotReadManifestMsg, plugins[i].getAbsolutePath()), null));
}
}
return null;
}
/**
* generates a map which stores the key-value pairs; Feature id(or plugin id) is key and version is value;
* or <code>null</code> when <code>objects</code> is empty
*
* @param objects array of feature or plugin entry objects
* @return Map instance contains id-version pairs or <code>null</code>
* @throws CoreException <p>if any nested CoreException has been thrown</p>
*/
private Map generateTable(Object[] objects) throws CoreException {
if (objects == null || objects.length == 0) {
return null;
}
Hashtable hashTable = new Hashtable();
// set the ids and versions of plugins into hashtable, id is key, version is value
if (objects[0] instanceof IIncludedFeatureReference) {
for (int i = 0; i < objects.length; i++)
if (objects[i] != null && (compareOptionFileHelper == null ? true : compareOptionFileHelper.shouldCompare(objects[i])))
hashTable.put(((IIncludedFeatureReference) objects[i]).getVersionedIdentifier().getIdentifier(), new Version(((IIncludedFeatureReference) objects[i]).getVersionedIdentifier().getVersion().toString()));
} else if (objects[0] instanceof PluginEntryModel) {
for (int i = 0; i < objects.length; i++)
if (objects[i] != null && (compareOptionFileHelper == null ? true : compareOptionFileHelper.shouldCompare(objects[i])))
hashTable.put(((PluginEntryModel) objects[i]).getPluginIdentifier(), new Version(((PluginEntryModel) objects[i]).getPluginVersion()));
}
return hashTable;
}
/**
* Checks if there is any change from <version2> to <version1>
*
* @param version1 feature version
* @param version2 feature version
* @return compare result indicates change in major, minor, micro, qualifier ,no change or error if <code>version1</code> is lower than <code>version2</code>
*/
private int checkVersionChange(Version version1, Version version2) {
// overall compare
if (version1.compareTo(version2) < 0) {
return IVersionCompare.ERROR_OCCURRED;
}
// compare major version
if (version1.getMajor() > version2.getMajor()) {
return IVersionCompare.MAJOR_CHANGE;
}
// compare minor version
if (version1.getMinor() > version2.getMinor()) {
return IVersionCompare.MINOR_CHANGE;
}
// compare micro version
if (version1.getMicro() > version2.getMicro()) {
return IVersionCompare.MICRO_CHANGE;
}
// compare qualifier version
if (version1.getQualifier().compareTo(version2.getQualifier()) > 0) {
return IVersionCompare.QUALIFIER_CHANGE;
}
// if we got to this point, it means no change
return IVersionCompare.NO_CHANGE;
}
/**
* Returns the compare result for the feature with the specified key. Returns <code>null</code>
* if it is not found.
*
* @param key FeatureKey instance (feature id + "#" + feature version)
* @return the compare result or <code>null</code>
*/
private CheckedItem searchFeatureResult(Object key) {
return (CheckedItem) verifiedFeatureTable.get(key);
}
/**
* Returns the compare result for the plugin with the specified key. Returns <code>null</code>
* if it is not found.
*
* @param key compare source plugin key instance (plugin id + "#" + plugin version) + "#" +
* compare destination plugin key instance (plugin id + "#" + plugin version)
* @return the compare result or <code>null</code> if not found
*/
private CheckedItem searchPluginResult(Object key) {
return (CheckedItem) verifiedPluginTable.get(key);
}
/**
* Return a new status object populated with the given information.
*
* @param severity severity of status
* @param code indicates type of this IStatus instance, it could be one of: FEATURE_OVERALL_STATUS,
* FEATURE_DETAIL_STATUS, PLUGIN_OVERALL_STATUS, PLUGIN_DETAIL_STATUS, PROCESS_ERROR_STATUS,
* CLASS_OVERALL_STATUS, CLASS_DETAIL_STATUS
* @param message the status message
* @param exception exception which has been caught, or <code>null</code>
* @return the new status object
*/
private IStatus resultStatusHandler(int severity, int code, String message, Exception exception) {
if (message == null) {
if (exception != null)
message = exception.getMessage();
// extra check because the exception message can be null
if (message == null)
message = EMPTY_STRING;
}
return new Status(severity, PLUGIN_ID, code, message, exception);
}
/**
* get path of "plugins/" based on the given feature file path
* @param featurePath path of a "feature.xml" file
* @return path of "plugins/"
*/
private IPath getPluginDirPath(IPath featurePath) {
IPath path = featurePath.removeLastSegments(3);
return path.append(PLUGIN_DIR_NAME);
}
}