Better unknown field warnings for patcher
This commit is contained in:
@@ -59,6 +59,8 @@ public class ContentParser{
|
|||||||
|
|
||||||
ObjectMap<Class<?>, ContentType> contentTypes = new ObjectMap<>();
|
ObjectMap<Class<?>, ContentType> contentTypes = new ObjectMap<>();
|
||||||
Seq<ParseListener> listeners = new Seq<>();
|
Seq<ParseListener> listeners = new Seq<>();
|
||||||
|
/** If false, arbitrary class names cannot be resolved with Class.forName. */
|
||||||
|
boolean allowClassResolution = true;
|
||||||
|
|
||||||
ObjectMap<Class<?>, FieldParser> classParsers = new ObjectMap<>(){{
|
ObjectMap<Class<?>, FieldParser> classParsers = new ObjectMap<>(){{
|
||||||
put(Effect.class, (type, data) -> {
|
put(Effect.class, (type, data) -> {
|
||||||
@@ -458,7 +460,7 @@ public class ContentParser{
|
|||||||
if(consumeType != Consume.class){
|
if(consumeType != Consume.class){
|
||||||
block.removeConsumers(b -> consumeType.isAssignableFrom(b.getClass()));
|
block.removeConsumers(b -> consumeType.isAssignableFrom(b.getClass()));
|
||||||
}else{
|
}else{
|
||||||
Log.warn("Unknown consumer type '@' (Class: @) in consume: remove.", type, "Consume" + Strings.capitalize(type));
|
warn("Unknown consumer type '@' (Class: @) in consume: remove.", type, "Consume" + Strings.capitalize(type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,7 +505,7 @@ public class ContentParser{
|
|||||||
|
|
||||||
if(locate(ContentType.block, name) != null){
|
if(locate(ContentType.block, name) != null){
|
||||||
if(value.has("type")){
|
if(value.has("type")){
|
||||||
Log.warn("Warning: '" + currentMod.name + "-" + name + "' re-declares a type. This will be interpreted as a new block. If you wish to override a vanilla block, omit the 'type' section, as vanilla block `type`s cannot be changed.");
|
warn("Warning: '" + currentMod.name + "-" + name + "' re-declares a type. This will be interpreted as a new block. If you wish to override a vanilla block, omit the 'type' section, as vanilla block `type`s cannot be changed.");
|
||||||
block = make(resolve(value.getString("type", ""), Block.class), mod + "-" + name);
|
block = make(resolve(value.getString("type", ""), Block.class), mod + "-" + name);
|
||||||
}else{
|
}else{
|
||||||
block = locate(ContentType.block, name);
|
block = locate(ContentType.block, name);
|
||||||
@@ -1122,7 +1124,7 @@ public class ContentParser{
|
|||||||
FieldMetadata metadata = fields.get(child.name().replace(" ", "_"));
|
FieldMetadata metadata = fields.get(child.name().replace(" ", "_"));
|
||||||
if(metadata == null){
|
if(metadata == null){
|
||||||
if(ignoreUnknownFields){
|
if(ignoreUnknownFields){
|
||||||
Log.warn("[@]: Ignoring unknown field: @ (@)", currentContent == null ? null : currentContent.minfo.sourceFile.name(), child.name, type.getSimpleName());
|
warn("@Unknown field '@' for class '@'", currentContent == null ? "" : "[" + currentContent.minfo.sourceFile.name() + "]: ", child.name, type.getSimpleName());
|
||||||
continue;
|
continue;
|
||||||
}else{
|
}else{
|
||||||
SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")");
|
SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")");
|
||||||
@@ -1241,7 +1243,7 @@ public class ContentParser{
|
|||||||
TechNode parent = TechTree.all.find(t -> t.content.name.equals(researchName) || t.content.name.equals(currentMod.name + "-" + researchName) || t.content.name.equals(SaveVersion.mapFallback(researchName)));
|
TechNode parent = TechTree.all.find(t -> t.content.name.equals(researchName) || t.content.name.equals(currentMod.name + "-" + researchName) || t.content.name.equals(SaveVersion.mapFallback(researchName)));
|
||||||
|
|
||||||
if(parent == null){
|
if(parent == null){
|
||||||
Log.warn("Content '" + researchName + "' isn't in the tech tree, but '" + unlock.name + "' requires it to be researched.");
|
warn("Content '" + researchName + "' isn't in the tech tree, but '" + unlock.name + "' requires it to be researched.");
|
||||||
}else{
|
}else{
|
||||||
//add this node to the parent
|
//add this node to the parent
|
||||||
if(!parent.children.contains(node)){
|
if(!parent.children.contains(node)){
|
||||||
@@ -1252,7 +1254,7 @@ public class ContentParser{
|
|||||||
node.planet = parent.planet;
|
node.planet = parent.planet;
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Log.warn(unlock.name + " is not a root node, and does not have a `parent: ` property. Ignoring.");
|
warn(unlock.name + " is not a root node, and does not have a `parent: ` property. Ignoring.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1279,7 +1281,7 @@ public class ContentParser{
|
|||||||
if(out != null) return (Class<T>)out;
|
if(out != null) return (Class<T>)out;
|
||||||
|
|
||||||
//try to resolve it as a raw class name
|
//try to resolve it as a raw class name
|
||||||
if(base.indexOf('.') != -1){
|
if(base.indexOf('.') != -1 && allowClassResolution){
|
||||||
try{
|
try{
|
||||||
return (Class<T>)Class.forName(base);
|
return (Class<T>)Class.forName(base);
|
||||||
}catch(Exception ignored){
|
}catch(Exception ignored){
|
||||||
@@ -1291,12 +1293,21 @@ public class ContentParser{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(def != null){
|
if(def != null){
|
||||||
if(warn) Log.warn("[@] No type '" + base + "' found, defaulting to type '" + def.getSimpleName() + "'", currentContent == null && currentMod != null ? currentMod.name : "");
|
if(warn) warn("[@] No type '" + base + "' found, defaulting to type '" + def.getSimpleName() + "'", currentContent == null && currentMod != null ? currentMod.name : "");
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("Type not found: " + base);
|
throw new IllegalArgumentException("Type not found: " + base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void warn(String string, Object... format){
|
||||||
|
Log.warn(string, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Json getJson(){
|
||||||
|
checkInit();
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
|
|
||||||
private interface FieldParser{
|
private interface FieldParser{
|
||||||
Object parse(Class<?> type, JsonValue value) throws Exception;
|
Object parse(Class<?> type, JsonValue value) throws Exception;
|
||||||
}
|
}
|
||||||
@@ -1305,11 +1316,6 @@ public class ContentParser{
|
|||||||
T parse(String mod, String name, JsonValue value) throws Exception;
|
T parse(String mod, String name, JsonValue value) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Json getJson(){
|
|
||||||
checkInit();
|
|
||||||
return parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
//intermediate class for parsing
|
//intermediate class for parsing
|
||||||
static class UnitReq{
|
static class UnitReq{
|
||||||
public Block block;
|
public Block block;
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ import java.util.*;
|
|||||||
public class ContentPatcher{
|
public class ContentPatcher{
|
||||||
private static final Object root = new Object();
|
private static final Object root = new Object();
|
||||||
private static final ObjectMap<String, ContentType> nameToType = new ObjectMap<>();
|
private static final ObjectMap<String, ContentType> nameToType = new ObjectMap<>();
|
||||||
|
private static ContentParser parser = createParser();
|
||||||
|
|
||||||
private Json json;
|
|
||||||
private boolean applied;
|
private boolean applied;
|
||||||
private ContentLoader contentLoader;
|
private ContentLoader contentLoader;
|
||||||
private ObjectSet<Object> usedpatches = new ObjectSet<>();
|
private ObjectSet<Object> usedpatches = new ObjectSet<>();
|
||||||
@@ -41,13 +41,27 @@ public class ContentPatcher{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ContentParser createParser(){
|
||||||
|
ContentParser cont = new ContentParser(){
|
||||||
|
@Override
|
||||||
|
void warn(String string, Object... format){
|
||||||
|
//forward warnings to the current patcher - this is a bit hacky, but I do not want to re-initialize the parser every time
|
||||||
|
if(Vars.state.patcher != null){
|
||||||
|
Vars.state.patcher.warn(string, format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cont.allowClassResolution = false;
|
||||||
|
|
||||||
|
return cont;
|
||||||
|
}
|
||||||
|
|
||||||
/** Applies the specified patches. If patches were already applied, the previous ones are un-applied - they do not stack! */
|
/** Applies the specified patches. If patches were already applied, the previous ones are un-applied - they do not stack! */
|
||||||
public void apply(Seq<String> patchArray) throws Exception{
|
public void apply(Seq<String> patchArray) throws Exception{
|
||||||
if(applied){
|
if(applied){
|
||||||
unapply();
|
unapply();
|
||||||
applied = false;
|
applied = false;
|
||||||
}
|
}
|
||||||
json = Vars.mods.getContentParser().getJson();
|
|
||||||
|
|
||||||
applied = true;
|
applied = true;
|
||||||
contentLoader = Vars.content.copy();
|
contentLoader = Vars.content.copy();
|
||||||
@@ -55,7 +69,7 @@ public class ContentPatcher{
|
|||||||
|
|
||||||
for(String patch : patchArray){
|
for(String patch : patchArray){
|
||||||
try{
|
try{
|
||||||
JsonValue value = json.fromJson(null, Jval.read(patch).toString(Jformat.plain));
|
JsonValue value = parser.getJson().fromJson(null, Jval.read(patch).toString(Jformat.plain));
|
||||||
PatchSet set = new PatchSet(patch, value);
|
PatchSet set = new PatchSet(patch, value);
|
||||||
patches.add(set);
|
patches.add(set);
|
||||||
currentlyApplying = set;
|
currentlyApplying = set;
|
||||||
@@ -267,7 +281,7 @@ public class ContentPatcher{
|
|||||||
Class<?> actualType = object.getClass();
|
Class<?> actualType = object.getClass();
|
||||||
if(actualType.isAnonymousClass()) actualType = actualType.getSuperclass();
|
if(actualType.isAnonymousClass()) actualType = actualType.getSuperclass();
|
||||||
|
|
||||||
var fields = json.getFields(actualType);
|
var fields = parser.getJson().getFields(actualType);
|
||||||
var fdata = fields.get(field);
|
var fdata = fields.get(field);
|
||||||
if(fdata != null){
|
if(fdata != null){
|
||||||
if(checkField(fdata.field)) return;
|
if(checkField(fdata.field)) return;
|
||||||
@@ -292,13 +306,13 @@ public class ContentPatcher{
|
|||||||
after(bl::reinitializeConsumers);
|
after(bl::reinitializeConsumers);
|
||||||
|
|
||||||
try{
|
try{
|
||||||
Vars.mods.getContentParser().readBlockConsumers(bl, jsv);
|
parser.readBlockConsumers(bl, jsv);
|
||||||
}catch(Throwable e){
|
}catch(Throwable e){
|
||||||
Log.err(e);
|
Log.err(e);
|
||||||
warn("Failed to read consumers for '@': @", bl, Strings.getSimpleMessage(e));
|
warn("Failed to read consumers for '@': @", bl, Strings.getSimpleMessage(e));
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
warn("Unknown field: '@' for class '@'", field, actualType.getSimpleName());
|
warn("Unknown field '@' for class '@'", field, actualType.getSimpleName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,16 +330,16 @@ public class ContentPatcher{
|
|||||||
if(modify) modifiedField(object, field, getter.get());
|
if(modify) modifiedField(object, field, getter.get());
|
||||||
|
|
||||||
//HACK: listen for creation of objects once
|
//HACK: listen for creation of objects once
|
||||||
Vars.mods.getContentParser().listeners.add((type, jsonData, result) -> created(result, object));
|
parser.listeners.add((type, jsonData, result) -> created(result, object));
|
||||||
try{
|
try{
|
||||||
setter.get(json.readValue(metadata.type, metadata.elementType, jsv));
|
setter.get(parser.getJson().readValue(metadata.type, metadata.elementType, jsv));
|
||||||
}catch(Throwable e){
|
}catch(Throwable e){
|
||||||
warn("Failed to read value @.@ = @: @ (type = @ elementType = @)\n@", object, field, value, e.getMessage(), metadata.type, metadata.elementType, Strings.getStackTrace(e));
|
warn("Failed to read value @.@ = @: @ (type = @ elementType = @)\n@", object, field, value, e.getMessage(), metadata.type, metadata.elementType, Strings.getStackTrace(e));
|
||||||
}
|
}
|
||||||
Vars.mods.getContentParser().listeners.pop();
|
parser.listeners.pop();
|
||||||
}else{
|
}else{
|
||||||
//assign each field manually
|
//assign each field manually
|
||||||
var childFields = json.getFields(prevValue.getClass().isAnonymousClass() ? prevValue.getClass().getSuperclass() : prevValue.getClass());
|
var childFields = parser.getJson().getFields(prevValue.getClass().isAnonymousClass() ? prevValue.getClass().getSuperclass() : prevValue.getClass());
|
||||||
for(var child : jsv){
|
for(var child : jsv){
|
||||||
if(child.name != null){
|
if(child.name != null){
|
||||||
assign(prevValue, child.name, child, !childFields.containsKey(child.name) ? null : new FieldData(childFields.get(child.name)), object, field);
|
assign(prevValue, child.name, child, !childFields.containsKey(child.name) ? null : new FieldData(childFields.get(child.name)), object, field);
|
||||||
@@ -383,7 +397,7 @@ public class ContentPatcher{
|
|||||||
Class<?> actualType = object.getClass();
|
Class<?> actualType = object.getClass();
|
||||||
if(actualType.isAnonymousClass()) actualType = actualType.getSuperclass();
|
if(actualType.isAnonymousClass()) actualType = actualType.getSuperclass();
|
||||||
|
|
||||||
var fields = json.getFields(actualType);
|
var fields = parser.getJson().getFields(actualType);
|
||||||
var fdata = fields.get(field);
|
var fdata = fields.get(field);
|
||||||
if(fdata != null){
|
if(fdata != null){
|
||||||
if(checkField(fdata.field)) return null;
|
if(checkField(fdata.field)) return null;
|
||||||
@@ -407,7 +421,7 @@ public class ContentPatcher{
|
|||||||
void modifiedField(Object target, String field, Object value){
|
void modifiedField(Object target, String field, Object value){
|
||||||
if(!applied || target == null) return;
|
if(!applied || target == null) return;
|
||||||
|
|
||||||
var fields = json.getFields(target.getClass());
|
var fields = parser.getJson().getFields(target.getClass());
|
||||||
var meta = fields.get(field);
|
var meta = fields.get(field);
|
||||||
if(meta != null){
|
if(meta != null){
|
||||||
|
|
||||||
@@ -431,7 +445,7 @@ public class ContentPatcher{
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object convertKeyType(String string, Class<?> type){
|
Object convertKeyType(String string, Class<?> type){
|
||||||
return json.fromJson(type, string);
|
return parser.getJson().fromJson(type, string);
|
||||||
}
|
}
|
||||||
|
|
||||||
void warn(String error, Object... fmt){
|
void warn(String error, Object... fmt){
|
||||||
|
|||||||
@@ -971,10 +971,6 @@ public class Mods implements Loadable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContentParser getContentParser(){
|
|
||||||
return parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return the mods that the client is missing.
|
/** @return the mods that the client is missing.
|
||||||
* The inputted array is changed to contain the extra mods that the client has but the server doesn't.*/
|
* The inputted array is changed to contain the extra mods that the client has but the server doesn't.*/
|
||||||
public Seq<String> getIncompatibility(Seq<String> out){
|
public Seq<String> getIncompatibility(Seq<String> out){
|
||||||
|
|||||||
@@ -207,6 +207,20 @@ public class PatcherTests{
|
|||||||
assertEquals(1, Blocks.conveyor.size);
|
assertEquals(1, Blocks.conveyor.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnknownFieldWarn() throws Exception{
|
||||||
|
Vars.state.patcher.apply(Seq.with("""
|
||||||
|
unit.dagger.weapons.+: {
|
||||||
|
bullet: {
|
||||||
|
frogs: 99
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unit.dagger.frogs: 10
|
||||||
|
"""));
|
||||||
|
|
||||||
|
assertEquals(2, Vars.state.patcher.patches.first().warnings.size);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testBigPatch() throws Exception{
|
void testBigPatch() throws Exception{
|
||||||
Vars.state.patcher.apply(Seq.with("""
|
Vars.state.patcher.apply(Seq.with("""
|
||||||
|
|||||||
Reference in New Issue
Block a user