com.github.xemiru.luamesh.LuaMeta Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of luamesh Show documentation
Show all versions of luamesh Show documentation
Use LuaJ to mesh your Java objects as they get passed to Lua.
The newest version!
/**
* MIT License
*
* Copyright (c) 2016 Tellerva, Marc Lawrence G.
*
* 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 com.github.xemiru.luamesh;
import com.github.xemiru.luamesh.LuaType.MetaEntry;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Contains metadata for a Lua-coercible Java type.
*/
public class LuaMeta {
/**
* Performs name enforcement based on options set in
* {@link LuaMesh} on a given name.
*/
static String convertName(String name) {
String n = name;
if (LuaMesh.enforceFirstLower) {
n = Character.toLowerCase(n.charAt(0)) + n.substring(1);
}
if (LuaMesh.enforceUnderscore) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n.length(); i++) {
char ch = n.charAt(i);
if (i == 0) {
sb.append(ch);
continue;
}
if (Character.isUpperCase(ch)) {
sb.append('_');
sb.append(Character.toLowerCase(ch));
} else {
sb.append(ch);
}
}
n = sb.toString();
}
if (LuaMesh.enforceLowercase) {
n = n.toLowerCase();
}
return n;
}
/**
* Quick method to perform class name enforcement.
*
* @param type the target class
* @param override a name override, if any
*
* @return enforced name
*/
static String convertClassName(Class> type, String override) {
boolean noOverride = override == null || override.trim().isEmpty();
String name = noOverride ? type.getSimpleName() : override;
if (LuaMesh.enforcementOption == 1 || LuaMesh.enforcementOption == 3) {
if (noOverride || LuaMesh.enforceOverrides) {
name = convertName(name);
}
}
return name;
}
/**
* Quick method to perform method name enforcement.
*
* @param method the target method
* @param override a name override, if any
*
* @return enforced name
*/
static String convertMethodName(Method method, String override) {
boolean noOverride = override == null || override.trim().isEmpty();
String name = noOverride ? method.getName() : override;
if (LuaMesh.enforcementOption == 2 || LuaMesh.enforcementOption == 3) {
if (noOverride || LuaMesh.enforceOverrides) {
name = convertName(name);
}
}
return name;
}
private Class> type;
private LuaTable metatable;
private String name;
private Map names;
private Set meta;
LuaMeta(Class> type, String name) {
this.names = new HashMap<>();
this.meta = new HashSet<>();
this.type = type;
this.name = name;
// generate metatable
this.metatable = new LuaTable();
// first, get the parents' stuff
List> parents = new ArrayList<>();
Class> parent = type.getSuperclass();
while (parent != null && parent != Object.class) {
parents.add(parent);
parent = parent.getSuperclass();
}
// reverse the order so we dont fuck up overrides
// clone parents' metatables
while (!parents.isEmpty()) {
Class> p = parents.get(parents.size() - 1);
if (p.getDeclaredAnnotation(LuaType.class) != null) {
LuaMeta meta = LuaMesh.getMeta(p);
if (meta == null) {
throw new InvalidCoercionTargetException(String.format(
"Parent class %s of class %s has not been registered; could not inherit",
p.getName(), type.getName()));
}
LuaUtil.clone(this.metatable, meta.metatable, true);
this.names.putAll(meta.names);
this.meta.addAll(meta.meta); // wew, meta meta
}
parents.remove(p);
}
// apply the target class's stuff
if (LuaMesh.useTypeMetakey) {
this.metatable.set("__type", LuaValue.valueOf(name));
}
LuaValue __index = this.metatable.get(LuaValue.INDEX);
if (__index.isnil()) {
__index = new LuaTable();
this.metatable.set(LuaValue.INDEX, __index);
}
}
/**
* Constructor generating two-way metadata based on
* {@link LuaType} annotations found within the given
* type's class.
*
* @param rannot the annotation of the class
* @param type the class to create metadata with
*/
LuaMeta(LuaType rannot, Class> type) {
this(type, convertClassName(type, (
type.getDeclaredAnnotation(LuaType.class) != null
? type.getDeclaredAnnotation(LuaType.class)
: rannot).name()));
// register annotated methods
LuaValue __index = this.metatable.get(LuaValue.INDEX);
for (Method method : type.getDeclaredMethods()) {
LuaType typeAnnot = method.getDeclaredAnnotation(LuaType.class);
if (typeAnnot != null) {
String mName = method.getName();
// in case of override
if (this.names.containsKey(mName)) {
if (typeAnnot.entry() != MetaEntry.INDEX) {
this.metatable.set(typeAnnot.entry().getKey(), LuaValue.NIL);
this.meta.remove(mName);
} else {
__index.set(this.names.get(mName), LuaValue.NIL);
}
}
// apply the name override if its there
// perform name enforcement
String aName = typeAnnot.name().trim();
// check if we need to replace, in case of override
if (!aName.isEmpty() || !this.names.containsKey(mName)) {
aName = convertMethodName(method, aName);
}
try {
// register
method.setAccessible(true);
LuaMethodBind lfunc = new LuaMethodBind(method);
method.setAccessible(false);
if (typeAnnot.entry() != MetaEntry.INDEX) {
this.metatable.set(typeAnnot.entry().getKey(), lfunc);
this.names.put(mName, typeAnnot.entry().getKey().tojstring());
this.meta.add(mName);
} else {
__index.set(aName, lfunc);
this.names.put(mName, aName);
}
} catch (IllegalAccessException e) {
// let it cause a crash, this isn't good
throw new RuntimeException(e);
}
}
}
}
/**
* Constructor generating one-way metadata for the
* methods contained within the given class.
*
* The filter receives the names of candidate
* methods within the given class to be written to
* the Lua instance. It can return the same name, or
* a different name to rename the method in the Lua
* environment. It can also return null to signify
* that the candidate method should not be written to
* to the class's Lua counterpart.
*
* If the filter is null, all methods will be
* registered with their default names.
*
* @param type the class to generate metadata with
* @param filter a filter determining which methods
* should be accessible from the Lua instance
*/
LuaMeta(Class> type, Function filter) {
this(type, convertClassName(type, null));
// register methods
LuaValue __index = this.metatable.get(LuaValue.INDEX);
for(Method method : type.getDeclaredMethods()) {
String mname = convertMethodName(method, null);
String name = filter == null ? mname : filter.apply(mname);
if(name == null) { continue; }
try {
method.setAccessible(true);
__index.set(name, new LuaMethodBind(method));
method.setAccessible(false);
} catch(IllegalAccessException e) {
// let it cause a crash, this isn't good
throw new RuntimeException(e);
}
}
}
/**
* Returns the name associated with the owning LuaType
* class.
*
* @return the Lua name of the associated class
*/
public String getName() {
return this.name;
}
/**
* Returns the object type targetted by this
* {@link LuaMeta}.
*
* @return the type targetted by this LuaMeta
*/
public Class> getTargetType() {
return this.type;
}
/**
* Returns the full Lua metatable for this
* {@link LuaMeta} and its associated object type.
*
* This generally should not be modified by anything
* outside of the LuaMeta itself.
*
* @return this LuaMeta's metatable
*/
public LuaTable getMetatable() {
return this.metatable;
}
/**
* Returns whether or not the provided member's Java
* name was registered within the Lua objects' main
* metatable (the one holding __index).
*
* @param memberName the name of the Java member
*
* @return if the member resides in the main metatable
*/
public boolean isMeta(String memberName) {
return meta.contains(memberName);
}
/**
* Returns the Lua name of the provided member's Java
* name.
*
* @param memberName the name of the Java member
*
* @return the name of the Lua member
*/
public String getLuaName(String memberName) {
return this.names.get(memberName);
}
}