/*
* Copyright 2003-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.smodel.adapter.structure.concept;
import jetbrains.mps.smodel.Language;
import jetbrains.mps.smodel.LanguageAspect;
import jetbrains.mps.smodel.SNodeUtil;
import jetbrains.mps.smodel.adapter.ids.SContainmentLinkId;
import jetbrains.mps.smodel.adapter.ids.SPropertyId;
import jetbrains.mps.smodel.adapter.ids.SReferenceLinkId;
import jetbrains.mps.smodel.adapter.structure.FormatException;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import jetbrains.mps.smodel.adapter.structure.link.InvalidContainmentLink;
import jetbrains.mps.smodel.adapter.structure.link.SContainmentLinkAdapterById;
import jetbrains.mps.smodel.adapter.structure.property.InvalidProperty;
import jetbrains.mps.smodel.adapter.structure.property.SPropertyAdapterById;
import jetbrains.mps.smodel.adapter.structure.ref.InvalidReferenceLink;
import jetbrains.mps.smodel.adapter.structure.ref.SReferenceLinkAdapterById;
import jetbrains.mps.smodel.language.ConceptRegistry;
import jetbrains.mps.smodel.legacy.ConceptMetaInfoConverter;
import jetbrains.mps.smodel.runtime.ConceptDescriptor;
import jetbrains.mps.smodel.runtime.ConceptPresentation;
import jetbrains.mps.smodel.runtime.LinkDescriptor;
import jetbrains.mps.smodel.runtime.PropertyDescriptor;
import jetbrains.mps.smodel.runtime.ReferenceDescriptor;
import jetbrains.mps.util.NameUtil;
import jetbrains.mps.util.annotation.ToRemove;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import org.jetbrains.mps.openapi.language.SAbstractLink;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
/**
* Common ancestor of adapter classes {@link SConceptAdapter} and {@link SInterfaceConceptAdapter}.
* It serves as an adapter from ConceptDescriptor to SAbstractConcept and the base class for all the implementations
* of the {@link SAbstractConcept}.
* The common idea is that on every client request it looks for the proper {@link ConceptDescriptor}
* in the special registry and redirects the client request to it.
* So it has only an id (string or smth else) as its state.
* <p/>
* One calls the SAbstractConcept instance <\it>valid</\it> if and only if its {@link ConceptDescriptor} is present
* ({@link #getConceptDescriptor()} != null).
* <p/>
* Whenever the descriptor is absent (the concept instance is NOT valid) the "fail-safe" behavior is provided:
* please look at each method individually to acknowledge the contract.
* <p/>
* NB: If a client of this API wants to distinguish the case when the concept is invalid, he/she
* needs to use the method {@link #isValid()}. (!)
* <p/>
* Currently a lot of "hacks" introduced to fix some common cases (e.g. not valid concept still is a subconcept of the BaseConcept).
* Also there is an editor issue when an instance of abstract concept (interface concept) might be created.
* (E.g. the method {@link #isSubConceptOf(SAbstractConcept)} works not as expected for such concepts)
*/
public abstract class SAbstractConceptAdapter implements SAbstractConcept, ConceptMetaInfoConverter {
public static final String ID_DELIM = ":";
protected String myFqName;
protected SAbstractConceptAdapter(String fqName) {
myFqName = fqName;
}
/**
* @return the backing {@link ConceptDescriptor}
*/
@Nullable
public abstract ConceptDescriptor getConceptDescriptor();
/**
* a helper method to get a declaration node for this concept
* in the case of the legacy concept resolving (by string id)
*/
protected abstract SNode findInModel(SModel structureModel);
@Nullable
@Override
public SNodeReference getSourceNode() {
ConceptDescriptor d = getConceptDescriptor();
if (d == null) {
return null;
}
return d.getSourceNode();
}
@NotNull
@Override
public String getName() {
return NameUtil.shortNameFromLongName(getQualifiedName());
}
@Override
public Collection<SReferenceLink> getReferenceLinks() {
ConceptDescriptor d = getConceptDescriptor();
if (d == null) {
return Collections.emptyList();
}
ArrayList<SReferenceLink> result = new ArrayList<SReferenceLink>();
for (ReferenceDescriptor rd : d.getReferenceDescriptors()) {
result.add(MetaAdapterFactory.getReferenceLink(rd.getId(), rd.getName()));
}
return result;
}
public boolean hasReference(SReferenceLink r) {
if (r instanceof InvalidReferenceLink) {
return false;
}
assert r instanceof SReferenceLinkAdapterById : r.getClass().getName();
ConceptDescriptor d = getConceptDescriptor();
return d != null && d.getRefDescriptor(((SReferenceLinkAdapterById) r).getId()) != null;
}
@Override
public Collection<SContainmentLink> getContainmentLinks() {
ConceptDescriptor d = getConceptDescriptor();
if (d == null) {
return Collections.emptyList();
}
ArrayList<SContainmentLink> result = new ArrayList<SContainmentLink>();
for (LinkDescriptor ld : d.getLinkDescriptors()) {
result.add(MetaAdapterFactory.getContainmentLink(ld.getId(), ld.getName()));
}
return result;
}
public boolean hasLink(SContainmentLink l) {
if (l instanceof InvalidContainmentLink) {
return false;
}
assert l instanceof SContainmentLinkAdapterById : l.getClass().getName();
ConceptDescriptor d = getConceptDescriptor();
return d != null && d.getLinkDescriptor(((SContainmentLinkAdapterById) l).getId()) != null;
}
@Override
@Deprecated
public SAbstractLink getLink(String role) {
// INTENTIONALLY DISTURBING
Logger.getLogger(SAbstractConceptAdapter.class).error("The method is scheduled for removal. There were no uses, do not introduce a new one", new Throwable());
ConceptDescriptor nodeConcept = getConceptDescriptor();
if (nodeConcept == null) {
return null;
}
LinkDescriptor d = nodeConcept.getLinkDescriptor(role);
if (d != null) {
SContainmentLinkId linkId = d.getId();
return MetaAdapterFactory.getContainmentLink(linkId, role);
} else {
ReferenceDescriptor r = nodeConcept.getRefDescriptor(role);
if (r == null) {
return null;
}
SReferenceLinkId linkId = r.getId();
return MetaAdapterFactory.getReferenceLink(linkId, role);
}
}
@Override
public Iterable<SAbstractLink> getLinks() {
// INTENTIONALLY DISTURBING
Logger.getLogger(SAbstractConceptAdapter.class).error("The method is scheduled for removal. There were no uses, do not introduce a new one", new Throwable());
ArrayList<SAbstractLink> result = new ArrayList<SAbstractLink>();
ConceptDescriptor cd = getConceptDescriptor();
if (cd == null) {
return Collections.emptyList();
}
for (LinkDescriptor ld : cd.getLinkDescriptors()) {
result.add(MetaAdapterFactory.getContainmentLink(ld.getId(), ld.getName()));
}
return result;
}
@Override
@Deprecated
public SProperty getProperty(String name) {
// INTENTIONALLY DISTURBING
Logger.getLogger(SAbstractConceptAdapter.class).error("The method is scheduled for removal. There were no uses, do not introduce a new one", new Throwable());
ConceptDescriptor cd = getConceptDescriptor();
if (cd == null) {
return null;
}
PropertyDescriptor d = cd.getPropertyDescriptor(name);
if (d == null) {
return new InvalidProperty(myFqName, name);
}
SPropertyId pid = d.getId();
return MetaAdapterFactory.getProperty(pid, name);
}
public boolean hasProperty(SProperty p) {
if (p instanceof InvalidProperty) {
return false;
}
assert p instanceof SPropertyAdapterById : p.getClass().getName();
ConceptDescriptor d = getConceptDescriptor();
return d != null && d.getPropertyDescriptor(((SPropertyAdapterById) p).getId()) != null;
}
@Override
public Collection<SProperty> getProperties() {
ConceptDescriptor descriptor = getConceptDescriptor();
if (descriptor == null) {
return Collections.emptyList();
}
ArrayList<SProperty> result = new ArrayList<SProperty>();
for (PropertyDescriptor pd : descriptor.getPropertyDescriptors()) {
result.add(MetaAdapterFactory.getProperty(pd.getId(), pd.getName()));
}
return result;
}
/**
* @param anotherConcept -- another SAbstractConcept
* @return true iff this concept is a subconcept of another concept.
* if one of the concepts is not valid then false is returned
*/
@Override
public boolean isSubConceptOf(SAbstractConcept anotherConcept) {
// todo: hack, need for working node attributes on nodes of not generated concepts
// todo: remove
if (SNodeUtil.concept_BaseConcept.equals(anotherConcept)) { // providing the '* is a subconcept of BaseConcept' contract
return true;
}
if (equals(anotherConcept)) { // providing the 'A is a subconcept of A' contract
return true;
}
ConceptDescriptor descriptor = getConceptDescriptor();
if (descriptor == null) {
return false;
}
ConceptDescriptor anotherDescriptor = ((SAbstractConceptAdapter) anotherConcept).getConceptDescriptor();
if (anotherDescriptor == null) {
return false;
}
if (anotherDescriptor.isInterfaceConcept() && anotherConcept instanceof SConceptAdapter) {
// anotherDescriptor is in fact an interface concept
// however is created as a SConceptAdapter, not a SInterfaceConceptAdapter (!)
// currently the editor has to perform hacky operations as this
return false;
}
return isSubConceptOfSpecial(descriptor, anotherDescriptor, anotherConcept);
}
protected abstract boolean isSubConceptOfSpecial(@NotNull ConceptDescriptor thisDescriptor, ConceptDescriptor anotherDescriptor,
SAbstractConcept anotherConcept);
@Override
public boolean isAbstract() {
ConceptDescriptor d = getConceptDescriptor();
return d == null || d.isAbstract();
}
@Nullable
@Override
@Deprecated
@ToRemove(version = 3.4)
public SNode getDeclarationNode() {
Language lang = ((Language) getLanguage().getSourceModule());
if (lang == null) {
return null;
}
SModel structureModel = LanguageAspect.STRUCTURE.get(lang);
if (structureModel == null) {
return null;
}
return findInModel(structureModel);
}
@NotNull
@Override
public String getConceptAlias() {
ConceptDescriptor d = getConceptDescriptor();
if (d == null) {
return "";
}
return d.getConceptAlias();
}
@NotNull
@Override
public String getShortDescription() {
ConceptPresentation pres = ConceptRegistry.getInstance().getConceptProperties(this);
if (pres != null) {
return pres.getShortDescription();
}
// fallback for legacy code
ConceptDescriptor d = getConceptDescriptor();
if (d == null) {
return "";
}
return d.getConceptShortDescription();
}
@NotNull
@Override
public String getHelpUrl() {
ConceptPresentation pres = ConceptRegistry.getInstance().getConceptProperties(this);
if (pres != null) {
return pres.getHelpUrl();
}
ConceptDescriptor d = getConceptDescriptor();
if (d == null) {
return "";
}
return d.getHelpUrl();
}
/**
* @return true iff the underlying descriptor is present
*/
@Override
public final boolean isValid() {
return getConceptDescriptor() != null;
}
@NotNull
@Override
public SProperty convertProperty(String propertyName) {
for (SProperty p : getProperties()) {
if (p.getName().equals(propertyName)) {
return p;
}
}
return new InvalidProperty(getQualifiedName(), propertyName);
}
@NotNull
@Override
public SReferenceLink convertAssociation(String role) {
for (SReferenceLink r : getReferenceLinks()) {
if (r.getName().equals(role)) {
return r;
}
}
return new InvalidReferenceLink(getQualifiedName(), role);
}
@NotNull
@Override
public SContainmentLink convertAggregation(String role) {
for (SContainmentLink l : getContainmentLinks()) {
if (l.getName().equals(role)) {
return l;
}
}
return new InvalidContainmentLink(getQualifiedName(), role);
}
@Override
public String toString() {
return myFqName;
}
public abstract String serialize();
public static SAbstractConcept deserialize(String s) {
if (s.startsWith(SInterfaceConceptAdapterById.INTERFACE_PREFIX)) {
return SInterfaceConceptAdapterById.deserialize(s);
} else if (s.startsWith(SConceptAdapterById.CONCEPT_PREFIX)) {
return SConceptAdapterById.deserialize(s);
} else if (s.startsWith(InvalidConcept.INVALID_PREFIX)) {
return InvalidConcept.deserialize(s);
} else {
throw new FormatException("Illegal concept type: " + s);
}
}
}