/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.integration.regression; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; import java.util.List; import java.util.Map; import com.google.common.collect.Maps; import com.opengamma.util.ArgumentChecker; /** * Abstraction of the I/O pattern for the database dump/restore tools. Data is written to a logical sub-directory for each object type with a logical file for each object. Sub-classes may implement * this with physical directories and files, or use a structured file format such as a ZIP file. */ public abstract class RegressionIO { /** * The base location. Depending on the sub-class this might be a physical directory root or a single physical file such as a ZIP archive. */ private final File _baseFile; /** * Formatting SPI. Possible implementations might include, for example, Java serialization, Fudge binary encodings or more verbose XML representations. */ public interface Format { /** * Initializes any internal context needed by an I/O instance for {@link #read} operations. * * @param context the previous context (created by {@link #write} for example) or null if none * @return the context object, or null if none required */ Object openRead(Object context); /** * Initializes any internal context needed by an I/O instance for {@link #write} operations. * * @param context the previous context (created by {@link #write} for example) or null if none * @return the context object, or null if none required */ Object openWrite(Object context); /** * Returns a logical file extension, if any. * * @param context the context object returned by {@link #init} * @return the logical extension, for example ".txt", or null if none */ String getLogicalFileExtension(Object context); /** * Formats an object to an output stream. * * @param context the context object returned by {@link #init} * @param o the object to write, not null * @param dest the target stream, not null */ void write(Object context, Object o, OutputStream dest) throws IOException; /** * Decodes an object from an input stream. * * @param context the context object returned by {@link #init} * @param in the input stream, not null * @return the object read, not null */ Object read(Object context, InputStream in) throws IOException; /** * Terminates a context returned by {@link #initRead}. * * @param context the value returned by a previous call to {@code initRead}. * @return the updated context, or null if none now exists */ Object closeRead(Object context); /** * Terminates a context returned by {@link #initWrite}. * * @param context the value returned by a previous call to {@code initWrite}. * @return the updated context, or null if none now exists */ Object closeWrite(Object context); } /** * The formatter to use for actual I/O. */ private final Format _format; /** * The formatting context, if any. */ private Object _formatContext; /** * Creates a new instance. * * @param baseFile the base file - the exact meaning will depend on the sub-class, not null * @param format the format to use for each object, not null */ public RegressionIO(final File baseFile, final Format format) { _baseFile = ArgumentChecker.notNull(baseFile, "notNull"); _format = ArgumentChecker.notNull(format, "format"); } /** * Returns the base location - the exact meaning will depend on the sub-class. * * @return the base location, not null */ protected File getBaseFile() { return _baseFile; } /** * Returns the format to use for each object. * * @return the format, not null */ protected Format getFormat() { return _format; } /** * Returns the current format context. * * @return the format context, if any */ protected Object getFormatContext() { return _formatContext; } /** * Performs any initialization prior to the first write, such as opening files or preparing caches/buffers. */ public void beginWrite() throws IOException { _formatContext = getFormat().openWrite(_formatContext); } /** * Writes out an object. * * @param type the type classifier, null for none * @param o the object to write out, not null * @param identifier the object identifier, must be unique within a given object type, not null */ public abstract void write(String type, Object o, String identifier) throws IOException; /** * Bulk write operation. * <p> * The default implementation just calls {@link #write} but a sub-class might have a more efficient/appropriate form to use. * * @param type the type classifier, null for none * @param os the objects to write out, as a map of identifier to value, not null */ public void write(final String type, final Map<String, Object> os) throws IOException { for (Map.Entry<String, Object> oe : os.entrySet()) { write(type, oe.getValue(), oe.getKey()); } } /** * Performs any finalization after the final write, such as flushing caches/buffers or closing files. */ public void endWrite() throws IOException { _formatContext = getFormat().closeWrite(_formatContext); } /** * Performs any initialization prior to the first read, such as opening files or preparing caches/buffers. */ public void beginRead() throws IOException { _formatContext = getFormat().openRead(_formatContext); } /** * Reads an object. * * @param type the type classifier, null for none * @param identifier the object identifier, must be unique within a given object type, not null * @return the read object, not null */ public abstract Object read(String type, String identifier) throws IOException; /** * Enumerates all available objects for a type. * * @param type the type classifier, null for none * @return the object identifiers, not null */ public abstract List<String> enumObjects(String type) throws IOException; /** * Bulk read operation to fetch an object sub-set. * <p> * The default implementation just calls {@link #read(String,String)} but a sub-class might have a more efficient/appropriate form to use. * * @param type the type classifier, null for none * @param identifiers the object identifiers to read, not null * @return the objects as a map from identifiers to values, not null */ public Map<String, Object> read(final String type, final Collection<String> identifiers) throws IOException { final Map<String, Object> result = Maps.newHashMapWithExpectedSize(identifiers.size()); for (String identifier : identifiers) { result.put(identifier, read(type, identifier)); } return result; } /** * Bulk read operation to fetch all objects. * <p> * The default implementation just calls {@link #enumObjects} and {@link #read(String,Collection)} but a sub-class might have a more efficient/appropriate form to use. * * @param type the type classifier, null for none * @return the objects as a map of identifiers to values, not null */ public Map<String, Object> readAll(final String type) throws IOException { return read(type, enumObjects(type)); } /** * Performs any finalization after the final read, such as closing files. */ public void endRead() throws IOException { _formatContext = getFormat().closeRead(_formatContext); } protected String createFilename(final String identifier) { final String ext = getFormat().getLogicalFileExtension(getFormatContext()); if (ext != null) { return identifier + ext; } else { return identifier; } } protected boolean isIdentifierIncluded(String name) { String ext = getFormat().getLogicalFileExtension(getFormatContext()); return ext == null || name.endsWith(ext); } protected String stripIdentifierExtension(String name) { String ext = getFormat().getLogicalFileExtension(getFormatContext()); if (ext == null) { return name; } else if (name.endsWith(ext)) { return name.substring(0, name.length() - ext.length()); } else { return name; } } }