/* * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2002 * Copyright by ESO (in the framework of the ALMA collaboration) * and Cosylab 2002, All rights reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package alma.acs.logging.engine.io; import com.cosylab.logging.engine.log.LogTypeHelper; /** * <code>LogStringBuffer</code> is a buffer of chars supporting the loading of logs * from a file. * <P> * The buffer contains the chars read from the file and passed through the <code>append()</code>. * For each new chars, <code>append()</code> checks if a closing or an ending XML tag of a log is in the buffer. * <P> * When a whole XML representing a log is in the buffer, it is returned to the caller and the * buffer cleared to be ready to look for the next XML. * <BR> * The size of the buffer is initially set to a default value and it is doubled whenever * the algorithm needs more room in the array of char. * <P> * This functionality was initially implemented encapsulating a <code>StringBuilder</code> property * but it was too slow compared to basic chars manipulation. * * @author acaproni * */ public class LogStringBuffer { /** * The array of chars where the chars read from the input file are written. * <P> * The size passed in the construct is doubled when the length is not enough by calling * <code>doubleBuffer()</code>. * <BR> * While reading logs from a file, each log is temporarily written in this array do it * begins with an XML opening tag and terminates with the closing tag. * Any extra char read at the beginning of an opening tag is skipped. */ private char[] buffer = new char[1024]; /** * The position where a new char must be written in the array <code>buffer</code>. * <P> * It represents also the length of the string in the buffer. */ private int bufferPos=0; /** * The position of the starting XML tag * <P> * The value of this property initially is -1 and is set to be equal to the position of the * '<' char of the starting tag until a valid tag is found * */ private int startTagPos; /** * The XML tags of the log types (including the leading '<') for example * <Trace and <Info * <P> * The first dimension is the position of the tag in the array of <code>LogTypeHelper</code>, * the second dimension is the length of the tag */ private char[][] xmlOpeningTags; /** * This property, initialized in the constructor, contains the size of the shortest start tag * and is used to reduce the number of checks while looking for the starting tag */ private final int minStartXMLTagSize; /** * The closing XML tags of the log types for example </Trace> and </Info> * <P> * The first dimension is the position of the tag in the array of <code>LogTypeHelper</code>, * the second dimension is the length of the tag */ private char[][] xmlClosingTags; /** * The starting of a CDATA section */ private char[] cdataStart = (new String("<![CDATA[")).toCharArray(); /** * The end of a CDATA section */ private char[] cdataEnd= (new String("]]>")).toCharArray(); /** * The position of the starting of the cdata section */ private int cdataStartPos; /** * The position of the ending of the cdata section */ private int cdataEndPos; /** * The starting XML tag (it is <code>null</code> when the start tag has not yet * been found in the buffer */ private LogTypeHelper startTag; /** * The constructor * * @param size The size of the rotating buffer */ public LogStringBuffer() { clear(); // Build the xml tags int minSz = Integer.MAX_VALUE; xmlOpeningTags = new char[LogTypeHelper.values().length][]; for (int t=0; t<LogTypeHelper.values().length; t++) { xmlOpeningTags[t]=("<"+LogTypeHelper.values()[t].logEntryType).toCharArray(); if (xmlOpeningTags[t].length<minSz) { minSz=xmlOpeningTags[t].length; } } minStartXMLTagSize=minSz; // The closing tag has one item more for the trace: "/>" xmlClosingTags = new char[LogTypeHelper.values().length+1][]; for (int t=0; t<LogTypeHelper.values().length; t++) { xmlClosingTags[t]=("</"+LogTypeHelper.values()[t].logEntryType+">").toCharArray(); } xmlClosingTags[xmlClosingTags.length-1]="/>".toCharArray(); } /** * Clear the buffer * */ private void clear() { bufferPos=0; startTag=null; startTagPos=cdataStartPos=cdataEndPos=-1; } /** * Append a char to the end of the buffer. * <P> * This method checks if the buffer contains the XML representing a log entry. * The <code>str</code> parameter is used to return the string with a log and * <I>must</I> be empty. * The method does not perform any check about the content of the passed * <code>StringBuilder</code> so it is the caller that must ensure the correctness. * <P> * <B>Note</B>: the content of the <code>StringBuilder</code> is changed only * and only if there is a whole log in the buffer. * * @param ch The char to append * @param str A string representing the new log; * the content of this parameter is changed if and only if there is * a log in the buffer */ public void append(char ch, StringBuilder str) { if (bufferPos==buffer.length) { buffer=doubleBuffer(buffer); } buffer[bufferPos++]=ch; if (ch!='<' && ch!='>' && ch!='[') { // The buffer is processed only if a special char is found: <, >, or [ return; } if (ch=='<' && startTag==null) { startTagPos=bufferPos-1; return; } if (startTagPos>-1 && startTag==null && bufferPos-startTagPos>=minStartXMLTagSize) { startTag=lookForStartTag(buffer, startTagPos); if (ch!='>') { return; } } if (ch=='[' && cdataStartPos==-1) { if (compareFromLastChar(buffer, bufferPos-1, cdataStart)) { cdataStartPos=bufferPos-1; return; } } if (ch=='>' && cdataStartPos!=-1 && cdataEndPos==-1) { if (compareFromLastChar(buffer, bufferPos-1, cdataEnd)) { cdataEndPos=bufferPos-1; return; } } if (ch=='>' && startTag!=null) { // Check if we are inside a CDATA if (cdataStartPos!=-1 && cdataEndPos<cdataStartPos) { return; } // Check if there is a closing XML tag if ( compareFromLastChar(buffer, bufferPos-1, xmlClosingTags[startTag.ordinal()]) || (startTag==LogTypeHelper.TRACE && compareFromLastChar(buffer, bufferPos-1, xmlClosingTags[xmlClosingTags.length-1]))) { // A log is in the buffer str.append(buffer,startTagPos, bufferPos-startTagPos); clear(); } } } /** * @return a String representation of the buffer */ public String toString() { return new String(buffer).substring(0, bufferPos); } /** * Doubles the array. * <P> * It creates a new array having the size double as the size of the passed array. * <BR> * The content of the first array is copied in the newly created one. * * @param originalBuffer The buffer to copy in the new array of a double size */ private char[] doubleBuffer(char[] originalBuffer) { if (originalBuffer==null) { throw new IllegalArgumentException("Invalid null array of char"); } char ret[] = new char[originalBuffer.length*2]; System.arraycopy(originalBuffer, 0, ret, 0, originalBuffer.length); return ret; } /** * Compare the <code>src</code> array starting at position <code>srcPos</code> with * the <code>dest</code> array. * <P> * The method checks if all the chars starting at <code>srcPos</code> position of the first array * are equal to the chars of the second array. * <BR> * The comparison lasts for all the chars of the <code>dest</code> array i.e. this method compares * the chars in <code>src[newPos, newPos+dest.length]</code> with those in <code>dest[0,dest.length]</code>. * * @param src The first array to compare * @param srcPos The position inside <code>src</code> to start the comparison at * @param dest The array to compare * @return <code>true</code> If the chars of the <code>src</code> starting at <code>srcPos</code> are * equal to the chars of <code>dest</code>. */ private boolean compare(char[] src, int srcPos, char[]dest) { if (bufferPos-srcPos<dest.length) { return false; } for (int c=0; c<dest.length; c++) { if (srcPos+c>=src.length || src[srcPos+c]!=dest[c]) { return false; } } return true; } /** * Compare the content of 2 array of chars starting from the position * of the last char of the first array. * <P> * The method checks is <code>src</code> and <code>dest</code> are equal. * The check is done in reverse order, starting from the position of the last * char of the first array, <code>src</code>. * <BR> * The comparison lasts for all the chars of the <code>dest</code> array i.e. this method compares * the chars in <code>src[lastCharPos-dest.length,lastCharPos]</code> with those in <code>dest[0,dest.length]</code>. * * @param src The first array to compare * @param lastCharPos The position of the last char of <code>src</code> to compare * @param dest The array to compare * @return <code>true</code> If the chars of the <code>src</code> starting at <code>lastCharPos-dest.length</code> * are equal to the chars of <code>dest</code>. */ private boolean compareFromLastChar(char src[], int lastCharPos, char[] dest) { if (lastCharPos-dest.length+1<0) { return false; } return compare(src, lastCharPos-dest.length+1, dest); } /** * Look for an XML tag in the passed string, starting at the given position. * * @param str The string to check * @param pos The position in the <code>str</code> to look for the tag * @return The <code>LogTypehelper</code> corresponding to the XML tag * <code>null</code> if not XML tag is found at the given position */ private LogTypeHelper lookForStartTag(char[] str, int pos) { for (int t=0; t<xmlOpeningTags.length; t++) { if (compare(str,pos,xmlOpeningTags[t])) { return LogTypeHelper.values()[t]; } } return null; } public void dump() { System.out.println("VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV"); System.out.println("startTag: "+startTag+"\t at "+startTagPos); System.out.println("cdata starts at "+cdataStartPos+", ends at "+cdataEndPos); System.out.println("Buffer (size: "+buffer.length+", pos:"+bufferPos+"): "+(new String(buffer).substring(0, bufferPos))); System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); } }