diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java index 061c3ea35..c3666b80d 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java @@ -44,7 +44,7 @@ public static void checkForTypeInTypeofClasses() throws Exception { } } catch (ReflectionUtils.ReflectionException ex) { errors.add(clazz.getClassName() + " needs to add the following:\n\t@SuppressWarnings(\"FieldNameHidesFieldInSuperclass\")\n" - + "\tpublic static final CClassType TYPE = CClassType.get(\"" + clazz.getAnnotation(typeof.class).getValue("value") + "\");"); + + "\tpublic static final CClassType TYPE = CClassType.get(" + clazz.getSimpleName() + ".class);"); } } if(!errors.isEmpty()) { diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/CheckOverrides.java b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/CheckOverrides.java index 31eafdf0b..2136d73d7 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/CheckOverrides.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/CheckOverrides.java @@ -64,6 +64,8 @@ public boolean process(Set annotations, RoundEnvironment } if(c != null) { if(!c.isInterface()) { + StreamUtils.GetSystemErr().println("Only interfaces may be annotated with " + + MustUseOverride.class.getName()); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Only interfaces may be annotated with " + MustUseOverride.class.getName()); } @@ -240,6 +242,7 @@ public String toString(Class item) { .append(" with @Override to continue the build process.") .append(StringUtils.NL) .append(StringUtils.Join(stringMethodsInError, StringUtils.NL)); + StreamUtils.GetSystemErr().println(b.toString()); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, b.toString()); } else { StreamUtils.GetSystemOut().println("No @Override annotations were found to be missing."); diff --git a/src/main/java/com/laytonsmith/core/Method.java b/src/main/java/com/laytonsmith/core/Method.java deleted file mode 100644 index 53085d48b..000000000 --- a/src/main/java/com/laytonsmith/core/Method.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.laytonsmith.core; - -import com.laytonsmith.PureUtilities.Version; -import com.laytonsmith.annotations.typeof; -import com.laytonsmith.core.constructs.CClassType; -import com.laytonsmith.core.constructs.Construct; -import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.environments.Environment; -import com.laytonsmith.core.exceptions.CancelCommandException; -import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.exceptions.ProgramFlowManipulationException; -import com.laytonsmith.core.natives.interfaces.Mixed; -import java.util.Arrays; - -/** - * A method is the foundational class for containing information about a method defined in a class. These may include - * interfaces, where the method isn't actually defined, abstract methods, and - */ -@typeof("ms.lang.Method") -public class Method extends Construct implements Callable { - - @SuppressWarnings("FieldNameHidesFieldInSuperclass") - public static final CClassType TYPE = CClassType.get(Method.class); - - private final CClassType returnType; - private final String name; - private final CClassType[] parameters; - private final ParseTree tree; - - public Method(Target t, CClassType returnType, String name, CClassType[] parameters, ParseTree tree) { - super(returnType + " " + name + " " + Arrays.toString(parameters), ConstructType.FUNCTION, t); - this.returnType = returnType; - this.name = name; - this.parameters = parameters; - this.tree = tree; - } - - @Override - public Mixed executeCallable(Environment env, Target t, Mixed... values) - throws ConfigRuntimeException, ProgramFlowManipulationException, CancelCommandException { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public boolean isDynamic() { - return false; - } - - @Override - public String docs() { - return "A Method is not instantiatable in the traditional sense, however, a Method can" - + " be defined within a class. This class represents that definition."; - } - - @Override - public CClassType[] getInterfaces() { - return CClassType.EMPTY_CLASS_ARRAY; - } - - @Override - public CClassType[] getSuperclasses() { - return new CClassType[]{Mixed.TYPE}; - } - - @Override - public Version since() { - return MSVersion.V3_3_4; - } - - public CClassType[] getParameters() { - return parameters; - } - - public String getMethodName() { - return name; - } - - public CClassType getReturnType() { - return returnType; - } - - public ParseTree getTree() { - return tree; - } - -} diff --git a/src/main/java/com/laytonsmith/core/constructs/CFunction.java b/src/main/java/com/laytonsmith/core/constructs/CFunction.java index 4504f4d8b..e42570c9d 100644 --- a/src/main/java/com/laytonsmith/core/constructs/CFunction.java +++ b/src/main/java/com/laytonsmith/core/constructs/CFunction.java @@ -3,7 +3,9 @@ import com.laytonsmith.PureUtilities.Common.ReflectionUtils; import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.functions.DryFunction; import com.laytonsmith.core.functions.Function; import com.laytonsmith.core.functions.FunctionBase; import com.laytonsmith.core.functions.FunctionList; @@ -82,6 +84,48 @@ public static boolean IsFunction(ParseTree tree, Class ofTyp return IsFunction(tree.getData(), ofType); } + /** + * Checks to see if a ParseTree represents a DryFunction or not. + * @param data + * @return + */ + public static boolean CanDryEval(ParseTree data) { + if(!(data.getData() instanceof CFunction)) { + return false; + } + try { + FunctionBase fb = FunctionList.getFunction((CFunction) data.getData()); + if(fb instanceof DryFunction) { + return true; + } else { + return false; + } + } catch (ConfigCompileException ex) { + return false; + } + } + + /** + * Evaluates a DryFunction, or if this is a primitive Construct, simply returns that. + * + * @param env The environment + * @param data The ParseTree to evaluate. + * @return The Mixed that this function evaluates to + * @throws ConfigCompileException If the function execution throws a CCE + * @throws IllegalArgumentException If the underlying function doesn't represent a DryFunction + */ + public static Mixed evaluateDryFunction(Environment env, ParseTree data) throws ConfigCompileException { + if(!(data.getData() instanceof CFunction) && data.getData() instanceof Construct + && data.getChildren().isEmpty()) { + return data.getData(); + } + if(!CanDryEval(data)) { + throw new IllegalArgumentException("Data (" + data.getData() + ") does not contain a DryFunction"); + } + DryFunction f = (DryFunction) FunctionList.getFunction((CFunction) data.getData()); + return f.dryExec(data.getTarget(), env, data.getChildren().toArray(new ParseTree[data.getChildren().size()])); + } + @Override public Version since() { return super.since(); diff --git a/src/main/java/com/laytonsmith/core/constructs/Construct.java b/src/main/java/com/laytonsmith/core/constructs/Construct.java index a695d401d..357a4a717 100644 --- a/src/main/java/com/laytonsmith/core/constructs/Construct.java +++ b/src/main/java/com/laytonsmith/core/constructs/Construct.java @@ -41,7 +41,7 @@ public enum ConstructType { TOKEN, COMMAND, FUNCTION, VARIABLE, LITERAL, ARRAY, MAP, ENTRY, INT, DOUBLE, BOOLEAN, NULL, STRING, VOID, IVARIABLE, CLOSURE, LABEL, SLICE, SYMBOL, IDENTIFIER, BRACE, BRACKET, BYTE_ARRAY, RESOURCE, LOCK, MUTABLE_PRIMITIVE, - CLASS_TYPE, FULLY_QUALIFIED_CLASS_NAME; + CLASS_TYPE, FULLY_QUALIFIED_CLASS_NAME, METHOD, FIELD; } private final ConstructType ctype; diff --git a/src/main/java/com/laytonsmith/core/functions/DryFunction.java b/src/main/java/com/laytonsmith/core/functions/DryFunction.java new file mode 100644 index 000000000..09fa2b115 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/DryFunction.java @@ -0,0 +1,20 @@ +package com.laytonsmith.core.functions; + +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.natives.interfaces.Mixed; + +/** + * A DryFunction is a function that can be invoked at all times, even during compilation, and cannot rely on any other + * devices other than other DryFunctions and primitives. These functions are typically compiled out, but create a more + * complex data type at compile time. Essentially, it provides a method that looks nearly identical to execs, but does + * not accept a Script parameter. + * + * Generally speaking, this is not strictly necessary to implement, because functions that useSpecialExec and then + * don't use the Script parameter are functionally equivalent. However, implementing this interface, then simply + * having execs call dryExec is a good indication that this function should not be used in such a way to violate the + * contract. + */ +public interface DryFunction { + Mixed dryExec(Target t, com.laytonsmith.core.environments.Environment env, ParseTree... nodes); +} diff --git a/src/main/java/com/laytonsmith/core/functions/ObjectManagement.java b/src/main/java/com/laytonsmith/core/functions/ObjectManagement.java index b292b5454..a6c1c2306 100644 --- a/src/main/java/com/laytonsmith/core/functions/ObjectManagement.java +++ b/src/main/java/com/laytonsmith/core/functions/ObjectManagement.java @@ -27,6 +27,7 @@ import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; import com.laytonsmith.core.exceptions.CRE.CREClassDefinitionError; +import com.laytonsmith.core.exceptions.CRE.CREError; import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigCompileGroupException; @@ -36,6 +37,8 @@ import com.laytonsmith.core.objects.AccessModifier; import com.laytonsmith.core.objects.DuplicateObjectDefintionException; import com.laytonsmith.core.objects.ElementDefinition; +import com.laytonsmith.core.objects.ElementModifier; +import com.laytonsmith.core.objects.FieldDefinition; import com.laytonsmith.core.objects.ObjectDefinition; import com.laytonsmith.core.objects.ObjectDefinitionNotFoundException; import com.laytonsmith.core.objects.ObjectDefinitionTable; @@ -44,12 +47,9 @@ import com.laytonsmith.core.objects.UserObject; import java.util.ArrayList; import java.util.EnumSet; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; /** * @@ -60,6 +60,90 @@ public static String docs() { + " all of them provide easier to use compiler support."; } + /** + * We can't use parent in the execs function, because we are expected to work at compile time. But + * that also means we can't use dynamic elements, like array. Though that is ok, so long as the + * array is hardcoded. But it also means we have to manually compile the array. + * @param data + * @param t + * @return + */ + private static Mixed evaluateArray(ParseTree data, Target t) { + if(data.getData() instanceof CNull) { + return CNull.NULL; + } + CArray n = new CArray(t); + if(!(data.getData() instanceof CFunction) || !data.getData().val().equals("array")) { + throw new CREClassDefinitionError("Expected array, but found " + data.getData() + " instead", t); + } + for(ParseTree child : data.getChildren()) { + if(child.isDynamic()) { + throw new CREClassDefinitionError("Dynamic elements may not be used in a class definition", t); + } + n.push(child.getData(), t); + } + return n; + } + + private static CArray evaluateArrayNoNull(ParseTree data, Target t) { + Mixed d = evaluateArray(data, t); + if(d instanceof CNull) { + throw new CREClassDefinitionError("Unexpected null value, expected an array", t); + } + return (CArray) d; + } + + private static CArray evaluateArrayNullIsEmpty(ParseTree data, Target t) { + Mixed d = evaluateArray(data, t); + if(d instanceof CNull) { + return new CArray(t); + } + return (CArray) d; + } + + /** + * This expects an array of functions. The data itself is not executed, but returned + * as a list of parse trees, which can then be evaluated. If the value is null, an empty + * array is returned. + * @param data + * @param t + * @return + */ + private static List evaluateCArrayParseTrees(ParseTree data, Target t) { + if(data.getData() instanceof CNull) { + return new ArrayList<>(); + } + if(!CFunction.IsFunction(data, DataHandling.array.class)) { + throw new CREClassDefinitionError("Expected an array, but found " + data.getData().typeof(), t); + } + return data.getChildren(); + } + + private static Mixed evaluateString(ParseTree data, Target t) { + if(data.getData() instanceof CNull) { + return CNull.NULL; + } + if(!(data.getData().isInstanceOf(CString.class))) { + throw new CREClassDefinitionError("Expected a string, but found " + data.getData() + " instead", t); + } + return data.getData(); + } + private static CString evaluateStringNoNull(ParseTree data, Target t) { + Mixed d = evaluateString(data, t); + if(d instanceof CNull) { + throw new CREClassDefinitionError("Expected a string, but found null instead", t); + } + return (CString) d; + } + + private static Mixed evaluateMixed(ParseTree data, Target t) { + if(data.isDynamic()) { + throw new CREClassDefinitionError("Expected a non-dynamic value, but " + data.getData() + + " was found.", t); + } + return data.getData(); + } + @api @hide("Not ready for consumption by mortals yet.") public static class dereference extends AbstractFunction { @@ -108,7 +192,7 @@ public Version since() { @api @hide("Not meant for normal use") - public static class define_object extends AbstractFunction implements Optimizable { + public static class define_object extends AbstractFunction implements Optimizable, DryFunction { @Override public Class[] thrown() { @@ -135,74 +219,20 @@ public Mixed exec(Target t, Environment environment, Mixed... args) throws Confi throw new Error(); } - /** - * We can't use parent in the execs function, because we are expected to work at compile time. But - * that also means we can't use dynamic elements, like array. Though that is ok, so long as the - * array is hardcoded. But it also means we have to manually compile the array. - * @param data - * @param t - * @return - */ - private Mixed evaluateArray(ParseTree data, Target t) { - if(data.getData() instanceof CNull) { - return CNull.NULL; - } - CArray n = new CArray(t); - if(!(data.getData() instanceof CFunction) || !data.getData().val().equals("array")) { - throw new CREClassDefinitionError("Expected array, but found " + data.getData() + " instead", t); - } - for(ParseTree child : data.getChildren()) { - if(child.isDynamic()) { - throw new CREClassDefinitionError("Dynamic elements may not be used in a class definition", t); - } - n.push(child.getData(), t); - } - return n; - } - - private CArray evaluateArrayNoNull(ParseTree data, Target t) { - Mixed d = evaluateArray(data, t); - if(d instanceof CNull) { - throw new CREClassDefinitionError("Unexpected null value, expected an array", t); - } - return (CArray) d; - } - - private Mixed evaluateString(ParseTree data, Target t) { - if(data.getData() instanceof CNull) { - return CNull.NULL; - } - if(!(data.getData().isInstanceOf(CString.class))) { - throw new CREClassDefinitionError("Expected a string, but found " + data.getData() + " instead", t); - } - return data.getData(); - } - private CString evaluateStringNoNull(ParseTree data, Target t) { - Mixed d = evaluateString(data, t); - if(d instanceof CNull) { - throw new CREClassDefinitionError("Expected a string, but found null instead", t); - } - return (CString) d; - } - - private Mixed evaluateMixed(ParseTree data, Target t) { - if(data.isDynamic()) { - throw new CREClassDefinitionError("Expected a non-dynamic value, but " + data.getData() - + " was found.", t); - } - return data.getData(); + @Override + public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes) { + return dryExec(t, env, nodes); } @Override - public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes) { + public Mixed dryExec(Target t, Environment env, ParseTree... nodes) { // 0 - Access Modifier AccessModifier accessModifier = ArgumentValidation.getEnum(evaluateStringNoNull(nodes[0], t), AccessModifier.class, t); // 1 - Object Modifiers - Set objectModifiers = evaluateArrayNoNull(nodes[1], t).asList().stream() - .map((item) -> ArgumentValidation.getEnum(item, ObjectModifier.class, t)) - .collect(Collectors.toSet()); + Set objectModifiers = ArgumentValidation.getEnumSet(evaluateArrayNoNull(nodes[1], t), + ObjectModifier.class, t); // 2 - Object Type ObjectType type = ArgumentValidation.getEnum(evaluateStringNoNull(nodes[2], t), ObjectType.class, t); @@ -259,9 +289,25 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes) // TODO } - // 7 - mapelement> - Map> elementDefinitions = new HashMap<>(); - // TODO + // 7 - array + List elementDefinitions = new ArrayList<>(); + for(ParseTree p : evaluateCArrayParseTrees(nodes[7], t)) { + Mixed m; + try { + m = CFunction.evaluateDryFunction(env, p); + } catch (ConfigCompileException ex) { + throw new CREClassDefinitionError("Could not compile element definition: " + ex.getMessage(), + ex.getTarget(), ex); + } catch (IllegalArgumentException ex) { + throw new CREClassDefinitionError("Could not compile element definition, invalid" + + " function used.", + p.getTarget(), ex); + } + if(!(m instanceof ElementDefinition)) { + throw new CREClassDefinitionError("Unexpected item sent as part of element definition.", t); + } + elementDefinitions.add((ElementDefinition) m); + } // 8 - array List annotations = new ArrayList<>(); @@ -304,13 +350,16 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes) // class is unconstructable (i.e. static utility class) it must define a private constructor, // and take care never to call it internally. if(objectModifiers.contains(ObjectModifier.NATIVE)) { - if(elementDefinitions.get("").isEmpty()) { - throw new CREClassDefinitionError(name + " was defined as a native class, but did not define" - + " any constructors. Native classes do not get a default constructor, and so must" - + " explicitely define at least one. (It may have no arguments and point to an" - + " @ExposedProperty constructor in the native code, however.) At least one" - + " native constructor must be defined, and called during construction.", t); - } + // TODO Need to check if native classes properly implement default constructor +// for(ElementDefinition d : elementDefinitions) { +// } +// if(elementDefinitions.get("").isEmpty()) { +// throw new CREClassDefinitionError(name + " was defined as a native class, but did not define" +// + " any constructors. Native classes do not get a default constructor, and so must" +// + " explicitely define at least one. (It may have no arguments and point to an" +// + " @ExposedProperty constructor in the native code, however.) At least one" +// + " native constructor must be defined, and called during construction.", t); +// } // TODO check if the non-native constructors actually call one of the native ones } @@ -461,9 +510,9 @@ public Mixed execs(final Target t, final Environment env, Script parent, ParseTr constructor = null; break; case UNDECIDEABLE: - for(ElementDefinition ed : od.getElements().get("")) { - // TODO - } +// for(ElementDefinition ed : od.getElements().get("")) { +// // TODO +// } constructor = null; // TODO REMOVE ME break; default: @@ -516,24 +565,24 @@ public ParseTree optimizeDynamic(Target t, Environment env, List chil // really didn't exist. throw new Error(ex); } - List constructors = od.getElements().get(""); - int id; - if(constructors == null || constructors.isEmpty()) { - // Default constructor - if(children.size() > 1) { - throw new ConfigCompileException("No suitable constructor found for " + fqcn + " only the default" - + " constructor is available.", t); - } - id = DEFAULT; - } else { - // Need to find the correct constructor from the list - int parameterCount = children.size() - 1; - for(ElementDefinition d : constructors) { - // TODO - } - id = UNDECIDEABLE; - } - children.add(1, new ParseTree(new CInt(id, t), fileOptions)); +// List constructors = od.getElements().get(""); +// int id; +// if(constructors == null || constructors.isEmpty()) { +// // Default constructor +// if(children.size() > 1) { +// throw new ConfigCompileException("No suitable constructor found for " + fqcn + " only the default" +// + " constructor is available.", t); +// } +// id = DEFAULT; +// } else { +// // Need to find the correct constructor from the list +// int parameterCount = children.size() - 1; +// for(ElementDefinition d : constructors) { +// // TODO +// } +// id = UNDECIDEABLE; +// } +// children.add(1, new ParseTree(new CInt(id, t), fileOptions)); return null; } @@ -566,4 +615,119 @@ public Set optimizationOptions() { } } + + @api + @hide("Not for normal use") + public static class create_field extends AbstractFunction implements Optimizable, DryFunction { + + @Override + public Class[] thrown() { + return new Class[]{}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public FieldDefinition execs(Target t, Environment env, Script parent, ParseTree... nodes) { + return dryExec(t, env, nodes); + } + + @Override + public boolean useSpecialExec() { + return true; + } + + @Override + public FieldDefinition dryExec(Target t, Environment env, ParseTree... nodes) { + AccessModifier accessModifier = ArgumentValidation.getEnum(evaluateStringNoNull(nodes[0], t), + AccessModifier.class, t); + Set elementModifiers = ArgumentValidation.getEnumSet(evaluateArrayNoNull(nodes[1], t), + ElementModifier.class, t); + UnqualifiedClassName unqualifiedType; + { + Mixed m = evaluateMixed(nodes[2], t); + if(m instanceof CClassType) { + unqualifiedType = new UnqualifiedClassName(((CClassType) m).getFQCN()); + } else { + unqualifiedType = new UnqualifiedClassName(m.val(), t); + } + } + + String name = evaluateString(nodes[3], t).val(); + + if(!name.startsWith("@")) { + throw new CREError("Invalid variable name definition, must start with '@'.", t); + } + + ParseTree defaultValue = nodes[4]; + + String signature = (accessModifier + " " + StringUtils.Join(elementModifiers, " ") + " " + + unqualifiedType.getUnqualifiedClassName() + " " + + name).replaceAll(" +", " "); + + FieldDefinition ed = new FieldDefinition( + accessModifier, + elementModifiers, + unqualifiedType, + name, + defaultValue, + signature, + t); + return ed; + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + FieldDefinition f = execs(t, env, null, children.toArray(new ParseTree[children.size()])); + return new ParseTree(f, fileOptions); + } + + + + @Override + public Mixed exec(Target t, Environment environment, Mixed... nodes) throws ConfigRuntimeException { + throw new Error(); + } + + @Override + public String getName() { + return "create_field"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } + + @Override + public String docs() { + return "Field {" + + "AccessModifier accessModifier," + + "array elementModifiers," + + "UnqualifiedClassName type," + + "string name," + + "ParseTree defaultValue" + + "} Creates a field."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC); + } + + } } diff --git a/src/main/java/com/laytonsmith/core/objects/Element.java b/src/main/java/com/laytonsmith/core/objects/Element.java new file mode 100644 index 000000000..5f6845536 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/Element.java @@ -0,0 +1,232 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.UnqualifiedClassName; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Construct; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.natives.interfaces.Mixed; +import java.util.Objects; +import java.util.Set; + +/** + * + */ +@typeof("ms.lang.Element") +public abstract class Element extends Construct { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(Element.class); + + private final AccessModifier accessModifier; + private final Set elementModifiers; + private CClassType definedIn; + private CClassType type; + private final UnqualifiedClassName unqualifiedType; + private final String name; + private final Target t; + + private final ParseTree tree; + private java.lang.reflect.Method nativeMethod = null; + private java.lang.reflect.Field nativeField = null; + + /** + * Constructs a new element definition. If this is a native method or field, + * you must also call {@link #setNativeField(java.lang.reflect.Field)} or + * {@link #setNativeMethod(java.lang.reflect.Method)} immediately after construction. + * @param definition The element definition this instance is based on. + * @param definedIn The class that this element is defined in. + * @throws NullPointerException If one of the required fields is null. + * @throws IllegalArgumentException If both defaultValue and method are non-null. + */ + public Element(ElementDefinition definition, CClassType definedIn) { + super(definition.getSignature(), definition.getCType(), definition.getTarget()); + Objects.requireNonNull(definition); + Objects.requireNonNull(definedIn); + + this.accessModifier = definition.getAccessModifier(); + this.elementModifiers = definition.getElementModifiers(); + this.unqualifiedType = definition.getUCN(); + this.name = definition.getElementName(); + this.tree = definition.getTree(); + this.t = definition.getTarget(); + this.definedIn = definedIn; + } + + /** + * Qualifies the type. Must be called before {@link #getType()} can be used. + * @param env + * @throws java.lang.ClassNotFoundException + */ + public void qualifyType(Environment env) throws ClassNotFoundException { + this.type = CClassType.get(unqualifiedType.getFQCN(env)); + } + + /** + * If the underlying class is a native class, the actual Method can be provided here. No + * checks are done at this point, but it MUST be true that the method return type is castable to {@link Mixed}, as + * well as all arguments. + * @param m + */ + public void setNativeMethod(java.lang.reflect.Method m) { + this.nativeMethod = m; + } + + /** + * If the underlying class is a native class, the actual Field can be provided here. No + * checks are done at this point, but it MUST be true that the field type is castable to {@link Mixed}. + * @param f + */ + public void setNativeField(java.lang.reflect.Field f) { + this.nativeField = f; + } + + /** + * Returns true if the underlying item is a reference to a native class. + * @return + */ + public boolean isNative() { + return this.isNativeMethod() || this.isNativeField(); + } + + /** + * Returns true if the underlying item is a reference to a native class method. + * @return + */ + public boolean isNativeMethod() { + return this.nativeMethod != null; + } + + /** + * Returns true if the underlying item is a reference to a native class field. + * @return + */ + public boolean isNativeField() { + return this.nativeField != null; + } + + /** + * The access modifier of the element. + * @return + */ + @Override + public AccessModifier getAccessModifier() { + return accessModifier; + } + + /** + * The element modifiers. + * @return + */ + public Set getElementModifiers() { + return elementModifiers; + } + + /** + * The type of the element. For methods, this is the return type. + * @return + */ + public CClassType getType() { + if(type == null) { + throw new Error("qualifyType must be called before getType can be used"); + } + return type; + } + + public CClassType getDefinedIn() { + if(definedIn == null) { + throw new Error("qualifyType must be called before getDefinedIn can be used"); + } + return definedIn; + } + + /** + * Returns the unqualified type of this object. This is never an error to call, unlike {@link #getType()}. + * @return + */ + public UnqualifiedClassName getUCN() { + return unqualifiedType; + } + + /** + * The name of the element. + * @return + */ + public String getElementName() { + return name; + } + + @Override + public Target getTarget() { + return t; + } + + /** + * The default value of the element. This will be {@link CNull#UNDEFINED} if this was a property of the class with + * no assignment at all, and {@link CNull#NULL} if it was defined as null. For methods, this will be the method + * code itself. + *

+ * Because this is the prototype of the element, we can't simply define this as a Mixed, we need + * to evaluate the prototypical value when we instantiate the object. Therefore, the ParseTree is stored here. For + * atomic values, it's ok to just pull them out and use them, but for others, you must invoke the ParseTree to get + * the value. + *

+ * Iff this is a native type, this will this return (java) null, though that fact should not be relied on, + * use {@link #isNative()} to determine that for sure. + * @return + */ + public ParseTree getTree() { + return tree; + } + + /** + * If this is a native class, and it represents a method, this should return a reference to the Java Method. + * It will return null if this is not a native class, but that + * fact should not be relied on, use {@link #isNative()} to determine that for sure. + * @return + */ + public java.lang.reflect.Method getNativeMethod() { + return nativeMethod; + } + + /** + * If this is a native class, and it represents a field, this should return a reference to the Java Field. + * It will return null if this is not a native class, but + * that fact should not be relied on, use {@link #isNative()} to determine that for sure. + * @return + */ + public java.lang.reflect.Field getNativeField() { + return nativeField; + } + + @Override + public boolean isDynamic() { + return false; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Mixed.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } + + @Override + public String docs() { + return "An Element is a value that is at the top level of a class, for instance, a field, or a method."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + +} diff --git a/src/main/java/com/laytonsmith/core/objects/ElementDefinition.java b/src/main/java/com/laytonsmith/core/objects/ElementDefinition.java index 0298ffb80..d019348e3 100644 --- a/src/main/java/com/laytonsmith/core/objects/ElementDefinition.java +++ b/src/main/java/com/laytonsmith/core/objects/ElementDefinition.java @@ -1,8 +1,14 @@ package com.laytonsmith.core.objects; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.UnqualifiedClassName; import com.laytonsmith.core.constructs.CClassType; import com.laytonsmith.core.constructs.CNull; +import com.laytonsmith.core.constructs.Construct; +import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.natives.interfaces.Mixed; import java.lang.reflect.Field; import java.util.Objects; @@ -13,18 +19,29 @@ * they are handled the same in most ways, this superclass can safely represent both. For methods, the defaultValue * should always be provided, a Callable. There is special support for native types, in which a Method/Field is * provided. + * + * In general, an ElementDefinition is not useable, until it is converted into an Element, which is the same as the + * definition, but includes a {@code definedIn} property. This is set and created when the field is actually associated + * with a class definition, which happens later. A free floating element definition is otherwise useless, however. */ -public class ElementDefinition { +@typeof("ms.lang.ElementDefinition") +public abstract class ElementDefinition extends Construct { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(ElementDefinition.class); + private final AccessModifier accessModifier; private final Set elementModifiers; - private final CClassType definedIn; - private final CClassType type; + private CClassType type; + private final UnqualifiedClassName unqualifiedType; private final String name; + private final Target t; - private final ParseTree defaultValue; + private final String signature; + + private final ParseTree tree; private java.lang.reflect.Method nativeMethod = null; private Field nativeField = null; - private final com.laytonsmith.core.Method method; /** * Constructs a new element definition. If this is a native method or field, @@ -32,46 +49,42 @@ public class ElementDefinition { * {@link #setNativeMethod(java.lang.reflect.Method)} immediately after construction. * @param accessModifier The access modifier of the element * @param elementModifiers The modifiers of the element - * @param definedIn The class that this element is defined in * @param type The type of the element (variable type for fields, return type * for methods, java null for constructors) * @param name The name of the element (should start with @ if this is a * variable declaration). - * @param defaultValue The default value, if this is a field, and null if this + * @param tree The default value, if this is a field, and null if this * is a method. If the default value is MethodScript null, or is not set, you MUST * send either {@link CNull#NULL} or {@link CNull#UNDEFINED}, rather than java * null. - * @param method The method, if this is a method. + * @param signature The signature, which serves as the "toString" of this element. + * @param constructType The ConstructType + * @param t The code target where this element is defined in. * @throws NullPointerException If one of the required fields is null * @throws IllegalArgumentException If both defaultValue and method are non-null. */ public ElementDefinition( AccessModifier accessModifier, Set elementModifiers, - CClassType definedIn, - CClassType type, + UnqualifiedClassName type, String name, - ParseTree defaultValue, - com.laytonsmith.core.Method method + ParseTree tree, + String signature, + ConstructType constructType, + Target t ) { + super(signature, constructType, t); Objects.requireNonNull(accessModifier); Objects.requireNonNull(elementModifiers); Objects.requireNonNull(name); - if(defaultValue == null && method == null) { - throw new NullPointerException("Either defaultValue must be" - + " set, or method must be set."); - } - if(defaultValue != null && method != null) { - throw new IllegalArgumentException("Both default value and" - + " method cannot be set, one must be null."); - } + + this.signature = signature; this.accessModifier = accessModifier; this.elementModifiers = elementModifiers; - this.definedIn = definedIn; - this.type = type; + this.unqualifiedType = type; this.name = name; - this.defaultValue = defaultValue; - this.method = method; + this.tree = tree; + this.t = t; } /** @@ -121,6 +134,7 @@ public boolean isNativeField() { * The access modifier of the element. * @return */ + @Override public AccessModifier getAccessModifier() { return accessModifier; } @@ -138,20 +152,37 @@ public Set getElementModifiers() { * @return */ public CClassType getType() { + if(type == null) { + throw new Error("qualifyType must be called before getType can be used"); + } return type; } + /** + * Returns the unqualified type of this object. This is never an error to call, unlike {@link #getType()}. + * @return + */ + public UnqualifiedClassName getUCN() { + return unqualifiedType; + } + /** * The name of the element. * @return */ - public String getName() { + public String getElementName() { return name; } + @Override + public Target getTarget() { + return t; + } + /** * The default value of the element. This will be {@link CNull#UNDEFINED} if this was a property of the class with - * no assignment at all, and {@link CNull#NULL} if it was defined as null. For methods, this will be java null. + * no assignment at all, and {@link CNull#NULL} if it was defined as null. For methods, this will be the method + * code itself. *

* Because this is the prototype of the element, we can't simply define this as a Mixed, we need * to evaluate the prototypical value when we instantiate the object. Therefore, the ParseTree is stored here. For @@ -162,8 +193,8 @@ public String getName() { * use {@link #isNative()} to determine that for sure. * @return */ - public ParseTree getDefaultValue() { - return defaultValue; + public ParseTree getTree() { + return tree; } /** @@ -186,13 +217,40 @@ public Field getNativeField() { return nativeField; } + @Override + public boolean isDynamic() { + return false; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Mixed.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } + + @Override + public String docs() { + return "An ElementDefinition is an intermediate step before creating an Element. This should not be used."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + public String getSignature() { + return signature; + } + /** - * Returns the MethodScript Method reference. If this is a field, this - * will be null. + * Creates a concrete type, based on this defintion, which requires the CClassType. + * @param definedIn * @return */ - public com.laytonsmith.core.Method getMethod() { - return method; - } + public abstract Element createConcreteType(CClassType definedIn); } diff --git a/src/main/java/com/laytonsmith/core/objects/Field.java b/src/main/java/com/laytonsmith/core/objects/Field.java new file mode 100644 index 000000000..518254973 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/Field.java @@ -0,0 +1,43 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; + +/** + * + */ +@typeof("ms.lang.Field") +public class Field extends Element { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(Field.class); + + public Field(FieldDefinition definition, CClassType definedIn) { + super(definition, definedIn); + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public String docs() { + return "Represents a Field defined within a class."; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Element.TYPE}; + } + + + +} diff --git a/src/main/java/com/laytonsmith/core/objects/FieldDefinition.java b/src/main/java/com/laytonsmith/core/objects/FieldDefinition.java new file mode 100644 index 000000000..7c803e1f1 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/FieldDefinition.java @@ -0,0 +1,68 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.UnqualifiedClassName; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; +import java.util.Set; + +/** + * A Field is a representation of a concrete field within an object definition. + */ +@typeof("ms.lang.FieldDefinition") +public class FieldDefinition extends ElementDefinition { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(FieldDefinition.class); + + public FieldDefinition( + AccessModifier accessModifier, + Set elementModifiers, + UnqualifiedClassName type, + String name, + ParseTree code, + String signature, + Target t + ) { + super( + accessModifier, + elementModifiers, + type, + name, + code, + signature, + ConstructType.FIELD, + t + ); + } + + @Override + public Element createConcreteType(CClassType definedIn) { + return new Field(this, definedIn); + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public String docs() { + return "Creates a FieldDefinition. This is only useful as an intermediary step, and should never be" + + " used directly."; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{ElementDefinition.TYPE}; + } + +} diff --git a/src/main/java/com/laytonsmith/core/objects/ObjectDefinition.java b/src/main/java/com/laytonsmith/core/objects/ObjectDefinition.java index e4ebc84c1..fe0c6253f 100644 --- a/src/main/java/com/laytonsmith/core/objects/ObjectDefinition.java +++ b/src/main/java/com/laytonsmith/core/objects/ObjectDefinition.java @@ -16,10 +16,10 @@ import com.laytonsmith.core.natives.interfaces.Commentable; import com.laytonsmith.core.natives.interfaces.MAnnotation; import com.laytonsmith.core.natives.interfaces.Mixed; +import java.util.ArrayList; import java.util.EnumSet; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; @@ -50,7 +50,7 @@ public class ObjectDefinition implements Commentable { private final Set interfaces; private final CClassType containingClass; private final Target definitionTarget; - private final Map> properties; + private final List properties; private final SmartComment classComment; private final List genericParameters; private final Class nativeClass; @@ -62,7 +62,7 @@ public ObjectDefinition(AccessModifier accessModifier, Set objec CClassType type, Set superclasses, Set interfaces, CClassType containingClass, Target t, - Map> properties, List annotations, + List properties, List annotations, SmartComment classComment, List genericParameters, Class nativeClass) { this.accessModifier = accessModifier; this.objectModifiers = objectModifiers; @@ -72,7 +72,10 @@ public ObjectDefinition(AccessModifier accessModifier, Set objec this.interfaces = interfaces; this.containingClass = containingClass; this.definitionTarget = t; - this.properties = properties; + this.properties = new ArrayList<>(); + for(ElementDefinition d : properties) { + this.properties.add(d.createConcreteType(type)); + } this.annotations = annotations; this.classComment = classComment; this.genericParameters = genericParameters; @@ -205,6 +208,14 @@ public void qualifyClasses(Environment env) throws ConfigCompileGroupException { ucn.getTarget(), ex)); } } + for(Element element : properties) { + try { + element.qualifyType(env); + } catch (ClassNotFoundException ex) { + uhohs.add(new ConfigCompileException("Could not find " + element.getUCN(), + element.getTarget(), ex)); + } + } if(!uhohs.isEmpty()) { throw new ConfigCompileGroupException(uhohs); } @@ -254,7 +265,7 @@ public Target getDefinitionTarget() { return definitionTarget; } - public Map> getElements() { + public List getElements() { return properties; } diff --git a/src/main/java/com/laytonsmith/core/objects/UserObject.java b/src/main/java/com/laytonsmith/core/objects/UserObject.java index 34e23686a..9fbff25ae 100644 --- a/src/main/java/com/laytonsmith/core/objects/UserObject.java +++ b/src/main/java/com/laytonsmith/core/objects/UserObject.java @@ -3,7 +3,6 @@ import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.core.Documentation; import com.laytonsmith.core.MSVersion; -import com.laytonsmith.core.Method; import com.laytonsmith.core.Script; import com.laytonsmith.core.constructs.CClassType; import com.laytonsmith.core.constructs.CNull; @@ -52,40 +51,51 @@ public UserObject(Target t, Script parent, Environment env, ObjectDefinition obj this.nativeObject = nativeObject; this.objectId = objectIdCounter++; this.fieldTable = new HashMap<>(); - for(Map.Entry> e : objectDefinition.getElements().entrySet()) { - // Fields can only have one element definition, so if the list contains more than one, it is - // certainly a method. - if(e.getValue().size() > 1) { - continue; - } - ElementDefinition ed = e.getValue().get(0); - if(ed.getMethod() != null) { + for(Element e : objectDefinition.getElements()) { + if(!(e instanceof Field)) { continue; } + Field f = (Field) e; + if(parent == null) { - fieldTable.put(e.getKey(), CNull.UNDEFINED); + fieldTable.put(f.getElementName(), CNull.UNDEFINED); } else { - Mixed value = parent.eval(ed.getDefaultValue(), env); - fieldTable.put(e.getKey(), value); + Mixed value = parent.eval(f.getTree(), env); + fieldTable.put(f.getElementName(), value); } } } +// private Method toStringMethod = null; + private Boolean useDefaultToString = null; + @Override public String val() { - List toStrings = this.objectDefinition.getElements().get("toString"); - if(toStrings != null) { - for(ElementDefinition ed : toStrings) { - Method m = ed.getMethod(); - if(m != null) { - if(m.getParameters().length == 0) { - return m.executeCallable(env, t).val(); - } + if(useDefaultToString == null) { + List elements = this.objectDefinition.getElements(); + for(Element e : elements) { + if(!e.getElementName().equals("toString")) { + continue; } +// if(e instanceof Method) { +// Method m = (Method) e; +// if(m.getParameters().length == 0) { +// toStringMethod = m; +// useDefaultToString = false; +// break; +// } +// } + } + if(useDefaultToString == null) { + useDefaultToString = true; } } - // Use the default toString - return objectDefinition.getClassName() + "@" + String.format("0x%X", objectId); +// if(useDefaultToString) { + // Use the default toString + return objectDefinition.getClassName() + "@" + String.format("0x%X", objectId); +// } else { +// return toStringMethod.executeCallable(env, t).val(); +// } } @Override diff --git a/src/main/resources/siteDeploy/resources/images/CommandHelper_Icon2000x2000.png b/src/main/resources/siteDeploy/resources/images/CommandHelper_Icon2000x2000.png new file mode 100644 index 000000000..ab3f03e08 Binary files /dev/null and b/src/main/resources/siteDeploy/resources/images/CommandHelper_Icon2000x2000.png differ diff --git a/src/main/resources/siteDeploy/resources/images/commandhelper_icon.gif b/src/main/resources/siteDeploy/resources/images/commandhelper_icon.gif new file mode 100644 index 000000000..d83c48aed Binary files /dev/null and b/src/main/resources/siteDeploy/resources/images/commandhelper_icon.gif differ diff --git a/src/main/resources/siteDeploy/resources/images/commandhelper_icon.svg b/src/main/resources/siteDeploy/resources/images/commandhelper_icon.svg new file mode 100644 index 000000000..128f5b55e --- /dev/null +++ b/src/main/resources/siteDeploy/resources/images/commandhelper_icon.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/com/laytonsmith/core/functions/ObjectManagementTest.java b/src/test/java/com/laytonsmith/core/functions/ObjectManagementTest.java index 303ce82fb..e382263b5 100644 --- a/src/test/java/com/laytonsmith/core/functions/ObjectManagementTest.java +++ b/src/test/java/com/laytonsmith/core/functions/ObjectManagementTest.java @@ -7,11 +7,13 @@ import com.laytonsmith.core.FullyQualifiedClassName; import com.laytonsmith.core.MethodScriptCompiler; +import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Static; import com.laytonsmith.core.compiler.CompilerEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.objects.Element; import com.laytonsmith.core.objects.ObjectDefinition; import com.laytonsmith.core.objects.ObjectType; import com.laytonsmith.testing.StaticTest; @@ -19,21 +21,36 @@ import java.util.ArrayList; import org.junit.Test; import static org.junit.Assert.*; +import static com.laytonsmith.testing.StaticTest.SRun; /** * */ public class ObjectManagementTest { - public static void Run(String code, CompilerEnvironment env) throws Exception { + public static Environment getEnv(CompilerEnvironment cEnv) throws Exception { GlobalEnv gEnv = Static.GenerateStandaloneEnvironment(false).getEnv(GlobalEnv.class); gEnv.SetCustom("define_object.noQualifyClasses", true); - Environment eenv = Environment.createEnvironment(gEnv, env); - Run(code, eenv); + Environment eenv = Environment.createEnvironment(gEnv, cEnv); + return eenv; } - public static void Run(String code, Environment env) throws Exception { - MethodScriptCompiler.compile(MethodScriptCompiler.lex(code, new File("test.ms"), true), env); + public static ParseTree Run(String code, CompilerEnvironment env) throws Exception { + Environment eenv = getEnv(env); + return Run(code, eenv); + } + + public static ParseTree Run(String code, Environment env) throws Exception { + return MethodScriptCompiler.compile(MethodScriptCompiler.lex(code, new File("test.ms"), true), env); + } + + public static void Execute(String code, CompilerEnvironment env) throws Exception { + Environment eenv = getEnv(env); + Execute(code, eenv); + } + + public static void Execute(String code, Environment env) throws Exception { + MethodScriptCompiler.execute(Run(code, env), env, null, null); } public ObjectManagementTest() { @@ -396,6 +413,47 @@ public void testDuplicateDefinitionsCauseErrors() throws Exception { env); } + @Test + public void testFieldCreation() throws Exception { + assertEquals("DEFAULT TestType @test", SRun("create_field(DEFAULT, array(), TestType, '@test', null);", null)); + assertEquals("DEFAULT STATIC TestType @test", + SRun("create_field(DEFAULT, array(STATIC), TestType, '@test', null);", null)); + assertEquals("ms.lang.FieldDefinition", + SRun("typeof(create_field(DEFAULT, array(), TestType, '@test', null));", null)); + try { + // Invalid variable name, needs @ sign + SRun("create_field(DEFAULT, array(), TestType, 'test', null)", null); + fail(); + } catch (Exception ex) { + // pass + } + } + @Test + public void testFieldCreationInClass() throws Exception { + CompilerEnvironment cEnv = new CompilerEnvironment(); + Environment env = getEnv(cEnv); + // class A {} + Execute("define_object(DEFAULT," // 0 - Access modifier + + "array()," // 1 - Object modifier + + "CLASS," // 2 - Object type + + "A," // 3 - Object name + + "array()," // 4 - Superclasses + + "array()," // 5 - Interfaces + + "null," // 6 - Enum list + + "array(" + + " create_field(DEFAULT, array(), string, '@test', null)" + + ")," // 7 - element definitions + + "array()," // 8 - annotations + + "null," // 9 - containing class + + "''," // 10 - class comment + + "null)", // 11 - Generic parameters + env); + ObjectDefinition obj = cEnv.getObjectDefinitionTable().get(FullyQualifiedClassName.forFullyQualifiedClass("A")); + obj.qualifyClasses(env); + assertFalse(obj.getElements().isEmpty()); + Element e = obj.getElements().get(0); + assertEquals("@test", e.getElementName()); + } }