/*******************************************************************************
 * Copyright (c) 2000, 2003 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.update.core.model;


import java.net.*;
import java.util.*;

import org.eclipse.update.core.*;
import org.eclipse.update.internal.core.*;

/**
 * Feature model object.
 * <p>
 * This class may be instantiated or subclassed by clients. However, in most 
 * cases clients should instead instantiate or subclass the provided 
 * concrete implementation of this model.
 * </p>
 * <p>
 * <b>Note:</b> This class/interface is part of an interim API that is still under development and expected to
 * change significantly before reaching stability. It is being made available at this early stage to solicit feedback
 * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken
 * (repeatedly) as the API evolves.
 * </p>
 * @see org.eclipse.update.core.Feature
 * @since 2.0
 */
public class FeatureModel extends ModelObject {

	private String featureId;
	private String featureVersion;
	private String label;
	private String localizedLabel;
	private String provider;
	private String localizedProvider;
	private String imageURLString;
	private URL imageURL;
	private String os;
	private String ws;
	private String nl;
	private String arch;
	private boolean primary = false;
	private boolean exclusive=false;
	private String primaryPluginID;
	private String application;
	private String affinity;
	private InstallHandlerEntryModel installHandler;
	private URLEntryModel description;
	private URLEntryModel copyright;
	private URLEntryModel license;
	private URLEntryModel updateSiteInfo;
	private List /*of InfoModel*/
	discoverySiteInfo;
	private List /*of ImportModel*/
	imports;
	private List /*of PluginEntryModel*/
	pluginEntries;
	private List /*of IncludedFeatureReferenceModel */
	featureIncludes;
	private List /*of NonPluginEntryModel*/
	nonPluginEntries;

	// performance
	private URL bundleURL;
	private URL base;
	private boolean resolved = false;

	/**
	 * Creates an uninitialized feature object.
	 * 
	 * @since 2.0
	 */
	public FeatureModel() {
		super();
	}

	/**
	 * Compares 2 feature models for equality
	 *  
	 * @param obj feature model to compare with
	 * @return <code>true</code> if the two models are equal, 
	 * <code>false</code> otherwise
	 * @since 2.0
	 */
	public boolean equals(Object obj) {
		if (!(obj instanceof FeatureModel))
			return false;
		FeatureModel model = (FeatureModel) obj;

		return (featureId.toLowerCase().equals(model.getFeatureIdentifier()) && featureVersion.toLowerCase().equals(model.getFeatureVersion()));
	}

	/**
	 * Returns the feature identifier as a string
	 * 
	 * @see org.eclipse.update.core.IFeature#getVersionedIdentifier()
	 * @return feature identifier
	 * @since 2.0
	 */
	public String getFeatureIdentifier() {
		//delayedResolve(); no delay
		return featureId;
	}

	/**
	 * Returns the feature version as a string
	 * 
	 * @see org.eclipse.update.core.IFeature#getVersionedIdentifier()
	 * @return feature version 
	 * @since 2.0
	 */
	public String getFeatureVersion() {
		//delayedResolve(); no delay
		return featureVersion;
	}

	/**
	 * Retrieve the displayable label for the feature. If the model
	 * object has been resolved, the label is localized.
	 * 
	 * @return displayable label, or <code>null</code>.
	 * @since 2.0
	 */
	public String getLabel() {
		delayedResolve();
		if (localizedLabel != null)
			return localizedLabel;
		else
			return label;
	}

	/**
	 * Retrieve the non-localized displayable label for the feature.
	 * 
	 * @return non-localized displayable label, or <code>null</code>.
	 * @since 2.0
	 */
	public String getLabelNonLocalized() {
		return label;
	}

	/**
	 * Retrieve the displayable label for the feature provider. If the model
	 * object has been resolved, the label is localized.
	 * 
	 * @return displayable label, or <code>null</code>.
	 * @since 2.0
	 */
	public String getProvider() {
		delayedResolve();
		if (localizedProvider != null)
			return localizedProvider;
		else
			return provider;
	}

	/**
	 * Retrieve the non-localized displayable label for the feature provider.
	 * 
	 * @return non-localized displayable label, or <code>null</code>.
	 * @since 2.0
	 */
	public String getProviderNonLocalized() {
		return provider;
	}

	/**
	 * Returns the unresolved URL string for the feature image.
	 *
	 * @return url string, or <code>null</code>
	 * @since 2.0
	 */
	public String getImageURLString() {
		delayedResolve();
		return imageURLString;
	}

	/**
	 * Returns the resolved URL for the image.
	 * 
	 * @return url, or <code>null</code>
	 * @since 2.0
	 */
	public URL getImageURL() {
		delayedResolve();
		return imageURL;
	}

	/**
	 * Get optional operating system specification as a comma-separated string.
	 * 
	 * @return the operating system specification string, or <code>null</code>.
	 * @since 2.0
	 */
	public String getOS() {
		return os;
	}

	/**
	 * Get optional windowing system specification as a comma-separated string.
	 * @return the windowing system specification string, or <code>null</code>.
	 * @since 2.0
	 */
	public String getWS() {
		return ws;
	}

	/**
	 * Get optional system architecture specification as a comma-separated string.
	 * 
	 * @return the system architecture specification string, or <code>null</code>.
	 * @since 2.0
	 */
	public String getOSArch() {
		return arch;
	}

	/**
	 * Get optional locale specification as a comma-separated string.
	 * 
	 * @return the locale specification string, or <code>null</code>.
	 * @since 2.0
	 */
	public String getNL() {
		return nl;
	}

	/**
	 * Indicates whether the feature can be used as a primary feature.
	 * 
	 * @return <code>true</code> if this is a primary feature, 
	 * otherwise <code>false</code>
	 * @since 2.0
	 */
	public boolean isPrimary() {
		return primary;
	}
	
	/**
	 * Indicates whether the feature must be processed alone
	 * during installation and configuration. Features that
	 * are not exclusive can be installed in a batch.
	 * 
	 * @return <code>true</code> if feature requires
	 * exclusive processing, <code>false</code> otherwise.
	 * @since 2.1
	 */
	public boolean isExclusive() {
		return exclusive;
	}

	/**
	 * Returns an optional identifier for the feature application
	 * 
	 * @return application identifier, or <code>null</code>.
	 * @since 2.0
	 */
	public String getApplication() {
		return application;
	}

	/**
	 * Returns an optional identifier for the colocation affinity feature
	 * 
	 * @return feature identifier, or <code>null</code>.
	 * @since 2.0
	 */
	public String getAffinityFeature() {
		return affinity;
	}

	/**
	 * Returns and optional custom install handler entry.
	 * 
	 * @return install handler entry, or <code>null</code> if
	 * none was specified
	 * @since 2.0
	 */
	public InstallHandlerEntryModel getInstallHandlerModel() {
		//delayedResolve(); no delay
		return installHandler;
	}

	/**
	 * Returns the feature description.
	 * 
	 * @return feature rescription, or <code>null</code>.
	 * @since 2.0
	 */
	public URLEntryModel getDescriptionModel() {
		//delayedResolve(); no delay
		return description;
	}

	/**
	 * Returns the copyright information for the feature.
	 * 
	 * @return copyright information, or <code>null</code>.
	 * @since 2.0
	 */
	public URLEntryModel getCopyrightModel() {
		//delayedResolve(); no delay
		return copyright;
	}

	/**
	 * Returns the license information for the feature.
	 * 
	 * @return feature license, or <code>null</code>.
	 * @since 2.0
	 */
	public URLEntryModel getLicenseModel() {
		//delayedResolve(); no delay;
		return license;
	}

	/**
	 * Returns an information entry referencing the location of the
	 * feature update site.
	 * 
	 * @return update site entry, or <code>null</code>.
	 * @since 2.0
	 */
	public URLEntryModel getUpdateSiteEntryModel() {
		//delayedResolve(); no delay;
		return updateSiteInfo;
	}

	/**
	 * Return an array of information entries referencing locations of other
	 * update sites. 
	 * 
	 * @return an array of site entries, or an empty array.
	 * @since 2.0 
	 * @since 2.0
	 */
	public URLEntryModel[] getDiscoverySiteEntryModels() {
		//delayedResolve(); no delay;
		if (discoverySiteInfo == null || discoverySiteInfo.size() == 0)
			return new URLEntryModel[0];

		return (URLEntryModel[]) discoverySiteInfo.toArray(arrayTypeFor(discoverySiteInfo));
	}

	/**
	 * Return a list of plug-in dependencies for this feature.
	 * 
	 * @return the list of required plug-in dependencies, or an empty array.
	 * @since 2.0
	 */
	public ImportModel[] getImportModels() {
		//delayedResolve(); no delay;
		if (imports == null || imports.size() == 0)
			return new ImportModel[0];

		return (ImportModel[]) imports.toArray(arrayTypeFor(imports));
	}

	/**
	 * Returns an array of plug-in entries referenced by this feature
	 * 
	 * @return an erray of plug-in entries, or an empty array.
	 * @since 2.0
	 */
	public PluginEntryModel[] getPluginEntryModels() {
		if (pluginEntries == null || pluginEntries.size() == 0)
			return new PluginEntryModel[0];

		return (PluginEntryModel[]) pluginEntries.toArray(arrayTypeFor(pluginEntries));
	}

	/**
	 * Returns an array of versioned identifier referenced by this feature
	 * 
	 * @return an array of versioned identifier, or an empty array.
	 * @deprecated use getFeatureIncludeIdentifier instead.
	 * @since 2.0
	 */
	public VersionedIdentifier[] getFeatureIncludeVersionedIdentifier() {
		//delayedResolve(); no delay
		if (featureIncludes == null)
			return new VersionedIdentifier[0];

		//
		Iterator iter = featureIncludes.iterator();
		VersionedIdentifier[] versionIncluded = new VersionedIdentifier[featureIncludes.size()];
		int index = 0;
		while (iter.hasNext()) {
			IncludedFeatureReferenceModel model = (IncludedFeatureReferenceModel) iter.next();
			versionIncluded[index] = model.getVersionedIdentifier();
			index++;
		}
		return versionIncluded;
	}

	/**
	 * Returns an array of included feature reference model referenced by this feature.
	 *
	 * @return an array of included feature reference model, or an empty array.
	 * @since 2.0
	 */
	public IIncludedFeatureReference[] getFeatureIncluded() {
		//delayedResolve(); no delay
		if (featureIncludes == null || featureIncludes.size() == 0)
			return new IIncludedFeatureReference[0];
		return (IIncludedFeatureReference[]) featureIncludes.toArray(arrayTypeFor(featureIncludes));
	}

	/**
	 * Returns an array of non-plug-in entries referenced by this feature
	 * 
	 * @return an erray of non-plug-in entries, or an empty array.
	 * @since 2.0
	 */
	public NonPluginEntryModel[] getNonPluginEntryModels() {
		if (nonPluginEntries == null || nonPluginEntries.size() == 0)
			return new NonPluginEntryModel[0];

		return (NonPluginEntryModel[]) nonPluginEntries.toArray(arrayTypeFor(nonPluginEntries));
	}

	/**
	 * Sets the feature identifier.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param featureId feature identifier
	 * @since 2.0
	 */
	public void setFeatureIdentifier(String featureId) {
		assertIsWriteable();
		this.featureId = featureId;
	}

	/**
	 * Sets the feature version.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param featureVersion feature version
	 * @since 2.0
	 */
	public void setFeatureVersion(String featureVersion) {
		assertIsWriteable();
		this.featureVersion = featureVersion;
	}

	/**
	 * Sets the feature displayable label.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param label displayable label
	 * @since 2.0
	 */
	public void setLabel(String label) {
		assertIsWriteable();
		this.label = label;
		this.localizedLabel = null;
	}

	/**
	 * Sets the feature provider displayable label.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param provider provider displayable label
	 * @since 2.0
	 */
	public void setProvider(String provider) {
		assertIsWriteable();
		this.provider = provider;
		this.localizedProvider = null;
	}

	/**
	 * Sets the unresolved URL for the feature image.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param imageURLString unresolved URL string
	 * @since 2.0
	 */
	public void setImageURLString(String imageURLString) {
		assertIsWriteable();
		this.imageURLString = imageURLString;
		this.imageURL = null;
	}

	/**
	 * Sets the operating system specification.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param os operating system specification as a comma-separated list
	 * @since 2.0
	 */
	public void setOS(String os) {
		assertIsWriteable();
		this.os = os;
	}

	/**
	 * Sets the windowing system specification.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param ws windowing system specification as a comma-separated list
	 * @since 2.0
	 */
	public void setWS(String ws) {
		assertIsWriteable();
		this.ws = ws;
	}

	/**
	 * Sets the locale specification.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param nl locale specification as a comma-separated list
	 * @since 2.0
	 */
	public void setNL(String nl) {
		assertIsWriteable();
		this.nl = nl;
	}

	/**
	 * Sets the system architecture specification.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param arch system architecture specification as a comma-separated list
	 * @since 2.0
	 */
	public void setArch(String arch) {
		assertIsWriteable();
		this.arch = arch;
	}

	/**
	 * Indicates whether this feature can act as a primary feature.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param primary <code>true</code> if this feature can act as primary,
	 * <code>false</code> otherwise
	 * 
	 * @since 2.0
	 */
	public void setPrimary(boolean primary) {
		assertIsWriteable();
		this.primary = primary;
	}
	
	/**
	 * Indicates whether this feature can act as a primary feature.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param exclusive <code>true</code> if this feature must be
	 * processed independently from other features, <code>false</code> 
	 * if feature can be processed in a batch with other features.
	 * 
	 * @since 2.1
	 */
	public void setExclusive(boolean exclusive) {
		assertIsWriteable();
		this.exclusive = exclusive;
	}

	/**
	 * Sets the feature application identifier.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param application feature application identifier
	 * @since 2.0
	 */
	public void setApplication(String application) {
		assertIsWriteable();
		this.application = application;
	}

	/**
	 * Sets the identifier of the Feature this feature should be
	 * installed with.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param affinity the identifier of the Feature
	 * @since 2.0
	 */
	public void setAffinityFeature(String affinity) {
		assertIsWriteable();
		this.affinity = affinity;
	}

	/**
	 * Sets the custom install handler for the feature.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param installHandler install handler entry
	 * @since 2.0
	 */
	public void setInstallHandlerModel(InstallHandlerEntryModel installHandler) {
		assertIsWriteable();
		this.installHandler = installHandler;
	}

	/**
	 * Sets the feature description information.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param description feature description information
	 * @since 2.0
	 */
	public void setDescriptionModel(URLEntryModel description) {
		assertIsWriteable();
		this.description = description;
	}

	/**
	 * Sets the feature copyright information.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param copyright feature copyright information
	 * @since 2.0
	 */
	public void setCopyrightModel(URLEntryModel copyright) {
		assertIsWriteable();
		this.copyright = copyright;
	}

	/**
	 * Sets the feature license information.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param license feature license information
	 * @since 2.0
	 */
	public void setLicenseModel(URLEntryModel license) {
		assertIsWriteable();
		this.license = license;
	}

	/**
	 * Sets the feature update site reference.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param updateSiteInfo feature update site reference
	 * @since 2.0
	 */
	public void setUpdateSiteEntryModel(URLEntryModel updateSiteInfo) {
		assertIsWriteable();
		this.updateSiteInfo = updateSiteInfo;
	}

	/**
	 * Sets additional update site references.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param discoverySiteInfo additional update site references
	 * @since 2.0
	 */
	public void setDiscoverySiteEntryModels(URLEntryModel[] discoverySiteInfo) {
		assertIsWriteable();
		if (discoverySiteInfo == null)
			this.discoverySiteInfo = null;
		else
			this.discoverySiteInfo = new ArrayList(Arrays.asList(discoverySiteInfo));
	}

	/**
	 * Sets the feature plug-in dependency information.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param imports feature plug-in dependency information
	 * @since 2.0
	 */
	public void setImportModels(ImportModel[] imports) {
		assertIsWriteable();
		if (imports == null)
			this.imports = null;
		else
			this.imports = new ArrayList(Arrays.asList(imports));
	}

	/**
	 * Sets the feature plug-in references.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param pluginEntries feature plug-in references
	 * @since 2.0
	 */
	public void setPluginEntryModels(PluginEntryModel[] pluginEntries) {
		assertIsWriteable();
		if (pluginEntries == null)
			this.pluginEntries = null;
		else
			this.pluginEntries = new ArrayList(Arrays.asList(pluginEntries));
	}

	/**
	 * Sets the feature non-plug-in data references.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param nonPluginEntries feature non-plug-in data references
	 * @since 2.0
	 */
	public void setNonPluginEntryModels(NonPluginEntryModel[] nonPluginEntries) {
		assertIsWriteable();
		if (nonPluginEntries == null)
			this.nonPluginEntries = null;
		else
			this.nonPluginEntries = new ArrayList(Arrays.asList(nonPluginEntries));
	}

	/**
	 * Adds an additional update site reference.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param discoverySiteInfo update site reference
	 * @since 2.0
	 */
	public void addDiscoverySiteEntryModel(URLEntryModel discoverySiteInfo) {
		assertIsWriteable();
		if (this.discoverySiteInfo == null)
			this.discoverySiteInfo = new ArrayList();
		if (!this.discoverySiteInfo.contains(discoverySiteInfo))
			this.discoverySiteInfo.add(discoverySiteInfo);
	}

	/**
	 * Adds a plug-in dependency entry.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param importEntry plug-in dependency entry
	 * @since 2.0
	 */
	public void addImportModel(ImportModel importEntry) {
		assertIsWriteable();
		if (this.imports == null)
			this.imports = new ArrayList();
		if (!this.imports.contains(importEntry))
			this.imports.add(importEntry);
	}

	/**
	 * Adds a plug-in reference.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param pluginEntry plug-in reference
	 * @since 2.0
	 */
	public void addPluginEntryModel(PluginEntryModel pluginEntry) {
		assertIsWriteable();
		if (this.pluginEntries == null)
			this.pluginEntries = new ArrayList();
		//PERF: no ListContains()
		//if (!this.pluginEntries.contains(pluginEntry))
		this.pluginEntries.add(pluginEntry);
	}

	/**
	 * Adds a feature identifier.
	 * Throws a runtime exception if this object is marked read-only.
	 * @param include the included feature
	 * @since 2.1
	 */
	public void addIncludedFeatureReferenceModel(IncludedFeatureReferenceModel include) {
		assertIsWriteable();
		if (this.featureIncludes == null)
			this.featureIncludes = new ArrayList();
		//PERF: no ListContains()
		//if (!this.featureIncludes.contains(include))
		this.featureIncludes.add(include);
	}

	/**
	 * Adds a non-plug-in data reference.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param nonPluginEntry non-plug-in data reference
	 * @since 2.0
	 */
	public void addNonPluginEntryModel(NonPluginEntryModel nonPluginEntry) {
		assertIsWriteable();
		if (this.nonPluginEntries == null)
			this.nonPluginEntries = new ArrayList();
		//PERF: no ListContains()
		//if (!this.nonPluginEntries.contains(nonPluginEntry))
		this.nonPluginEntries.add(nonPluginEntry);
	}

	/**
	 * Removes an update site reference.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param discoverySiteInfo update site reference
	 * @since 2.0
	 */
	public void removeDiscoverySiteEntryModel(URLEntryModel discoverySiteInfo) {
		assertIsWriteable();
		if (this.discoverySiteInfo != null)
			this.discoverySiteInfo.remove(discoverySiteInfo);
	}

	/**
	 * Removes a plug-in dependency entry.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param importEntry plug-in dependency entry
	 * @since 2.0
	 */
	public void removeImportModel(ImportModel importEntry) {
		assertIsWriteable();
		if (this.imports != null)
			this.imports.remove(importEntry);
	}

	/**
	 * Removes a plug-in reference.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param pluginEntry plug-in reference
	 * @since 2.0
	 */
	public void removePluginEntryModel(PluginEntryModel pluginEntry) {
		assertIsWriteable();
		if (this.pluginEntries != null)
			this.pluginEntries.remove(pluginEntry);
	}

	/**
	 * Removes a non-plug-in data reference.
	 * Throws a runtime exception if this object is marked read-only.
	 * 
	 * @param nonPluginEntry non-plug-in data reference
	 * @since 2.0
	 */
	public void removeNonPluginEntryModel(NonPluginEntryModel nonPluginEntry) {
		assertIsWriteable();
		if (this.nonPluginEntries != null)
			this.nonPluginEntries.remove(nonPluginEntry);
	}

	/**
	 * Marks the model object as read-only.
	 * 
	 * @since 2.0
	 */
	public void markReadOnly() {
		super.markReadOnly();
		markReferenceReadOnly(getDescriptionModel());
		markReferenceReadOnly(getCopyrightModel());
		markReferenceReadOnly(getLicenseModel());
		markReferenceReadOnly(getUpdateSiteEntryModel());
		markListReferenceReadOnly(getDiscoverySiteEntryModels());
		markListReferenceReadOnly(getImportModels());
		markListReferenceReadOnly(getPluginEntryModels());
		markListReferenceReadOnly(getNonPluginEntryModels());
	}

	/**
	 * Resolve the model object.
	 * Any URL strings in the model are resolved relative to the 
	 * base URL argument. Any translatable strings in the model that are
	 * specified as translation keys are localized using the supplied 
	 * resource bundle.
	 * 
	 * @param base URL
	 * @param bundleURL resource bundle url
	 * @exception MalformedURLException
	 * @since 2.0
	 */
	public void resolve(URL base,URL bundleURL) throws MalformedURLException {
		this.bundleURL = bundleURL;
		this.base = base;

		// plugin entry and nonpluginentry are optimized too
		resolveListReference(getPluginEntryModels(), base, bundleURL);
		resolveListReference(getNonPluginEntryModels(), base, bundleURL);
		
		//URLSiteModel are optimized
		resolveReference(getDescriptionModel(),base, bundleURL);
		resolveReference(getCopyrightModel(),base, bundleURL);
		resolveReference(getLicenseModel(),base, bundleURL);
		resolveReference(getUpdateSiteEntryModel(),base, bundleURL);
		resolveListReference(getDiscoverySiteEntryModels(),base, bundleURL);
		
		// Import Models are optimized
		resolveListReference(getImportModels(),base, bundleURL);
	}

	private void delayedResolve() {

		// PERF: delay resolution
		if (resolved)
			return;

		resolved = true;
		// resolve local elements
		localizedLabel = resolveNLString(bundleURL, label);
		localizedProvider = resolveNLString(bundleURL, provider);
		try {
			imageURL = resolveURL(base,bundleURL, imageURLString);
		} catch (MalformedURLException e){
			UpdateCore.warn("",e); //$NON-NLS-1$
		}
	}

	/**
	 * Method setPrimaryPlugin.
	 * @param plugin
	 */
	public void setPrimaryPluginID(String plugin) {
		if (primary && primaryPluginID == null) {
			primaryPluginID = featureId;
		}
		primaryPluginID = plugin;
	}
	/**
	 * Returns the primaryPluginID.
	 * @return String
	 */
	public String getPrimaryPluginID() {
		return primaryPluginID;
	}

	/**
	 * Returns <code>true</code> if this feature is patching another feature,
	 * <code>false</code> otherwise
	 * @return boolean
	 */
	public boolean isPatch() {
		ImportModel[] imports = getImportModels();

		for (int i = 0; i < imports.length; i++) {
			if (imports[i].isPatch())
				return true;
		}
		return false;
	}
}