/*
* Copyright 2003-2017 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 org.jetbrains.mps.openapi.model;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.annotations.Immutable;
/**
* Name of a model is complicated matter, we distinguish qualified/long and simple name, namespace fraction, and optional stereotype fraction of it.
* <pre>[ {namespace} '.'] {simple name} [ '@' {stereotype} ]</pre>
* To avoid use of utility methods scattered around the code that extract certain fractions of the model name, this object
* gives access to all relevant parts of the name.
*
* @author Artem Tikhomirov
* @since 3.4
*/
@Immutable
public final class SModelName {
private final String myValue;
public SModelName(@NotNull String qualifiedCompleteName) {
myValue = checkIllegalChars(qualifiedCompleteName);
}
public SModelName(@Nullable CharSequence namespace, @NotNull CharSequence simpleName, @Nullable CharSequence stereotype) {
StringBuilder sb = new StringBuilder();
if (namespace != null && namespace.length() > 0) {
sb.append(namespace);
sb.append('.');
assert namespace.toString().indexOf('@') == -1;
}
sb.append(simpleName);
assert simpleName.toString().indexOf('.') == -1;
assert simpleName.toString().indexOf('@') == -1;
if (stereotype != null && stereotype.length() > 0) {
assert simpleName.length() > 0;
sb.append('@');
sb.append(stereotype);
}
myValue = checkIllegalChars(sb.toString());
}
public SModelName(@NotNull CharSequence qualifiedName, @Nullable CharSequence stereotype) {
if (stereotype != null && stereotype.length() > 0) {
assert stereotype.toString().indexOf('@') == -1;
myValue = checkIllegalChars(new StringBuilder(qualifiedName).append('@').append(stereotype).toString());
} else {
myValue = checkIllegalChars(qualifiedName.toString());
}
}
/**
* Covers the case when we constructed a {@link SModelReference} with {@link SModelId} only, unaware of actual model name.
* @return <code>true</code> iff model name is blank.
*/
public boolean isEmpty() {
return myValue.isEmpty();
}
/**
* @return complete name of the model which includes optional namespace part, model name and optional stereotype, such as 'generator' or 'tests',
* separated by the '@' character e.g. 'jetbrains.mps.sample.generator.main@generator'
*/
@NotNull
public String getValue() {
return myValue;
}
/**
* @return qualified model name (namespace and simple name), without stereotype
*/
@NotNull
public String getLongName() {
int atIndex = myValue.lastIndexOf('@');
return atIndex != -1 ? myValue.substring(0, atIndex) : myValue;
}
/**
* @return name of the model without namespace nor stereotype, empty string iff model name is blank.
*/
@NotNull
public String getSimpleName() {
String qualifiedName = getLongName();
int nsSeparator = qualifiedName.lastIndexOf('.');
return nsSeparator == -1 ? qualifiedName : qualifiedName.substring(nsSeparator + 1);
}
@NotNull
public String getNamespace() {
String qualifiedName = getLongName();
int nsSeparator = qualifiedName.lastIndexOf('.');
return nsSeparator == -1 ? "" : qualifiedName.substring(0, nsSeparator);
}
/**
* @return <code>true</code> iff {@link #getStereotype()} would return non-empty value
*/
public boolean hasStereotype() {
return myValue.lastIndexOf('@') > 0;
}
@NotNull
public String getStereotype() {
int atIndex = myValue.lastIndexOf('@');
return atIndex != -1 ? myValue.substring(atIndex+1) : "";
}
/**
*
* @param newStereotype stereotype for the constructed name, or {@code null} to indicate new name
* shall not specify stereotype (identical to {@link #withoutStereotype()}
* @return model name with {@linkplain #getLongName() qualified name} identical to this model name and with a given stereotype.
* May return same instance if new stereotype is the same as actual.
*/
@NotNull
public SModelName withStereotype(@Nullable CharSequence newStereotype) {
if (newStereotype == null) {
return withoutStereotype();
}
return new SModelName(getLongName(), newStereotype);
}
/**
* Construct a name with the identical {@linkplain #getLongName() qualified name}, and without any stereotype.
* May return {@code this} if there's no stereotype in the actual name ({@code SModelName} is immutable).
* @return model name without a stereotype, never {@code null}
*/
@NotNull
public SModelName withoutStereotype() {
int atIndex = myValue.lastIndexOf('@');
return atIndex == -1 ? this : new SModelName(myValue.substring(0, atIndex)); // superfluous check for illegal chars, but no private cons.
}
@Override
public String toString() {
return getValue();
}
@Override
public boolean equals(Object obj) {
return obj.getClass() == SModelName.class && myValue.equals(((SModelName) obj).myValue);
}
@Override
public int hashCode() {
return myValue.hashCode();
}
private static String checkIllegalChars(String qualifiedCompleteName) throws IllegalArgumentException {
qualifiedCompleteName = qualifiedCompleteName.trim();
if (qualifiedCompleteName.isEmpty()) {
return qualifiedCompleteName;
}
int atIndex = qualifiedCompleteName.lastIndexOf('@');
if (atIndex == 0 || (!qualifiedCompleteName.isEmpty() && atIndex == qualifiedCompleteName.length() - 1)) {
throw new IllegalArgumentException(String.format("Stereotype separator '@' shall not appear at the position %d in '%s'", atIndex, qualifiedCompleteName));
}
int nameLastChar = atIndex > 0 ? atIndex - 1 : qualifiedCompleteName.length() - 1;
if (qualifiedCompleteName.charAt(nameLastChar) == '.') {
throw new IllegalArgumentException("Name of the model shall not end with '.'");
}
return qualifiedCompleteName;
}
}