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

liquibase.parser.core.ParsedNode Maven / Gradle / Ivy

There is a newer version: 4.30.0
Show newest version
package liquibase.parser.core;

import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.SequenceCurrentValueFunction;
import liquibase.statement.SequenceNextValueFunction;
import liquibase.util.ISODateFormat;
import liquibase.util.StringUtil;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

/**
 * Acts as a standard abstract syntax layer for changelogs defined in different formats.
 * {@link liquibase.parser.ChangeLogParser} implementations and other classes that work with multiple formats can create objects
 * directs or create instances of this class which can then be passed to the load() method of the object they want to configure.
 * For example, {@link liquibase.change.Change#load(ParsedNode, liquibase.resource.ResourceAccessor)}.
 * 

* ParsedNodes are a simple key/value structure with the following characteristics: *

    *
  • Keys include a namespace as well as the node name
  • *
  • There can be multiple children nodes with the same node namespace+name
  • *
  • There is an unkeyed "value" object in addition to the children nodes
  • *
  • The value node cannot be a ParsedNode. If you attempt to set value to be or contain a ParsedNode it will actually be set as a child
  • *
*/ public class ParsedNode { private final String namespace; private final String name; private final List children = new ArrayList<>(); private Object value; public ParsedNode(String namespace, String name) { this.namespace = namespace; this.name = name; } /** * Each node key contains both a namespace and a name which together identifies the node. */ public String getNamespace() { return namespace; } /** * Each node key contains both a namespace and a name which together identifies the node. */ public String getName() { return name; } /** * Returns the child ParsedNodes of this node. Returned list is unmodifiableList. */ public List getChildren() { return Collections.unmodifiableList(children); } /** * Returns all child nodes with the given namespace and name. * If none match, an empty list is returned. * Returned list is unmodifiableList. */ public List getChildren(String namespace, String nodename) { List returnList = new ArrayList<>(); for (ParsedNode node : children) { if (nodeMatches(node, namespace, nodename)) { returnList.add(node); } } return Collections.unmodifiableList(returnList); } /** * Return the value associated with this node. */ public Object getValue() { return value; } /** * Sets the value of this ParsedNode. * If the passed value is a ParsedNode, it is added as a child, not as the value. * If the passed value is a Map, it is converted to a ParsedNode and added as a child, not as the value. * If the passed value is a Collection, each object is added as a child if it is a ParsedNode or a Map. * If there are multiple simple values in a passed collection, value is set to a List. If there is a single value in a collection, value is set to the single value. */ public ParsedNode setValue(Object value) throws ParsedNodeException { if (value instanceof ParsedNode) { this.addChild((ParsedNode) value); } else if (value instanceof Collection) { List newValue = new ArrayList(); for (Object obj : ((Collection) value)) { if (obj instanceof Map) { addChildren((Map) obj); } else if (obj instanceof ParsedNode) { addChild(((ParsedNode) obj)); } else { newValue.add(obj); } } if (newValue.isEmpty()) { //do nothing } else if (newValue.size() == 1) { this.value = newValue.get(0); } else { this.value = newValue; } } else if (value instanceof Map) { addChildren(((Map) value)); } else { this.value = value; } return this; } /** * Return the value associated with this node converted to the given type. * * @throws ParsedNodeException if the current value type cannot be converted */ public T getValue(Class type) throws ParsedNodeException { return convertObject(value, type); } /** * Convenience method to add a new ParsedNode with the passed namespace/name and value */ public ParsedNode addChild(String namespace, String nodeName, Object value) throws ParsedNodeException { addChild(createNode(namespace, nodeName).setValue(value)); return this; } protected ParsedNode createNode(String namespace, String nodeName) { return new ParsedNode(namespace, nodeName); } /** * Adds the given ParsedNode as a child */ public ParsedNode addChild(ParsedNode node) throws ParsedNodeException { children.add(node); return this; } /** * Adds the given map as children of this node. * If the passed map is empty, it is a no-op * For each key in the map, a new child is added with the key as the name and the value (with all {@link #setValue(Object)}) logic) is the value. */ public ParsedNode addChildren(Map child) throws ParsedNodeException { if ((child == null) || child.isEmpty()) { return this; //do nothing } for (Map.Entry entry : child.entrySet()) { this.addChild(null, entry.getKey(), entry.getValue()); } return this; } /** * Returns the ParsedNode defined by the given namespace and name. * @throws liquibase.parser.core.ParsedNodeException if multiple nodes match. */ public ParsedNode getChild(String namespace, String name) throws ParsedNodeException { ParsedNode returnNode = null; for (ParsedNode node : children) { if (nodeMatches(node, namespace, name)) { if (returnNode != null) { throw new ParsedNodeException("Multiple nodes match "+namespace+"/"+name); } returnNode = node; } } return returnNode; } public ParsedNode removeChild(String namespace, String name) throws ParsedNodeException { children.removeIf(node -> nodeMatches(node, namespace, name)); return this; } /** * Matches the namespace and name of this node. Matching is performed case-insensitively. * * @param node The node to match against. * @param namespace The namespace to match against. * @param nodename The name to match against. * @return {@code true} if the namespace and name of the node match the specified namespace and name, * ignoring case; {@code false} otherwise. */ protected boolean nodeMatches(ParsedNode node, String namespace, String nodename) { return namespaceMatches(node, namespace) && node.getName().equalsIgnoreCase(nodename); } protected boolean namespaceMatches(ParsedNode node, String namespace) { if (node.getNamespace() == null) { return namespace == null; } return node.getNamespace().equals(namespace); } /** * Convenience method for {@link #getChildValue(String, String, Class)} but returns the passed defaultValue if the given node is null or not defined. */ public T getChildValue(String namespace, String nodename, T defaultValue) throws ParsedNodeException { T value = (T) getChildValue(namespace, nodename, defaultValue.getClass()); if (value == null) { return defaultValue; } return value; } /** * Returns the value of the given node, converted to the passed type. * @throws liquibase.parser.core.ParsedNodeException if there is an error converting the value */ public T getChildValue(String namespace, String nodename, Class type) throws ParsedNodeException { Object rawValue = getChildValue(namespace, nodename); if (rawValue == null) { return null; } return convertObject(rawValue, type); } protected T convertObject(Object rawValue, Class type) throws ParsedNodeException { if (rawValue == null) { return null; } if (type.isAssignableFrom(rawValue.getClass())) { return (T) rawValue; } try { if (type.equals(String.class)) { return (T) rawValue.toString(); } else if (type.equals(Integer.class) || type.equals(int.class)) { return (T) Integer.valueOf(rawValue.toString()); } else if (type.equals(Byte.class) || type.equals(byte.class)) { return (T) Byte.valueOf(rawValue.toString()); } else if ((type.equals(Character.class) || type.equals(char.class)) && (rawValue instanceof String) && ((String) rawValue).length() == 1) { return (T) Character.valueOf(((String) rawValue).charAt(0)); } else if (type.equals(Short.class) || type.equals(short.class)) { return (T) Short.valueOf(rawValue.toString()); } else if (type.equals(Float.class) || type.equals(float.class)) { return (T) Float.valueOf(rawValue.toString()); } else if (type.equals(Double.class) || type.equals(double.class)) { return (T) Double.valueOf(rawValue.toString()); } else if (type.equals(Long.class) || type.equals(long.class)) { return (T) Long.valueOf(rawValue.toString()); } else if (type.equals(BigInteger.class)) { return (T) new BigInteger(rawValue.toString()); } else if (type.equals(BigDecimal.class)) { return (T) new BigDecimal(rawValue.toString()); } else if ((type.equals(Boolean.class) || type.equals(boolean.class))) { return (T) Boolean.valueOf(rawValue.toString()); } else if (type.isAssignableFrom(Date.class)) { return (T) new ISODateFormat().parse(rawValue.toString()); } else if (type.equals(SequenceNextValueFunction.class)) { return (T) new SequenceNextValueFunction(rawValue.toString()); } else if (type.equals(SequenceCurrentValueFunction.class)) { return (T) new SequenceCurrentValueFunction(rawValue.toString()); } else if (type.equals(DatabaseFunction.class)) { return (T) new DatabaseFunction(rawValue.toString()); } else if (type.isEnum()) { return (T) Enum.valueOf((Class)type, rawValue.toString()); } else { throw new UnexpectedLiquibaseException("Cannot convert " + rawValue.getClass().getName() + " '" + rawValue + "' to " + type.getName()); } } catch (Exception e) { if (e instanceof UnexpectedLiquibaseException) { throw (UnexpectedLiquibaseException) e; } throw new ParsedNodeException(e); } } /** * Returns the value of the given node with no conversion attempted. */ public Object getChildValue(String namespace, String nodename) throws ParsedNodeException { ParsedNode child = getChild(namespace, nodename); if (child == null) { return null; } return child.getValue(); } @Override public String toString() { String string = name; if (!children.isEmpty()) { string += "[" + StringUtil.join(children, ",", new StringUtil.ToStringFormatter(), true) + "]"; } if (value != null) { String valueString; if (value instanceof Collection) { valueString = "("+ StringUtil.join(((Collection) value), ",", new StringUtil.ToStringFormatter(), true)+")"; } else { valueString = value.toString(); } string += "="+valueString; } return string; } @Override public boolean equals(Object obj) { return (obj instanceof ParsedNode) && this.toString().equals(obj.toString()); } @Override public int hashCode() { return this.toString().hashCode(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy