/*
* This file is part of Bracket Properties
* Copyright 2011 David R. Smith
*
*/
package asia.redact.bracket.properties;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.text.ParseException;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import asia.redact.bracket.properties.alt.DotPropertiesParser;
import asia.redact.bracket.properties.line.LineScanner;
/**
* <pre>
* In java, java.util.Properties is not an Interface. Bracket Properties
* has one, which allows (among other things) for both a standard and a sorted implementation. The
* standard implementation is backed by a LinkedHashMap, which keeps insertion
* order intact. This is a critical issue for non-trivial use of Properties files.
*
* Properties is also the home to the static Factory, which is the supported way to
* instantiate a Bracket Properties object.
*
* </pre>
* @author Dave
*
*/
public interface Properties {
/**
* Can be used to get direct access to the Entry data structures
* @return
*/
public Map<String, ValueModel> getPropertyMap();
/**
* Get the value.
* @param key
* @return
*/
public String get(String key);
/**
* Coerce to an integer value. Obviously this works better if the value is actually an integer.
* @param key
* @return
*/
public int intValue(String key);
/**
* Coerce to a long value. Obviously this works better if the value is actually a long.
* @param key
* @return
*/
public long longValue(String key);
/**
* Date value here is assumed to be a long
* @param key
* @return
*/
public java.util.Date dateValue(String key);
/**
* Just syntactical sugar to use a SimpleDateFormat
*
* @param key
* @param format
* @return
* @throws ParseException
*/
public java.util.Date dateValue(String key, String format) throws ParseException;
/**
* <pre>Get the properties as a tree of nodes. For example,
*
* a.b.c=something
* a.b.c.d=something else
*
* looks like
*
* a
* b
* c - something
* d - something else
*
* This method is identical in results to getTree(regex) where the regex
* is "\\.". That is, the separator token in the key is a full stop
*
* Obviously this works better if your keys are delimited by dot characters
* </pre>
*
* @return
*/
public Node getTree();
/**
* <pre>
* Get the properties as a tree of nodes with a selector
*
* a.b.c=something
* a.b.c.d=something else
* a.b.c.e.f=item
* a.b.c.e=item2
* </pre>
*
*
*/
public Node getTree(GroupParams params);
/**
* Get the list of comments, return an empty list if none
*
* @param key
* @return
*/
public List<String> getComments(String key);
/**
* The char found in the parse, normally '='
*
* @param key
* @return
*/
public char getSeparator(String key);
/**
* Add the key and value or values. Useful with multi-line entries
*
* @param key
* @param values
*/
public void put(String key, String ... values);
/**
* Number of entries in the underlying map
*
* @return
*/
public int size();
/**
* remove all entries from the underlying map
*/
public void clear();
/**
* <pre>
* get(key) will throw a RuntimeException if the key does not
* exist. This method can be used to test for a key prior to
* calling get().
*
* Returns true if the underlying map has this key
*
* </pre>
* @param key
* @return
*/
public boolean containsKey(String key);
/**
* Returns true if the key exists and has a non-empty value
*
* @param key
* @return
*/
public boolean hasValue(String key);
/**
* Overwrite existing keys with the new ones, keep those existing ones that don't collide
* This operation is non-destructive on the input
* does not concatenate comments
*
* @param props
* @return the merged properties
*/
public Properties merge(Properties props);
/**
* Overwrite existing keys with the new ones, keep those existing ones that don't collide
* This operation is non-destructive on the input
*
* @param props
* @return the merged properties
*/
public Properties merge(Properties props, boolean mergeComments);
/**
* Cause a graph to become the contents of the properties file. Destructive
* to current entries, so this is not very useful yet
*
*
* @param rootNode
*/
public void synchronize(Node rootNode);
/**
* <pre>
* Mode is the available combinations of lexer and parser
*
* BasicToken - PropertiesLexer and PropertiesParser.
* Input is a String and a list of tokens is created, then the list
* is parsed. Trivial. Possibly good for small properties files
*
* Compatibility - PropertiesLexer and PropertiesParser.
* Same parser as above but an attempt is made to retain
* java.util.Properties compatibility in the parse.
*
* Line - LineScanner and PropertiesParser2.
* Uses the LineScanner which is essentially a BufferedReader, and there is no
* separate token list, parser works directly off lines. Should work best for
* larger properties files. Probably the best option full stop.
*
* Usage:
*
* Properties.Factory.Mode = Properties.Mode.StreamingToken;
* Properties props = Properties.Factory.getInstance(reader);
*
* </pre>
*
* @author Dave
*
*/
public enum Mode {
BasicToken,Compatibility,Line;
}
/**
* <pre>
* The default mode is the trivial memory mode, which is BasicToken.
*
* You can change the Mode of the factory globally by setting it to a different value like this:
*
* Properties.mode = Mode.Line;
*
* which changes the mode (and thus the parser/lexer) to Line.
*
* </pre>
*
* @author Dave
*
*/
public static final class Factory {
public static Mode mode = Mode.BasicToken;
// private final static Logger log = Logger.getLogger("asia.redact.bracket.properties.Properties.Factory");
public static Properties getInstance(){
return new PropertiesImpl();
}
public synchronized static Properties getInstance(URL url){
try {
return Factory.getInstance(url.openStream());
} catch (IOException e) {
throw new RuntimeException("IOException caught",e);
}
}
public synchronized static Properties getInstance(Reader reader){
switch(mode){
case BasicToken: return new PropertiesImpl(reader);
case Compatibility: {
PropertiesLexer lexer = new PropertiesLexer(reader);
lexer.lex();
List<PropertiesToken> list = lexer.getList();
PropertiesImpl props = new PropertiesImpl();
PropertiesParser p = new PropertiesParser(list, props);
p.setTrimValues(true);
p.parse();
return props;
}
case Line:{
LineScanner lexer = new LineScanner(reader);
PropertiesImpl props = new PropertiesImpl();
new PropertiesParser2(lexer,props).parse();
return props;
}
}
return new PropertiesImpl(reader);
}
public synchronized static Properties getInstance(InputStream in){
switch(mode){
case BasicToken: return new PropertiesImpl(in);
case Compatibility: {
PropertiesLexer lexer = new PropertiesLexer(in);
lexer.lex();
List<PropertiesToken> list = lexer.getList();
PropertiesImpl props = new PropertiesImpl();
PropertiesParser p = new PropertiesParser(list, props);
p.setTrimValues(true);
p.parse();
return props;
}
case Line:{
LineScanner lexer = new LineScanner(new InputStreamReader(in));
PropertiesImpl props = new PropertiesImpl();
new PropertiesParser2(lexer,props).parse();
return props;
}
}
return new PropertiesImpl(in);
}
/**
* Load from a legacy Properties file object
*
* @param legacy
* @return
*/
public static Properties getInstance(java.util.Properties legacy){
return new PropertiesImpl(legacy);
}
/**
* baseName is something like a.b.c.MyProperty which with Locale.AU will be
* a search path like /a.b.c.MyProperty_en_AU.properties
*
* @param baseName
* @param locale
* @return
*/
public static Properties getInstance(String baseName, Locale locale) {
LocaleStringBuilder builder = new LocaleStringBuilder(baseName,locale);
List<String> list = builder.getSearchStrings();
PropertiesImpl base = new PropertiesImpl();
for(String s: list){
InputStream in = null;
try {
in = Thread.currentThread().getClass().getResourceAsStream(s);
if(in != null){
base.merge(Properties.Factory.getInstance(in));
}
}finally{
if(in != null)
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return base;
}
/**
* The input file must have been generated by OutputAdapter.writeAsXML(Writer) or meet the same
* requirements as regards form. This is not yet documented but looking at the test case files you can
* figure it out.
*
* @throws RuntimeException if it fails due to I/O
*
* @param file
* @return
*/
public synchronized static Properties getInstanceFromXML(File file) {
FileInputStream in = null;
try {
in = new FileInputStream(file);
InputStreamReader reader = new InputStreamReader(in);
BufferedReader breader = new BufferedReader(reader);
ParseXML parser = new ParseXML();
parser.parse(breader);
return parser.getProps();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
public synchronized static Properties sortedInstance(Properties props){
SortedPropertiesImpl impl = new SortedPropertiesImpl();
return impl.merge(props);
}
public synchronized static Properties sortedInstance(Properties props, Comparator<String> comp){
SortedPropertiesImpl impl = new SortedPropertiesImpl(comp);
return impl.merge(props);
}
public synchronized static Properties getDotInstance(Reader reader){
LineScanner lexer = new LineScanner(reader);
DotPropertiesParser p = new DotPropertiesParser(lexer);
p.parse();
return p.getProperties();
}
public synchronized static Properties getDotInstance(InputStream in){
LineScanner lexer = new LineScanner(new InputStreamReader(in));
DotPropertiesParser p = new DotPropertiesParser(lexer);
p.parse();
return p.getProperties();
}
}
}