Permalink
Browse files

Fix some bugs with class type system

  • Loading branch information...
LadyCailin committed Nov 17, 2018
1 parent 75446ab commit fbc415c4c736add93648d3fed66e23575726caf8
@@ -1,6 +1,7 @@
package com.laytonsmith.core.constructs;
import com.laytonsmith.PureUtilities.Version;
import com.laytonsmith.annotations.MEnum;
import com.laytonsmith.annotations.typeof;
import com.laytonsmith.core.CHLog;
import com.laytonsmith.core.MSVersion;
@@ -826,7 +827,8 @@ public Version since() {
}
}
public enum SortType {
@MEnum("ms.lang.ArraySortType")
public enum ArraySortType {
/**
* Sorts the elements without converting types first. If a non-numeric string is compared to a numeric string,
* it is compared as a string, otherwise, it's compared as a natural ordering.
@@ -846,7 +848,7 @@ public Version since() {
STRING_IC
}
public void sort(final SortType sort) {
public void sort(final ArraySortType sort) {
if(this.associativeMode) {
array = new ArrayList(associativeArray.values());
this.associativeArray.clear();
@@ -26,6 +26,7 @@
* A CClassType represent
*/
@typeof("ms.lang.ClassType")
@SuppressWarnings("checkstyle:overloadmethodsdeclarationorder")
public final class CClassType extends Construct implements ArrayAccess {
public static final String PATH_SEPARATOR = FullyQualifiedClassName.PATH_SEPARATOR;
@@ -60,11 +61,17 @@
private final boolean isTypeUnion;
private final FullyQualifiedClassName fqcn;
/**
* Used to differentiate between null and uninitialized.
*/
private static final Mixed[] UNINITIALIZED = new Mixed[0];
/**
* 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.
*
* DO NOT USE THIS VALUE WITHOUT FIRST CALLING {@link #instantiateInvalidType()}
*/
private final Mixed[] invalidType;
private Mixed[] invalidType = UNINITIALIZED;
/**
* This *MUST* contain a list of non type union types.
@@ -85,7 +92,7 @@
public static CClassType get(String type) {
try {
return get(FullyQualifiedClassName.forFullyQualifiedClass(type));
} catch(ClassNotFoundException ex) {
} catch (ClassNotFoundException ex) {
throw new Error(ex);
}
}
@@ -155,26 +162,83 @@ private CClassType(String type, Target t) throws ClassNotFoundException {
* @param type
* @param t
*/
@SuppressWarnings("ConvertToStringSwitch")
private CClassType(FullyQualifiedClassName type, Target t) throws ClassNotFoundException {
super(type.getFQCN(), ConstructType.CLASS_TYPE, t);
isTypeUnion = type.isTypeUnion();
fqcn = type;
if(isTypeUnion) {
// Split them out
types.addAll(Stream.of(type.getFQCN().split("|"))
.map(e -> FullyQualifiedClassName.forFullyQualifiedClass(e)).collect(Collectors.toList()));
types.addAll(Stream.of(type.getFQCN().split("\\|"))
.map(e -> FullyQualifiedClassName.forFullyQualifiedClass(e.trim())).collect(Collectors.toList()));
} 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);
boolean found = false;
String localFQCN = fqcn.getFQCN();
if(localFQCN.equals("auto") || localFQCN.equals("ms.lang.ClassType")) {
// If we get here, we are within this class, and calling resolveNativeType won't work,
// but anyways, we know we exist, so mark it as found. It is important to note, however,
// if we end up defining more magic types within this class, this block needs to be updated.
found = true;
}
// Do this to ensure at construction time that the class really does exist. We can't actually construct
// the instance yet, because this might be the stack for the TYPE assignment, which means that this class
// is not initialized yet. See the docs for instantiateInvalidType().
// This works because we assume that resolveNativeTypes only uses the ClassMirror system. If that assumption
// changes, we will need to basically re-implement that ourselves.
if(!found) {
if(isTypeUnion) {
// For type unions, we need to find all component parts
boolean foundAllTypeUnion = true;
for(FullyQualifiedClassName c : types) {
if(null == NativeTypeList.resolveNativeType(c.getFQCN())) {
foundAllTypeUnion = false;
break;
}
}
if(foundAllTypeUnion) {
found = true;
}
} else {
found = null != NativeTypeList.resolveNativeType(fqcn.getFQCN());
}
}
// TODO: When user types are added, we will need to do some more digging here
if(!found) {
throw new ClassNotFoundException("Could not find class of type " + type);
}
}
/**
* While we would prefer to instantiate invalidType in the constructor, we can't, because this initializes the type,
* which occurs first when TYPE is initialized, that is, before the class is valid. Therefore, we cannot actually
* do that in the constructor, we need to lazy load it. We do take pains in the constructor to ensure that there is
* at least no way this will throw a ClassCastException, so given that, we are able to supress that exception here.
*/
private void instantiateInvalidType() {
if(invalidType != UNINITIALIZED) {
return;
}
@SuppressWarnings("LocalVariableHidesMemberVariable")
String fqcn = this.fqcn.getFQCN();
try {
if("auto".equals(fqcn)) {
invalidType = null;
} else if("ms.lang.ClassType".equals(fqcn)) {
invalidType = new Mixed[]{this};
} 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(this.fqcn);
}
}
} catch (ClassNotFoundException ex) {
throw new Error(ex);
}
}
@Override
@@ -304,6 +368,7 @@ public boolean unsafeIsExtendedBy(CClassType checkClass) {
* @return
*/
public CClassType[] getSuperclassesForType() {
instantiateInvalidType();
return Stream.of(invalidType).flatMap(e -> Stream.of(e.getSuperclasses()))
.collect(Collectors.toSet()).toArray(CClassType.EMPTY_CLASS_ARRAY);
}
@@ -313,6 +378,7 @@ public boolean unsafeIsExtendedBy(CClassType checkClass) {
* @return
*/
public CClassType[] getInterfacesForType() {
instantiateInvalidType();
return Stream.of(invalidType).flatMap(e -> Stream.of(e.getInterfaces()))
.collect(Collectors.toSet()).toArray(CClassType.EMPTY_CLASS_ARRAY);
}
@@ -330,7 +396,7 @@ public boolean unsafeIsExtendedBy(CClassType checkClass) {
for(FullyQualifiedClassName type : types) {
try {
t.add(CClassType.get(type));
} catch(ClassNotFoundException ex) {
} catch (ClassNotFoundException ex) {
// This can't happen, because
throw new Error(ex);
}
@@ -398,7 +464,7 @@ public Mixed get(String index, Target t) throws ConfigRuntimeException {
if(isEnum()) {
try {
return NativeTypeList.getNativeEnumType(fqcn).get(index, t);
} catch(ClassNotFoundException ex) {
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
@@ -410,7 +476,7 @@ public Mixed get(int index, Target t) throws ConfigRuntimeException {
if(isEnum()) {
try {
return NativeTypeList.getNativeEnumType(fqcn).get(index, t);
} catch(ClassNotFoundException ex) {
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
@@ -422,7 +488,7 @@ public Mixed get(Mixed index, Target t) throws ConfigRuntimeException {
if(isEnum()) {
try {
return NativeTypeList.getNativeEnumType(fqcn).get(index, t);
} catch(ClassNotFoundException ex) {
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
@@ -434,7 +500,7 @@ public Mixed get(Mixed index, Target t) throws ConfigRuntimeException {
if(isEnum()) {
try {
return NativeTypeList.getNativeEnumType(fqcn).keySet();
} catch(ClassNotFoundException ex) {
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
@@ -446,7 +512,7 @@ public long size() {
if(isEnum()) {
try {
return NativeTypeList.getNativeEnumType(fqcn).size();
} catch(ClassNotFoundException ex) {
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
@@ -1,40 +1,16 @@
package com.laytonsmith.core.constructs;
import com.laytonsmith.PureUtilities.Common.ClassUtils;
import com.laytonsmith.annotations.typeof;
import com.laytonsmith.core.FullyQualifiedClassName;
import com.laytonsmith.core.Static;
import com.laytonsmith.core.natives.interfaces.Mixed;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* This class checks "instanceof" for native MethodScript objects, unlike the java "instanceof" keyword.
*/
public class InstanceofUtil {
/**
* Returns true whether or not a given MethodScript value is an instance of the specified MethodScript type.
*
* @param value The value to check for
* @param instanceofThis The string type to check. This must be the fully qualified name.
* @return
*/
public static boolean isInstanceof(Mixed value, FullyQualifiedClassName instanceofThis) {
Static.AssertNonNull(instanceofThis, "instanceofThis may not be null");
if(instanceofThis.getFQCN().equals("auto")) {
return true;
}
for(CClassType c : getAllCastableClasses(value.typeof())) {
FullyQualifiedClassName typeof = c.getFQCN();
if(typeof != null && typeof.equals(instanceofThis)) {
return true;
}
}
return false;
}
/**
* Returns a list of all classes that the specified class can be validly cast to. This includes all super classes,
* as well as all interfaces (and superclasses of those interfaces, etc) and java.lang.Object, as well as the class
@@ -63,19 +39,38 @@ public static boolean isInstanceof(Mixed value, FullyQualifiedClassName instance
blacklist.add(c);
try {
for(CClassType s : c.getSuperclassesForType()) {
blacklist.add(s);
blacklist.addAll(getAllCastableClassesWithBlacklist(s, blacklist));
}
for(CClassType iface : c.getInterfacesForType()) {
blacklist.add(iface);
blacklist.addAll(getAllCastableClassesWithBlacklist(iface, blacklist));
}
} catch(UnsupportedOperationException ex) {
} catch (UnsupportedOperationException ex) {
// This is a phantom class, which is allowed
}
return blacklist;
}
/**
* Returns true whether or not a given MethodScript value is an instance of the specified MethodScript type.
*
* @param value The value to check for
* @param instanceofThis The string type to check. This must be the fully qualified name.
* @return
*/
public static boolean isInstanceof(Mixed value, FullyQualifiedClassName instanceofThis) {
Static.AssertNonNull(instanceofThis, "instanceofThis may not be null");
if(instanceofThis.getFQCN().equals("auto")) {
return true;
}
for(CClassType c : getAllCastableClasses(value.typeof())) {
FullyQualifiedClassName typeof = c.getFQCN();
if(typeof != null && typeof.equals(instanceofThis)) {
return true;
}
}
return false;
}
/**
* Returns whether or not a given MethodScript value is an instanceof the specified MethodScript type.
* @param value
@@ -71,6 +71,9 @@ public static String resolveNativeType(String simpleName) {
* @return
*/
public static Set<FullyQualifiedClassName> getNativeTypeList() {
// NOTE: We have to be incredibly careful here to not actually load the underlying classes, because
// this method is used very early in the startup process, and loading the classes may interfere with
// the bootstrapping process.
@SuppressWarnings("LocalVariableHidesMemberVariable")
Set<String> nativeTypes = NativeTypeList.nativeTypes;
if(nativeTypes == null) {
@@ -84,9 +87,10 @@ public static String resolveNativeType(String simpleName) {
// in production, this should actually be redundant.
ClassDiscovery.getDefaultInstance()
.addDiscoveryLocation(ClassDiscovery.GetClassContainer(Mixed.class));
for(ClassMirror<? extends Mixed> c : ClassDiscovery.getDefaultInstance()
.getClassesWithAnnotationThatExtend(typeof.class, Mixed.class)) {
nativeTypes.add(c.loadAnnotation(typeof.class).value());
nativeTypes.add((String) c.getAnnotation(typeof.class).getValue("value"));
}
for(ClassMirror<? extends Enum> c : ClassDiscovery.getDefaultInstance()
Oops, something went wrong.

0 comments on commit fbc415c

Please sign in to comment.