All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.enterprisemath.utils.DomainUtils Maven / Gradle / Ivy

Go to download

Collection of utility classes for large scale projects focusing on robust and testable code.

There is a newer version: 4.1.1
Show newest version
package com.enterprisemath.utils;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Contains static method which simplifies the manipulation with domain objects.
 * 
 * @author radek.hecl
 *
 */
public class DomainUtils {

    /**
     * To prevent construction.
     */
    private DomainUtils() {
    }
    
    /**
     * Null safe copy date function.
     * 
     * @param source source date, can be null
     * @return copy of the source date or null if source is null 
     */
    public static Date copyDate(Date source) {
        if (source == null) {
            return null;
        }
        return new Date(source.getTime());
    }

    /**
     * Null safe function for creating a soft copy of the list.
     * It the source is null, then empty list will be returned.
     * 
     * @param source source object, can be any collection
     * @return soft copy of the source object
     */
    public static  List softCopyList(Collection source) {
        if (source == null) {
            source = Collections.emptyList();
        }
        return new ArrayList(source);
    }

    /**
     * Null safe function for creating a soft copy of the set.
     * If the source is null, then empty set will be returned.
     * 
     * @param source source object, can be any collection
     * @return soft copy of the source object
     */
    public static  Set softCopySet(Collection source) {
        if (source == null) {
            source = Collections.emptySet();
        }
        return new HashSet(source);
    }

    /**
     * Null safe function for creating a soft copy of the sorted set.
     * If the source is null, then empty set will be returned.
     * 
     * @param source source object, can be any collection, but elements must 
     * @return soft copy of the source object
     */
    public static > SortedSet softCopySortedSet(Collection source) {
        if (source == null) {
            source = Collections.emptySet();
        }
        return new TreeSet(source);
    }
    
    
    /**
     * Null safe function for creating a soft copy of the map.
     * If the source is null, then empty map will be returned.
     * 
     * @param source source object
     * @return soft copy of the source object
     */
    public static  Map softCopyMap(Map source) {
        if (source == null) {
            source = Collections.emptyMap();
        }
        return new HashMap(source);
    }

    /**
     * Make a soft copy of the source map, but without the keys which match the specified regular expression.
     * For example if the given map is { "hello" : 1, "world" : 2} and regexp is "hello", then
     * result copy will be map { "hello" : 1 }.
     * 
     * @param source source map, must be without null keys
     * @param regexp regular expression to filter out the keys from the copy of the map
     * @return soft copy of the source map without the keys which are matching the expression
     */
    public static  Map softCopyMapWithoutMatchedKeys(Map source, String regexp) {
        Map res = new HashMap();
        for (String key : source.keySet()) {
            if (!key.matches(regexp)) {
                res.put(key, source.get(key));
            }
        }
        return res;
    }

    /**
     * Merges several maps into one.
     * If there is no map to merge, then returns empty map.
     * If there is at least one map, then takes elements form the first, then apply from the second
     * and so on.
     * Examples (in JSON notation):
     * 
    *
  • {"a" : 1} + {"b" : 2} -> {"a" : 1, "b" : 2}
  • *
  • {"a" : 1} + {"a" : 2} -> {"a" : 2}
  • *
  • {"a" : 1} + {"b" : 2} + {"a" : "hello"} -> {"a" : "hello", "b" : 2}
  • *
* * @param maps maps to merge * @return merged map */ public static Map mergeMaps(Map... maps) { Map res = new HashMap(); for (int i = 0; i < maps.length; ++i) { res.putAll(maps[i]); } return res; } /** * Creates map where prefix is added to the each key. * Examples: *
    *
  • "hello.", {"world" : 1, "baby" : 2} -> {"hello.world" : 1, "hello.baby" : 2}
  • *
  • "$", {"world" : 1, "baby" : 2} -> {"$world" : 1, "$baby" : 2}
  • *
* * @param prefix prefix which will be used, cannot be null * @param source source map, cannot be null or have null as a key * @return map with all keys being prefixed */ public static Map createMapPrefixOnKeys(String prefix, Map source) { ValidationUtils.guardNotNull(prefix, "prefix cannot be null"); ValidationUtils.guardNotNull(source, "source cannot be null"); ValidationUtils.guardNotNullCollection(source.keySet(), "source.keySet() cannot have null element"); Map res = new HashMap(); for (String key : source.keySet()) { res.put(prefix + key, source.get(key)); } return res; } /** * Returns the copy of the object. This is not the deep copy. * Should provide the balance between performance and security. * If the source is null, then null will be returned. * If the source is date, then the copy will be returned. * If the source is list, set or map, then soft copy will be returned. * If the source is any other type, then source object will be returned. * * @param source source object * @return copy of the given object */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static Object copyObject(Object source) { if (source instanceof Date) { return copyDate((Date) source); } if (source instanceof List) { return softCopyList((List) source); } if (source instanceof Set) { return softCopySet((Set) source); } if (source instanceof Map) { return softCopyMap((Map) source); } return source; } /** * Wrapper function around java.util.Arrays.asList(T... a) to create a set of objects. * * @return created set */ public static Set createSet(T... a) { return new HashSet(Arrays.asList(a)); } /** * Returns the set which contains all the numbers from the specified interval. * Borders are included. * Examples: *
    *
  • min = 1, max = 3 -> [1, 2, 3]
  • *
  • min = 1, max = 1 -> [1]
  • *
  • min = 1, max = 0 -> []
  • *
* * @param min minimum (included) * @param max maximum (included) * @return created interval set */ public static Set createLongIntervalSet(long min, long max) { Set res = new HashSet(); for (long i = min; i <= max; ++i) { res.add(i); } return res; } /** * Takes the collection and splits it into the several lists. * Every of these lists has maximum number of elements which is less or equal to the max parameters. * If source collection has some duplicated elements, then all of them stays there. * If source collection is null or empty, then list with no sub lists will be returned. * The ordering within the split lists is the same as within the source collection. * * @param source source collection * @param max maximum allowed number of elements per one list, must be positive number * @return source collection split into lists */ public static List> splitIntoLists(Collection source, int max) { ValidationUtils.guardPositiveInt(max, "max must be positive number"); List sourceCopy = softCopyList(source); if (sourceCopy.isEmpty()) { return Collections.emptyList(); } List> res = new ArrayList>(); List part = new ArrayList(); for (T obj : sourceCopy) { part.add(obj); if (part.size() == max) { res.add(part); part = new ArrayList(); } } if (!part.isEmpty()) { res.add(part); } return res; } /** * Takes the collection and splits it into the several sets. * Every of these sets has maximum number of elements which is less or equal to the max parameters. * If source collection has some duplicated elements, then all of them are merged. * If source collection is null or empty, then list with no sub sets will be returned. * The ordering within the split sets is not defined. * * @param source source collection * @param max maximum allowed number of elements per one list, must be positive number * @return source collection split into lists */ public static List> splitIntoSets(Collection source, int max) { ValidationUtils.guardPositiveInt(max, "max must be positive number"); Set sourceCopy = softCopySet(source); if (sourceCopy.isEmpty()) { return Collections.emptyList(); } List> res = new ArrayList>(); Set part = new HashSet(); for (T obj : sourceCopy) { part.add(obj); if (part.size() == max) { res.add(part); part = new HashSet(); } } if (!part.isEmpty()) { res.add(part); } return res; } /** * Creates the document from a given XML file. * * @param file file to parse * @return created DOM document */ public static Document createDocumentFromXmlFile(File file) { InputStream is = null; try { is = new FileInputStream(file); DocumentBuilder docBuilder = DocumentBuilderFactory. newInstance(). newDocumentBuilder(); Document doc = docBuilder.parse(is); is.close(); return doc; } catch (ParserConfigurationException e) { throw new RuntimeException(e); } catch (SAXException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } finally { IOUtils.closeQuietly(is); } } /** * Creates the document from a given XML file, specified by bytes. * * @param bytes bytes containing file to parse * @return created DOM document */ public static Document createDocumentFromXmlBytes(byte[] bytes) { InputStream is = null; try { is = new ByteArrayInputStream(bytes); DocumentBuilder docBuilder = DocumentBuilderFactory. newInstance(). newDocumentBuilder(); Document doc = docBuilder.parse(is); is.close(); return doc; } catch (ParserConfigurationException e) { throw new RuntimeException(e); } catch (SAXException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } finally { IOUtils.closeQuietly(is); } } /** * Returns object property by the specified enumeration name. * First searches for getter which starting with "get". * If this getter is not presented, then search for the one which starting with "is". * Examples of getters for a specified enumeration: *
    *
  • ID => getId(), isId()
  • *
  • EVENT_TIMESTAMP => getEventTimestamp(), isEventTimestamp()
  • *
* If getter is presented, then returns the value. * Note: This method uses reflection, so speed is not great. * * @param obj object from which property should be obtained * @param enm enumeration which will be used to find the property * @return value of the property */ public static Object getPropertyByEnum(Object obj, Enum enm) { ValidationUtils.guardNotNull(obj, "obj cannot be null"); ValidationUtils.guardNotNull(enm, "enm cannot be null"); String name = enm.name().toLowerCase(); String getterBase = ""; boolean capital = true; for (int i = 0; i < name.length(); ++i) { char c = name.charAt(i); if (c == '_') { capital = true; } else if (capital) { getterBase += String.valueOf(c).toUpperCase(); capital = false; } else { getterBase += String.valueOf(c); } } Method method = null; Class clazz = obj.getClass(); try { method = clazz.getMethod("get" + getterBase); } catch (NoSuchMethodException e) { try { method = clazz.getMethod("is" + getterBase); } catch (NoSuchMethodException e1) { throw new IllegalArgumentException("getter doesn't exists: Class = " + clazz + "; Enum = " + enm); } catch (SecurityException e1) { throw new RuntimeException(e1); } } catch (SecurityException e) { throw new RuntimeException(e); } try { return method.invoke(obj); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } /** * Null safe equals method. Returns true if both objects are null or equals each other. * This method expects equals method to be well defined (reflexivity, symetricity, transitivity) in both objects. * * @param obj1 first object * @param obj2 second object * @return true if both objects are null or if they equals, false otherwise */ public static boolean safeEquals(Object obj1, Object obj2) { if (obj1 == null && obj2 == null) { return true; } if (obj1 == null || obj2 == null) { return false; } return obj1.equals(obj2); } /** * Converts property map into the velocity model. * This means deep conversion of these property key types is done. *
    *
  • Properties with key in XX format are converted as they are.
  • *
  • Properties with key in XX.YY format are converted into map XX = {YY}
  • *
  • Properties with key in XX[NUM] format are converted into list
  • *
  • Properties with key in XX[NUM].YY format are converted into list of maps
  • *
* * @param properties properties to be converted * @return velocity model */ public static Map convertPropertyMapIntoVelocityModel(Map properties) { Map res = new HashMap(); Map> maps = new HashMap>(); Map>> arrayMaps = new HashMap>>(); Map> lists = new HashMap>(); for (String key : properties.keySet()) { if (key.matches("^[^\\.\\[\\]]+$")) { res.put(key, properties.get(key)); } else if (key.matches("^[^\\[\\]]+\\..+$")) { String[] parts = key.split("\\.", 2); String mapKey = parts[0]; String subKey = parts[1]; if (!maps.containsKey(mapKey)) { maps.put(mapKey, new HashMap()); } maps.get(mapKey).put(subKey, properties.get(key)); } else if (key.matches("^[^\\.]+\\[[0-9]+\\]\\..+$")) { String[] parts = key.split("[\\[\\]]", 3); String mapKey = parts[0]; int index = Integer.valueOf(parts[1]); String subKey = parts[2].substring(1); if (!arrayMaps.containsKey(mapKey)) { arrayMaps.put(mapKey, new TreeMap>()); } if (!arrayMaps.get(mapKey).containsKey(index)) { arrayMaps.get(mapKey).put(index, new HashMap()); } arrayMaps.get(mapKey).get(index).put(subKey, properties.get(key)); } else if (key.matches("^[^\\.]+\\[[0-9]+\\]$")) { String[] parts = key.split("[\\[\\]]", 3); String mapKey = parts[0]; int index = Integer.valueOf(parts[1]); if (!lists.containsKey(mapKey)) { lists.put(mapKey, new TreeMap()); } lists.get(mapKey).put(index, properties.get(key)); } else { throw new RuntimeException("unsupported property key, please implement me: key = " + key); } } // transform maps and arrays for (String key : maps.keySet()) { res.put(key, convertPropertyMapIntoVelocityModel(maps.get(key))); } for (String key : arrayMaps.keySet()) { List> list = new ArrayList>(); for (int index : arrayMaps.get(key).keySet()) { list.add(convertPropertyMapIntoVelocityModel(arrayMaps.get(key).get(index))); } res.put(key, list); } for (String key : lists.keySet()) { List list = new ArrayList(); for (int index : lists.get(key).keySet()) { list.add(lists.get(key).get(index)); } res.put(key, list); } return res; } /** * Builds the object with the specified configuration. * * @param className class name of the result object. This class must be public and have inner static class * called Builder which contains public default constructor and public method build. * Result of the build method is returned from this method. * @param configuration configuration for the builder. Keys are property names. Values are list of objects. * If there is a method set + upperCaseFirst(key), then this method is called. Otherwise exception is thrown. * @return created object */ public static Object buildObject(String className, Map configuration) { try { Class builderClass = Class.forName(className + "$Builder"); Constructor constructor = builderClass.getConstructor(); Object builder = constructor.newInstance(); Method[] methods = builderClass.getMethods(); for (String key : configuration.keySet()) { Object val = configuration.get(key); Method setter = null;//builderClass.getMethod("set" + StringUtils.capitalize(key), val.getClass()); for (Method candidate : methods) { if (!candidate.getName().equals("set" + StringUtils.capitalize(key))) { continue; } Class[] parameters = candidate.getParameterTypes(); if (parameters.length != 1) { continue; } if (parameters[0].isPrimitive()) { if (parameters[0].equals(double.class) && val.getClass().equals(Double.class) || parameters[0].equals(float.class) && val.getClass().equals(Float.class) || parameters[0].equals(long.class) && val.getClass().equals(Long.class) || parameters[0].equals(int.class) && val.getClass().equals(Integer.class) || parameters[0].equals(byte.class) && val.getClass().equals(Byte.class) || parameters[0].equals(boolean.class) && val.getClass().equals(Boolean.class)) { setter = candidate; continue; } } if (!parameters[0].isAssignableFrom(val.getClass())) { continue; } setter = candidate; } ValidationUtils.guardNotNull(setter, "unable to find setter: property = " + key); setter.invoke(builder, val); } Method build = builderClass.getMethod("build"); return build.invoke(builder); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } /** * Builds object from the specified xml node. * Xml node has the following structure:
* * <object>
*   <class>org.netwia.ObjectClass</class>
*   <configuration>
*     <property>
*       <name>property1</name>
*       <type>String</type>
*       <value>value1</value>
*     </property>
*   </configuration>
* </object> *

* Where object class is any class which is following the builder pattern. Configuration block allows to have multiple * properties. Supported property types are String, Double, Float, Long, Integer, Byte, Boolean, Enumeration and Dependency. * In case of Enumeration, value is fully qualified value of the enumeration. * In case of Dependency type, value must match key in the specified environment. * * @param node XML node to be parsed, expected to be the object node specified in the structure * @param environment environment * @return created object * @throws IndexOutOfBoundsException in case of Dependency property points to the non existing key in the specified environment */ public static Object buildObjectFromXml(Node node, Map environment) { try { Map configuration = new HashMap(); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression nameExpr = xpath.compile("name"); XPathExpression typeExpr = xpath.compile("type"); XPathExpression valueExpr = xpath.compile("value"); String className = xpath.compile("class").evaluate(node); NodeList properties = (NodeList)xpath.compile("configuration/property").evaluate(node, XPathConstants.NODESET); for (int i = 0; i < properties.getLength(); ++i) { Node propertyNode = properties.item(i); String name = nameExpr.evaluate(propertyNode); String type = typeExpr.evaluate(propertyNode); String value = valueExpr.evaluate(propertyNode); if (configuration.containsKey(name)) { throw new IllegalArgumentException("property has been defined twice: property = " + name); } if (type.equals("String")) { configuration.put(name, value); } else if (type.equals("Double")) { configuration.put(name, Double.valueOf(value)); } else if (type.equals("Float")) { configuration.put(name, Float.valueOf(value)); } else if (type.equals("Long")) { configuration.put(name, Long.valueOf(value)); } else if (type.equals("Integer")) { configuration.put(name, Integer.valueOf(value)); } else if (type.equals("Byte")) { configuration.put(name, Byte.valueOf(value)); } else if (type.equals("Boolean")) { configuration.put(name, Boolean.valueOf(value)); } else if (type.equals("Enumeration")) { int idx = value.lastIndexOf("."); Class clazz = Class.forName(value.substring(0, idx)); Method m = clazz.getMethod("valueOf", String.class); Object val = m.invoke(null, value.substring(idx + 1)); configuration.put(name, val); } else if (type.equals("Dependency")) { if (environment.containsKey(value)) { configuration.put(name, environment.get(value)); } else { throw new IndexOutOfBoundsException("dependency not found in the environment: " + "name = " + name + "; environment = " + environment); } } else { throw new IllegalArgumentException("unsupported property type: name = " + name + "; type = " + type); } } return buildObject(className, configuration); } catch (XPathExpressionException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (SecurityException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } /** * Builds the environment from the specified xml node. * Environment is map of string to object where key is unique name of the object. * Result environment contains the originally specified environment plus the new objects from xml. * Xml node cannot contain the name which is already included in the original environment. * In such cases exception is thrown. * Objects can refer each other with non-circular references. * Example of xml:
* <environment>
*   <object>
*     <name>unique-object-name</object>
*     ... class and configuration same as in buildObjectFromXml
*   </object>
*   ... other objects
* </environment>
* * @param node xml node to parse * @param environment original environment which will be used for dependencies and included in the created one * @return created environment * @throws IndexOutOfBoundsException in case of Dependency property points to the non existing key * within the specified environment and other objects from xml * @throws IllegalArgumentException if name from xml is duplicated or points to the existing object in the environment */ public static Map buildEnvironmentFromXml(Node node, Map environment) { try { // phase 1 - extract names with nodes Map name2Node = new HashMap(); Set existingNames = new HashSet(environment.keySet()); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression nameExpr = xpath.compile("name"); NodeList objects = (NodeList)xpath.compile("object").evaluate(node, XPathConstants.NODESET); for (int i = 0; i < objects.getLength(); ++i) { Node objectNode = objects.item(i); String name = nameExpr.evaluate(objectNode); if (existingNames.contains(name)) { throw new IllegalArgumentException("object name already exists, check environment and xml: " + "name = " + name + "; environment = " + environment); } existingNames.add(name); name2Node.put(name, objectNode); } // phase 2 - build the objects Map res = new HashMap(); res.putAll(environment); while (!name2Node.isEmpty()) { Map newName2Node = new HashMap(name2Node); // to prevent concurrent modification exception for (String key : name2Node.keySet()) { try { Object object = buildObjectFromXml(name2Node.get(key), res); res.put(key, object); newName2Node.remove(key); } catch (IndexOutOfBoundsException e) { // nothing here, as this situation is handled after the cycle } } if (name2Node.size() == newName2Node.size()) { throw new IndexOutOfBoundsException("unable to create all object, missing or circular dependency: " + "remainingObjects = " + name2Node.keySet() + "; environment = " + environment); } name2Node = newName2Node; } return res; } catch (XPathExpressionException e) { throw new RuntimeException(e); } catch (SecurityException e) { throw new RuntimeException(e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy