/*- * Copyright © 2009 Diamond Light Source Ltd. * * This file is part of GDA. * * GDA is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License version 3 as published by the Free * Software Foundation. * * GDA is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with GDA. If not, see <http://www.gnu.org/licenses/>. */ package uk.ac.gda.util.io; import java.io.IOException; import java.io.Reader; import java.io.Writer; /** * This parses an XML file (and applies string interpolation (a.k.a. variable substitution. Every * XML field in the XML document is examined during processing. Fields which contain a pattern beginning with "${", * followed by a property key name and terminating with a "}", will trigger string interpolation. */ public class StreamMacroSubstitutor { /** * @param in input xml containing macros of the form ${macroname} to be substituted * @param out the source with macros found in macroSupplier change to the suppled values * @param macroSupplier - supplier of macros for substition * @throws IOException */ static public void process(Reader in, Writer out, MacroSupplier macroSupplier) throws IOException{ (new StreamMacroSubstitutor()).doProcessing(in, out, macroSupplier); } /** * Reads byte from in and write to out until start pattern found. * * @return true if next header found * @throws IOException */ private boolean processToNextHeader(Reader in, Writer out) throws IOException { // two characters read in - so can read ahead to detect "${" pair int i1 = in.read(); int i2 = in.read(); char c1 = (char) i1; char c2 = (char) i2; while (!(c1 == '$' && c2 == '{') && (i1 != -1 && i2 != -1)) { out.write(c1); i1 = i2; c1 = c2; i2 = in.read(); c2 = (char) i2; if (i2 == -1) { // flush previous character read to output if (i1 != -1) { out.write(c1); } return false; } } return true; } /** * Reads in text into a string until a terminator character found, thus forming a property key to be substituted * with its value. * * @return property key name if found. null if not found. * @throws IOException */ private String readPropertyKeyUntilTerminator(Reader in) throws IOException { String key = ""; int i = in.read(); char c = (char) i; if (i == -1) { return null; } while (c != '}') { key += c; i = in.read(); c = (char) i; if (i == -1) { return null; } } return key; } /** * Scans through XML instance file looking for Property keys inside ${} patterns. Each instance should contain a * reference to a named java property (e.g. "${gda.src.java}"). If any found, look for a matching java property of * same name and replace the pattern instance with the value of that property using string interpolation. N.B. * Should handle multiple pattern instances per property value, but doesnt handle recursive interpolation yet (i.e. * a pattern containing a property with a value containing further patterns). * * @throws IOException */ private void doProcessing(Reader in, Writer out, MacroSupplier macroSupplier) throws IOException { boolean done = false; while (done == false) { // scan for pattern start in buffer boolean rval = processToNextHeader(in, out); if (rval == false) { return; } String propertyName = readPropertyKeyUntilTerminator(in); if (propertyName == null) { return; } String propertyValue = macroSupplier.get(propertyName); if (propertyValue == null) { propertyValue = "${" + propertyName + "}"; } out.write(propertyValue.toCharArray()); } } }