/*
* This file is part of LanternServer, licensed under the MIT License (MIT).
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the Software), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.lanternpowered.server.script.transformer;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Joiner;
import org.lanternpowered.api.script.Parameter;
import org.lanternpowered.server.script.LanternScriptGameRegistry;
import org.spongepowered.api.plugin.Plugin;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RelocatedMethodScriptTransformer implements Transformer {
private static final Pattern PATTERN = Pattern.compile(
"^\\s*" + SCRIPT_PREFIX + "?\\s*\\{\\s*(" + Plugin.ID_PATTERN + ")\\s*,\\s*([a-zA-Z0-9\\-/]+)\\s*\\}\\s*$");
@Override
public boolean transform(ScriptTransformerContext context) {
String scriptBody = context.getScriptBody();
final Matcher matcher = PATTERN.matcher(scriptBody);
if (!matcher.matches()) {
return false;
}
final String scriptId = matcher.group(1);
final String methodName = matcher.group(2);
context.setScriptBody(writeMethod(context, scriptId, methodName, context.getFunctionMethod().get().getMethod()));
return true;
}
static String writeMethod(ScriptTransformerContext context, String targetScriptId, String targetMethodName, Method method) {
final StringBuilder builder = new StringBuilder();
final String targetScriptClass = TransformerUtil.generateClassNameFromAssetPath(targetScriptId);
context.addDependency(targetScriptId);
writeMethod(builder, method, (theBuilder, parameterNames) ->
theBuilder.append(String.format("((%s) %s.get().getScript(\"%s\").get().get()).%s(%s)",
targetScriptClass, LanternScriptGameRegistry.class.getName(), targetScriptId, targetMethodName,
Joiner.on(", ").join(parameterNames))));
return builder.toString();
}
static String writeMethod(Method method, BiConsumer<StringBuilder, String[]> contentBuilder) {
final StringBuilder builder = new StringBuilder();
writeMethod(builder, method, contentBuilder);
return builder.toString();
}
static void writeMethod(StringBuilder builder, Method method, BiConsumer<StringBuilder, String[]> contentBuilder) {
builder.append("@Override\n");
builder.append("public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(");
final Type[] parameters = method.getGenericParameterTypes();
final String[] parameterNames = getParameterNames(method);
for (int i = 0; i < parameters.length; i++) {
if (i > 0) {
builder.append(",");
}
builder.append(parameters[i].getTypeName()).append(" ").append(parameterNames[i]);
}
builder.append(") {\n");
final StringBuilder contentStringBuilder = new StringBuilder();
contentBuilder.accept(contentStringBuilder, parameterNames);
for (String line : contentStringBuilder.toString().split("\n")) {
builder.append(" ").append(line).append("\n");
}
builder.append("}");
}
private static String[] getParameterNames(Method method) {
final Class<?>[] parameters = method.getParameterTypes();
final Annotation[][] annotations = method.getParameterAnnotations();
final String[] parameterNames = new String[parameters.length];
for (int i = 0; i < parameters.length; i++) {
String name = null;
final Set<String> usedNames = new HashSet<>();
final Annotation[] parameterAnnotations = annotations[i];
for (Annotation annotation : parameterAnnotations) {
if (annotation instanceof Parameter) {
name = ((Parameter) annotation).value();
}
}
checkState(!usedNames.contains(name), "There are multiple parameters annotated with the name %s", name);
if (name == null) {
int index = 0;
do {
if (parameters[i].isPrimitive()) {
name = parameters[i].getName().substring(0, 1) + index;
} else {
name = parameters[i].getSimpleName();
name = name.substring(0, 1).toLowerCase() + name.substring(1);
if (index > 0) {
name += index;
}
}
index++;
} while (usedNames.contains(name));
}
parameterNames[i] = name;
usedNames.add(name);
}
return parameterNames;
}
}