blob: ffd8251aed2501b9f7903befa8702a2caa7eccc7 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2008-2013 BMW Car IT, See4sys, itemis 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:
* BMW Car IT - Initial API and implementation
* See4sys - Added support for EPackage URIs and inheritance of descriptors
* BMW Car IT - [373481] Performance optimizations for model loading
* itemis - [406203] Enable navigation from a version-specific metamodel descriptor to the underlying base metamodel descriptor
* itemis - [409367] Add a custom URI scheme to metamodel descriptor allowing mapping URI scheme to metamodel descriptor
*
* </copyright>
*/
package org.eclipse.sphinx.emf.metamodel;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EPackage.Registry;
import org.eclipse.sphinx.platform.util.ReflectUtil;
/**
* A base implementation for meta-model descriptors. Provides default implementations for all methods specified by the
* <code>IMetaModelDescriptor</code> interface.
* <p>
* Extends {@linkplain PlatformObject} in order to be adaptable by Eclipse adapters mechanism.
*
* @see IMetaModelDescriptor
* @since 0.7.0
*/
public abstract class AbstractMetaModelDescriptor extends PlatformObject implements IMetaModelDescriptor {
protected static final String URI_SEGMENT_SEPARATOR = "/"; //$NON-NLS-1$
private String fIdentifier;
protected URI fBaseNamespaceURI;
private String fEPackageNsURIPostfixPattern;
private MetaModelVersionData fVersionData;
private String fName = null;
private URI fNamespaceURI;
private Pattern fEPackageNsURIPattern;
private EPackage fRootEPackage = null;
private Collection<EPackage> fEPackages = null;
private List<String> fContentTypeIds;
private Registry fEPkgRegistry;
public Registry getEPackageRegistry() {
if (fEPkgRegistry == null) {
fEPkgRegistry = EPackage.Registry.INSTANCE;
}
return fEPkgRegistry;
}
// This method is only needed for testing the MetaModelDescriptor class
protected void setEPackageRegistry(EPackage.Registry ePkgRegistry) {
fEPkgRegistry = ePkgRegistry;
}
/**
* Creates a descriptor for a meta-model. This constructor is to be used if the descriptor shall describe an
* abstract meta-model family (without an implementation backing it) or a meta-model (backed by an implementation)
* which exists only in one version and consists of only one {@link EPackage}, i.e., a
* {@link AbstractMetaModelDescriptor#getRootEPackage() root EPackage} without sub-packages.
*
* @param identifier
* The identifier of the described meta-model. A unique identifier used for referencing the meta-model
* from within an extension point.
* @param namespace
* The namespace of the described meta-model.
* @param name
* The name of the described meta-model.
* @see #getRootEPackage()
*/
protected AbstractMetaModelDescriptor(String identifier, String namespace, String name) {
this(identifier, namespace, null, name);
}
/**
* Creates a descriptor for a meta-model. This constructor is to be used if the descriptor shall describe a
* meta-model (backed by an implementation) which exists in only one version and consists of multiple
* {@link EPackage}s.
*
* @param identifier
* The identifier of the described meta-model. A unique identifier used for referencing the meta-model
* from within an extension point.
* @param baseNamespace
* Typically the namespace of the {@link AbstractMetaModelDescriptor#getRootEPackage() root EPackage}.
* @param ePackageNsURIPostfixPattern
* A regular expression when appended to the baseNamespace will match the namespace of the meta-model
* sub-packages.
* @param name
* The name of the described meta-model.
* @see #getRootEPackage()
*/
protected AbstractMetaModelDescriptor(String identifier, String baseNamespace, String ePackageNsURIPostfixPattern, String name) {
Assert.isNotNull(identifier);
Assert.isNotNull(baseNamespace);
fIdentifier = identifier;
try {
fBaseNamespaceURI = new URI(baseNamespace);
} catch (URISyntaxException ex) {
throw new WrappedException(ex);
}
fEPackageNsURIPostfixPattern = ePackageNsURIPostfixPattern;
initNamespace(); // Not calculated lazily in order to detect malformed nsPostfixes
fName = name;
}
/**
* Creates a descriptor for a concrete version of a meta-model. The constructor is to be used if the descriptor
* shall describe a concrete version of a meta-model (backed by an implementation).
*
* @param identifier
* The identifier of the described meta-model. A unique identifier used for referencing the meta-model
* from within an extension point.
* @param baseNamespace
* The base namespace of the described meta-model. Is equal to the full namespace if no version data is
* provided.
* @param versionData
* Data describing the meta-model version details.
*/
protected AbstractMetaModelDescriptor(String identifier, String baseNamespace, MetaModelVersionData versionData) {
Assert.isNotNull(identifier);
Assert.isNotNull(baseNamespace);
fIdentifier = identifier;
try {
fBaseNamespaceURI = new URI(baseNamespace);
} catch (URISyntaxException ex) {
throw new WrappedException(ex);
}
fVersionData = versionData;
initNamespace(); // Not calculated lazily in order to detect malformed nsPostfixes
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getIdentifier()
*/
@Override
public String getIdentifier() {
return fIdentifier;
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getNamespaceURI()
*/
@Override
public URI getNamespaceURI() {
return fNamespaceURI;
}
/**
* Initializes the meta-model's namespace.
*/
protected void initNamespace() {
StringBuilder namespace = new StringBuilder(fBaseNamespaceURI.toString());
if (fVersionData != null && fVersionData.getNsPostfix() != null && fVersionData.getNsPostfix().length() > 0) {
namespace.append(URI_SEGMENT_SEPARATOR);
namespace.append(fVersionData.getNsPostfix());
}
try {
fNamespaceURI = new URI(namespace.toString());
} catch (URISyntaxException ex) {
throw new WrappedException(ex);
}
}
protected URI getBaseNamespaceURI() {
return fBaseNamespaceURI;
}
protected String getNsPostfix() {
if (fVersionData != null) {
return fVersionData.getNsPostfix();
}
return null;
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getNamespace()
*/
@Override
public String getNamespace() {
return getNamespaceURI().toString();
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getName()
*/
@Override
public String getName() {
if (fName == null) {
initName();
}
return fName;
}
protected void initName() {
if (fVersionData != null) {
fName = fVersionData.getName();
} else {
fName = getIdentifier();
}
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getBaseDescriptor()
*/
@Override
public IMetaModelDescriptor getBaseDescriptor() {
if (fVersionData != null) {
return fVersionData.getBaseDescriptor();
}
return null;
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getSupportedCustomURIScheme()
*/
@Override
public String getCustomURIScheme() {
return null;
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getOrdinal()
*/
@Override
@Deprecated
public int getOrdinal() {
if (fVersionData != null) {
return fVersionData.getOrdinal();
}
return -1;
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getEPackageNsURIPattern()
*/
@Override
@Deprecated
public String getEPackageNsURIPattern() {
if (fEPackageNsURIPattern == null) {
initEPackageNsURIPattern();
}
return fEPackageNsURIPattern.pattern();
}
@Override
public boolean matchesEPackageNsURIPattern(String uri) {
if (fEPackageNsURIPattern == null) {
initEPackageNsURIPattern();
}
return fEPackageNsURIPattern.matcher(uri).matches();
}
/**
* Initializes the URI pattern used for determining if an EPackage belongs to the meta-model or not.
*/
protected void initEPackageNsURIPattern() {
StringBuilder buffer = new StringBuilder(fBaseNamespaceURI.toString());
if (fEPackageNsURIPostfixPattern != null) {
buffer.append(URI_SEGMENT_SEPARATOR);
buffer.append(fEPackageNsURIPostfixPattern);
} else if (fVersionData != null) {
buffer.append(URI_SEGMENT_SEPARATOR);
buffer.append(fVersionData.getEPackageNsURIPostfixPattern());
}
fEPackageNsURIPattern = Pattern.compile(buffer.toString());
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getEPackages()
*/
@Override
public Collection<EPackage> getEPackages() {
synchronized (getEPackageRegistry()) {
if (fEPackages == null) {
initEPackages();
}
}
return fEPackages;
}
protected void initEPackages() {
Set<EPackage> ePackages = new HashSet<EPackage>();
Set<String> safeNsURIs = new HashSet<String>(getEPackageRegistry().keySet());
for (String nsURI : safeNsURIs) {
if (matchesEPackageNsURIPattern(nsURI)) {
ePackages.add(getEPackageRegistry().getEPackage(nsURI));
}
}
fEPackages = Collections.unmodifiableSet(ePackages);
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getRootEPackage()
*/
@Override
public EPackage getRootEPackage() {
synchronized (getEPackageRegistry()) {
if (fRootEPackage == null) {
initRootEPackage();
}
}
return fRootEPackage;
}
protected void initRootEPackage() {
fRootEPackage = getEPackageRegistry().getEPackage(getNamespace());
if (fRootEPackage == null) {
// FIXME Consider to remove this part. In case that an older metamodel version has had a root
// EPackage but the latest metamodel version doesn't proceeding this way cannot be possibly a good
// thing
// Refer to compatible namespaces URI if no package can be found under native namespace URI
for (URI compatibleNamespaceURI : getCompatibleNamespaceURIs()) {
fRootEPackage = getEPackageRegistry().getEPackage(compatibleNamespaceURI.toString());
if (fRootEPackage != null) {
break;
}
}
}
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getEPackage()
*/
@Override
@Deprecated
public EPackage getEPackage() {
return getRootEPackage();
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#isEPackageRegistered()
*/
@Override
@Deprecated
public boolean isEPackageRegistered() {
return getRootEPackage() != null;
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getRootEFactory()
*/
@Override
public EFactory getRootEFactory() {
EPackage ePackage = getRootEPackage();
if (ePackage != null) {
return ePackage.getEFactoryInstance();
}
return null;
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getEFactory()
*/
@Override
@Deprecated
public EFactory getEFactory() {
return getRootEFactory();
}
@Override
public List<String> getContentTypeIds() {
if (fContentTypeIds == null) {
initContentTypeIds();
}
return Collections.unmodifiableList(fContentTypeIds);
}
protected void initContentTypeIds() {
/*
* !! Important Note !! Initialize content type ids in a safe copy but not directly in the resulting list field
* to avoid that when multiple threads pass through this method some of these threads are already operating on
* the returned content type ids while others are still initializing them.
*/
List<String> safeContentTypeIds = new ArrayList<String>(1);
String defaultContentTypeId = getDefaultContentTypeId();
if (defaultContentTypeId != null && defaultContentTypeId.length() > 0) {
safeContentTypeIds.add(defaultContentTypeId);
}
fContentTypeIds = safeContentTypeIds;
}
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#getDefaultContentTypeId()
*/
@Override
public abstract String getDefaultContentTypeId();
/*
* @see org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor#addAssociatedContentTypeId(java.lang.String)
*/
@Override
public void addAssociatedContentTypeId(String contentTypeId) {
if (fContentTypeIds == null) {
initContentTypeIds();
}
fContentTypeIds.add(contentTypeId);
}
/**
* Returns the content type identifier exposed on the metamodel's {@link #getRootEPackage() root EPackage}, if any.
*
* @return The content type identifier of the metamodel's {@link #getRootEPackage() root EPackage}, or and empty
* string if no such is available.
* @deprecated This method could be used to implement {@link #getDefaultContentTypeId()}. It is however not
* recommended to do so because the content type id retrieval strategy implemented here has the side
* effect of that it triggers a full initialization of the metamodel's root {@link EPackage} and all
* dependent {@link EPackage}s. This can have significant impact on runtime performance, e.g., in the UI
* when {@link IMetaModelDescriptor metamodel descriptor}s are used to determine if some common
* navigator content needs to be activated or not, and may cause that the {@link EPackage}s of a
* metamodel become initialized even though not a single instance of that metamodel exists in the
* workspace.
*/
@Deprecated
protected String getRootEPackageContentTypeId() {
try {
EPackage rootPackage = getRootEPackage();
if (rootPackage != null) {
Object contentTypeId = ReflectUtil.getFieldValue(rootPackage, "eCONTENT_TYPE"); //$NON-NLS-1$;
if (contentTypeId instanceof String) {
return (String) contentTypeId;
}
}
} catch (Exception ex) {
// Ignore exception
}
return ""; //$NON-NLS-1$
}
@Override
public List<String> getCompatibleContentTypeIds() {
Set<String> compatibleContentTypeIds = new HashSet<String>();
for (IMetaModelDescriptor resourceVersionDescriptor : getCompatibleResourceVersionDescriptors()) {
if (resourceVersionDescriptor != this) {
compatibleContentTypeIds.addAll(resourceVersionDescriptor.getContentTypeIds());
}
}
return Collections.unmodifiableList(new ArrayList<String>(compatibleContentTypeIds));
}
@Override
public List<URI> getCompatibleNamespaceURIs() {
List<URI> compatibleNamespaceURIs = new ArrayList<URI>();
for (IMetaModelDescriptor resourceVersionDescriptor : getCompatibleResourceVersionDescriptors()) {
if (resourceVersionDescriptor != this) {
compatibleNamespaceURIs.add(resourceVersionDescriptor.getNamespaceURI());
}
}
return Collections.unmodifiableList(compatibleNamespaceURIs);
}
@Override
public Collection<IMetaModelDescriptor> getCompatibleResourceVersionDescriptors() {
return Collections.emptyList();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AbstractMetaModelDescriptor) {
AbstractMetaModelDescriptor otherRelease = (AbstractMetaModelDescriptor) obj;
return getIdentifier().equals(otherRelease.getIdentifier());
}
return false;
}
@Override
public int hashCode() {
return fIdentifier.hashCode();
}
@Override
public String toString() {
return getName();
}
}