/*
* Copyright (c) 2012 Diamond Light Source Ltd.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package uk.ac.diamond.scisoft.analysis.rpc.flattening;
import java.util.List;
import java.util.Map;
/**
* Implementations can "flatten" or "unflatten" instances of objects. Flattening, allowing the object to be transmitted
* using a small set of more basic types. Unflattening reconstructs the original object. A particular flattener may be
* capable of flattening/unflattening many types of object.
* <p>
* The flattened form must be suitable for passing over XML-RPC with no extensions on. This means the flattened form
* must follow the rules as specified in {@link FlattenedFormChecker#legal(Object)}
* <p>
* When writing a new implementation of IFlattener, it may be worth referring to some examples provided as Unit Tests:
* <ul>
* <li> {@link AddHelperSimpleFlatteningTest} - Flattener for a simple class.
* <li> {@link AddHelperSpecializedMapFlatteningTest} - Flattener for a class that is a specialisation of a class that
* flattening is already supported for, in this example a specialised {@link Map}.
* <li> {@link AddHelperSimpleWithInterfaceFlatteningTest} - Flattener for a simple class. Demonstrates use of
* {@link IFlattens}.
* </ul>
*
* @param <T>
*/
public interface IFlattener<T> {
/**
* Common name for all objects that serialize as a Map for the key name specifying the type of the object. Normally
* the value associated is the canonical name from Java, but it can be any unique string.
*
* @see Class#getCanonicalName()
*/
public static final String TYPE_KEY = "__type__";
/**
* Flattens the given object. Will only be called if canFlatten(obj) is true.
* <p>
* The normal thing to do would be to represent the object as a dictionary ({@link Map}) with the special key
* __type__ ({@link IFlattener#TYPE_KEY}) used to identify the type of the object so that the unflattener can
* identify and unflatten it. By convention, the __type__ key is the qualified Java type name.
*
* @param obj
* object to flatten
* @param rootFlattener
* instance of the IRootFlattener to use to flatten any contained objects
* @return the flattened form of obj. Returned value must be {@link FlattenedFormChecker#legal(Object)}
*/
public Object flatten(Object obj, IRootFlattener rootFlattener);
/**
* Takes a flattened object and reconstructs the original object. Will only be called if canUnFlatten(obj) is true
*
* @param obj
* which is of flattened form and which is {@link FlattenedFormChecker#legal(Object)}
* @param rootFlattener
* instance of the IRootFlattener to use to unflatten any contained objects
* @return the unflattened version of obj
*/
public Object unflatten(Object obj, IRootFlattener rootFlattener);
/**
* Tests whether an object can be flattened by this IFlattener
*
* @param obj
* object to test
* @return true if obj can be flattened
*/
public boolean canFlatten(Object obj);
/**
* Tests whether an object can be unflattened by this IFlattener
*
* @param obj
* which is of flattened form and which is {@link FlattenedFormChecker#legal(Object)}
* @return true if obj can be unflattened
*/
public boolean canUnFlatten(Object obj);
/**
* Specification of legal form for flattened objects. flatten must return an object which will return true when
* passed to {@link #legal(Object)}.
* <p>
* When a flattened form object is passed to {@link IFlattener#canUnFlatten(Object)} or
* {@link IFlattener#canUnFlatten(Object)} the guarantee is that the Key is of type {@link String}. Therefore it is
* safe to add @SuppressWarnings("unchecked") in a case like this:
*
* <pre>
* public boolean canUnFlatten(Object obj) {
* if (obj instanceof Map) {
* @SuppressWarnings("unchecked")
* Map<String, Object> map = (Map<String, Object>) obj;
* // ...
*
* </pre>
*/
public static class FlattenedFormChecker {
/**
* Make sure the flattened object contains only legal types for XML RPC transmission.
* <p>
*
* @param flat
* the flattened object
* @return true if the flat object is legal
*/
public boolean legal(Object flat) {
if (flat instanceof String)
return true;
if (flat instanceof Integer)
return true;
if (flat instanceof Double)
return true;
if (flat instanceof Boolean)
return true;
if (flat instanceof byte[])
return true;
if (flat instanceof Object[]) {
Object[] flatArray = (Object[]) flat;
for (Object elem : flatArray) {
if (!legal(elem))
return false;
}
return true;
}
if (flat instanceof List) {
List<?> flatList = (List<?>) flat;
for (Object elem : flatList) {
if (!legal(elem))
return false;
}
return true;
}
if (flat instanceof Map) {
Map<?, ?> flatMap = (Map<?, ?>) flat;
for (Object elem : flatMap.keySet()) {
if (!(elem instanceof String))
return false;
}
for (Object elem : flatMap.values()) {
if (!legal(elem))
return false;
}
return true;
}
return false;
}
}
}