package com.siberika.idea.pascal.lang.psi.impl;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.siberika.idea.pascal.lang.psi.PasEntityScope;
import com.siberika.idea.pascal.lang.psi.PasTypeDecl;
import com.siberika.idea.pascal.lang.psi.PascalNamedElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
/**
* Author: George Bakhtadze
* Date: 14/09/2013
*/
public class PasField {
public static final String DUMMY_IDENTIFIER = "____;";
public enum FieldType {UNIT, TYPE, VARIABLE, CONSTANT, ROUTINE, PROPERTY, PSEUDO_VARIABLE}
public enum Kind {BOOLEAN, POINTER, INTEGER, FLOAT, CHAR, STRING, SET, STRUCT, CLASSREF, FILE, PROCEDURE, ENUM, SUBRANGE, ARRAY}
public static final Set<FieldType> TYPES_ALL = EnumSet.allOf(FieldType.class);
public static final Set<FieldType> TYPES_LEFT_SIDE = EnumSet.of(FieldType.UNIT, FieldType.VARIABLE, FieldType.PSEUDO_VARIABLE, FieldType.PROPERTY, FieldType.ROUTINE);
public static final Set<FieldType> TYPES_TYPE = EnumSet.of(FieldType.TYPE);
public static final Set<FieldType> TYPES_TYPE_UNIT = EnumSet.of(FieldType.UNIT, FieldType.TYPE);
public static final Set<FieldType> TYPES_ROUTINE = EnumSet.of(FieldType.ROUTINE);
public static final Set<FieldType> TYPES_PROPERTY_SPECIFIER = EnumSet.of(FieldType.ROUTINE, FieldType.VARIABLE);
public static final Set<FieldType> TYPES_STRUCTURE = EnumSet.of(FieldType.TYPE, FieldType.VARIABLE, FieldType.CONSTANT, FieldType.PROPERTY, FieldType.ROUTINE);
public static final Set<FieldType> TYPES_STATIC = EnumSet.of(FieldType.UNIT, FieldType.TYPE, FieldType.CONSTANT, FieldType.ROUTINE);
public static final Set<FieldType> TYPES_LOCAL = EnumSet.of(FieldType.VARIABLE, FieldType.PROPERTY, FieldType.ROUTINE, FieldType.PSEUDO_VARIABLE);
public enum Visibility {INTERNAL, STRICT_PRIVATE, PRIVATE, STRICT_PROTECTED, PROTECTED, PUBLIC, PUBLISHED, AUTOMATED}
public static final List<String> VISIBILITY_STR = Arrays.asList("INTERNAL", "STRICT PRIVATE", "PRIVATE", "STRICT PROTECTED", "PROTECTED", "PUBLIC", "PUBLISHED", "AUTOMATED");
public static final Map<String, Visibility> VISIBILITY_MAP = getVisibilityMap();
public static final ValueType INTEGER = new ValueType(null, Kind.INTEGER, null, null);
public static final ValueType FLOAT = new ValueType(null, Kind.FLOAT, null, null);
public static final ValueType STRING = new ValueType(null, Kind.STRING, null, null);
public static final ValueType BOOLEAN = new ValueType(null, Kind.BOOLEAN, null, null);
public static final ValueType POINTER = new ValueType(null, Kind.POINTER, null, null);
private static final ValueType NOT_INITIALIZED = new ValueType(null, null, null, null);
private static Map<String, Visibility> getVisibilityMap() {
Map<String, Visibility> res = new HashMap<String, Visibility>();
res.put("INTERNAL", Visibility.INTERNAL);
res.put("STRICT PRIVATE", Visibility.STRICT_PRIVATE);
res.put("PRIVATE", Visibility.PRIVATE);
res.put("STRICT PROTECTED", Visibility.STRICT_PROTECTED);
res.put("PROTECTED", Visibility.PROTECTED);
res.put("PUBLIC", Visibility.PUBLIC);
res.put("PUBLISHED", Visibility.PUBLISHED);
res.put("AUTOMATED", Visibility.AUTOMATED);
return Collections.unmodifiableMap(res);
}
public static boolean isAllowed(Visibility check, Visibility minAllowed) {
return check.compareTo(minAllowed) >= 0;
}
@Nullable
public final PasEntityScope owner;
@Nullable
private final SmartPsiElementPointer<PascalNamedElement> element;
public final String name;
public final FieldType fieldType;
@NotNull
public final Visibility visibility;
public int offset;
// Reference target if different from element
@Nullable
public final PsiElement target;
private ValueType valueType;
private final int cachedHash;
private ReentrantLock typeLock = new ReentrantLock();
public PasField(@Nullable PasEntityScope owner, @Nullable PascalNamedElement element, String name, FieldType fieldType,
@NotNull Visibility visibility, @Nullable PsiElement target, ValueType valueType) {
this.owner = owner;
Project project = element != null ? element.getProject() : null;
this.element = project != null ? SmartPointerManager.getInstance(project).createSmartPsiElementPointer(element) : null;
this.name = name;
this.fieldType = fieldType;
this.visibility = visibility;
this.offset = element != null ? element.getTextRange().getStartOffset() : 0;
this.target = target;
this.valueType = valueType;
this.cachedHash = updateHashCode();
}
public PasField(@Nullable PasEntityScope owner, @Nullable PascalNamedElement element, String name, FieldType fieldType,
@NotNull Visibility visibility, ValueType valueType) {
this(owner, element, name, fieldType, visibility, null, valueType);
}
public PasField(@Nullable PasEntityScope owner, @Nullable PascalNamedElement element, String name, FieldType fieldType,
@NotNull Visibility visibility) {
this(owner, element, name, fieldType, visibility, null, NOT_INITIALIZED);
}
@Nullable
public PascalNamedElement getElement() {
return element != null ? element.getElement() : null;
}
@Nullable
public SmartPsiElementPointer<PascalNamedElement> getElementPtr() {
return element;
}
@Override
public String toString() {
return visibility + " " + fieldType + ": " + (owner != null ? owner.getName() : "-") + "." + name + ", " + getElement();
}
@Override
public int hashCode() {
return cachedHash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PasField pasField = (PasField) o;
if (owner != null ? !owner.equals(pasField.owner) : pasField.owner != null) return false;
if (name != null ? !name.equals(pasField.name) : pasField.name != null) return false;
if (fieldType != pasField.fieldType) return false;
if (visibility != pasField.visibility) return false;
if (getElement() != null ? !getElement().equals(pasField.getElement()) : pasField.getElement() != null) return false;
return true;
}
private int updateHashCode() {
int result = owner != null ? owner.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (fieldType != null ? fieldType.hashCode() : 0);
result = 31 * result + visibility.hashCode();
PascalNamedElement el = getElement();
result = 31 * result + (el != null ? el.hashCode() : 0);
return result;
}
public boolean isTypeResolved() {
return (valueType != NOT_INITIALIZED);
}
public boolean isInteger() {
return (getValueType() != null) && (valueType.kind == Kind.INTEGER);
}
public boolean isFloat() {
return (getValueType() != null) && (valueType.kind == Kind.FLOAT);
}
public boolean isNumeric() {
return isInteger() || isFloat();
}
public ValueType getValueType() {
return valueType;
}
public void setValueType(ValueType valueType) {
this.valueType = valueType;
}
public static ValueType getValueType(String name) {
Kind kind = getKindByName(name);
if (null == kind) {
return null;
}
switch (kind) {
case BOOLEAN:
return BOOLEAN;
case INTEGER:
return INTEGER;
case FLOAT:
return FLOAT;
case POINTER:
return POINTER;
case STRING:
return STRING;
default:
return null;
}
}
private static Kind getKindByName(String value) {
for (Kind kind : Kind.values()) {
if (kind.name().equalsIgnoreCase(value)) {
return kind;
}
}
return null;
}
public static class ValueType {
// referenced type field (TRefType in type TValueType = TRefType)
public PasField field;
// additional information about type
public Kind kind;
// base type (TBaseType in TRefType = array of TBaseType)
public ValueType baseType;
// type declaration element
public SmartPsiElementPointer<PasTypeDecl> declaration;
public ValueType(PasField field, Kind kind, ValueType baseType, PasTypeDecl declaration) {
this.field = field;
this.kind = kind;
this.baseType = baseType;
this.declaration = declaration != null ? SmartPointerManager.getInstance(declaration.getProject()).createSmartPsiElementPointer(declaration) : null;
}
@Override
public String toString() {
return String.format("%s: %s (%s)", field != null ? field.name : "<anon>",
kind != null ? kind.name() : "-",
baseType != null ? (" of " + baseType.toString()) : "-");
}
// Searches all type chain for structured type
@Nullable
public PasEntityScope getTypeScope() { //TODO: resolve unresolved types
ValueType type = this;
while (type.baseType != null) {
type = type.baseType;
}
PasTypeDecl typeDecl = type.declaration != null ? type.declaration.getElement() : null;
if ((typeDecl != null) && (typeDecl.getFirstChild() instanceof PasEntityScope)) {
return (PasEntityScope) typeDecl.getFirstChild();
}
return null;
}
}
public ReentrantLock getTypeLock() {
return typeLock;
}
}