/**
 * Code Beautifier for Java.
 *    Copyright (c)2001 Jason Pell.
 *
 * Email: jasonpell@hotmail.com
 * @author Jason Pell
 * @version 1.001
 *
 * Based on "Code Beautifier for C/C++"
 *    Copyright (c) 1997, 1998 Van Di-Han HO. All Rights Reserved.
 *
 * Email: starkville@geocities.com
 * @author Van Di-Han HO
 * @version 4.2 Date: 13 May 1998
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * This General Public License does not permit incorporating your program into
 * proprietary programs. If your program is a subroutine library, you may
 * consider it more useful to permit linking proprietary applications with the
 * library. If this is what you want to do, use the GNU Library General Public
 * License instead of this License.
 *
 * This class has been heavily modified from the original source.  It is
 * now provided as a pluggable library for parsing java source.  It is not
 * heavily tested and will no doubt have problems.  It has also been modified
 * to read from a Reader object instead of an InputStream, as this made it
 * much easier to pass in a String (via StringReader) or a File (via FileReader)
 */

 /*
 * ---------------------------------------------------------------------------------
 * Version History for Java Beautifier (Based on "Code Beautifier for C/C++")
 * ---------------------------------------------------------------------------------
 * @version 1.002	Changed parse(...) method to more meaningful beautify(...).
 					Changed getBuffer() method to more meaningful getBeautified().
					04/09/2001
 * @version 1.001   Fix to cpp_comment where a cpp comment appears as the last thing
 * 					in a file.  The bug would only occur if no newline was included.
 *					31/08/2001
 * @version 1.0		Initial rerelease.  28/08/2001
 */

import java.io.Reader;
import java.io.IOException;

public class JavaBeautifier
{
	/**
	 * Statically defined parsing constants.
	 */
	private static final String w_if = "if";
	private static final String w_else = "else";
	private static final String w_for = "for";
	private static final String w_ds = "default";
	private static final String w_case = "case";
	private static final String w_cpp_comment = "//";
	private static final String w_jdoc = "/**";
	private static final String line_feed = "\n";
	private static final int BLOCK_MAXLEN = 256;
	
	private StringBuffer buffer;
	private int    Van_EOF;
	
	private Reader reader = null;
	private int nCharsRead;
	private String Van_sblock;
	private char cArray[];
	private int Van_idxblock;
	private int Van_LineLen;
	private int Van_LineNo;
	private int s_level[];
	private int c_level;
	private int sp_flg[][];
	private int s_ind[][];
	private int s_if_lev[];
	private int s_if_flg[];
	private int if_lev;
	private int if_flg;
	private int level;
	private int ind[];
	private int e_flg;
	private int paren;
	private static int p_flg[];
	private char l_char;
	private char p_char;
	private int a_flg;
	private int ct;
	private int s_tabs[][];
	private int q_flg;
	private int jdoc;
	private int j;
	
	private char string[];
	private char cc;
	private int s_flg;
	private int b_flg;
	private int peek;
	private char peekc;    /* char */
	private int tabs;
	private char next_char, last_char;
	private char c, c0;
	
	// If zero, indicates we should use tabs.
	private int Van_Indent = 0;
	
	/**
	 * Use tabs constructor.
	 */
	public JavaBeautifier()
	{
		this(0);
	}
	
	/**
	 *	@param indent	If 0 (zero), use tabs.  Otherwise specifies number of spaces
	 *					to use for tabs.
	 */
	public JavaBeautifier(int indent)
	{
		Van_Indent = indent;
		
		Van_LineNo = 0;
		c_level = if_lev = level = e_flg = paren = 0;
		a_flg = q_flg = j = b_flg = tabs = 0;
		if_flg = peek = -1;
		peekc = '`';
		s_flg = 1;
		jdoc = 0;
		
		s_level  = new int[10];
		sp_flg   = new int[20][10];
		s_ind    = new int[20][10];
		s_if_lev = new int[10];
		s_if_flg = new int[10];
		ind      = new int[10];
		p_flg    = new int[10];
		s_tabs   = new int[20][10];
	}
	
	/**
	 * Will close reader when finished, so be aware!
	 */
	public void beautify(Reader reader) throws IOException
	{
		this.reader = reader;
		if (buffer==null)
			buffer = new StringBuffer();
		else
			buffer.setLength(0);
		
		StringBuffer onechar;
		
		// now read as long as there is something to read
		Van_EOF = 0; // =1 set in getchr when reach end of file
		
		cArray = new char[BLOCK_MAXLEN];
		string = new char[BLOCK_MAXLEN];
		
		// Clear out array.
		for (int ib=0; ib<BLOCK_MAXLEN; ib++)
			cArray[ib] = '\0';
		
		Van_LineLen = nCharsRead = 0;
		
		// read up a block - remember how many chars read
		nCharsRead = reader.read(cArray);
		Van_sblock = new String(cArray);
		
		Van_LineLen = nCharsRead;
		Van_LineNo  = 1;
		
		// We have read nothing from buffer via getchr()
		Van_idxblock = -1;
		
		j = 0;
		while(Van_EOF == 0)
		{
			c = getchr();
			switch(c)
			{
				case ' ':
				case '\t':
					if(lookup(w_else) == 1)
					{
						gotelse();
						if(s_flg == 0 || j > 0)string[j++] = c;
						indent_puts();
						s_flg = 0;
						break;
					}
					if(s_flg == 0 || j > 0)
						string[j++] = c;
					break;
				case '\r': // <CR> for MS Windows 95
				case '\n':
					// 4.06Van_debug
					Van_LineNo++;
					if (Van_EOF==1)
						break;
					
					String j_string = new String(string);
					
					e_flg = lookup(w_else);
					if(e_flg == 1)
						gotelse();
					
					// 4.06Van_debug
					if (lookup_com(w_cpp_comment) == 1)
					{
						if (string[j] == '\n')
						{
							string[j] = '\0';
							j--;
						}
					}
					
					indent_puts();
					fprintf(line_feed);
					s_flg = 1;
					if(e_flg == 1)
					{
						p_flg[level]++;
						tabs++;
					}
					else
					{
						if(p_char == l_char)
							a_flg = 1;
					}
					break;
				case '{':
					if(lookup(w_else) == 1)
						gotelse();
					
					s_if_lev[c_level] = if_lev;
					s_if_flg[c_level] = if_flg;
					if_lev = if_flg = 0;
					c_level++;
					if(s_flg == 1 && p_flg[level] != 0)
					{
						p_flg[level]--;
						tabs--;
					}
					string[j++] = c;
					indent_puts();
					getnl();
								/* 4.06
								 if (getnl() == 1)
								 {
								 peek = 1;
								 peekc= '\n';
								 }
								 */
					indent_puts();
					fprintf(line_feed);
					tabs++;
					s_flg = 1;
					if(p_flg[level] > 0)
					{
						ind[level] = 1;
						level++;
						s_level[level] = c_level;
					}
					break;
				case '}':
					c_level--;
					if (c_level < 0)
					{
						Van_EOF = 1;
						string[j++] = c;
						indent_puts();
						break;
					}
					
					if( (if_lev = s_if_lev[c_level]-1) < 0)
						if_lev = 0;
					
					if_flg = s_if_flg[c_level];
					indent_puts();
					tabs--;
					p_tabs();
					peekc = getchr();
					if( peekc == ';')
					{
						//fprintf(outfil,"%c;",c);
						onechar = new StringBuffer();
						onechar.append(c);                       // }
						onechar.append(';');
						fprintf(onechar.toString());
						peek = -1;
						peekc = '`';
					}
					else
					{
						// fprintf(outfil,"%c",c);
						onechar = new StringBuffer();
						onechar.append(c);
						fprintf(onechar.toString());
						peek = 1;   // 4.06V no! put back newline for getnl
					}
					getnl();
								/*
					if (getnl()==1)
								 {
								 peek = 1;
								 peekc = '\n';
								 }
								 */
					indent_puts();
					fprintf(line_feed); // 4.06V
					s_flg = 1;
					if(c_level < s_level[level])
						if(level > 0) level--;
					if(ind[level] != 0)
					{
						tabs -= p_flg[level];
						p_flg[level] = 0;
						ind[level] = 0;
					}
					break;
				case '"':
				case '\'':
					string[j++] = c;
					cc = getchr();
					while(cc != c)
					{
										/*   Van_: max. length of line should be 256 */
										/*                if (ck_MAXLEN()) exit(90);*/
						string[j++] = cc;
						
						if(cc == '\\')
						{
							cc = string[j++] = getchr();
						}
						if(cc == '\n')
						{
							Van_LineNo++;
							indent_puts();
							s_flg = 1;
						}
						cc = getchr();
					}
					string[j++] = cc;
					if(getnl() == 1)
					{
						l_char = cc;
						peek = 1;
						peekc = '\n';
					}
					break;
				case ';':
					string[j++] = c;
					indent_puts();
					if(p_flg[level] > 0 && ind[level] == 0)
					{
						tabs -= p_flg[level];
						p_flg[level] = 0;
					}
					getnl();
					indent_puts();
					fprintf("\n");
					s_flg = 1;
					if(if_lev > 0)
						if(if_flg == 1)
						{
							if_lev--;
							if_flg = 0;
						}
						else
							if_lev = 0;
					break;
				case '\\':
					string[j++] = c;
					string[j++] = getchr();
					break;
				case '?':
					q_flg = 1;
					string[j++] = c;
					break;
				case ':':
					string[j++] = c;
					peekc = getchr();
					if(peekc == ':')
					{
						indent_puts();
						fprintf(":");
						peek = -1;
						peekc = '`';
						break;
					}
					else
					{
						int double_colon = 0;
						peek = 1;
					}
					
					if(q_flg == 1)
					{
						q_flg = 0;
						break;
					}
					if(lookup(w_ds) == 0 && lookup(w_case) == 0)
					{
						s_flg = 0;
						indent_puts();
					}
					else
					{
						tabs--;
						indent_puts();
						tabs++;
					}
					peekc = getchr();
					if(peekc == ';')
					{
						fprintf(";");
						peek = -1;
						peekc = '`';
					}
					else
						peek = 1;
					
					getnl();
					indent_puts();
					fprintf(line_feed);
					s_flg = 1;
					break;
				case '/':
					c0 = string[j];
					string[j++] = c;
					peekc = getchr();
					
					if(peekc == '/')
					{
						string[j++] = peekc;
						peekc = '`';
						peek = -1;
						cpp_comment();
						fprintf(line_feed);
						break;
					}
					else
						peek = 1;
					
					// peekc = getchr();
					if(peekc != '*')
						break;
					else // if (peekc == '*')
					{
						if (j > 0) string[j--] = '\0';
						if (j > 0) indent_puts();
						string[j++] = '/';
						string[j++] = '*';
						peek = -1;
						peekc = '`';
						comment();
						break;
					}
				case '#':
					string[j++] = c;
					cc = getchr();
					while(cc != '\n')
					{
						string[j++] = cc;
						cc = getchr();
					}
					string[j++] = cc;
					s_flg = 0;
					indent_puts();
					s_flg = 1;
					break;
				case ')':
					paren--;
					if (paren < 0)
					{
						Van_EOF = 1;
					}
					string[j++] = c;
					indent_puts();
					if(getnl() == 1)
					{
						peekc = '\n';
						peek = 1;
						if(paren != 0)
						{
							a_flg = 1;
						}
						else if(tabs > 0)
						{
							p_flg[level]++;
							tabs++;
							ind[level] = 0;
						}
					}
					break;
				case '(':
					string[j++] = c;
					paren++;
					if ((lookup(w_for) == 1))
					{
						c = get_string();
						while(c != ';') c = get_string();
						ct=0;
						int for_done = 0;
						while (for_done==0)
						{
							c = get_string();
							while(c != ')')
							{
								if(c == '(') ct++;
								c = get_string();
							}
							if(ct != 0)
							{
								ct--;
								//            goto cont;
							}
							else
								for_done = 1;
						} // end while for_done
						
						paren--;
						if (paren < 0)
							Van_EOF = 1;
						
						indent_puts();
						if(getnl() == 1)
						{
							peekc = '\n';
							peek = 1;
							p_flg[level]++;
							tabs++;
							ind[level] = 0;
						}
						break;
					}
					
					if(lookup(w_if) == 1)
					{
						indent_puts();
						//   tabs++;  // VDHPlay
						s_tabs[c_level][if_lev] = tabs;
						sp_flg[c_level][if_lev] = p_flg[level];
						s_ind[c_level][if_lev] = ind[level];
						if_lev++;
						if_flg = 1;
					}
					break;
					
				default:
					string[j++] = c;
					if(c != ',')
					{
						l_char = c;
					}
					break;
			}// end swith
			String j_string = new String(string);
		} // end while not Van_EOF
		reader.close();
	}//end parse
	
	private void comment() throws IOException
	{
		boolean lineRead=false;
		int save_s_flg;
		save_s_flg = s_flg;
		int done = 0;
		c = string[j++] = getchr(); // get one extra char for *
		while(done == 0)
		{
			c = string[j++] = getchr();
			while(c != '/')
			{
				if(c == '\n' || c == '\r')
				{
					Van_LineNo++;
					putcoms();
					s_flg = 1;
					lineRead=true;
				}
				
				// Remove tabs/spaces before start of comment.
				c = getchr();
				if (lineRead)
				{
					while(Van_EOF!=1 && c==' ' || c=='\t')
						c = getchr();
					lineRead=false;
				}
				string[j++] = c;
			}
			
			String tmpstr = new String(string);
			if (j>1 && string[j-2] == '*')
			{
				done = 1;
				jdoc = 0;
			}
		}
		
		putcoms();
		s_flg = save_s_flg;
		jdoc = 0;
		return;
	}
	
	private char get_string() throws IOException
	{
		char ch;
		ch = '*';
		int beg_done = 0;
		while (true)
		{
			switch(ch)
			{
				default:
					ch = string[j++] = getchr();
					if(ch == '\\')
					{
						string[j++] = getchr();
						break ;
					}
					if(ch == '\'' || ch == '"')
					{
						cc = string[j++] = getchr();
						while(cc != ch)
						{
							if(cc == '\\')
								string[j++] = getchr();
							cc = string[j++] = getchr();
						}
						break ;
					}
					if(ch == '\n' || ch == '\r')
					{
						indent_puts();
						a_flg = 1;
						break ;
					}
					else return(ch);
			}
		}
	}
	
	private void indent_puts()
	{
		string[j] = '\0';
		if(j > 0)
		{
			if(s_flg != 0)
			{
				if((tabs > 0) && (string[0] != '{') && (a_flg == 1))
				{
					tabs++;
				}
				p_tabs();
				s_flg = 0;
				if((tabs > 0) && (string[0] != '{') && (a_flg == 1))
				{
					tabs--;
				}
				a_flg = 0;
			}
			
			String j_string = new String(string);
			fprintf(j_string.substring(0,j));
			for (int i=0; i<j; i++) string[i] = '\0';
			j = 0;
		}
		else
		{
			if(s_flg != 0)
			{
				s_flg = 0;
				a_flg = 0;
			}
		}
	}
	
	/**
	 * Returns beautified text, from last call to beautify.
	 */
	public String getBeautified()
	{
		if (buffer!=null)
			return buffer.toString();
		else
			return null;
	}
	
	/**
		Java fprintf :)
	*/
	private void fprintf(String out_string)
	{
		buffer.append(out_string);
	}
	
	/**
	 *  Special edition of put string for comment processing
	 *
	 *  It looks like it swallows extra '\n' if found.
	 */
	private void putcoms()
	{
		int i = 0;
		int sav_s_flg = s_flg;
		if(j > 0)
		{
			if(s_flg != 0)
			{
				p_tabs();
				s_flg = 0;
			}
			string[j] = '\0';
			i = 0;
			
			// Read in all spaces.
			while (string[i] == ' ')
				i++;
			
			// If Javadoc comment found as very next 3 chars.
			if (lookup_com(w_jdoc) == 1)
				jdoc = 1;//should be using booleans here.
			
			String strBuffer = new String(string,0,j);
			if (string[i] == '/' && string[i+1]=='*')
			{
				if ((last_char != ';') && (sav_s_flg==1) )
					fprintf(strBuffer.substring(i,j));
				else
					fprintf(strBuffer);
			}
			else
			{
				if (string[i]=='*' || jdoc == 0)
					fprintf(" "+strBuffer.substring(i,j));
				else
					fprintf(" * "+strBuffer.substring(i,j));
			}
			j = 0;
			string[0] = '\0';
		}
	}
	
	/**
	 *
	 */
	private void cpp_comment()  throws IOException
	{
		c = getchr();
		while(c != '\n' && c != '\r' && Van_EOF!=1)
		{
			string[j++] = c;
			c = getchr();
		}
		
		Van_LineNo++;
		indent_puts();
		s_flg = 1;
	}
	
	/**
	 * Expand tabs and spaces.  If Van_Indent==0 then tabs
	 * will be used.  Otherwise each tab stop will be expanded
	 * Van_Indent spaces each.
	 */
	private void p_tabs()
	{
		if (tabs<0) tabs = 0; // Van_ 20b C++ inline gets next {
		if (tabs==0) return;
		for (int i=0; i<tabs; i++)
		{
			// Write spaces
			if (Van_Indent>0)
				for (int j=0; j<Van_Indent; j++) fprintf(" ");
			else // Or use tabs.
				fprintf("\t");
		}
	}
	
	/**
	 *
	 */
	private char getchr() throws IOException
	{
		if((peek < 0) && (last_char != ' ') && (last_char != '\t'))
		{
			if((last_char != '\n') && (last_char != '\r'))
				p_char = last_char;
		}
		if(peek > 0) // char was read previously
		{
			last_char = peekc;
			peek = -1;
		}
		else // read next char in file
		{
			Van_idxblock++;
			if (Van_idxblock >= Van_LineLen)
			{
				// Resetting cArray elements to all null.
				for (int ib=0; ib<nCharsRead; ib++) cArray[ib] = '\0';
				
				Van_LineLen = nCharsRead = 0;
				// to get the next block
				if ( (nCharsRead = reader.read(cArray))!=-1)
				{
					Van_LineLen = nCharsRead;
					Van_sblock = new String(cArray);
					Van_idxblock = 0;
					last_char = Van_sblock.charAt(Van_idxblock);
					peek = -1;
					peekc = '`';
				}
				else
				{
					Van_EOF = 1;
					peekc  = '\0';
				}
			}
			else
			{
				last_char = Van_sblock.charAt(Van_idxblock);
								/*
								 *                 peek = -1;
								 *                 peekc = '`';
								 */
			}
		}
		peek = -1;
		if (last_char == '\r')
		{
			last_char = getchr();
		}
		return last_char;
	}
	
		/* else processing */
	private void gotelse()
	{
		tabs = s_tabs[c_level][if_lev];
		p_flg[level] = sp_flg[c_level][if_lev];
		ind[level] = s_ind[c_level][if_lev];
		if_flg = 1;
	}
	
		/* read to new_line */
	private int getnl()  throws IOException
	{
		int save_s_flg;
		save_s_flg = tabs;
		peekc = getchr();
		while(peekc == '\t' || peekc == ' ')
		{
			string[j++] = peekc;
			peek = -1;
			peekc = '`';
			peekc = getchr();
			peek = 1;
		}
		peek = 1;
		
		if (peekc == '/')
		{
			peek = -1;
			peekc = '`';
			peekc = getchr();
			if (peekc == '*')
			{
				string[j++] = '/';
				string[j++] = '*';
				peek = -1;
				peekc = '`';
				comment();
			}
			else if (peekc == '/')
			{
				string[j++] = '/';
				string[j++] = '/';
				peek = -1;
				peekc = '`';
				cpp_comment();
				return (1);
			}
			else
			{
				string[j++] = '/';
				peek = 1; // VanCBJ004
			}
		}
		peekc = getchr();
		if(peekc == '\n')
		{
			Van_LineNo++;
			peek = -1;
			peekc = '`';
			tabs = save_s_flg;
			return(1);
		}
		else
		{
			peek = 1;
		}
		return 0;
	}
	
	private int lookup(String keyword)
	{
		char r;
		int  l,kk,k,i;
		String j_string = new String(string);
		
		if (j<1) return (0);
		kk=0;
		while(string[kk] == ' ')kk++;
		l=0;
		l = j_string.indexOf(keyword);
		if (l<0 || l!=kk)
		{
			return 0;
		}
		r = string[kk+keyword.length()];
		if(r >= 'a' && r <= 'z') return(0);
		if(r >= 'A' && r <= 'Z') return(0);
		if(r >= '0' && r <= '9') return(0);
		if(r == '_' || r == '&') return(0);
		return (1);
	}
	
	/**
	 *
	 */
	private int lookup_com(String keyword)
	{
		char r;
		int  l,kk,k,i;
		String j_string = new String(string);
		
		if (j<1) return (0);
		kk=0;
		
		// Read in spaces.
		while(string[kk] == ' ')
			kk++;
		
		l=0;
		l = j_string.indexOf(keyword);
		if (l<0 || l!=kk)
		{
			return 0;
		}
		
		return 1;
	}
	
	// Tester
	public static void main(String args[])
	{
		try
		{
			if (args.length>0)
			{
				JavaBeautifier beauty = new JavaBeautifier();
				beauty.beautify(new java.io.FileReader(new java.io.File(args[0])));
				System.out.println(beauty.getBeautified());
				System.exit(0);
			}
			else
			{
				System.err.println("Usage: JavaBeautifier <inputfile>\n");
				System.exit(1);
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}

