org.lenskit.specs.SpecUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lenskit-specs Show documentation
Show all versions of lenskit-specs Show documentation
JavaBeans describing various LensKit objects, used for serializing between Gradle and the CLI.
/*
* LensKit, an open source recommender systems toolkit.
* Copyright 2010-2014 LensKit Contributors. See CONTRIBUTORS.md.
* Work on LensKit has been funded by the National Science Foundation under
* grants IIS 05-34939, 08-08692, 08-12148, and 10-17697.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.lenskit.specs;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.apache.commons.lang3.reflect.MethodUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.util.List;
import java.util.ServiceLoader;
/**
* Utility functions for working with specifications.
*/
public final class SpecUtils {
private SpecUtils() {}
public static ObjectMapper createMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("LenskitSpecs");
mod.addSerializer(Path.class, new PathSerializer());
mod.addDeserializer(Path.class, new PathDeserializer());
mapper.registerModule(mod);
return mapper;
}
/**
* Read a specification type from a file.
* @param type The specification type.
* @param file The file to read from.
* @param The specification type.
* @return A deserialized specification.
* @throws IOException if there is an error reading the file.
*/
public static T load(Class type, Path file) throws IOException {
ObjectReader reader = createMapper().reader(type);
return reader.readValue(file.toFile());
}
/**
* Read a list of specifications from a file.
* @param type The specification type.
* @param file The file to read from.
* @param The specification type.
* @return A deserialized specification.
* @throws IOException if there is an error reading the file.
*/
public static List loadList(Class type, Path file) throws IOException {
ObjectMapper mapper = createMapper();
JavaType listType = mapper.getTypeFactory().constructCollectionType(List.class, type);
ObjectReader reader = createMapper().reader(listType);
return reader.readValue(file.toFile());
}
/**
* Parse a specification from a string.
* @param type The specification type.
* @param json A string of JSON data.
* @param The specification type.
* @return A deserialized specification.
*/
public static T parse(Class type, String json) {
ObjectReader reader = createMapper().reader(type);
try {
return reader.readValue(json);
} catch (IOException e) {
throw new RuntimeException("error parsing JSON specification", e);
}
}
/**
* Convert a specification to a string.
* @param spec The specification.
* @return The JSON string representation of the specification.
*/
public static String stringify(AbstractSpec spec) {
ObjectWriter writer = createMapper().writer();
try {
return writer.writeValueAsString(spec);
} catch (JsonProcessingException e) {
throw new RuntimeException("Error stringifying JSON", e);
}
}
/**
* Write a specification to a file.
* @param spec The specification.
* @param file The file to write to.
* @throws IOException if there is an error writing the specification.
*/
public static void write(Object spec, Path file) throws IOException {
ObjectWriter writer = createMapper().writer()
.with(SerializationFeature.INDENT_OUTPUT);
writer.writeValue(file.toFile(), spec);
}
/**
* Build an object from a specification. On its own, this method does very little. It depends on support from
* objects in order to work.
*
* It operates as follows:
*
* 1. Load the {@link SpecHandler}s using {@link java.util.ServiceLoader}.
* 2. Query each spec handler, in turn, to try to find one that can build an object of type `type` from the spec.
* 3. If no such handler is found, try to invoke a static `fromSpec(AbstractSpec)` method on `type`.
* 4. If no such method is found, or it returns `null`, throw {@link NoSpecHandlerFound}.
*
* @param type The type of object to build.
* @param spec The specification to use.
* @param The built object type.
* @return The built object. Will be `null` if and only if `spec` is `null`.
* @throws NoSpecHandlerFound if no spec handler or `fromSpec` method can be found.
*/
@Nullable
public static T buildObject(@Nonnull Class type, @Nullable AbstractSpec spec) {
return buildObject(type, spec, null);
}
/**
* Build an object from a specification. On its own, this method does very little. It depends on support from
* objects in order to work.
*
* It operates as follows:
*
* 1. Load the {@link SpecHandler}s using {@link java.util.ServiceLoader}.
* 2. Query each spec handler, in turn, to try to find one that can build an object of type `type` from the spec.
* 3. If no such handler is found, try to invoke a static `fromSpec(AbstractSpec)` method on `type`.
* 4. If no such method is found, or it returns `null`, throw {@link NoSpecHandlerFound}.
*
* @param type The type of object to build.
* @param spec The specification to use.
* @param cl The class loader to search.
* @param The built object type.
* @return The built object. Will be `null` if and only if `spec` is `null`.
* @throws NoSpecHandlerFound if no spec handler or `fromSpec` method can be found.
*/
@Nullable
public static T buildObject(@Nonnull Class type, @Nullable AbstractSpec spec, ClassLoader cl) {
if (spec == null) {
return null;
}
ServiceLoader loader;
if (cl == null) {
loader = ServiceLoader.load(SpecHandler.class);
} else {
loader = ServiceLoader.load(SpecHandler.class, cl);
}
for (SpecHandler h: loader) {
T obj = h.build(type, spec);
if (obj != null) {
return obj;
}
}
try {
T obj = type.cast(MethodUtils.invokeStaticMethod(type, "fromSpec", spec));
if (obj == null) {
throw new NoSpecHandlerFound("No spec handler to build " + type + " from " + spec);
} else {
return obj;
}
} catch (NoSuchMethodException e) {
throw new NoSpecHandlerFound("No spec handler to build " + type + " from " + spec);
} catch (IllegalAccessException e) {
throw new RuntimeException("access error invoking fromSpec on " + type, e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occurred in fromSpec on " + type, e);
}
}
/**
* Make a copy of a spec. Rather than implementing the complicated {@link Cloneable} infrastructure, we just
* round-trip the spec through JSON and copy all specs easily.
*
* @param spec The spec to copy.
* @param The spec type.
* @return The copied spec.
*/
@SuppressWarnings("unchecked")
public static T copySpec(T spec) {
if (spec == null) {
return null;
}
ObjectMapper mapper = createMapper();
JsonNode node = mapper.convertValue(spec, JsonNode.class);
return (T) mapper.convertValue(node, spec.getClass());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy