package de.spieleck.config;
import de.spieleck.net.URLTools;
import java.io.*;
import java.net.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import javax.xml.parsers.*;
import org.xml.sax.*;
public class Config
{
public final static String INCLUDEELEM = "se:include";
public final static String PARAMELEM = "se:param";
public final static String THISATTR = "se:this";
public final static String NAME_ATTR = "se:name";
public final static String VALUE_ATTR = "se:value";
public final static char TEXTSEPARATOR = ' ';
public final static String SETUPEXTENSION = ".conf";
public final static String INC_HREF = "href";
public final static String INC_PATH = "path";
public final static String INC_LIST = "list";
public final static String INC_DIR = "dir";
public final static String INC_EXCL = "exclude";
public final static String INC_SEP = "@";
protected static List listeners;
protected static ConfigFileNode defaultConfig = null;
private Config()
{
}
public static ConfigFileNode parse(InputSource is)
throws IOException, SAXException
{
return parse(is, NullParamMap.getInstance());
}
public static ConfigFileNode parse(InputSource is, ConfigParamMap pm)
throws IOException, SAXException
{
ConfigFileNode top = new ConfigFileNode(null, null,
is.getSystemId(), pm);
ConfigSaxHandler handler = new ConfigSaxHandler(top, pm);
try
{
SAXParser parser = newSAXParser();
parser.parse(is, handler);
}
catch (Exception e)
{
e.printStackTrace();
throw new SAXException(e);
}
return top;
}
protected static SAXParserFactory spf = null;
protected static synchronized SAXParser newSAXParser()
throws SAXException, ParserConfigurationException
{
if (spf == null)
{
spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(false);
spf.setValidating(false);
}
return spf.newSAXParser();
}
public static synchronized ConfigNode setConfig(ConfigFileNode setup)
{
ConfigFileNode old = defaultConfig;
defaultConfig = setup;
handleChange(setup);
return old;
}
public static synchronized ConfigNode getConfig()
{
return defaultConfig;
}
public synchronized static void addListener(ConfigListener listener)
{
if (listeners == null)
listeners = new LinkedList();
listeners.add(listener);
}
public synchronized static void removeListener(ConfigListener listener)
{
if (listeners != null)
listeners.remove(listener);
}
protected static void handleChange(ConfigNode s)
{
if (listeners == null)
return;
Iterator e = listeners.iterator();
while (e.hasNext())
{
ConfigListener listener = (ConfigListener)e.next();
listener.handleConfigChange(s);
}
}
protected final static class ConfigSaxHandler
extends HandlerBase
{
private ConfigParamMap pm;
private Locator locator;
private ConfigNodeImpl node;
private StringBuffer text = new StringBuffer(200);
private boolean hasThis;
ConfigSaxHandler(ConfigNodeImpl top, ConfigParamMap pm)
{
this.node = top;
this.pm = pm;
}
public void setDocumentLocator(Locator locator)
{
this.locator = locator;
}
public void startElement(String name, AttributeList attrs)
throws SAXException
{
if (text.length() != 0)
configError("subnodes must preceede text '"+name+"'", null);
hasThis = false;
if (name.equals(PARAMELEM))
doParam(attrs);
else if (name.equals(INCLUDEELEM))
doInclude(attrs);
else
{
node = node.addChild(name, null);
int length = attrs.getLength();
for (int i = 0; i < length; i++)
{
String key = attrs.getName(i);
String value = attrs.getValue(i);
if (key.equals(THISATTR))
{
hasThis = true;
node.setValue(value);
}
else if ( !key.startsWith("xmlns:") )
node.addChild(key, value);
}
}
}
protected void doParam(AttributeList atts)
throws SAXException
{
String name = atts.getValue(NAME_ATTR);
String value = atts.getValue(VALUE_ATTR);
if ( name == null || value == null )
configError(PARAMELEM+" needs "+NAME_ATTR+" and "+VALUE_ATTR,null);
pm.set(name, value);
}
private URL obtainExtraConfig(String pathlist)
throws SAXException, IOException
{
StringTokenizer st = new StringTokenizer(pathlist, ",");
while (st.hasMoreTokens())
{
String path = st.nextToken().trim();
StringTokenizer str = new StringTokenizer(path, INC_SEP);
if (str.countTokens() != 2)
configError(INCLUDEELEM
+ " with list needs the syntax "
+ INC_HREF + INC_SEP
+ "relativepath or " + INC_PATH
+ INC_SEP + "absolutepath", null);
String type = str.nextToken();
path = str.nextToken();
URL url = null;
if (INC_HREF.equals(type))
{
url = getHrefURL(path);
try
{
url.openStream();
return url;
}
catch (IOException iox)
{
configError("path error: ", iox);
}
}
else if (INC_PATH.equals(type))
{
if (new File(path).exists())
{
url = getPathURL(path);
return url;
}
else
configError(INCLUDEELEM + ": " + path
+ " not found", null);
}
}
return null;
}
private URL getHrefURL(String href)
throws SAXException, IOException
{
String sid = locator.getSystemId();
if (sid == null)
configError(INCLUDEELEM
+ " with href needs a SystemId with inputSource",
null);
return new URL(new URL(sid), href);
}
private URL getPathURL(String path)
throws IOException
{
return URLTools.toURL(path);
}
private URL[] getDirURL(String type, String dir, String exclude)
throws SAXException, IOException
{
if (INC_HREF.equals(type))
{
dir = getHrefURL(dir).getPath();
}
final List fv = new LinkedList();
if (exclude != null)
{
StringTokenizer str = new StringTokenizer(exclude, ",");
while (str.hasMoreTokens())
fv.add(str.nextToken());
}
File file = new File(dir);
if (!file.exists())
configError(INCLUDEELEM+" with dir: "+dir+" not found",null);
File[] files = file.listFiles(new FileFilter() {
public boolean accept(File name)
{
String fname = name.toString();
if (fv.contains(name.getName()))
return false;
return fname.endsWith(SETUPEXTENSION);
}
});
URL[] url = new URL[files.length];
for (int i = 0; i < url.length; i++)
url[i] = getPathURL(files[i].toString());
return url;
}
private void doInclude(AttributeList args)
throws SAXException
{
try
{
int count = 0;
String href = args.getValue(INC_HREF);
if (href != null)
count++;
String path = args.getValue(INC_PATH);
if (path != null)
count++;
String list = args.getValue(INC_LIST);
if (list != null)
count++;
String dir = args.getValue(INC_DIR);
if (dir != null)
count++;
if (count != 1)
configError(INCLUDEELEM + " needs one of '"
+ INC_PATH + "', '" + INC_HREF
+ "', '" + INC_LIST + "' or '"
+ INC_DIR + "' attribute", null);
String exclude = args.getValue(INC_EXCL);
if (exclude != null && dir == null)
configError(INCLUDEELEM + " " + INC_EXCL
+ " only valid with " + INC_DIR
+ ".", null);
URL[] url = null;
if (href != null)
{
url = new URL[] { getHrefURL(href) };
}
else if (path != null)
{
url = new URL[] { getPathURL(path) };
}
else if (list != null)
{
url = new URL[] { obtainExtraConfig(list) };
}
else if (dir != null)
{
StringTokenizer str = new StringTokenizer(dir,
INC_SEP);
if (str.countTokens() == 2)
url = getDirURL(str.nextToken(),
str.nextToken(), exclude);
else
configError(INCLUDEELEM + " with attribute '"
+ INC_DIR + "': " + INC_HREF
+ "/" + INC_PATH + INC_SEP
+ "directory e.g. " + INC_HREF
+ INC_SEP + "d:\tmp", null);
}
parseIncludes(url);
}
catch (IOException e)
{
configError("include has IO problem :", e);
}
}
private void parseIncludes(URL[] url)
throws SAXException, IOException
{
for (int i = 0; i < url.length; i++)
{
InputSource is = new InputSource(url[i].toExternalForm());
ConfigNodeImpl incNode = null;
try
{
incNode = Config.parse(is);
}
catch ( SAXException e )
{
e.printStackTrace();
}
if ( incNode != null )
{
ConfigFileNode branch = node.getBranchNode();
if (branch != null)
branch.addSubReader(incNode);
if (incNode != null )
node.copyChildren(incNode);
}
}
}
public void endElement(String name)
throws SAXException
{
if (!name.equals(INCLUDEELEM) && !name.equals(PARAMELEM) )
{
if (text.length() != 0)
{
node.setValue(text.toString());
text.setLength(0);
}
node = (ConfigNodeImpl)node.getParent();
}
else if (text.length() != 0 )
configError(name+" nodes must not have text.", null);
}
public void characters(char[] chars, int offset, int length)
throws SAXException
{
for (; length > 0; length--)
{
if (!Character.isWhitespace(chars[offset]))
break;
offset++;
}
for (; length > 0; length--)
if (!Character.isWhitespace(chars[offset + length - 1]))
break;
if (length <= 0)
return;
if (hasThis)
configError("Mixes use of this and text at '"
+ new String(chars, offset, length)
+ "'", null);
if (text.length() != 0)
text.append(TEXTSEPARATOR);
text.append(chars, offset, length);
}
public void warning(SAXParseException e)
{
showError("warning", e);
}
public void error(SAXParseException e)
{
showError("error", e);
}
public void fatalError(SAXParseException e)
{
showError("fatal", e);
}
private void showError(String type, SAXParseException e)
{
String inputFile = e.getSystemId();
if (inputFile == null)
inputFile = "input file";
System.err.println(
"Parser " + type + " @" + inputFile + ",line "
+ e.getLineNumber() + ",column "
+ e.getColumnNumber());
System.err.println(" " + e.getMessage());
}
private void configError(String text, Exception e)
throws SAXParseException
{
SAXParseException spe;
if (e == null)
spe = new SAXParseException(text, locator);
else
spe = new SAXParseException(text, locator, e);
showError("configError", spe);
throw spe;
}
}
}