/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of the Common Development * and Distribution License("CDDL") (the "License"). You may not use this file * except in compliance with the License. * * You can obtain a copy of the License at * http://opensource.org/licenses/cddl1.php * See the License for the specific language governing permissions and limitations * under the License. * * When distributing the Covered Code, include this CDDL Header Notice in each file * and include the License file at http://opensource.org/licenses/cddl1.php. * If applicable, add the following below this CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== */ package org.identityconnectors.patternparser; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.w3c.dom.Element; import org.w3c.dom.NodeList; /** * The MapTransform parses a String into a Map, using an array of PatternNodes, * which contain regular expressions to break up the input. The values stored in * the Map can be modified by an array of Transforms attached to the * PatternNode. * <p> * The XML for a MapTransform looks like * * <pre> * <MapTransform> * <PatternNode key='<b>key</b>' pattern='<b>pattern</b>' optional='<b>optional</b>' reset='<b>reset</b>'> * ...optional Transforms... * </PatternNode> * ...optional additional PatternNodes... * </MapTransform> * </pre> * * where * <ul> * <li><b>key</b> -- The key used to store the result of the embedded Transforms * in the Map. If there are no Transforms, the entire matched String is stored.</li> * <li><b>pattern</b> -- A pattern, which, when matched, is used as input to the * first Transform stored in the PatternNode.</li> * <li><b>optional</b> -- A boolean indicating if matching the pattern is * optional.</li> * <li><b>reset</b> -- If true, the the next PatternNode is matched starting at * the first character after the substring matched by this PatternNode. If * false, the next PatternNode is matched against the same point in the input * String as this PatternNode.</li> * </ul> */ public class MapTransform extends Transform<Map<String, Object>> { private boolean debug = false; /** * Store the information about a map element. */ public static class PatternNode { public static final String PATTERN_NODE = "PatternNode"; private static final String KEY = "key"; private static final String PATTERN = "pattern"; private static final String OPTIONAL = "optional"; private static final String RESET = "reset"; private String key; private Pattern pattern; private Transform[] transforms; private boolean optional; private boolean reset; private String getChildren(int indent) { StringBuffer buffer = new StringBuffer(); for (Transform transform : transforms) { buffer.append(transform.toXml(indent + 2)); } return buffer.toString(); } private String getAttributes() { return attributeToString(KEY, key) + attributeToString(PATTERN, pattern.pattern()) + attributeToString(OPTIONAL, Boolean.toString(optional)) + attributeToString(RESET, Boolean.toString(reset)); } public String toXml(int indent) { String children = getChildren(indent); StringBuffer pad = new StringBuffer(); for (int i = 0; i < indent; i++) { pad.append(" "); } if (children.length() == 0) { return pad + "<" + PATTERN_NODE + getAttributes() + "/>\n"; } else { return pad + "<" + PATTERN_NODE + getAttributes() + ">\n" + children + pad + "</" + PATTERN_NODE + ">\n"; } } public PatternNode(Element element) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { this(element.getAttribute(KEY), element.getAttribute(PATTERN), Boolean .parseBoolean(element.getAttribute(OPTIONAL)), Boolean.parseBoolean(element .getAttribute(RESET)), null); NodeList nodeList = element.getChildNodes(); int nodeCount = nodeList.getLength(); List<Transform> transforms = new ArrayList<Transform>(); for (int i = 0; i < nodeCount; i++) { if (nodeList.item(i) instanceof Element) { transforms.add(Transform.newTransform((Element) nodeList.item(i))); } } this.transforms = transforms.toArray(new Transform[transforms.size()]); } /** * Recognize a Pattern in the input, and produce a value. * * @param key * key to store the result in a Map * @param pattern * pattern for which to search */ public PatternNode(String key, String pattern) { this(key, pattern, false, false, new Transform[0]); } /** * Recognize a Pattern in the input, and produce a value, after applying * transformations. * * @param key * key to store the result in a Map * @param pattern * pattern for which to search * @param optional * indicates if the pattern is optional * @param reset * indicates if the current location is not to be advanced * even if the pattern patches * @param transforms * an array of transformations to be applied to the result */ public PatternNode(String key, String pattern, boolean optional, boolean reset, Transform[] transforms) { this.key = key; this.pattern = Pattern.compile(pattern); this.optional = optional; this.reset = reset; this.transforms = transforms == null ? new Transform[0] : transforms; } } private List<PatternNode> parseInfo; public MapTransform(Element element) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { parseInfo = new ArrayList<PatternNode>(); NodeList nodes = element.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { if (nodes.item(i) instanceof Element) { parseInfo.add(new PatternNode((Element) nodes.item(i))); } } } public MapTransform(List<PatternNode> parseInfo) { this.parseInfo = parseInfo; } protected String getChildren(int indent) { StringBuffer children = new StringBuffer(); for (PatternNode patternNode : parseInfo) { children.append(patternNode.toXml(indent + 2)); } return children.toString(); } private String resultToString(Object result) { if (result == null) { return "<null>"; } else if (result.getClass().isArray()) { return Arrays.deepToString((Object[]) result); } else { return result.toString(); } } @SuppressWarnings("unchecked") private int parse(String inputString, final Map<String, Object> output, int offset) throws Exception { int newOffset = offset; for (PatternNode patternNode : parseInfo) { Matcher matcher = patternNode.pattern.matcher(inputString); if (matcher.find(newOffset)) { if (debug) { System.out.println("Matched regex '" + patternNode.pattern.pattern() + "' to '" + matcher.group() + "' at character " + matcher.start()); System.out.println(" Match value:'" + matcher.group(1) + "'"); } if (!patternNode.reset) { newOffset = matcher.end(); } Object result = matcher.group(1); for (Transform transform : patternNode.transforms) { String oldResult = resultToString(result); result = transform.transform(result); if (debug) { System.out.println(" Transform " + transform.getClass().getName() + ":'" + oldResult + "'->'" + resultToString(result) + "'"); } } if (patternNode.key.length() > 0) { output.put(patternNode.key, result); } else { if (result instanceof Map) { output.putAll((Map<String, Object>) result); } else if (result instanceof List) { List<?> list = (List<?>) result; for (int i = 0; i < list.size(); i += 2) { output.put((String) list.get(i), list.get(i + 1)); } } else { throw new Exception("Nameless pattern returns " + result.getClass()); } } } else { if (!patternNode.optional) { throw new Exception("No Match with pattern " + patternNode.pattern.toString() + " at " + newOffset); } } } return newOffset; } void setDebug(boolean debug) { this.debug = debug; } /** * Parse the inputString, according to the pattern information in parseInfo, * and produce a Map of values. * <p> * First, the input is converted to a string, then, the PatternNodes are * evaluated in sequence. * * @param input * input to be parsed * @return Map<String, Object> representing the parsed inputString * @throws Exception */ @Override public Map<String, Object> transform(Object input) throws Exception { final Map<String, Object> output = new HashMap<String, Object>(); parse(input.toString(), output, 0); return output; } }