/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.helpers.bundleGen;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.ResourceBundle;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Reads questions from an XML file and shows them to the user.
* The reader first tries to load a locale based version of the
* questions (file.<locale>.xml) if this fails, it falls back
* to using a default version (file.xml)
*
* @author Heiko W. Rupp
*/
public class XmlQuestionsReader {
private final Log log = LogFactory.getLog(XmlQuestionsReader.class);
String baseName;
ResourceBundle resourceBundle = ResourceBundle.getBundle("bundleGen");
/**
* Create a new XmlQuestionReader
* @param fileBaseName the basename of a file - sans .xml suffix!
*/
public XmlQuestionsReader(String fileBaseName) {
baseName = fileBaseName;
}
/**
* Present the questions to the user and read answers from the provided <i>reader</i>.
* @param reader Buffered Reader to read from. Usually an InputStreamReader(System.in)
* @param props Props object to fill the answers into.
* @throws FileNotFoundException if no question file is found
* @throws Exception On various other occasions
*/
public void readQuestions(BufferedReader reader, Props props) throws Exception {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Class<? extends XmlQuestionsReader> clazz = getClass();
String lang = Locale.getDefault().getLanguage();
InputStream input = clazz.getResourceAsStream("/"+baseName +".xml" );
if (input == null) {
throw new FileNotFoundException("Input file [" + baseName + "] not found");
}
Document doc = builder.parse(input);
Element root = doc.getDocumentElement(); // <questions>
NodeList questionNodes = root.getChildNodes();// "question|block"
for (int i = 0; i < questionNodes.getLength(); i++ ) {
Node node = questionNodes.item(i);
if (node instanceof Element) {
String nodeName = node.getNodeName();
if (nodeName.equals("question")) {
Element question = (Element)node;
String res = handleQuestion(reader,question,lang);
String prop = question.getAttribute("prop");
String type = question.getAttribute("type");
fillObject(props,prop,type,res);
}
else if (nodeName.equals("block")) {
// Map res = handleBlock(reader,node,props,lang);
// TODO
}
}
}
}
catch (Exception e ) {
e.printStackTrace();
}
}
private void handleBlock(BufferedReader reader, Node node, Props props, String lang) throws Exception
{
Element block = (Element)node;
String repeat = block.getAttribute("repeat");
if (repeat==null || repeat.isEmpty())
repeat="one";
int repeatCount = 0;
// for () {
// // first ask the standalone question and decide from there
// Element question = (Element) block.getElementsByTagName("question").item(0);
//
//
// // Now loop over the questions
// NodeList questions = block.getElementsByTagName("question");
// for (int i = 1; i < questions.getLength(); i++) {
// Node qnode = questions.item(i);
// // handleQuestion(reader,qnode,props,lang); TODO
// }
// }
}
/**
* Handle <question> elements
* @param reader
* @param question
* @param lang
* @throws Exception
*/
private String handleQuestion(BufferedReader reader, Element question,String lang) throws Exception
{
String type = question.getAttribute("type");
if (type.equals(""))
type="string";
if (!isValidType(type)) {
System.err.println("Type " + type + " is invalid in " + question );
return null;
}
Element text = getElementForLang(question,"text",lang);
NodeList prefillNodes = question.getElementsByTagName("prefill");
String prefill = null;
if (prefillNodes.getLength()>0) {
prefill = prefillNodes.item(0).getTextContent();
}
Element helpNode = getElementForLang(question,"help",lang);
String help = null;
if (helpNode!=null) {
help = helpNode.getTextContent();
}
System.out.print(text.getTextContent());
if ("bool".equals(type)) {
System.out.print(resourceBundle.getString("yes.no"));
}
if (prefill!=null) {
System.out.print("[" + prefill + "]");
}
String answer;
boolean helpRequested = false;
do {
helpRequested = false;
System.out.print(": ");
answer = reader.readLine();
if (answer.startsWith("?")) {
helpRequested = true;
if (help!=null) {
System.out.println(help);
}
else
System.out.println(resourceBundle.getString("no.help.available"));
}
} while (helpRequested);
if (type.equals("bool")) {
String yesString = resourceBundle.getString("yes.key");
if (answer.toLowerCase(Locale.getDefault()).startsWith(yesString))
return Boolean.TRUE.toString();
else
return Boolean.FALSE.toString();
}
else {
if (prefill!= null && answer.length()==0) {
return prefill;
}
else
return answer;
}
}
/**
* Search for an element <i>tagWanted</i> within parent. If multiple
* elements are present, use the one with the matching <i>lang</i>.
* If no matching element is present, use one without a <i>lang</i>
* attribute
* @param parent containing element
* @param tagWanted the tag to search for
* @param lang the desired language version
* @return an element or null if not found
*/
Element getElementForLang(Element parent, String tagWanted, String lang) {
NodeList elements = parent.getElementsByTagName(tagWanted);
if (elements==null || elements.getLength()==0)
return null;
Element noLang = (Element) elements.item(0);
for (int i = 0; i < elements.getLength(); i++) {
Element ele = (Element) elements.item(i);
String attr = ele.getAttribute("lang");
if (attr==null || attr.isEmpty()) {
noLang = ele;
}
else if (attr.equals(lang)) {
return ele;
}
}
return noLang;
}
private void fillObject(Object target, String prop, String type, String res) throws Exception {
String setterName = "set" + caps(prop);
Method setter;
Class clazz = target.getClass();
Method[] methods = clazz.getDeclaredMethods();
if ("bool".equals(type)) {
setter = clazz.getMethod(setterName, Boolean.TYPE);
setter.invoke(target, Boolean.valueOf(res));
}
else {
setter = clazz.getMethod(setterName, String.class);
setter.invoke(target,res);
}
}
/**
* Returns true if the passed type is a valid data type of the properties (not for a single
* property).
* @param type A type as string
* @return true if its valid, false otherwise
*/
private boolean isValidType(String type) {
if ("bool".equals(type) || "string".equals(type))
return true;
return false;
}
static String caps(String in) {
if (in == null)
return null;
return in.substring(0, 1).toUpperCase(Locale.getDefault()) + in.substring(1);
}
}