/** * Copyright (c) 2007-2011, JAGaToo Project Group all rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the 'Xith3D Project Group' nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.jagatoo.util.ini; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.Charset; import org.jagatoo.util.errorhandling.ParsingException; import org.jagatoo.util.io.UnicodeBOM; /** * The {@link AbstractIniParser} parses ini files ;). * * @author Marvin Froehlich (aka Qudus) */ public abstract class AbstractIniParser { public static final String DEFAULT_OPERATOR = "="; /** * This method is invoked, when an empty line has been found. * * @param lineNr * @param group * * @return true, to indicate, that parsing should be proceeded, false to stop parsing. * * @throws ParsingException */ protected boolean onEmptyLineParsed( int lineNr, String group ) throws ParsingException { return ( true ); } /** * This method is invoked, when a standalone comment line has been found. * * @param lineNr * @param group * @param comment * * @return true, to indicate, that parsing should be proceeded, false to stop parsing. * * @throws ParsingException */ protected boolean onCommentParsed( int lineNr, String group, String comment ) throws ParsingException { return ( true ); } /** * This method is invoked, when a new group has been found. * * @param lineNr * @param group * * @return true, to indicate, that parsing should be proceeded, false to stop parsing. * * @throws ParsingException */ protected boolean onGroupParsed( int lineNr, String group ) throws ParsingException { return ( true ); } /** * This method is invoked, when a new setting has been found. * * @param lineNr * @param group * @param key * @param value * @param comment the comment behind the value. (can be null) * * @return true, to indicate, that parsing should be proceeded, false to stop parsing. * * @throws ParsingException */ protected abstract boolean onSettingParsed( int lineNr, String group, String key, String value, String comment ) throws ParsingException; /** * Override this method and return true to accept a missing trailing (double-)quote as a correct line. * * @return true to accept lines like that. */ protected boolean acceptMissingTrailingQuote() { return ( false ); } /** * This method is called when an illigal line was detected. * * @param lineNr * @param group * @param line * * @return true, if this line was handled, false otherwise * @throws ParsingException */ protected boolean verifyIllegalLine( int lineNr, String group, String line ) throws ParsingException { return ( false ); } /** * This method is called when an illigal line was detected. * * @param lineNr * @param group * @param line * @param t * * @return true, to indicate, that parsing should be proceeded, false to stop parsing. * * @throws ParsingException */ protected boolean handleParsingException( int lineNr, String group, String line, Throwable t ) throws ParsingException { if ( t instanceof ParsingException ) throw (ParsingException)t; if ( t instanceof RuntimeException ) throw (RuntimeException)t; if ( t instanceof Error ) throw (Error)t; throw new ParsingException( t ); } /** * This method is invoked when the parsing of the file as been finished. */ protected void onParsingFinished() { } /** * Parses the given line (must be trimmed). If it is a group header, the group name is returned, null otherwise. * * @param lineNr * @param line * * @return the group name, if the given line is a group name, null otherwise. * * @throws ParsingException */ public static String tryToParseGroup( int lineNr, String line ) throws ParsingException { if ( line.startsWith( "[" ) ) { int idx = -1; if ( line.endsWith( "]" ) ) { return ( line.substring( 1, line.length() - 1 ).trim() ); } else if ( ( idx = line.indexOf( ']', 1 ) ) >= 0 ) { return ( line.substring( 1, idx ).trim() ); } if ( lineNr <= 0 ) throw new ParsingException( "Illegal line: " + line ); throw new ParsingException( "Illegal line #" + lineNr + ": " + line ); } return ( null ); } /** * Parses the given line (must be trimmed). If it is a group header, the group name is returned, null otherwise. * * @param line * * @return the group name, if the given line is a group name, null otherwise. * * @throws ParsingException */ public static String tryToParseGroup( String line ) throws ParsingException { return ( tryToParseGroup( -1, line ) ); } /** * Parses the given line.<br> * This method implements the actual parsing code for a single line. * * @param lineNr * @param currentGroup * @param line * @param operator * @param iniLine * @param handler * * @return success? * * @throws IOException * @throws ParsingException */ public static boolean parseLine( int lineNr, String currentGroup, String line, String operator, IniLine iniLine, AbstractIniParser handler ) throws IOException, ParsingException { if ( iniLine != null ) { iniLine.reset(); iniLine.setLine( lineNr, currentGroup, line ); line = iniLine.getLine(); } else { line = line.trim(); } if ( ( ( iniLine != null ) && ( iniLine.isEmpty() ) ) || ( line.length() == 0 ) ) { if ( handler == null ) return ( true ); return ( handler.onEmptyLineParsed( iniLine.getLineNr(), iniLine.getCurrentGroup() ) ); } if ( line.startsWith( "#" ) ) { try { String comment = line.substring( 1 ).trim(); if ( iniLine != null ) iniLine.setComment( comment ); if ( handler == null ) return ( true ); return ( handler.onCommentParsed( lineNr, currentGroup, comment ) ); } catch ( Throwable t ) { if ( iniLine != null ) iniLine.setComment( null ); if ( handler == null ) return ( false ); return ( handler.handleParsingException( lineNr, currentGroup, line, t ) ); } } if ( line.startsWith( "/" ) ) { int skipChars = 1; if ( line.startsWith( "//" ) ) skipChars = 2; try { String comment = line.substring( skipChars ).trim(); if ( iniLine != null ) iniLine.setComment( comment ); if ( handler == null ) return ( true ); return ( handler.onCommentParsed( lineNr, currentGroup, comment ) ); } catch ( Throwable t ) { if ( iniLine != null ) iniLine.setComment( null ); if ( handler != null ) handler.handleParsingException( lineNr, currentGroup, line, t ); else if ( t instanceof ParsingException ) throw (ParsingException)t; else if ( t instanceof RuntimeException ) throw (RuntimeException)t; else if ( t instanceof Error ) throw (Error)t; else throw new ParsingException( t ); } } if ( line.startsWith( "[" ) ) { ParsingException pe = new ParsingException( "This method cannot parse groups." ); if ( handler == null ) throw pe; return ( handler.handleParsingException( lineNr, currentGroup, line, pe ) ); } int idx = line.indexOf( operator ); if ( idx < 0 ) { boolean proceedParse = false; try { if ( iniLine != null ) iniLine.setValid( false ); if ( handler == null ) proceedParse = false; else proceedParse = handler.verifyIllegalLine( lineNr, currentGroup, line ); } catch ( Throwable t ) { boolean result; if ( handler == null ) result = false; else result = handler.handleParsingException( lineNr, currentGroup, line, t ); if ( iniLine != null ) iniLine.setValid( result ); return ( result ); } if ( !proceedParse ) { if ( handler == null ) return ( false ); return ( handler.handleParsingException( lineNr, currentGroup, line, new ParsingException( "Illegal line #" + lineNr + ": " + line ) ) ); } return ( true ); } String key = line.substring( 0, idx ).trim(); if ( iniLine != null ) iniLine.setKey( key ); String value = line.substring( idx + operator.length() ).trim(); boolean proceedParse = true; if ( ( value.length() > 0 ) && ( value.charAt( 0 ) == '"' ) ) { char lastChar = '\0'; for ( int i = 1; i < value.length(); i++ ) { char ch = value.charAt( i ); if ( ( ch == '"' ) && ( lastChar != '\\' ) ) { idx = value.indexOf( "//", i + 1 ); try { String value2 = value.substring( 1, i ); if ( iniLine != null ) iniLine.setValue( value2 ); if ( idx >= 0 ) { String comment = value.substring( idx + 2, value.length() ).trim(); if ( iniLine != null ) iniLine.setComment( comment ); if ( handler == null ) proceedParse = true; else proceedParse = handler.onSettingParsed( lineNr, currentGroup, key, value2, comment ); } else { if ( handler == null ) proceedParse = true; else proceedParse = handler.onSettingParsed( lineNr, currentGroup, key, value2, null ); } if ( iniLine != null ) iniLine.setValid( true ); } catch ( Throwable t ) { if ( iniLine != null ) iniLine.setValid( false ); if ( handler == null ) proceedParse = false; else proceedParse = handler.handleParsingException( lineNr, currentGroup, line, t ); } lastChar = ch; break; } lastChar = ch; } if ( lastChar != '"' ) { if ( ( handler != null ) && handler.acceptMissingTrailingQuote() ) { try { String value2 = value.substring( 1 ); if ( iniLine != null ) { iniLine.setValid( true ); iniLine.setValue( value2 ); } proceedParse = handler.onSettingParsed( lineNr, currentGroup, key, value2, null ); } catch ( Throwable t ) { if ( iniLine != null ) iniLine.setValid( false ); proceedParse = handler.handleParsingException( lineNr, currentGroup, line, t ); } } else { Boolean b = null; try { if ( iniLine != null ) iniLine.setValid( false ); if ( handler == null ) b = false; else b = handler.verifyIllegalLine( lineNr, currentGroup, line ); } catch ( Throwable t ) { if ( iniLine != null ) iniLine.setValid( false ); if ( handler == null ) proceedParse = false; else proceedParse = handler.handleParsingException( lineNr, currentGroup, line, t ); } if ( b != null ) { if ( iniLine != null ) iniLine.setValid( b ); proceedParse = b.booleanValue(); } else { if ( handler == null ) proceedParse = false; else proceedParse = handler.handleParsingException( lineNr, currentGroup, line, new ParsingException( "Illegal line #" + lineNr + ": " + line ) ); } } } } else { idx = value.indexOf( "//" ); try { if ( iniLine != null ) iniLine.setValid( true ); if ( idx >= 0 ) { String value2 = value.substring( 0, idx ).trim(); String comment = value.substring( idx + 2, value.length() ).trim(); if ( iniLine != null ) { iniLine.setValue( value2 ); iniLine.setComment( comment ); } if ( handler == null ) proceedParse = true; else proceedParse = handler.onSettingParsed( lineNr, currentGroup, key, value2, comment ); } else { if ( iniLine != null ) iniLine.setValue( value ); if ( handler == null ) proceedParse = true; else proceedParse = handler.onSettingParsed( lineNr, currentGroup, key, value, null ); } } catch ( Throwable t ) { if ( iniLine != null ) iniLine.setValid( false ); if ( handler == null ) proceedParse = false; else proceedParse = handler.handleParsingException( lineNr, currentGroup, line, t ); } } return ( proceedParse ); } /** * Parses the given line.<br> * This method implements the actual parsing code for a single line. * * @param lineNr * @param currentGroup * @param line * * @return success? * * @throws IOException * @throws ParsingException */ protected boolean parseLine( int lineNr, String currentGroup, String line ) throws IOException, ParsingException { return ( parseLine( lineNr, currentGroup, line, DEFAULT_OPERATOR, null, this ) ); } /** * Parses the given file.<br> * This method implements the actual parsing code. * * @param in * @param codepage * @param charset * * @throws IOException * @throws ParsingException */ protected void parseImpl( InputStream in, String codepage, Charset charset ) throws IOException, ParsingException { if ( !in.markSupported() ) in = new BufferedInputStream( in ); UnicodeBOM bom = UnicodeBOM.skipBOM( in ); if ( ( bom != null ) && ( bom.getCharset() != null ) ) charset = bom.getCharset(); else if ( codepage != null ) charset = Charset.forName( codepage ); BufferedReader reader = ( charset == null ) ? new BufferedReader( new InputStreamReader( in ) ) : new BufferedReader( new InputStreamReader( in, charset ) ); IniLine iniLine = new IniLine(); String line2 = null; int lineNr = 0; boolean proceedParse = true; while ( proceedParse && ( ( line2 = reader.readLine() ) != null ) ) { iniLine.setLine( ++lineNr, iniLine.getCurrentGroup(), line2 ); if ( iniLine.isEmpty() ) { proceedParse = onEmptyLineParsed( lineNr, iniLine.getCurrentGroup() ); continue; } String groupName = tryToParseGroup( lineNr, iniLine.getLine() ); if ( groupName == null ) { proceedParse = parseLine( lineNr, iniLine.getCurrentGroup(), iniLine.getLine(), DEFAULT_OPERATOR, iniLine, this ); } else { iniLine.setCurrentGroup( groupName ); proceedParse = onGroupParsed( iniLine.getLineNr(), iniLine.getCurrentGroup() ); } } reader.close(); onParsingFinished(); } /** * Parses the given file. * * @param in * @param charset * * @throws IOException * @throws ParsingException */ public final void parse( InputStream in, String charset ) throws IOException, ParsingException { parseImpl( in, charset, null ); } /** * Parses the given file. * * @param in * @param charset * * @throws IOException * @throws ParsingException */ public final void parse( InputStream in, Charset charset ) throws IOException, ParsingException { parseImpl( in, null, charset ); } /** * Parses the given file. * * @param in * * @throws IOException * @throws ParsingException */ public final void parse( InputStream in ) throws IOException, ParsingException { parseImpl( in, null, null ); } /** * Parses the given file. * * @param url * @param charset * * @throws IOException * @throws ParsingException */ public final void parse( URL url, String charset ) throws IOException, ParsingException { parseImpl( url.openStream(), charset, null ); } /** * Parses the given file. * * @param url * @param charset * * @throws IOException * @throws ParsingException */ public final void parse( URL url, Charset charset ) throws IOException, ParsingException { parseImpl( url.openStream(), null, charset ); } /** * Parses the given file. * * @param url * * @throws IOException * @throws ParsingException */ public final void parse( URL url ) throws IOException, ParsingException { parseImpl( url.openStream(), null, null ); } /** * Parses the given file. * * @param file * @param charset * * @throws IOException * @throws ParsingException */ public final void parse( File file, String charset ) throws IOException, ParsingException { parseImpl( new FileInputStream( file ), charset, null ); } /** * Parses the given file. * * @param file * @param charset * * @throws IOException * @throws ParsingException */ public final void parse( File file, Charset charset ) throws IOException, ParsingException { parseImpl( new FileInputStream( file ), null, charset ); } /** * Parses the given file. * * @param file * * @throws IOException * @throws ParsingException */ public final void parse( File file ) throws IOException, ParsingException { parseImpl( new FileInputStream( file ), null, null ); } /** * Parses the given file. * * @param filename * @param charset * * @throws IOException * @throws ParsingException */ public final void parse( String filename, String charset ) throws IOException, ParsingException { parseImpl( new FileInputStream( filename ), charset, null ); } /** * Parses the given file. * * @param filename * @param charset * * @throws IOException * @throws ParsingException */ public final void parse( String filename, Charset charset ) throws IOException, ParsingException { parseImpl( new FileInputStream( filename ), null, charset ); } /** * Parses the given file. * * @param filename * * @throws IOException * @throws ParsingException */ public final void parse( String filename ) throws IOException, ParsingException { parseImpl( new FileInputStream( filename ), null, null ); } /** * Creates a new {@link AbstractIniParser}. */ protected AbstractIniParser() { } /** * Parses a single value from the given ini file. * * @param in * @param codepage * @param charset * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ private static String parseIniValueImpl( InputStream in, String codepage, Charset charset, final String group, final String key, String defaultValue ) { final String[] result = { defaultValue }; try { new AbstractIniParser() { @Override protected boolean onSettingParsed( int lineNr, String group_, String key_, String value, String comment ) throws ParsingException { if ( group.equals( group_ ) && key.equals( key_ ) ) { result[0] = value; return ( false ); } return ( true ); } }.parseImpl( in, codepage, charset ); } catch ( Throwable t ) { // Ignore this and return the default. } return ( result[0] ); } /** * Parses a single value from the given ini file. * * @param in * @param charset * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( InputStream in, String charset, String group, String key, String defaultValue ) { return ( parseIniValueImpl( in, charset, null, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param in * @param charset * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( InputStream in, Charset charset, String group, String key, String defaultValue ) { return ( parseIniValueImpl( in, null, charset, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param in * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( InputStream in, String group, String key, String defaultValue ) { return ( parseIniValueImpl( in, null, null, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param url * @param charset * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( URL url, String charset, String group, String key, String defaultValue ) { InputStream in; try { in = url.openStream(); } catch ( IOException e ) { return ( defaultValue ); } return ( parseIniValueImpl( in, charset, null, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param url * @param charset * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( URL url, Charset charset, String group, String key, String defaultValue ) { InputStream in; try { in = url.openStream(); } catch ( IOException e ) { return ( defaultValue ); } return ( parseIniValueImpl( in, null, charset, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param url * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( URL url, String group, String key, String defaultValue ) { InputStream in; try { in = url.openStream(); } catch ( IOException e ) { return ( defaultValue ); } return ( parseIniValueImpl( in, null, null, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param file * @param charset * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( File file, String charset, String group, String key, String defaultValue ) { InputStream in; try { in = new FileInputStream( file ); } catch ( IOException e ) { return ( defaultValue ); } return ( parseIniValueImpl( in, charset, null, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param file * @param charset * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( File file, Charset charset, String group, String key, String defaultValue ) { InputStream in; try { in = new FileInputStream( file ); } catch ( IOException e ) { return ( defaultValue ); } return ( parseIniValueImpl( in, null, charset, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param file * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( File file, String group, String key, String defaultValue ) { InputStream in; try { in = new FileInputStream( file ); } catch ( IOException e ) { return ( defaultValue ); } return ( parseIniValueImpl( in, null, null, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param filename * @param charset * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( String filename, String charset, String group, String key, String defaultValue ) { InputStream in; try { in = new FileInputStream( filename ); } catch ( IOException e ) { return ( defaultValue ); } return ( parseIniValueImpl( in, charset, null, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param filename * @param charset * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( String filename, Charset charset, String group, String key, String defaultValue ) { InputStream in; try { in = new FileInputStream( filename ); } catch ( IOException e ) { return ( defaultValue ); } return ( parseIniValueImpl( in, null, charset, group, key, defaultValue ) ); } /** * Parses a single value from the given ini file. * * @param filename * @param group * @param key * @param defaultValue * * @return the value or 'defaultValue'. */ public static final String parseIniValue( String filename, String group, String key, String defaultValue ) { InputStream in; try { in = new FileInputStream( filename ); } catch ( IOException e ) { return ( defaultValue ); } return ( parseIniValueImpl( in, null, null, group, key, defaultValue ) ); } }