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

com.imsweb.validation.internal.context.JavaContextParser Maven / Gradle / Ivy

/*
 * Copyright (C) 2011 Information Management Services, Inc.
 */
package com.imsweb.validation.internal.context;

import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;

import org.apache.commons.lang3.math.NumberUtils;

import com.imsweb.validation.ConstructionException;
import com.imsweb.validation.ValidationEngine;
import com.imsweb.validation.internal.context.JavaContextSymbol.JavaContextSymbolType;

/**
 * Created on Oct 4, 2011 by murphyr
 * @author murphyr
 */
@SuppressWarnings({"java:S1149", "rawtypes"})
public final class JavaContextParser {

    /**
     * Created on Oct 4, 2011 by murphyr
     */
    private JavaContextParser() {
    }

    /**
     * Generates an object (a map, list, integer, or string) based on the expression passed in.
     * 

* Created on Oct 4, 2011 by murphyr * @param expression expression to parse * @param currentContext current context * @return the parsed expression (a tree) */ @SuppressWarnings({"unchecked", "JdkObsolete", "java:S1141"}) public static Object parseContext(String expression, Map currentContext) throws ConstructionException { Object result; // this is a bit hackish, but I am parsing out the type hint at the end of the expression instead of using the lexer... String typeHint = null; int typeHintIdx = expression.lastIndexOf(" as "); if (typeHintIdx > 0) { String content = expression.substring(0, typeHintIdx); if (content.endsWith("]")) { typeHint = expression.substring(typeHintIdx + 4); expression = content; } } JavaContextLexer lexer = new JavaContextLexer(new StringReader(expression)); try { JavaContextSymbol token = lexer.next_token(); Queue queue = new LinkedList<>(); queue.add(token); if (token.getType() == JavaContextSymbolType.NUMBER || token.getType() == JavaContextSymbolType.STRING_VAL) { JavaContextSymbol nextToken = lexer.next_token(); //just a string or a integer if (nextToken == null) return token.getValue(); //at this point we only expect a string, an integer, or a range so anything else is invalid syntax if (nextToken.getType() != JavaContextSymbolType.RANGE) throw new ConstructionException("Invalid syntax."); else //its a range so add it to the queue queue.add(nextToken); } while (token != null) { token = lexer.next_token(); queue.add(token); } result = buildListOrMapFromQueue(queue, currentContext, false); // a better way to do this is to create the correct instance of the class right away, but this code is a bit complicated... if (typeHint != null) { try { Class typeClazz = Class.forName(typeHint); Object obj = typeClazz.getDeclaredConstructor().newInstance(); if (result instanceof List) { if (!(obj instanceof List) && !(obj instanceof Set)) throw new ConstructionException("Unable to assign list to type " + typeHint); if (obj instanceof List) ((List)obj).addAll((List)result); else ((Set)obj).addAll((List)result); result = obj; } else if (result instanceof Map) { if (!(obj instanceof Map)) throw new ConstructionException("Unable to assign map to type " + typeHint); ((Map)obj).putAll((Map)result); result = obj; } } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { throw new ConstructionException(e); } } } catch (IOException e) { //should not happen throw new ConstructionException("Invalid syntax.", e); } catch (RuntimeException e2) { throw new ConstructionException(e2.getMessage(), e2); } return result; } /** * Recursive function that takes a queue of tokens, the current context, and a boolean which keeps * track of whether or not it's currently parsing something that is contained in a list. *

* Created on Oct 4, 2011 by murphyr * @param queue queue of tokens * @param currentContext current context * @param containsListBeginToken whether the queue contains a list beginning token * @return a parsed context */ @SuppressWarnings({"unchecked", "JdkObsolete", "ConstantConditions"}) private static Object buildListOrMapFromQueue(Queue queue, Map currentContext, boolean containsListBeginToken) throws ConstructionException { Stack stack = new Stack<>(); Object returnValue = null; JavaContextSymbol s = queue.remove(); while (!queue.isEmpty() && s != null) { if (s.getType() == JavaContextSymbolType.NUMBER || s.getType() == JavaContextSymbolType.STRING_VAL) stack.add(s.getValue()); else if (s.getType() == JavaContextSymbolType.LEFT_BRACKET) stack.add(buildListOrMapFromQueue(queue, currentContext, true)); else if (s.getType() == JavaContextSymbolType.COLON) returnValue = new HashMap<>(); else if (s.getType() == JavaContextSymbolType.COMMA) returnValue = new ArrayList<>(); else if (s.getType() == JavaContextSymbolType.VARIABLE) { if (s.getValue() != null && currentContext.containsKey(s.getValue().toString())) stack.add(currentContext.get(s.getValue().toString())); else if (!ValidationEngine.VALIDATOR_CONTEXT_KEY.equals(s.getValue())) throw new ConstructionException("Could not find key '" + s.getValue() + "' in current contexts."); } else if (s.getType() == JavaContextSymbolType.RANGE) { if (stack.isEmpty() || queue.isEmpty()) throw new ConstructionException("Invalid range syntax."); stack.add(createRange(stack.pop(), queue)); } else if (s.getType() == JavaContextSymbolType.RIGHT_BRACKET) break; s = queue.remove(); } if (returnValue == null) { if (!stack.isEmpty()) returnValue = stack.pop(); if (containsListBeginToken && !(returnValue instanceof RangeObject) && !(returnValue instanceof List) && !(returnValue instanceof Map)) returnValue = returnValue == null ? new ArrayList<>() : Collections.singletonList(returnValue); if (returnValue instanceof RangeObject) returnValue = ((RangeObject)returnValue).getRangeList(); } else { //list or map if (returnValue instanceof ArrayList) { while (!stack.isEmpty()) { Object item = stack.pop(); //if it's a range then insert them into the list one by one, starting from the last element. we reverse them later. if (item instanceof RangeObject) for (int i = ((RangeObject)item).getRangeList().size() - 1; i >= 0; i--) ((ArrayList)returnValue).add(((RangeObject)item).getRangeList().get(i)); else ((ArrayList)returnValue).add(item); } //elements are in reverse order because we got them off a stack, so un-reverse them Collections.reverse(((ArrayList)returnValue)); } else { while (!stack.isEmpty()) { Object value = stack.pop(); Object key = stack.pop(); ((HashMap)returnValue).put(key, value); } returnValue = explodeMap(((HashMap)returnValue)); } } return returnValue; } /** * If there is a list of values as a key to a map, this makes each individual * value in the list map to the value the list maps to. *

* Created on Oct 4, 2011 by murphyr * @param map map to explode * @return exploded map */ @SuppressWarnings("rawtypes") private static Map explodeMap(Map map) { //explode map if list if the key Map tempMap = new HashMap<>(); List keysToDelete = new ArrayList<>(); for (Entry entry : map.entrySet()) { Object key = entry.getKey(); if (key instanceof List) { Object value = entry.getValue(); keysToDelete.add(key); for (Object element : (List)key) { tempMap.put(element, value); if (element instanceof List) explodeMap(tempMap); } } } for (Object key : keysToDelete) map.remove(key); map.putAll(tempMap); return map; } /** * Creates a range object given a low value and and high value which would be * the next item in the queue. *

* Created on Oct 4, 2011 by murphyr * @param lowValue low value * @param queue queue of tokens * @return a ruange */ @SuppressWarnings("rawtypes") private static RangeObject createRange(Object lowValue, Queue queue) throws ConstructionException { List list = new ArrayList<>(); String nextValue = ((JavaContextSymbol)queue.remove()).getValue().toString(); if (lowValue == null || !NumberUtils.isDigits(lowValue.toString()) || !NumberUtils.isDigits(nextValue)) throw new ConstructionException("Invalid range syntax."); int low = Integer.parseInt(lowValue.toString()); int high = Integer.parseInt(nextValue); for (int i = low; i <= high; i++) list.add(i); return new RangeObject(list); } /** * Created on Oct 5, 2011 by murphyr * @author murphyr */ private static class RangeObject { private final List _rangeList; /** * Created on Oct 5, 2011 by murphyr * @param rangeList range list */ public RangeObject(List rangeList) { _rangeList = rangeList; } /** * Created on Oct 5, 2011 by murphyr * @return range list */ public List getRangeList() { return _rangeList; } } }