blob: 742c7ecc083e05a6a1f8012bbd12bf61f6904cc1 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.r.core.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import com.ibm.icu.text.Collator;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.text.core.input.StringParserInput;
import org.eclipse.statet.ltk.core.ElementName;
import org.eclipse.statet.r.core.RSymbolComparator;
import org.eclipse.statet.r.core.data.RValueFormatter;
import org.eclipse.statet.r.core.rlang.RTerminal;
import org.eclipse.statet.r.core.rsource.RLexer;
/**
* Base class for R element names
*
* Defines type constants and provides static utility methods.
*/
@NonNullByDefault
public abstract class RElementName implements ElementName {
public static final int RESOURCE= 0x0_0f;
public static final int MAIN_OTHER= 0x0_10;
public static final int MAIN_DEFAULT= 0x0_11;
public static final int MAIN_CLASS= 0x0_15;
public static final int SUB_NAMEDSLOT= 0x0_19;
public static final int SUB_NAMEDPART= 0x0_1a;
public static final int SUB_INDEXED_S= 0x0_1b;
public static final int SUB_INDEXED_D= 0x0_1c;
public static final int SCOPE_NS= 0x0_21;
public static final int SCOPE_NS_INT= 0x0_22;
public static final int SCOPE_SEARCH_ENV= 0x0_25;
public static final int SCOPE_PACKAGE= 0x0_26;
public static final int SCOPE_SYSFRAME= 0x0_27;
public static final int SCOPE_PROJECT= 0x0_29;
public static final int ANONYMOUS= 0x0_30;
public static final int DISPLAY_FQN= 1 << 0;
public static final int DISPLAY_EXACT= 1 << 1;
public static boolean isMainType(final int type) {
return (type >= MAIN_OTHER && type < SUB_NAMEDSLOT);
}
public static boolean isRegularMainType(final int type) {
return (type > MAIN_OTHER && type < SUB_NAMEDSLOT);
}
public static boolean isScopeType(final int type) {
return ((type & 0x0_F0) == 0x0_20);
}
public static boolean isNamespaceScopeType(final int type) {
return (type >= SCOPE_NS && type < SCOPE_SEARCH_ENV);
}
public static boolean isSearchScopeType(final int type) {
return (type >= SCOPE_SEARCH_ENV && type < ANONYMOUS);
}
public static boolean isPackageFacetScopeType(final int type) {
switch (type) {
case SCOPE_NS:
case SCOPE_NS_INT:
case SCOPE_PACKAGE:
return true;
default:
return false;
}
}
/**
* Element names providing the exact index as number (for SUB_INDEXED_D).
*/
public static interface IndexElementName extends ElementName {
int getIndex();
}
@Deprecated // use #getDisplayName(int)
public static @Nullable String createDisplayName(RElementName elementName, final int options) {
if (elementName == null) {
throw new NullPointerException("elementName"); //$NON-NLS-1$
}
RValueFormatter sb= null;
if ((options & DISPLAY_FQN) != 0) {
RElementName scopeName= elementName.getScope();
if (scopeName == null && isScopeType(elementName.getType())) {
scopeName= elementName;
elementName= elementName.getNextSegment();
}
if (scopeName != null) {
if (elementName != null && elementName.getType() != MAIN_DEFAULT) {
return null;
}
sb= new RValueFormatter();
if (!printScopeFQ(scopeName, sb, options, elementName != null)) {
return null;
}
if (elementName != null) {
final String name= elementName.getSegmentName();
if (name != null) {
appendSymbol(sb, name);
}
elementName= elementName.getNextSegment();
}
}
}
if (sb == null) {
String firstName;
final int type= elementName.getType();
switch (type) {
case MAIN_DEFAULT:
case MAIN_CLASS:
case SUB_NAMEDPART:
case SUB_NAMEDSLOT:
firstName= elementName.getSegmentName();
if (firstName != null) {
sb= appendSymbol(sb, firstName);
}
else if ((options & DISPLAY_EXACT) == 0) {
firstName= ""; //$NON-NLS-1$
}
else {
return null;
}
elementName= elementName.getNextSegment();
if (elementName == null) {
return (sb != null) ? sb.getString() : firstName;
}
if (sb == null) {
sb= new RValueFormatter();
sb.append(firstName);
}
break;
case SCOPE_NS:
if (elementName.getNextSegment() == null) {
return printScopeUI("namespace:", elementName.getSegmentName(), options); //$NON-NLS-1$
}
else {
printScopeFQ(elementName, sb, options, true);
break;
}
case SCOPE_NS_INT:
if (elementName.getNextSegment() == null) {
return printScopeUI("namespace-env:", elementName.getSegmentName(), options); //$NON-NLS-1$
}
else {
printScopeFQ(elementName, sb, options, true);
break;
}
case SCOPE_SEARCH_ENV:
if (elementName.getNextSegment() == null) {
firstName= elementName.getSegmentName();
if (firstName != null) {
return firstName;
}
}
return null;
case SCOPE_SYSFRAME:
if (elementName.getNextSegment() == null) {
return printScopeUI("frame:", elementName.getSegmentName(), options); //$NON-NLS-1$
}
else {
return null;
}
case SCOPE_PACKAGE:
if (elementName.getNextSegment() == null) {
return printScopeUI("package:", elementName.getSegmentName(), options); //$NON-NLS-1$
}
else {
return null;
}
case SCOPE_PROJECT:
if (elementName.getNextSegment() == null) {
return printScopeUI("project:", elementName.getSegmentName(), options); //$NON-NLS-1$
}
else {
return null;
}
case SUB_INDEXED_D:
if (elementName instanceof DefaultImpl) {
sb= new RValueFormatter();
sb.append("[["); //$NON-NLS-1$
sb.append(elementName.getSegmentName());
sb.append("]]"); //$NON-NLS-1$
elementName= elementName.getNextSegment();
break;
}
return null;
case RESOURCE:
case MAIN_OTHER:
return elementName.getSegmentName();
case ANONYMOUS:
if ((options & DISPLAY_EXACT) == 0) {
return "<anonymous>"; //$NON-NLS-1$
}
else {
return null;
}
default:
return null;
}
}
APPEND_SUB : while (elementName != null) {
String name;
switch (elementName.getType()) {
case MAIN_DEFAULT:
case MAIN_CLASS:
case SUB_NAMEDPART:
if (((options & DISPLAY_EXACT) != 0) && elementName instanceof IndexElementName) {
sb.append("[["); //$NON-NLS-1$
sb.appendInt(((IndexElementName) elementName).getIndex());
sb.append("L]]"); //$NON-NLS-1$
}
else {
sb.append('$');
name= elementName.getSegmentName();
if (name != null) {
appendSymbol(sb, name);
}
}
elementName= elementName.getNextSegment();
continue APPEND_SUB;
case SUB_NAMEDSLOT:
sb.append('@');
name= elementName.getSegmentName();
if (name != null) {
appendSymbol(sb, name);
}
elementName= elementName.getNextSegment();
continue APPEND_SUB;
case SUB_INDEXED_S:
if (((options & DISPLAY_EXACT) == 0)) {
sb.append("[…]"); //$NON-NLS-1$
break APPEND_SUB;
}
else {
return null;
}
case SUB_INDEXED_D:
if (elementName instanceof DefaultImpl) {
sb.append("[["); //$NON-NLS-1$
sb.append(elementName.getSegmentName());
sb.append("]]"); //$NON-NLS-1$
elementName= elementName.getNextSegment();
continue APPEND_SUB;
}
else if ((options & DISPLAY_EXACT) == 0) {
sb.append("[[…]]"); //$NON-NLS-1$
elementName= elementName.getNextSegment();
continue APPEND_SUB;
}
else {
return null;
}
default:
if (((options & DISPLAY_EXACT) == 0)) {
sb.append(" …"); //$NON-NLS-1$
break APPEND_SUB;
}
return null;
}
}
return sb.getString();
}
private static boolean isValidSymbol(final String name) {
final int l= name.length();
if (l == 0) {
return false;
}
final char c0= name.charAt(0);
int check;
if (Character.isLetter(c0)) {
check= 1;
}
else if (c0 == '.') {
if (l == 1) {
check= 1;
}
else {
final char c1= name.charAt(1);
if (c1 == '.' || c1 == '_' || Character.isLetter(c1)) {
check= 2;
}
else {
return false;
}
}
}
else {
return false;
}
for (; check < l; check++) {
final char cn= name.charAt(check);
if ((cn < 'a' || cn > 'z') && cn != '.' && cn != '_' && !Character.isLetterOrDigit(cn)) {
return false;
}
}
return true;
}
private static @Nullable RValueFormatter appendSymbol(@Nullable RValueFormatter sb, final String name) {
if (isValidSymbol(name)) {
if (sb != null) {
sb.append(name);
}
return sb;
}
if (sb == null) {
sb= new RValueFormatter();
}
if (name.isEmpty()) {
sb.append("``"); //$NON-NLS-1$
}
else {
sb.appendName(name, true);
}
return sb;
}
private static @Nullable String printScopeUI(final String itemPrefix, @Nullable String segmentName,
final int options) {
if (segmentName == null) {
if ((options & DISPLAY_EXACT) == 0) {
segmentName= "<unknown>"; //$NON-NLS-1$
}
else {
return null;
}
}
final StringBuilder sb= new StringBuilder(itemPrefix.length() + segmentName.length());
sb.append(itemPrefix);
sb.append(segmentName);
return sb.toString();
}
private static boolean printScopeFQ(final RElementName a, RValueFormatter sb,
final int options, final boolean operator) {
final String scopeName;
switch (a.getType()) {
case SCOPE_NS:
scopeName= a.getSegmentName();
if (scopeName == null) {
return false;
}
if (operator) {
appendSymbol(sb, scopeName);
sb.append("::"); //$NON-NLS-1$
}
else {
return false;
}
return true;
case SCOPE_NS_INT:
scopeName= a.getSegmentName();
if (scopeName == null) {
return false;
}
if (operator) {
appendSymbol(sb, scopeName);
sb.append(":::"); //$NON-NLS-1$
}
else {
sb.append("getNamespace(\""); //$NON-NLS-1$
sb.append(scopeName);
sb.append("\")"); //$NON-NLS-1$
}
return true;
case SCOPE_SEARCH_ENV:
scopeName= a.getSegmentName();
if (scopeName == null) {
return false;
}
sb.append("as.environment(\""); //$NON-NLS-1$
sb.append(scopeName);
sb.append("\")"); //$NON-NLS-1$
if (operator) {
sb.append('$');
}
return true;
case SCOPE_PACKAGE:
scopeName= a.getSegmentName();
if (scopeName == null) {
return false;
}
sb.append("as.environment(\"package:"); //$NON-NLS-1$
sb.append(scopeName);
sb.append("\")"); //$NON-NLS-1$
if (operator) {
sb.append('$');
}
return true;
case SCOPE_SYSFRAME:
scopeName= a.getSegmentName();
if (scopeName == null) {
return false;
}
sb.append("sys.frame("); //$NON-NLS-1$
sb.append(scopeName);
sb.append(((options & DISPLAY_EXACT) != 0) ? "L)" : ")"); //$NON-NLS-1$ //$NON-NLS-2$
if (operator) {
sb.append('$');
}
return true;
case SCOPE_PROJECT:
sb= new RValueFormatter();
sb.append(".GlobalEnv"); //$NON-NLS-1$
if (operator) {
sb.append('$');
}
return true;
default:
return false;
}
}
private static final Collator NAME_COLLATOR= RSymbolComparator.R_NAMES_COLLATOR;
public static final Comparator<ElementName> NAMEONLY_COMPARATOR= new Comparator<ElementName>() {
@Override
public int compare(ElementName o1, ElementName o2) {
final String n1= o1.getSegmentName();
final String n2= o2.getSegmentName();
if (n1 != null) {
if (n2 != null) {
final int diff= NAME_COLLATOR.compare(n1, n2);
if (diff != 0) {
return diff;
}
}
else {
return Integer.MIN_VALUE;
}
}
else if (n2 != null) {
return Integer.MAX_VALUE;
}
o1= o1.getNextSegment();
o2= o2.getNextSegment();
if (o1 != null) {
if (o2 != null) {
final int diff= o1.getType() - o2.getType();
if (diff != 0) {
return diff;
}
return compare(o1, o2);
}
else {
return Integer.MIN_VALUE + 100;
}
}
else if (n2 != null) {
return Integer.MAX_VALUE - 100;
}
return 0;
}
};
private static boolean equals(final @Nullable String s1, final @Nullable String s2) {
return (s1 == s2)
|| (s1 != null && s2 != null && s1.hashCode() == s2.hashCode() && s1.equals(s2) );
}
private static class DefaultImpl extends RElementName implements Serializable {
private static final long serialVersionUID= 315497720879434929L;
private final int type;
private final @Nullable String segmentName;
private @Nullable RElementName scope;
private @Nullable RElementName nextSegment;
public DefaultImpl(final int type,
final @Nullable String segmentName) {
this.type= type;
this.segmentName= segmentName;
this.nextSegment= null;
}
public DefaultImpl(final int type, final @Nullable RElementName scope,
final @Nullable String segmentName,
final @Nullable RElementName next) {
this.type= type;
this.segmentName= segmentName;
this.scope= scope;
this.nextSegment= next;
}
public DefaultImpl(final int type,
final @Nullable String segmentName,
final @Nullable RElementName next) {
this.type= type;
this.segmentName= segmentName;
this.nextSegment= next;
}
@Override
public int getType() {
return this.type;
}
@Override
public @Nullable String getSegmentName() {
return this.segmentName;
}
@Override
public @Nullable RElementName getScope() {
return this.scope;
}
@Override
public @Nullable RElementName getNextSegment() {
return this.nextSegment;
}
}
private static class DualImpl extends DefaultImpl implements IndexElementName {
private static final long serialVersionUID= 7040207683623992047L;
private final int idx;
public DualImpl(final int type,
final @Nullable String segmentName, final int idx) {
super(type, segmentName);
this.idx= idx;
}
public DualImpl(final int type,
final @Nullable String segmentName, final int idx,
final @Nullable RElementName next) {
super(type, segmentName, next);
this.idx= idx;
}
@Override
protected boolean isDefaultImpl() {
return (getClass() == DualImpl.class);
}
@Override
protected DefaultImpl cloneSegment0(final @Nullable RElementName next) {
return new DualImpl(getType(), getSegmentName(), this.idx, next);
}
@Override
public int getIndex() {
return this.idx;
}
}
private static DefaultImpl checkSegment(final RElementName name, final @Nullable RElementName next) {
return (name.isDefaultImpl() && name.getNextSegment() == next) ?
(DefaultImpl) name :
name.cloneSegment0(next);
}
public static RElementName create(final int type, final @Nullable String segmentName) {
return new DefaultImpl(type, segmentName);
}
public static RElementName create(final int type, final @Nullable String segmentName,
final int idx) {
if (!(type == SUB_NAMEDPART || type == SUB_INDEXED_D)) {
throw new IllegalArgumentException();
}
return new DualImpl(type, segmentName, idx);
}
public static @Nullable RElementName create(final List<RElementName> segments) {
if (segments.isEmpty()) {
return null;
}
if (segments.size() == 1) {
return checkSegment(segments.get(0), null);
}
int first= 0;
RElementName scopeName= segments.get(0);
if (isScopeType(scopeName.getType())) {
scopeName= checkSegment(scopeName, null);
first= 1;
}
else {
scopeName= null;
}
RElementName next= null;
for (int i= segments.size() - 1; i > first; i--) {
next= checkSegment(segments.get(i), next);
}
next= new DefaultImpl(segments.get(first).getType(), scopeName,
segments.get(first).getSegmentName(), next );
return next;
}
/**
* Creates a copy of segments of the specified element. The copy starts with the first element
* of the element name and ends at the specified end segment (exclusive).
*
* @param name the element name to copy
* @param end the end segment (exlusive) or <code>null</code>, to copy the complete name
* @param withScope to include the scope in the copy, if available
* @return the copy of the element name
*/
public static @Nullable RElementName create(final @Nullable RElementName name,
final @Nullable RElementName end, final boolean withScope) {
if (name == null) {
return null;
}
final List<RElementName> segments= new ArrayList<>();
if (withScope && name.getScope() != null) {
segments.add(name.getScope());
}
addSegments(segments, name, end);
return create(segments);
}
private static final int PARSE_OP= -1;
private static final int PARSE_EXIT= -3;
public static @Nullable RElementName parseDefault(final String code) {
final RLexer lexer= new RLexer((RLexer.DEFAULT |
RLexer.SKIP_WHITESPACE | RLexer.SKIP_LINEBREAK | RLexer.SKIP_COMMENT ));
lexer.reset(new StringParserInput(code).init());
int mode= MAIN_DEFAULT;
DefaultImpl main= null;
DefaultImpl last= null;
while (mode != PARSE_EXIT) {
DefaultImpl tmp= null;
RTerminal type= lexer.next();
if (type == null || type == RTerminal.EOF) {
if (mode < 0) {
return main;
}
tmp= new DefaultImpl(mode, ""); //$NON-NLS-1$
mode= PARSE_EXIT;
}
else {
switch (type) {
case IF:
case ELSE:
case FOR:
case IN:
case WHILE:
case REPEAT:
case NEXT:
case BREAK:
case FUNCTION:
case TRUE:
case FALSE:
case NA:
case NA_INT:
case NA_REAL:
case NA_CPLX:
case NA_CHAR:
case NULL:
case NAN:
case INF:
if (mode != MAIN_DEFAULT
&& mode != SUB_NAMEDPART && mode != SUB_NAMEDSLOT) {
return null;
}
tmp= new DefaultImpl(mode, type.text);
type= lexer.next();
if (type != null && type != RTerminal.EOF) {
return null;
}
mode= PARSE_EXIT; // valid prefix
break;
case SYMBOL:
case SYMBOL_G:
if (mode != MAIN_DEFAULT
&& mode != SUB_NAMEDPART && mode != SUB_NAMEDSLOT) {
return null;
}
tmp= new DefaultImpl(mode, lexer.getText());
mode= PARSE_OP;
break;
case STRING_S:
case STRING_D:
if (mode != MAIN_DEFAULT
&& mode != SUB_NAMEDPART && mode != SUB_NAMEDSLOT) {
return null;
}
tmp= new DefaultImpl(mode, lexer.getText());
mode= PARSE_OP;
break;
case NUM_INT:
case NUM_NUM:
if (mode != SUB_INDEXED_S && mode != SUB_INDEXED_D) {
return null;
}
tmp= new DefaultImpl(mode, lexer.getText());
type= lexer.next();
if (type != RTerminal.SUB_INDEXED_CLOSE) {
return null;
}
if (mode == SUB_INDEXED_D) {
type= lexer.next();
if (type != RTerminal.SUB_INDEXED_CLOSE) {
return null;
}
}
mode= PARSE_OP;
break;
case SUB_NAMED_PART:
if (main == null || mode >= 0) {
return null;
}
mode= SUB_NAMEDPART;
continue;
case SUB_NAMED_SLOT:
if (main == null || mode >= 0) {
return null;
}
mode= SUB_NAMEDSLOT;
continue;
case SUB_INDEXED_S_OPEN:
if (main == null || mode >= 0) {
return null;
}
mode= SUB_INDEXED_S;
continue;
case SUB_INDEXED_D_OPEN:
if (main == null || mode >= 0) {
return null;
}
mode= SUB_INDEXED_D;
continue;
// case SUB_INDEXED_CLOSE:
// return null;
case NS_GET:
if (main == null || main != last || mode >= 0) {
return null;
}
if (main.getType() == MAIN_DEFAULT) {
main= new DefaultImpl(SCOPE_NS, main.getSegmentName());
}
else {
return null;
}
mode= MAIN_DEFAULT;
continue;
case NS_GET_INT:
if (main == null || main != last || mode >= 0) {
return null;
}
if (main.getType() == MAIN_DEFAULT) {
main= new DefaultImpl(SCOPE_NS_INT, main.getSegmentName());
}
else {
return null;
}
mode= MAIN_DEFAULT;
continue;
default:
return null;
}
}
if (main == null) {
main= last= tmp;
}
else if (isScopeType(main.getType())) {
tmp.scope= main;
main= last= tmp;
}
else {
last.nextSegment= tmp;
last= tmp;
}
}
return main;
}
/**
* Creates a copy of the specified element name.
*
* @param name the element name to copy
* @param withScope to include the scope in the copy, if available
* @return the copy of the element name
*/
public static @Nullable RElementName cloneName(@Nullable RElementName name,
final boolean withScope) {
if (name == null) {
return null;
}
RElementName scopeName= (withScope) ? name.getScope() : null;
if (scopeName != null) {
scopeName= new DefaultImpl(scopeName.getType(), scopeName.getSegmentName(), null);
}
final DefaultImpl main= new DefaultImpl(name.getType(), scopeName, name.getSegmentName(), null);
DefaultImpl last= main;
name= name.getNextSegment();
while (name != null) {
final DefaultImpl copy= name.cloneSegment0(null);
last.nextSegment= copy;
last= copy;
name= name.getNextSegment();
}
return main;
}
/**
* Creates a copy of the first segment of the specified element name.
*
* @param name the element name to copy
* @return the copy of the element name
*/
public static RElementName cloneSegment(final RElementName name) {
return name.cloneSegment0(null);
}
public static RElementName addScope(final RElementName name, final RElementName scope) {
if (!isScopeType(scope.getType())) {
throw new IllegalArgumentException("scope.type= " + scope.getType()); //$NON-NLS-1$
}
return new DefaultImpl(name.getType(),
checkSegment(scope, null),
name.getSegmentName(), name.getNextSegment() );
}
public static RElementName removeScope(final RElementName name) {
final RElementName scope= name.getScope();
if (scope == null) {
return name;
}
if (!isScopeType(scope.getType())) {
throw new IllegalArgumentException("scope.type= " + scope.getType()); //$NON-NLS-1$
}
return new DefaultImpl(name.getType(), scope, name.getSegmentName(), name.getNextSegment());
}
public static RElementName normalize(final RElementName name) {
if (name != null && name.getScope() == null && isScopeType(name.getType())
&& name.getNextSegment() != null) {
return addScope(name.getNextSegment(), name);
}
return name;
}
public static void addSegments(final Collection<? super RElementName> segments,
@Nullable RElementName name) {
while (name != null) {
segments.add(name);
name= name.getNextSegment();
}
}
public static void addSegments(final Collection<? super RElementName> segments,
@Nullable RElementName name, final @Nullable RElementName end) {
while (name != null && name != end) {
segments.add(name);
name= name.getNextSegment();
}
}
protected RElementName() {
}
public abstract @Nullable RElementName getScope();
@Override
public abstract @Nullable RElementName getNextSegment();
@Override
public RElementName getLastSegment() {
@NonNull RElementName lastSegment;
RElementName nextSegment= this;
do {
lastSegment= nextSegment;
} while ((nextSegment= nextSegment.getNextSegment()) != null);
return lastSegment;
}
@Override
public String getDisplayName() {
return createDisplayName(this, 0);
}
public @Nullable String getDisplayName(final int options) {
return createDisplayName(this, options);
}
@Override
public int[] correctDisplayNameRegions(final int[] regions, final int offset) {
final String segmentName= getSegmentName();
if (segmentName != null && !isValidSymbol(segmentName)) {
new RValueFormatter().correctNameRegions(regions, segmentName, true, offset);
}
return regions;
}
protected boolean isDefaultImpl() {
return (getClass() == DefaultImpl.class);
}
protected RElementName.DefaultImpl cloneSegment0(final @Nullable RElementName next) {
return new DefaultImpl(getType(), getSegmentName(), next);
}
@Override
public final int hashCode() {
final String name= getSegmentName();
final ElementName next= getNextSegment();
if (next != null) {
return getType() * ((name != null) ? name.hashCode() : 1) * (next.hashCode() + 7);
}
else {
return getType() * ((name != null) ? name.hashCode() : 1);
}
}
@Override
public final boolean equals(final @Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof RElementName) {
final ElementName other= (RElementName) obj;
return ((getType() == other.getType())
&& equals(getSegmentName(), other.getSegmentName())
&& Objects.equals(getNextSegment(), other.getNextSegment()) );
}
return false;
}
@Override
public String toString() {
return getDisplayName();
}
}