Permalink
Browse files

Implement Enums as objects, rather than strings

  • Loading branch information...
LadyCailin committed Nov 17, 2018
1 parent 7fbc5ca commit 75446ab467b17fa9f7258746e40664c8c0879e5c
@@ -7,6 +7,8 @@
import com.laytonsmith.PureUtilities.Common.StringUtils;
import com.laytonsmith.core.constructs.NativeTypeList;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.exceptions.CRE.CRECastException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -37,20 +39,28 @@ private FullyQualifiedClassName(String name) {
* will change, unlike forDefaultClass. So, when given user input, this method should always be used, and eventually
* when this method is changed, it will be a compile error, but if you know for sure it's a system class, you can
* use forDefaultClass instead, and there will be no code changes required in the future.
* @param unqualified
* @return
* @param unqualified The (potentially) unqualified type.
* @param t The code target.
* @return The fully qualified class name.
* @throws CRECastException If the class type can't be found
*/
public static FullyQualifiedClassName forName(String unqualified) {
return forDefaultClasses(unqualified);
public static FullyQualifiedClassName forName(String unqualified, Target t) throws CRECastException {
return forDefaultClasses(unqualified, t);
}
/**
* If the class is known for sure to be within the default import list, this method can be used.
* @param unqualified
* @return
* @param unqualified The (potentially) unqualified type.
* @param t The code target.
* @return The FullyQualifiedClassName.
* @throws CRECastException If the class type can't be found
*/
public static FullyQualifiedClassName forDefaultClasses(String unqualified) {
return new FullyQualifiedClassName(NativeTypeList.resolveNativeType(unqualified));
public static FullyQualifiedClassName forDefaultClasses(String unqualified, Target t) throws CRECastException {
String fqcn = NativeTypeList.resolveNativeType(unqualified);
if(fqcn == null) {
throw new CRECastException("Cannot find \"" + unqualified + "\" type", t);
}
return new FullyQualifiedClassName(fqcn);
}
/**
@@ -6,6 +6,10 @@
import com.laytonsmith.annotations.typeof;
import com.laytonsmith.core.FullyQualifiedClassName;
import com.laytonsmith.core.MSVersion;
import com.laytonsmith.core.exceptions.CRE.CREUnsupportedOperationException;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import com.laytonsmith.core.natives.interfaces.ArrayAccess;
import com.laytonsmith.core.natives.interfaces.MEnumType;
import com.laytonsmith.core.natives.interfaces.Mixed;
import java.util.Arrays;
import java.util.HashMap;
@@ -22,15 +26,26 @@
* A CClassType represent
*/
@typeof("ms.lang.ClassType")
public final class CClassType extends Construct {
public final class CClassType extends Construct implements ArrayAccess {
public static final String PATH_SEPARATOR = FullyQualifiedClassName.PATH_SEPARATOR;
private static final Map<FullyQualifiedClassName, CClassType> CACHE = new HashMap<>();
// The only types that can be created here are the ones that don't have a real class associated with them, or the
// TYPE value itself
@SuppressWarnings("FieldNameHidesFieldInSuperclass")
public static final CClassType TYPE = new CClassType("ms.lang.ClassType", Target.UNKNOWN);
public static final CClassType AUTO = new CClassType("auto", Target.UNKNOWN);
public static final CClassType VOID = new CClassType("void", Target.UNKNOWN);
public static final CClassType TYPE;
public static final CClassType AUTO;
static {
try {
TYPE = new CClassType("ms.lang.ClassType", Target.UNKNOWN);
AUTO = new CClassType("auto", Target.UNKNOWN);
} catch (ClassNotFoundException e) {
throw new Error(e);
}
}
/**
* This should generally be used instead of creating a new empty array in getInterfaces, if no interfaces are
@@ -45,6 +60,12 @@
private final boolean isTypeUnion;
private final FullyQualifiedClassName fqcn;
/**
* This is an invalid instance of the underlying type that can only be used for Documentation purposes or finding
* out meta information about the class. Because these can be a type union, this is an array.
*/
private final Mixed[] invalidType;
/**
* This *MUST* contain a list of non type union types.
*/
@@ -53,13 +74,20 @@
/**
* Returns the singular instance of CClassType that represents this type.
*
* <p>IMPORTANT: The type MUST be fully qualified, or this will cause errors. The only time this method is
* preferred vs {@link #get(com.laytonsmith.core.FullyQualifiedClassName)} is when used to define the TYPE value.
* <p>IMPORTANT: The type MUST be fully qualified AND exist as a real, instantiable class, or this will cause
* errors. The only time this method is preferred vs {@link #get(com.laytonsmith.core.FullyQualifiedClassName)} is
* when used to define the TYPE value.
*
* Unlike the other getters, this will not throw a ClassNotFoundException, it will instead throw an Error.
* @param type
* @return
*/
public static CClassType get(String type) {
return get(FullyQualifiedClassName.forFullyQualifiedClass(type));
try {
return get(FullyQualifiedClassName.forFullyQualifiedClass(type));
} catch(ClassNotFoundException ex) {
throw new Error(ex);
}
}
/**
@@ -68,7 +96,7 @@ public static CClassType get(String type) {
* @param type
* @return
*/
public static CClassType get(FullyQualifiedClassName type) {
public static CClassType get(FullyQualifiedClassName type) throws ClassNotFoundException {
assert type != null;
if(!CACHE.containsKey(type)) {
CACHE.put(type, new CClassType(type, Target.UNKNOWN));
@@ -86,7 +114,7 @@ public static CClassType get(FullyQualifiedClassName type) {
* @param types
* @return
*/
public static CClassType get(FullyQualifiedClassName... types) {
public static CClassType get(FullyQualifiedClassName... types) throws ClassNotFoundException {
SortedSet<FullyQualifiedClassName> t = new TreeSet<>(Arrays.asList(types));
FullyQualifiedClassName type
@@ -104,7 +132,7 @@ public static CClassType get(FullyQualifiedClassName... types) {
* @param types
* @return
*/
public static CClassType get(CClassType... types) {
public static CClassType get(CClassType... types) throws ClassNotFoundException {
return get(Stream.of(types)
.map(e -> e.getFQCN())
.sorted()
@@ -117,7 +145,7 @@ public static CClassType get(CClassType... types) {
* @param type This must be the fully qualified string name.
* @param t
*/
private CClassType(String type, Target t) {
private CClassType(String type, Target t) throws ClassNotFoundException {
this(FullyQualifiedClassName.forFullyQualifiedClass(type), t);
}
@@ -127,7 +155,7 @@ private CClassType(String type, Target t) {
* @param type
* @param t
*/
private CClassType(FullyQualifiedClassName type, Target t) {
private CClassType(FullyQualifiedClassName type, Target t) throws ClassNotFoundException {
super(type.getFQCN(), ConstructType.CLASS_TYPE, t);
isTypeUnion = type.isTypeUnion();
fqcn = type;
@@ -138,6 +166,15 @@ private CClassType(FullyQualifiedClassName type, Target t) {
} else {
types.add(type);
}
if("auto".equals(type.getFQCN())) {
invalidType = null;
} else {
// TODO: This must change once user types are introduced
invalidType = new Mixed[types.size()];
for(int i = 0; i < invalidType.length; i++) {
invalidType[i] = NativeTypeList.getInvalidInstanceForUse(fqcn);
}
}
}
@Override
@@ -262,6 +299,24 @@ public boolean unsafeIsExtendedBy(CClassType checkClass) {
return CClassType.EMPTY_CLASS_ARRAY;
}
/**
* Returns the superclasses for the underlying type, not the superclasses for ClassType itself.
* @return
*/
public CClassType[] getSuperclassesForType() {
return Stream.of(invalidType).flatMap(e -> Stream.of(e.getSuperclasses()))
.collect(Collectors.toSet()).toArray(CClassType.EMPTY_CLASS_ARRAY);
}
/**
* Returns the interfaces for the underlying type, not the interfaces for ClassType itself.
* @return
*/
public CClassType[] getInterfacesForType() {
return Stream.of(invalidType).flatMap(e -> Stream.of(e.getInterfaces()))
.collect(Collectors.toSet()).toArray(CClassType.EMPTY_CLASS_ARRAY);
}
/**
* Returns a set of individual types for this type. If it is a class union, multiple types will be returned in the
* set. Each of the CClassTypes within this set are guaranteed to not be a type union.
@@ -273,7 +328,12 @@ public boolean unsafeIsExtendedBy(CClassType checkClass) {
protected Set<CClassType> getTypes() {
Set<CClassType> t = new HashSet<>();
for(FullyQualifiedClassName type : types) {
t.add(CClassType.get(type));
try {
t.add(CClassType.get(type));
} catch(ClassNotFoundException ex) {
// This can't happen, because
throw new Error(ex);
}
}
return t;
}
@@ -317,4 +377,97 @@ public FullyQualifiedClassName getFQCN() {
return fqcn;
}
public boolean isEnum() {
if("ms.lang.enum".equals(fqcn.getFQCN())) {
// By default, this returns true when something is instanceof a thing, but in this case, we don't want
// that, because ironically, ms.lang.enum is itself not an enum.
return false;
}
return doesExtend(MEnumType.TYPE);
}
@Override
public CClassType typeof() {
return CClassType.TYPE;
}
// TODO: These getters will eventually be re-done to support static methods, but for now that is out of scope,
// so we just specifically support enums for now.
@Override
public Mixed get(String index, Target t) throws ConfigRuntimeException {
if(isEnum()) {
try {
return NativeTypeList.getNativeEnumType(fqcn).get(index, t);
} catch(ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
throw new CREUnsupportedOperationException("Unsupported operation", t);
}
@Override
public Mixed get(int index, Target t) throws ConfigRuntimeException {
if(isEnum()) {
try {
return NativeTypeList.getNativeEnumType(fqcn).get(index, t);
} catch(ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
throw new CREUnsupportedOperationException("Unsupported operation", t);
}
@Override
public Mixed get(Mixed index, Target t) throws ConfigRuntimeException {
if(isEnum()) {
try {
return NativeTypeList.getNativeEnumType(fqcn).get(index, t);
} catch(ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
throw new CREUnsupportedOperationException("Unsupported operation", t);
}
@Override
public Set<Mixed> keySet() {
if(isEnum()) {
try {
return NativeTypeList.getNativeEnumType(fqcn).keySet();
} catch(ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
return new HashSet<>();
}
@Override
public long size() {
if(isEnum()) {
try {
return NativeTypeList.getNativeEnumType(fqcn).size();
} catch(ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
return 0;
}
@Override
public boolean isAssociative() {
return true;
}
@Override
public boolean canBeAssociative() {
return true;
}
@Override
public Mixed slice(int begin, int end, Target t) {
throw new CREUnsupportedOperationException("Unsupported operation", t);
}
}
@@ -11,7 +11,7 @@
public final class CVoid extends Construct implements Cloneable {
@SuppressWarnings("FieldNameHidesFieldInSuperclass")
public static final CClassType TYPE = CClassType.VOID;
public static final CClassType TYPE = CClassType.get("void");
/**
* Void values do not normally need to be duplicated, since they are immutable, and for values that have an unknown
@@ -485,18 +485,23 @@ protected String getQuote() {
}
/**
* Returns the typeof this Construct, as a string. Not all constructs are annotated with the @typeof annotation, in
* which case this is considered a "private" object, which can't be directly accessed via MethodScript. In this
* Returns the typeof this Construct, as a CClassType. Not all constructs are annotated with the @typeof annotation,
* in which case this is considered a "private" object, which can't be directly accessed via MethodScript. In this
* case, an IllegalArgumentException is thrown.
*
* This method may be overridden in special cases, such as dynamic types, but for most types, this
* @return
* @throws IllegalArgumentException If the class isn't public facing.
*/
@Override
public final CClassType typeof() {
public CClassType typeof() {
return typeof(this);
}
/**
* Returns the typeof for the given class, using the same mechanism as the default. (Whether or not that subtype
* overrode the original typeof() method.
*/
public static CClassType typeof(Mixed that) {
typeof ann = that.getClass().getAnnotation(typeof.class);
if(ann == null) {
@@ -32,7 +32,7 @@ public IVariable(String name, Target t) throws ConfigCompileException {
public IVariable(CClassType type, String name, Mixed value, Target t) {
super(name, ConstructType.IVARIABLE, t);
if(!type.equals(Auto.TYPE) && !(value instanceof CNull)) {
if(!InstanceofUtil.isInstanceof(value, type.val())) {
if(!InstanceofUtil.isInstanceof(value, type)) {
throw new CRECastException(name + " is of type " + type.val() + ", but a value of type "
+ value.typeof() + " was assigned to it.", t);
}
Oops, something went wrong.

0 comments on commit 75446ab

Please sign in to comment.