/* XBN Java: Generically useful, non-GUI Java code. http://sourceforge.net/projects/xbnjava Copyright (C) 1997-2003, Jeff Epstein All rights reserved. Modifications: No Redistribution in binary form, with or without modifications, are permitted provided that the following conditions are met: * 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. * If modifications are made to source code then this license should indicate that fact in the "Modifications" section above. * Neither the author, nor the contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [NOTE: This license contains NO advertising clause.] */ package xbn.config; import xbn.XBNObject; import xbn.output.Outputter; import xbn.string.SOBString; import xbn.string.SOBStringBuffer; import xbn.string.StringOrBuffer; import xbn.string.UtilSOB; import xbn.string.UtilString; /**

Reads in and analyzes a single line from the source text for a ConfigReader. See ConfigReader.

It seems that, in a multi-live value ending on the LAST LINE of the file, the last value line is being ignored
Also, check how it behaves when the SECOND or subsequent lines have an unescaped control character in it. For example, an SQL query with an equals sign in the fourth line.

Source code:  CRLineAnalyzer.java.  Exmaple code:  See the example code for ConfigReaderFA.

@version 0.9b @author Jeff Epstein, http://sourceforge.net/projects/xbnjava. **/ public class CRLineAnalyzer extends XBNObject { private CRLAObjects crlao = null; private int iLineNumber = -1; private int iMLCStartLine = -1; private boolean bLastLine = false; private Outputter optr = null; //Information about the previously started variable. private int iVarStartLine = -1; private StringOrBuffer sobVarStartName = null; private char cVarStartDelim = ' '; //Information about the variable retrieved on *this* line. private String sVarName = null; private char cDelimiter = ' '; private String sVarValue = null; private boolean bValueNull = false; private boolean bSLC = false; /**

Create a CRLineAnalyzer.

@param sb_line The current line, including the ending line separator. May not be null and may not contain the line separator at any point except the end. If this does not contain a line separator, this is assumed to be the very last line in the source text. @param i_lineNumber The line number that sb_line is in the source text. May not be less than one. For use in potential error messages. @param i_mlcStartLine The line number on which the current multi-line comment was started. If equal to -1, this line is not in the middle of a multi-line comment. Otherwise this is the line number in which the multi-line comment (in which sb_line is contained) began. Therefore, it may not be equal to or greater than i_lineNumber. @param i_varStartLine The line number on which the current variable's value (of which sb_line is the second or subsequent line) was started. If equal to -1, this line is not part of a value. Otherwise this is the line number in which a variable began. Therefore, it may not be equal to or greater than i_lineNumber. @param ssb_varStartedName The name of the variable started before this line. If i_varStartLine does not equal -1, this must be non-null. If i_varStartLine equals -1, this must be null. This is for potential error messages only, and is unrelated to what is returned by getName. @param c_varStartDelim The delimiter of the variable started before this line. If ssb_varStartedName equals null, this is ignored. This is for potential error messages only, and is unrelated to what is returned by getDelimiter. @param crla_objects Holds the internal objects used by this CRLineAnalyzer, including the CRConfig. May not be null. @param optr_dbg The Outputter to use for debugging. May not be null. @exception ConfigFormatException If any of the formatting rules for the source text of a ConfigReader are violated. **/ public CRLineAnalyzer(StringBuffer sb_line, int i_lineNumber, int i_mlcStartLine, int i_varStartLine, StringOrBuffer sob_varStartName, char c_varStartDelim, Outputter optr_dbg, CRLAObjects crla_objects) throws ConfigFormatException { //Validate parameters...START SOBStringBuffer ssbLine = new SOBStringBuffer(sb_line); if(!ssbLine.endsWith(sLINE_SEP)) { //This is the last line in the source text. bLastLine = true; } else { //The line *does* end with the line separator. int iAnotherLS = ssbLine.indexOf(sLINE_SEP, 0, ssbLine.length() - sLINE_SEP.length()); if(iAnotherLS != -1) { throwAX("constructor: sb_line contains a line separator (sLINE_SEP: '" + crlao.uStr.getVisible(sLINE_SEP) + "') at somewhere other than the end (at array index " + iAnotherLS + ")."); } } if(i_lineNumber < 1) { throwAX("constructor: i_lineNumber (" + i_lineNumber + ") is less than one."); } cibMLCVarStartLines(i_lineNumber, i_mlcStartLine, "mlc", i_varStartLine, "var"); cibMLCVarStartLines(i_lineNumber, i_varStartLine, "var", i_mlcStartLine, "mlc"); if(i_varStartLine != -1 && (sob_varStartName == null || sob_varStartName.length() < 1)) { throwAX("constructor: i_varStartLine equals (" + i_varStartLine + "), but sob_varStartName ('" + sob_varStartName + "') is null or zero characters in length."); } throwAXIfNull(optr_dbg, "optr_dbg", sCNSTR); //Validate parameters...END //To shorten code, and for better error messages (see //throwCFX) crlao = crla_objects; iLineNumber = i_lineNumber; iMLCStartLine = i_mlcStartLine; iVarStartLine = i_varStartLine; sobVarStartName = sob_varStartName; if(sobVarStartName == null) { cVarStartDelim = ' '; } else { cVarStartDelim = c_varStartDelim; } optr = optr_dbg; InsideWhitespace iwLine = new InsideWhitespace(ssbLine, crlao.uSOB); if(isComment(iwLine)) { return; } //This line is not a comment. It is either the first line //in a variable, or the second or subsequent line in the //value. int iDelimIW = -1; int iDelimActual = -1; if(iwLine.length() > 0) { iDelimIW = crlao.usVarDelims.getIdx1stUnesc2BEsc(iwLine); iDelimActual = iwLine.getActualIdx(iDelimIW); } if(iDelimIW == -1) { //No delimiters exist on this line. This line is //either not part of any variable, value or comment, or //this is the second or subsequent line in a value. if(iVarStartLine != -1) { //This is the second or subsequent line in a //variable's value. sVarValue = ssbLine.toString(); } else { //This line is not the second/subsequent line in a //value. It is a line *between* variables or //comments. if(iwLine.length() > 0) { String sMLCEnd = sES; if(iwLine.charAt(iwLine.length() - 1) == crlao.crc.getCRCDelimiters().getMLCEnd()) { //Special condition, for a clearer error message sMLCEnd = ". Also note: The last character in this line is a multi-line comment end delimiter ('" + crlao.crc.getCRCDelimiters().getMLCEnd() + "'). If this is meant to be the end of a multi-line comment, it was never started"; } throwCFX(iwLine, "Stray text found. This line is not part of any variable or comment, but contains non-whitespace characters. Note that cr_config.getCRCVariable().is1stLineWSEmptyString() equals " + crlao.crc.getCRCVariable().is1stLineWSEmptyString() + sMLCEnd); } } //This line is whitespace only, and can be safely ignored. } else { //This line contains an unescaped variable delimiter //and is therefore the first in a variable's value. sVarName = ssbLine.substring(0, iDelimActual); cDelimiter = ssbLine.charAt(iDelimActual); iVarStartLine = iLineNumber; sobVarStartName = new SOBString(sVarName); cVarStartDelim = cDelimiter; if(iDelimIW == (iwLine.length() - 1)) { //The delimiter is the last non-whitespace character on //the line. There is no space after it for other //unescaped variable delimiters to exist. if(iDelimActual == (ssbLine.length() - 1)) { //The delimiter is truly the last character in the //line. sVarValue = sES; //(This actually means that this line is the last in //the file. All lines previous to it end with a //line separator.) } else { //The delimiter is followed by some whitespace. sVarValue = ssbLine.substring(iDelimActual + 1); } } else { //There are non-whitespace characters following the //delimiter. int iDelimOther = crlao.usVarDelims.getIdx1stUnesc2BEsc(iwLine, (iDelimIW + 1)); if(iDelimOther != -1) { throwCFX(iwLine, iDelimOther, "A second unescaped variable delimiter was found"); } //No other (unescaped) variable delimiters exist. String s = ssbLine.substring(iDelimActual + 1); String sPotentiallyNull = s.trim(); if(sPotentiallyNull.equals(crlao.crc.getCRCVariable().getNullValue())) { sVarValue = null; bValueNull = true; } else { sVarValue = s; } } if(optr.isOn()) { optr.write("LINE " + iVarStartLine + " [delim=" + cDelimiter + "]: '" + crlao.uStr.getVisible(sVarName) + "'"); } } if(sVarName != null) { int iUnesc2BEsc = getIdxUnesc2BEsc(sVarName); if(iUnesc2BEsc != -1) { throwCFX(iwLine, iUnesc2BEsc, "An unescaped '" + sVarName.charAt(iUnesc2BEsc) + "' was found in the variable name"); } } if(sVarValue != null && sVarValue.length() > 0) { int iUnesc2BEsc = getIdxUnesc2BEsc(sVarValue); if(iUnesc2BEsc != -1) { throwCFX(iwLine, iUnesc2BEsc, "An unescaped '" + sVarValue.charAt(iUnesc2BEsc) + "' was found in this value line (entire value line: '" + crlao.uStr.getVisible(sVarValue) + "')"); } } //No illegal characters exist in the name or value. //Woohoo. } private int getIdxUnesc2BEsc(String s_nameValue) { String s = s_nameValue; int iDeletedFromLeft = 0; if(crlao.crc.getCRCVariable().getCRCVTrim().doUseESChar()) { s = crlao.tci.get(s); if(s.length() == 0) { return -1; } iDeletedFromLeft += crlao.tci.getTrimData().i1; s = crlao.tcESChar.get(s); if(s.length() == 0) { return -1; } iDeletedFromLeft += crlao.tci.getTrimData().i1; } int iUnesc2BEsc = crlao.usAll2BEscNotVD.getIdx1stUnesc2BEsc(s); if(iUnesc2BEsc != -1) { return iUnesc2BEsc + iDeletedFromLeft; } return -1; } /**

At what line did the current multi-line comment start?

@return -1 If this line is not part of a multi-line comment.
A number between 1 and i_lineNumber, the line at which the current multi-line comment was started. (i_lineNumber, as provided to the constructor) **/ public final int getMLCStartLine() { return iMLCStartLine; } /**

At which line did the current variable start?

@return -1 If this line is not part of a variable.
A number between 1 and i_lineNumber, the line at which the current variable was started. (i_lineNumber, as provided to the constructor) **/ public final int getVarStartLine() { return iVarStartLine; } /**

Is this line the beginning of a variable or comment?

@return (getMLCStartLine() == i_lineNumber  ||
              getVarStartLine() == i_lineNumber  ||
              [is this line a single line comment?])
**/ public final boolean isStartLine() { return (getMLCStartLine() == iLineNumber || getVarStartLine() == iLineNumber || bSLC); } /**

Is this the first line in a variable?

If it is, then the variable's name, delimiter and (first line of the) value can be retrieved with getName, getDelimiter and getValue.

@return true If this is the first line in a variable.
false If not. **/ public final boolean hasName() { return (sVarName != null); } /**

Does this line contain a line within a value?. If yes, get it with getValue.

@return true If this line contains a value.
false If not. **/ public final boolean hasValue() { return (bValueNull || sVarValue != null); } /**

Get the name of the variable that was started on this line.

@exception AssertException If hasName equals false. **/ public final String getName() { if(!hasName()) { throwAX("getName: hasName() equals false."); } return sVarName; } /**

Get the delimiter of the variable that was started on this line.

@exception AssertException If hasName equals false. **/ public final char getDelimiter() { if(!hasName()) { throwAX("getDelimiter: hasName() equals false."); } return cDelimiter; } /**

Get the value of the variable existing on this line.

@exception AssertException If hasValue equals false. **/ public final String getValue() { if(!hasValue()) { throwAX("getValue: hasValue() equals false."); } return sVarValue; } private void throwCFX(InsideWhitespace iw_line, String s_message) throws ConfigFormatException { throwCFX(iw_line, -1, s_message); } private void throwCFX(InsideWhitespace iw_line, int i_charIdx, String s_message) throws ConfigFormatException { String sMLC = "This line is not part of a multi-line comment. "; String sSLC = "This line is not a single-line comment. "; String sVar = "This line is not part of a variable. "; if(iMLCStartLine != -1) { sMLC = "Multi-line comment started on " + ((iLineNumber == iMLCStartLine)?"THIS line":"line " + iMLCStartLine) + ". "; } if(bSLC) { sSLC = "This line is a single line comment (the first non-whitespace character is '" + crlao.crc.getCRCDelimiters().getSingleLineCmt() + "'). "; } else if(iVarStartLine != -1) { sVar = "Variable named '" + crlao.uSOB.getVisible(sobVarStartName) + "' (delimiter='" + cVarStartDelim + "') started on " + ((iLineNumber == iVarStartLine)?"THIS line":"line " + iVarStartLine) + ". "; } ConfigFormatException cfx = new ConfigFormatException(": [" + crlao.crc.toString() + "]. [sb_line: " + iw_line.toString() + "]. NOTES: "+ sMLC + sSLC + sVar + " THE SPECIFIC ERROR MESSAGE (LINE " + iLineNumber + ((i_charIdx != -1)?", INDEX " + i_charIdx:sES) + "): " + s_message + "."); if(!optr.getOWriter().getClass().getName().equals("xbn.output.OWSysDotOut")) { //This is not writing to System.out. If it were, it would be printed //twice. optr.write(cfx.toString()); } throw cfx; } private boolean isComment(InsideWhitespace iw_line) throws ConfigFormatException { if(iw_line.length() < 1) { //There are no non-whitespace characters on this line. //The only way this is a comment is if it is the second //or subsequent line in an mlc. return (iMLCStartLine != -1); } char c1 = iw_line.charAt(0); char cLast = iw_line.charAt(iw_line.length() - 1); int i1stInBody = -1; int i1stAfterLastInBody = -1; if(iMLCStartLine != -1) { //An mlc was started on a previous line. i1stInBody = 0; if(cLast == crlao.crc.getCRCDelimiters().getMLCEnd()) { i1stAfterLastInBody = (iw_line.length() - 1); iMLCStartLine = -1; } else { //The mlc continues onto the next line. //iMLCStartLine remains as is. i1stAfterLastInBody = iw_line.length(); } } else if(c1 == crlao.crc.getCRCDelimiters().getMLCStart()) { //An mlc starts on *this* line. i1stInBody = 1; if(cLast == crlao.crc.getCRCDelimiters().getMLCEnd()) { i1stAfterLastInBody = (iw_line.length() - 1); //The mlc ends on this line. //iMLCStartLine remains as is (-1). } else { i1stAfterLastInBody = iw_line.length(); iMLCStartLine = iLineNumber; } } else if(c1 == crlao.crc.getCRCDelimiters().getSingleLineCmt()) { //A single line comment. i1stInBody = 1; i1stAfterLastInBody = iw_line.length(); //For throwCFX and isStartLine bSLC = true; } if(i1stInBody == -1) { //This isn't a comment line at all. return false; } //We're in a comment line. As long as there are no //mlcomment delimiters (escaped or not) in the body of //the comment, we're done. //This prevents potential error messages from saying //"variable named 'whatever' started...". iVarStartLine = -1; sobVarStartName = null; int iSD = iw_line.indexOf(crlao.crc.getCRCDelimiters().getMLCStart(), i1stInBody, i1stAfterLastInBody); if(iSD != -1) { throwCFX(iw_line, iSD, "A multi-line comment start delimiter ('" + crlao.crc.getCRCDelimiters().getMLCStart() + "') cannot exist within the body of a comment, even if it is escaped"); } int iED = iw_line.indexOf(crlao.crc.getCRCDelimiters().getMLCEnd(), i1stInBody, i1stAfterLastInBody); if(iED != -1) { throwCFX(iw_line, iED, "A multi-line comment end delimiter ('" + crlao.crc.getCRCDelimiters().getMLCEnd() + "') cannot exist within the body of a comment, even if it is escaped"); } //This comment line is valid! return true; } //Crash if the multi-line comment start line numbers are bad. private void cibMLCVarStartLines(int i_lineNumber, int i_slVarMLC1, String s_varMLC1, int i_slVarMLC2, String s_varMLC2) { if(i_slVarMLC1 == -1) { //This line is not the second/subsequent line in a //milti-line comment/variable return; } //This *is* the second/subsequent line in a multi-line //comment/variable. if(i_slVarMLC2 != -1) { throwAX("constructor (line=" + i_lineNumber + "): At least one of i_" + s_varMLC1 + "StartLine (currently " + i_slVarMLC1 + ") and i_" + s_varMLC2 + "StartLine (currently " + i_slVarMLC2 + ") must equal -1."); } if(i_slVarMLC1 < 1 || (i_slVarMLC1 >= i_lineNumber)) { throwAX("constructor: i_" + s_varMLC1 + "StartLine (" + i_slVarMLC1 + ") must either equal -1, or be between 0 and i_lineNumber (" + i_lineNumber + "), EX-clusive."); } } } //An awesome use of StringOrBuffer class InsideWhitespace extends StringOrBuffer { private UtilSOB uSOB = null; private int i1stNonWS = -1; private int iCharAfterLastNonWS = -1; private StringOrBuffer sob = null; public InsideWhitespace(StringOrBuffer str_orBfr, UtilSOB util_sob) { uSOB = util_sob; i1stNonWS = uSOB.getIdxOf1stNonWS(str_orBfr); iCharAfterLastNonWS = uSOB.getIdxOfLastNonWS(str_orBfr) + 1; if(i1stNonWS == -1) { i1stNonWS = 0; } if(iCharAfterLastNonWS == -1) { iCharAfterLastNonWS = str_orBfr.length(); } sob = str_orBfr; } public int getActualIdx(int i_iwIdx) { return i_iwIdx + getIdx1stNonWS(); } public int getIdx(int i_idx) { return getIdx(i_idx, "getIdx"); } public int getIdx(int i_idx, String s_callingFunc) { return getIdx(i_idx, s_callingFunc, "i_idx"); } public int getIdx(int i_idx, String s_callingFunc, String s_iIdxDesc) { int iIdx = i_idx + getIdx1stNonWS(); if(iIdx > getIdxAfterLastNonWS()) { throwAX(s_callingFunc + ": " + s_iIdxDesc + " (" + i_idx + ") invalid for " + length() + " length InsideWhitespace." + toString()); } return iIdx; } public int getIdxAfterRight(int i_idxAfterRight) { return getIdx(i_idxAfterRight, "getIdxAfterRight"); } public int getIdxAfterRight(int i_idxAfterRight, String s_callingFunc) { return getIdxAfterRight(i_idxAfterRight, s_callingFunc, "i_idxAfterRight"); } public int getIdxAfterRight(int i_idxAfterRight, String s_callingFunc, String s_iIARDesc) { int iIdx = i_idxAfterRight + getIdx1stNonWS(); if(iIdx > getIdxAfterLastNonWS()) { throwAX(s_callingFunc + ": " + s_iIARDesc + " (" + i_idxAfterRight + ") invalid for " + length() + " length InsideWhitespace." + toString()); } return iIdx; } public int length() { return (getIdxAfterLastNonWS() - getIdx1stNonWS()); } public boolean startsWith(String s_searchFor) { int i = sob.indexOf(s_searchFor, getIdx1stNonWS(), getIdxAfterLastNonWS()); return (i == getIdx1stNonWS()); } public boolean endsWith(String s_searchFor) { int i = sob.indexOf(s_searchFor, getIdx1stNonWS(), getIdxAfterLastNonWS()); return (i == (length() - s_searchFor.length())); } public char charAt(int i_idx) { return sob.charAt(getIdx(i_idx, "charAt")); } public String substring(int i_idxLeft) { return sob.substring(getIdx(i_idxLeft, "substring", "i_idxLeft"), getIdxAfterLastNonWS()); } public String substring(int i_idxLeft, int i_idxAfterRight) { return sob.substring( getIdx(i_idxLeft, "substring", "i_idxLeft"), getIdxAfterRight(i_idxAfterRight, "substring", "i_idxAfterRight")); } public int getIdx1stNonWS() { return i1stNonWS; } public int getIdxAfterLastNonWS() { return iCharAfterLastNonWS; } public int getActualLength() { return sob.length(); } public String toString() { return " [length()=" + length() + ", getActualLength()=" + getActualLength() + "), getIdx1stNonWS()=" + getIdx1stNonWS() + ", getIdxAfterLastNonWS()=" + getIdxAfterLastNonWS() + ", the string='" + uSOB.getVisible(sob) + "', without surrounding whitespace: '" + (new UtilString()).getVisible(substring(0)) + "']"; } //NOT NEEDED...start public boolean isString() { if(true) { throwAX("isString"); } return true; } public String getTrimmed() { if(true) { throwAX("getTrimmed"); } return null; } public void trim() { throwAX("trim"); } public void append(String s_toAppend) { throwAX("append"); } public void appendToLeft(String s_toAppend) { throwAX("appendToLeft"); } public void insert(int i_idx, String s_toInsert) { throwAX("insert"); } public void append(char c_toAppend) { throwAX("append"); } public void appendToLeft(char c_toAppend) { throwAX("appendToLeft"); } public void insert(int i_idx, char c_toInsert) { throwAX("insert"); } public void setLength(int i_newLength) { throwAX("setLength"); } public void deleteCharAt(int i_idx) { throwAX("deleteCharAt"); } public void delete(int i_idxLeft, int i_idxAfterRight) { throwAX("delete"); } //NOT NEEDED...end }