From 4088c8be3f91054787666a20b5e60c7fe0d43bfc Mon Sep 17 00:00:00 2001 From: kindlich Date: Mon, 27 Apr 2020 08:49:28 +0200 Subject: [PATCH] Started with adding BEP validation Works by having another Method annotated as BracketValidator that takes the String and returns a bool stating whether this worked out or not --- .../crafttweaker/api/CraftTweakerAPI.java | 21 +-- .../api/CraftTweakerRegistry.java | 110 +++++++++++-- .../api/annotations/BracketResolver.java | 2 + .../api/annotations/BracketValidator.java | 10 ++ .../api/managers/IRecipeManager.java | 16 +- .../ValidatedEscapableBracketParser.java | 148 ++++++++++++++++++ 6 files changed, 274 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/blamejared/crafttweaker/api/annotations/BracketValidator.java create mode 100644 src/main/java/com/blamejared/crafttweaker/api/zencode/brackets/ValidatedEscapableBracketParser.java diff --git a/src/main/java/com/blamejared/crafttweaker/api/CraftTweakerAPI.java b/src/main/java/com/blamejared/crafttweaker/api/CraftTweakerAPI.java index 130daf726..dfd908562 100644 --- a/src/main/java/com/blamejared/crafttweaker/api/CraftTweakerAPI.java +++ b/src/main/java/com/blamejared/crafttweaker/api/CraftTweakerAPI.java @@ -9,6 +9,7 @@ import com.blamejared.crafttweaker.api.logger.ILogger; import com.blamejared.crafttweaker.api.logger.LogLevel; import com.blamejared.crafttweaker.api.mods.MCMods; +import com.blamejared.crafttweaker.api.zencode.brackets.*; import com.blamejared.crafttweaker.api.zencode.expands.IDataRewrites; import com.blamejared.crafttweaker.api.zencode.impl.FileAccessSingle; import com.blamejared.crafttweaker.impl.brackets.RecipeTypeBracketHandler; @@ -142,14 +143,10 @@ public static void loadScripts(SourceFile[] sourceFiles, ScriptLoadingOptions sc PrefixedBracketParser bep = new PrefixedBracketParser(null); bep.register("recipetype", new RecipeTypeBracketHandler()); - crafttweakerModule.registerBEP(bep); - for(Method method : CraftTweakerRegistry.getBracketResolvers("crafttweaker")) { - String name = method.getAnnotation(BracketResolver.class).value(); - FunctionalMemberRef memberRef = crafttweakerModule.loadStaticMethod(method); - bep.register(name, new SimpleBracketParser(SCRIPTING_ENGINE.registry, memberRef)); - crafttweakerModule.getCompiled().setMethodInfo(memberRef.getTarget(), crafttweakerModule.getCompiled().getMethodInfo(memberRef)); + for(ValidatedEscapableBracketParser bracketResolver : CraftTweakerRegistry.getBracketResolvers("crafttweaker", SCRIPTING_ENGINE, crafttweakerModule)) { + bep.register(bracketResolver.getName(), bracketResolver); } - + crafttweakerModule.registerBEP(bep); CraftTweakerRegistry.getClassesInPackage("crafttweaker").forEach(crafttweakerModule::addClass); CraftTweakerRegistry.getZenGlobals().forEach(crafttweakerModule::addGlobals); @@ -163,15 +160,9 @@ public static void loadScripts(SourceFile[] sourceFiles, ScriptLoadingOptions sc } JavaNativeModule module = SCRIPTING_ENGINE.createNativeModule(key, key, crafttweakerModule); module.registerBEP(bep); - - for(Method method : CraftTweakerRegistry.getBracketResolvers(key)) { - String name = method.getAnnotation(BracketResolver.class).value(); - FunctionalMemberRef memberRef = module.loadStaticMethod(method); - bep.register(name, new SimpleBracketParser(SCRIPTING_ENGINE.registry, memberRef)); - crafttweakerModule.getCompiled().setMethodInfo(memberRef.getTarget(), module.getCompiled().getMethodInfo(memberRef)); + for(ValidatedEscapableBracketParser bracketResolver : CraftTweakerRegistry.getBracketResolvers(key, SCRIPTING_ENGINE, module)) { + bep.register(bracketResolver.getName(), bracketResolver); } - - CraftTweakerRegistry.getClassesInPackage(key).forEach(module::addClass); SCRIPTING_ENGINE.registerNativeProvided(module); modules.add(module); diff --git a/src/main/java/com/blamejared/crafttweaker/api/CraftTweakerRegistry.java b/src/main/java/com/blamejared/crafttweaker/api/CraftTweakerRegistry.java index 0f94ac633..92877d1e9 100644 --- a/src/main/java/com/blamejared/crafttweaker/api/CraftTweakerRegistry.java +++ b/src/main/java/com/blamejared/crafttweaker/api/CraftTweakerRegistry.java @@ -5,14 +5,16 @@ import com.blamejared.crafttweaker.api.brackets.CommandStringDisplayable; import com.blamejared.crafttweaker.api.managers.IRecipeManager; import com.blamejared.crafttweaker.api.zencode.IPreprocessor; +import com.blamejared.crafttweaker.api.zencode.brackets.*; import com.blamejared.crafttweaker.impl.brackets.RecipeTypeBracketHandler; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import net.minecraftforge.fml.ModList; import net.minecraftforge.forgespi.language.ModFileScanData; import org.objectweb.asm.Type; -import org.openzen.zencode.java.ZenCodeGlobals; -import org.openzen.zencode.java.ZenCodeType; +import org.openzen.zencode.java.*; +import org.openzen.zenscript.codemodel.member.ref.*; +import org.openzen.zenscript.parser.*; import java.lang.reflect.*; import java.util.*; @@ -27,7 +29,12 @@ public class CraftTweakerRegistry { private static final List ZEN_CLASSES = new ArrayList<>(); private static final List ZEN_GLOBALS = new ArrayList<>(); - private static final Map> BRACKET_RESOLVERS = new HashMap<>(); + //Name to BEP methods + private static final Map BRACKET_RESOLVERS = new HashMap<>(); + //Root package to BEP Names + private static final Map> BRACKET_RESOLVERS_2 = new HashMap<>(); + //BEP name to BEP validator method + private static final Map BRACKET_VALIDATORS = new HashMap<>(); private static final Map>> BRACKET_DUMPERS = new HashMap<>(); private static final Map ZEN_CLASS_MAP = new HashMap<>(); private static final List PREPROCESSORS = new ArrayList<>(); @@ -76,6 +83,8 @@ public static void findClasses() { e.printStackTrace(); } }); + + validateBrackets(); } /** @@ -127,7 +136,40 @@ private static void sortClasses() { for(Method method : zenClass.getDeclaredMethods()) { handleBracketResolver(method); handleBracketDumper(method); + handleBracketValidator(method); + } + } + } + + private static void handleBracketValidator(Method method) { + if(!method.isAnnotationPresent(BracketValidator.class)) { + return; + } + if(!Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers())) { + CraftTweakerAPI.logWarning("Method \"%s\" is marked as a BracketValidator, but it is not public and static.", method.toString()); + return; + } + boolean valid = true; + + final String value = method.getAnnotation(BracketValidator.class).value(); + Class[] parameters = method.getParameterTypes(); + if(parameters.length == 1 && parameters[0].equals(String.class)) { + if(BRACKET_VALIDATORS.containsKey(value)) { + CraftTweakerAPI.logError("Bracket validator for bep name %s was found twice: %s and %s", value, BRACKET_VALIDATORS.get(value), method); + valid = false; } + } else { + CraftTweakerAPI.logError("Method \"%s\" is marked as a BracketValidator, but it does not have a String as it's only parameter.", method.toString()); + valid = false; + } + + if(method.getReturnType() != boolean.class) { + CraftTweakerAPI.logError("Method \"%s\" is marked as a BracketValidator, so it must return a boolean", method); + valid = false; + } + + if(valid){ + BRACKET_VALIDATORS.put(value, method); } } @@ -139,19 +181,39 @@ private static void handleBracketResolver(Method method) { CraftTweakerAPI.logWarning("Method \"%s\" is marked as a BracketResolver, but it is not public and static.", method.toString()); return; } + + boolean isValid = true; Class[] parameters = method.getParameterTypes(); - if(parameters.length == 1 && parameters[0].equals(String.class)) { - Class cls = method.getDeclaringClass(); - final ZenCodeType.Name annotation = cls.getAnnotation(ZenCodeType.Name.class); - final String name = annotation == null ? cls.getCanonicalName() : annotation.value(); - BRACKET_RESOLVERS.computeIfAbsent(name.split("[.]", 2)[0], s -> new ArrayList<>()) - .add(method); - } else { - CraftTweakerAPI.logWarning("Method \"%s\" is marked as a BracketResolver, but it does not have a String as it's only parameter.", method.toString()); + final String name = method.getAnnotation(BracketResolver.class).value(); + + if(parameters.length != 1 || !parameters[0].equals(String.class)) { + //BRACKET_RESOLVERS.computeIfAbsent(name.split("[.]", 2)[0], s -> new ArrayList<>()) + // .add(method); + CraftTweakerAPI.logError("Method \"%s\" is marked as a BracketResolver, but it does not have a String as it's only parameter.", method.toString()); + isValid = false; } if(!CommandStringDisplayable.class.isAssignableFrom(method.getReturnType())){ - CraftTweakerAPI.logWarning("Method \"%s\" is marked as a BracketResolver, so it should return something that implements %s.", method.toString(), CommandStringDisplayable.class.getSimpleName()); + CraftTweakerAPI.logError("Method \"%s\" is marked as a BracketResolver, so it should return something that implements %s.", method.toString(), CommandStringDisplayable.class.getSimpleName()); + isValid = false; + } + + if(!BRACKET_RESOLVERS.getOrDefault(name, method).equals(method)) { + final Method other = BRACKET_RESOLVERS.get(name); + CraftTweakerAPI.logError("BracketResolve \"%s\" was registered twice: '%s' and '%s'", name, other, method); + isValid = false; + } + + if(isValid) { + BRACKET_RESOLVERS.put(name, method); + + final Class cls = method.getDeclaringClass(); + final String clsName = cls.isAnnotationPresent(ZenCodeType.Name.class) + ? cls.getAnnotation(ZenCodeType.Name.class).value() + : cls.getCanonicalName(); + + BRACKET_RESOLVERS_2.computeIfAbsent(clsName.split("[.]", 2)[0], s -> new ArrayList<>()) + .add(name); } } @@ -268,8 +330,28 @@ public static List getZenGlobals() { * * @return ImmutableList of the Bracket Resolvers */ - public static List getBracketResolvers(String rootPackage) { - return ImmutableList.copyOf(BRACKET_RESOLVERS.getOrDefault(rootPackage, Collections.emptyList())); + public static List getBracketResolvers(String name, ScriptingEngine scriptingEngine, JavaNativeModule crafttweakerModule) { + final List validatedEscapableBracketParsers = new ArrayList<>(); + for(String bepName : BRACKET_RESOLVERS_2.getOrDefault(name, Collections.emptyList())) { + final Method parserMethod = BRACKET_RESOLVERS.get(bepName); + final Method validatorMethod = CraftTweakerRegistry.getBracketValidator(bepName); + final FunctionalMemberRef functionalMemberRef = crafttweakerModule.loadStaticMethod(parserMethod); + final ValidatedEscapableBracketParser validated = new ValidatedEscapableBracketParser(bepName, functionalMemberRef, validatorMethod, scriptingEngine.registry); + validatedEscapableBracketParsers.add(validated); + } + return validatedEscapableBracketParsers; + } + + public static Method getBracketValidator(String bepName) { + return BRACKET_VALIDATORS.getOrDefault(bepName, null); + } + + private static void validateBrackets(){ + for(String validatedBep : BRACKET_VALIDATORS.keySet()) { + if(!BRACKET_RESOLVERS.containsKey(validatedBep)) { + CraftTweakerAPI.logError("BEP %s has a validator but no BEP method", validatedBep); + } + } } /** diff --git a/src/main/java/com/blamejared/crafttweaker/api/annotations/BracketResolver.java b/src/main/java/com/blamejared/crafttweaker/api/annotations/BracketResolver.java index 107b47e05..f282ddf26 100644 --- a/src/main/java/com/blamejared/crafttweaker/api/annotations/BracketResolver.java +++ b/src/main/java/com/blamejared/crafttweaker/api/annotations/BracketResolver.java @@ -6,6 +6,8 @@ * Used to mark a Method as a bracket handler resolver, the method NEEDS to be {@code public static (String bracket)}. *

* The String provided is the exact text inside the bracket {@code }, which can then be parsed by the method. + *

+ * The returned Type should implement ICommandStringDisplayable, otherwise it will log a warning at runtime, and an error at compile time if the annotation processors are part of your build Dependencies */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/src/main/java/com/blamejared/crafttweaker/api/annotations/BracketValidator.java b/src/main/java/com/blamejared/crafttweaker/api/annotations/BracketValidator.java new file mode 100644 index 000000000..589f737a7 --- /dev/null +++ b/src/main/java/com/blamejared/crafttweaker/api/annotations/BracketValidator.java @@ -0,0 +1,10 @@ +package com.blamejared.crafttweaker.api.annotations; + + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface BracketValidator { + String value(); +} diff --git a/src/main/java/com/blamejared/crafttweaker/api/managers/IRecipeManager.java b/src/main/java/com/blamejared/crafttweaker/api/managers/IRecipeManager.java index 8294dfe85..c6caa634f 100644 --- a/src/main/java/com/blamejared/crafttweaker/api/managers/IRecipeManager.java +++ b/src/main/java/com/blamejared/crafttweaker/api/managers/IRecipeManager.java @@ -205,29 +205,37 @@ default ResourceLocation getBracketResourceLocation() { @FunctionalInterface @ZenRegister + @ZenCodeType.Name("crafttweaker.api.recipe.RecipeFilter") + @Document("vanilla/api/recipe/RecipeFilter") interface RecipeFilter { - + @ZenCodeType.Method boolean test(String name); } @FunctionalInterface @ZenRegister + @ZenCodeType.Name("crafttweaker.api.recipe.RecipeFunctionSingle") + @Document("vanilla/api/recipe/RecipeFunctionSingle") interface RecipeFunctionSingle { - + @ZenCodeType.Method IItemStack process(IItemStack usualOut, IItemStack inputs); } @FunctionalInterface @ZenRegister + @ZenCodeType.Name("crafttweaker.api.recipe.RecipeFunctionArray") + @Document("vanilla/api/recipe/RecipeFunctionArray") interface RecipeFunctionArray { - + @ZenCodeType.Method IItemStack process(IItemStack usualOut, IItemStack[] inputs); } @FunctionalInterface @ZenRegister + @ZenCodeType.Name("crafttweaker.api.recipe.RecipeFunctionMatrix") + @Document("vanilla/api/recipe/RecipeFunctionMatrix") interface RecipeFunctionMatrix { - + @ZenCodeType.Method IItemStack process(IItemStack usualOut, IItemStack[][] inputs); } diff --git a/src/main/java/com/blamejared/crafttweaker/api/zencode/brackets/ValidatedEscapableBracketParser.java b/src/main/java/com/blamejared/crafttweaker/api/zencode/brackets/ValidatedEscapableBracketParser.java new file mode 100644 index 000000000..0ec29c1f2 --- /dev/null +++ b/src/main/java/com/blamejared/crafttweaker/api/zencode/brackets/ValidatedEscapableBracketParser.java @@ -0,0 +1,148 @@ +package com.blamejared.crafttweaker.api.zencode.brackets; + +import org.openzen.zencode.shared.*; +import org.openzen.zenscript.codemodel.*; +import org.openzen.zenscript.codemodel.expression.*; +import org.openzen.zenscript.codemodel.member.ref.*; +import org.openzen.zenscript.codemodel.partial.*; +import org.openzen.zenscript.codemodel.scope.*; +import org.openzen.zenscript.codemodel.type.*; +import org.openzen.zenscript.lexer.*; +import org.openzen.zenscript.parser.*; +import org.openzen.zenscript.parser.expression.*; + +import java.lang.reflect.*; +import java.util.*; + +public class ValidatedEscapableBracketParser implements BracketExpressionParser { + + private final FunctionalMemberRef method; + private final Method validationMethod; + private final String name; + private final DefinitionTypeID targetType; + + public ValidatedEscapableBracketParser(String name, FunctionalMemberRef parserMethod, Method validationMethod, GlobalTypeRegistry registry) { + this.method = parserMethod; + this.validationMethod = validationMethod; + this.name = name; + targetType = registry.getForDefinition(parserMethod.getTarget().definition); + } + + private static ParsedExpression createCallee(String methodFull, CodePosition position) { + ParsedExpression expression = null; + for(String s : methodFull.split("[.]")) { + if(expression == null) { + expression = new ParsedExpressionVariable(position, s, null); + } else { + expression = new ParsedExpressionMember(position, expression, s, null); + } + } + + return expression; + } + + public String getName() { + return name; + } + + @Override + public ParsedExpression parse(CodePosition position, ZSTokenParser tokens) throws ParseException { + StringBuilder string = new StringBuilder(); + + //This list will contain the BEP calls + //If this is only a normal BEP, then it will contain exactly one String ParsedExpression. + final List expressionList = new ArrayList<>(); + + while(tokens.optional(ZSTokenType.T_GREATER) == null) { + ZSToken next = tokens.next(); + + if(next.type != ZSTokenType.T_DOLLAR) { + string.append(next.content); + string.append(tokens.getLastWhitespace()); + continue; + } + + //We found a $, now check that it has a { directly after it. + final String ws = tokens.getLastWhitespace(); + if(!ws.isEmpty()) { + //$ {..} is not ${..} so we print it as literal + string.append(next.content).append(ws); + continue; + } + + next = tokens.next(); + //Now we check if it is a { + if(next.type == ZSTokenType.T_AOPEN) { + if(string.length() != 0) { + expressionList.add(new ParsedExpressionString(position, string.toString(), false)); + string = new StringBuilder(); + } + expressionList.add(ParsedExpression.parse(tokens)); + tokens.required(ZSTokenType.T_ACLOSE, "} expected."); + string.append(tokens.getLastWhitespace()); + } else { + //No { after the $, so we treat them both as literal + string.append("$") + .append(ws); //Technically, the whitespace here is empty, but let's be sure + string.append(next.content).append(tokens.getLastWhitespace()); + } + + } + + if(string.length() != 0) { + expressionList.add(new ParsedExpressionString(position, string.toString(), false)); + } + + if(expressionList.isEmpty()) { + expressionList.add(new ParsedExpressionString(position, "", false)); + } + + if(expressionList.size() == 1) { + final ParsedExpression parsedExpression = expressionList.get(0); + if(validationMethod != null && parsedExpression instanceof ParsedExpressionString) { + final String value = ((ParsedExpressionString) parsedExpression).value; + boolean valid; + try { + valid = (boolean) validationMethod.invoke(null, value); + } catch(IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + valid = false; + } + + if(!valid) { + final String format = String.format("Invalid parameters to BEP %s: '<%s:%s>", name, name, value); + throw new IllegalArgumentException(format); + } + } + } + + return new StaticMethodCallExpression(position, expressionList); + } + + private class StaticMethodCallExpression extends ParsedExpression { + + private final ParsedExpression call; + + public StaticMethodCallExpression(CodePosition position, List expressions) { + super(position); + ParsedExpression p = null; + for (ParsedExpression expression : expressions) { + p = p == null ? expression : new ParsedExpressionBinary(expression.position, p, expression, OperatorType.ADD); + } + + this.call = p; + } + + @Override + public IPartialExpression compile(ExpressionScope scope) throws CompileException { + final Expression methodCall = call.compile(scope.withHint(StringTypeID.AUTO)).eval(); + final CallArguments arguments = new CallArguments(methodCall); + return new CallStaticExpression(position, targetType, method, method.getHeader(), arguments); + } + + @Override + public boolean hasStrongType() { + return true; + } + } +}