package net.sourceforge.squirrel_sql.client.util.codereformat; /* * Copyright (C) 2003 Gerd Wagner * * 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 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. */ import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import net.sourceforge.squirrel_sql.fw.util.StringManager; import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory; import net.sourceforge.squirrel_sql.fw.util.StringUtilities; import net.sourceforge.squirrel_sql.fw.util.log.ILogger; import net.sourceforge.squirrel_sql.fw.util.log.LoggerController; public class CodeReformator implements ICodeReformator { private static final StringManager s_stringMgr = StringManagerFactory .getStringManager(CodeReformator.class); /** * Platform-specific line separator string */ private String _lineSep = StringUtilities.getEolStr(); private static ILogger s_log = LoggerController.createLogger(CodeReformator.class); private CodeReformatorConfig _codeReformatorConfig; public CodeReformator(CodeReformatorConfig codeReformatorConfig) { _codeReformatorConfig = codeReformatorConfig; } /** * @see ICodeReformator#reformat(java.lang.String) */ public String reformat(String in) { in = flatenWhiteSpaces(in, false); PieceMarkerSpec[] markerExcludeComma = createPieceMarkerSpecExcludeColon(); String[] pieces = getReformatedPieces(in, markerExcludeComma).toArray(new String[0]); if (_codeReformatorConfig.isDoInsertValuesAlign()) { pieces = doInsertSpecial(pieces); } StringBuffer ret = new StringBuffer(); int braketCount = 0; for (int i = 0; i < pieces.length; ++i) { if (")".equals(pieces[i])) { --braketCount; } ret.append(indent(pieces[i], braketCount)); ret.append(_lineSep); if ("(".equals(pieces[i])) { ++braketCount; } } validate(in, ret.toString()); return ret.toString(); } private void validate(String beforeReformat, String afterReformat) { String normalizedBefore = getNormalized(beforeReformat); String normalizedAfter = getNormalized(afterReformat); if (!normalizedBefore.equalsIgnoreCase(normalizedAfter)) { int minLen = Math.min(normalizedAfter.length(), normalizedBefore .length()); StringBuffer diffPos = new StringBuffer(); for (int i = 0; i < minLen; ++i) { if (Character.toUpperCase(normalizedBefore.charAt(i)) != Character .toUpperCase(normalizedAfter.charAt(i))) { break; } diffPos.append('-'); } diffPos.append('^'); // i18n[editextras.reformatFailed=Reformat failed, normalized // Strings differ] StringBuilder msg = new StringBuilder(s_stringMgr.getString("codereformat.reformatFailed")); msg.append(_lineSep); msg.append(normalizedBefore); msg.append(_lineSep); msg.append(normalizedAfter); msg.append(_lineSep); msg.append(diffPos.toString()); msg.append(_lineSep); if (s_log.isInfoEnabled()) { s_log.info(msg.toString()); } throw new IllegalStateException(msg.toString()); } } /** * Returns a normalized version of a SQL string. Normalized strings before * and after reformatting should be the same. So normalized Strings may be * used for validation of the reformating process. */ private String getNormalized(String s) { String ret = s.replaceAll("\\(", " ( "); ret = ret.replaceAll("\\)", " ) "); ret = ret.replaceAll(",", " , "); String sep = _codeReformatorConfig.getStatementSeparator(); // If our separator is the regular expression special char '|', then // quote it before formatting. if (sep.equals("|")) { sep = "\\|"; } ret = ret.replaceAll(sep, concat(" ", sep, " ")); return flatenWhiteSpaces(ret, true).trim(); } /** * Concatenates the specified strings and returns the result. * * @param strings the strings in array form to concatenate. * @return the concatenated result. */ private String concat(String... strings) { StringBuilder result = new StringBuilder(); for (String string : strings) { result.append(string); } return result.toString(); } private List<String> getReformatedPieces(String in, PieceMarkerSpec[] markers) { CodeReformatorKernel kernel = new CodeReformatorKernel( _codeReformatorConfig.getStatementSeparator(), markers, _codeReformatorConfig.getCommentSpecs()); String[] pieces = kernel.toPieces(in); ArrayList<String> piecesBuf = new ArrayList<String>(); for (int i = 0; i < pieces.length; ++i) { if (_codeReformatorConfig.getTrySplitLineLen() < pieces[i].length()) { String[] splitPieces = trySplit(pieces[i], 0, _codeReformatorConfig.getTrySplitLineLen()); piecesBuf.addAll(Arrays.asList(splitPieces)); } else { piecesBuf.add(pieces[i]); } } return piecesBuf; } private String[] doInsertSpecial(String[] pieces) { int insertBegin = -1; boolean hasValues = false; ArrayList<String> ret = new ArrayList<String>(); ArrayList<String> insertPieces = new ArrayList<String>(); for (int i = 0; i < pieces.length; ++i) { if ("INSERT ".length() <= pieces[i].length() && pieces[i].substring(0, "INSERT ".length()).equalsIgnoreCase("INSERT ")) { if (-1 != insertBegin) { // Inserts are not properly separated. We give up. return pieces; } insertBegin = i; } if (-1 == insertBegin) { ret.add(pieces[i]); } else { insertPieces.add(pieces[i]); } if (-1 < insertBegin && -1 != pieces[i].toUpperCase().indexOf("VALUES")) { hasValues = true; } if (-1 < insertBegin && _codeReformatorConfig.getStatementSeparator().equalsIgnoreCase(pieces[i])) { if (hasValues) { ret.addAll(reformatInsert(insertPieces)); } else { // No special treatment ret.addAll(insertPieces); } insertBegin = -1; hasValues = false; insertPieces = new ArrayList<String>(); } } if (-1 < insertBegin) { if (hasValues) { ret.addAll(reformatInsert(insertPieces)); } else { // No special treatment ret.addAll(insertPieces); } } return ret.toArray(new String[0]); } private ArrayList<String> reformatInsert(ArrayList<String> piecesIn) { String[] pieces = splitAsFarAsPossible(piecesIn.toArray(new String[piecesIn.size()])); ArrayList<String> insertList = new ArrayList<String>(); ArrayList<String> valuesList = new ArrayList<String>(); ArrayList<String> behindInsert = new ArrayList<String>(); StringBuffer statementBegin = new StringBuffer(); int braketCountAbsolute = 0; for (int i = 0; i < pieces.length; ++i) { if (3 < braketCountAbsolute) { behindInsert.add(pieces[i]); } if ("(".equals(pieces[i]) || ")".equals(pieces[i])) { ++braketCountAbsolute; } if (0 == braketCountAbsolute) { statementBegin.append(pieces[i]).append(' '); } if (1 == braketCountAbsolute && !"(".equals(pieces[i]) && !")".equals(pieces[i])) { String buf = pieces[i].trim(); if (buf.endsWith(",")) { buf = buf.substring(0, buf.length() - 1); } insertList.add(buf); } if (3 == braketCountAbsolute && !"(".equals(pieces[i]) && !")".equals(pieces[i])) { String buf = pieces[i].trim(); if (buf.endsWith(",")) { buf = buf.substring(0, buf.length() - 1); } valuesList.add(buf); } } ArrayList<String> ret = new ArrayList<String>(); if (0 == insertList.size()) { // Not successful ret.addAll(piecesIn); return ret; } if (insertList.size() == valuesList.size()) { ret.add(statementBegin.toString()); StringBuffer insert = new StringBuffer(); StringBuffer values = new StringBuffer(); String insBuf = insertList.get(0); String valsBuf = valuesList.get(0); insert.append('(').append(adjustLength(insBuf, valsBuf)); values.append('(').append(adjustLength(valsBuf, insBuf)); for (int i = 1; i < insertList.size(); ++i) { insBuf = insertList.get(i); valsBuf = valuesList.get(i); insert.append(',').append(adjustLength(insBuf, valsBuf)); values.append(',').append(adjustLength(valsBuf, insBuf)); } insert.append(") VALUES"); values.append(')'); ret.add(insert.toString()); ret.add(values.toString()); ret.addAll(behindInsert); return ret; } else { // Not successful ret.addAll(piecesIn); return ret; } } private String[] splitAsFarAsPossible(String[] pieces) { ArrayList<String> ret = new ArrayList<String>(); for (int i = 0; i < pieces.length; i++) { ret.addAll(Arrays.asList(trySplit(pieces[i], 0, 1))); } return ret.toArray(new String[ret.size()]); } private String adjustLength(String s1, String s2) { int max = Math.max(s1.length(), s2.length()); if (s1.length() == max) { return s1; } else { StringBuffer sb = new StringBuffer(); sb.append(s1); while (sb.length() < max) { sb.append(' '); } return sb.toString(); } } private String[] trySplit(String piece, int braketDepth, int trySplitLineLen) { String trimmedPiece = piece.trim(); CodeReformatorKernel dum = new CodeReformatorKernel( _codeReformatorConfig.getStatementSeparator(), new PieceMarkerSpec[0], _codeReformatorConfig.getCommentSpecs()); if (hasTopLevelColon(trimmedPiece, dum)) { PieceMarkerSpec[] pms = createPieceMarkerSpecIncludeColon(); CodeReformatorKernel crk = new CodeReformatorKernel( _codeReformatorConfig.getStatementSeparator(), pms, _codeReformatorConfig.getCommentSpecs()); String[] splitPieces1 = crk.toPieces(trimmedPiece); if (1 == splitPieces1.length) { return splitPieces1; } ArrayList<String> ret = new ArrayList<String>(); for (int i = 0; i < splitPieces1.length; ++i) { if (trySplitLineLen < splitPieces1[i].length() + braketDepth * _codeReformatorConfig.getIndent().length()) { String[] splitPieces2 = trySplit(splitPieces1[i], braketDepth, trySplitLineLen); for (int j = 0; j < splitPieces2.length; ++j) { ret.add(splitPieces2[j].trim()); } } else { ret.add(splitPieces1[i].trim()); } } return (purgeEmptyStrings(ret)).toArray(new String[0]); } else { int[] tlbi = getTopLevelBraketIndexes(trimmedPiece, dum); if (-1 != tlbi[0] && tlbi[0] < tlbi[1]) { // //////////////////////////////////////////////////////////////////////// // Split the first two matching toplevel brakets here PieceMarkerSpec[] pms = createPieceMarkerSpecExcludeColon(); CodeReformatorKernel crk = new CodeReformatorKernel( _codeReformatorConfig.getStatementSeparator(), pms, _codeReformatorConfig.getCommentSpecs()); String[] splitPieces1 = crk.toPieces(trimmedPiece.substring( tlbi[0] + 1, tlbi[1])); ArrayList<String> buf = new ArrayList<String>(); buf.add(trimmedPiece.substring(0, tlbi[0]).trim()); buf.add("("); for (int i = 0; i < splitPieces1.length; ++i) { buf.add(splitPieces1[i]); } buf.add(")"); if (tlbi[1] + 1 < trimmedPiece.length()) { buf.add(trimmedPiece.substring(tlbi[1] + 1, trimmedPiece.length()).trim()); } splitPieces1 = buf.toArray(new String[0]); // // //////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////// // Now check length of Strings in splitPieces1 again ArrayList<String> ret = new ArrayList<String>(); for (int i = 0; i < splitPieces1.length; ++i) { if (trySplitLineLen < splitPieces1[i].length() + braketDepth * _codeReformatorConfig.getIndent().length()) { String[] splitPieces2 = trySplit(splitPieces1[i], braketDepth + 1, trySplitLineLen); for (int j = 0; j < splitPieces2.length; ++j) { ret.add(splitPieces2[j]); } } else { ret.add(splitPieces1[i]); } } // // /////////////////////////////////////////////////////////////////// return (purgeEmptyStrings(ret)).toArray(new String[0]); } else { return new String[]{piece}; } } } /** * Takes the given list of string and removes elements that are either * null or empty strings * * @param items * @return */ private List<String> purgeEmptyStrings(List<String> items) { for (Iterator<String> iter = items.iterator(); iter.hasNext(); ) { String item = iter.next(); if (item == null || "".equals(item)) { iter.remove(); } } return items; } private boolean hasTopLevelColon(String piece, CodeReformatorKernel crk) { int ix = piece.indexOf(","); StateOfPosition[] stateOfPositions = crk.getStatesOfPosition(piece); while (-1 != ix) { if (stateOfPositions[ix].isTopLevel) { return true; } if (ix < piece.length() - 1) { ix = piece.indexOf(",", ix + 1); } else { break; } } return false; } private int[] getTopLevelBraketIndexes(String piece, CodeReformatorKernel crk) { int[] ret = new int[2]; ret[0] = -1; ret[1] = -1; StateOfPosition[] stateOfPositions = crk.getStatesOfPosition(piece); int bra = piece.indexOf("("); while (-1 != bra) { crk.getStatesOfPosition(piece); if (0 == bra || stateOfPositions[bra - 1].isTopLevel) { ret[0] = bra; break; // break when first braket found } if (bra < piece.length() - 1) { bra = piece.indexOf("(", bra + 1); } else { break; } } if (-1 == ret[0]) { return ret; } int ket = piece.indexOf(")", bra); while (-1 != ket) { if (ket == piece.length() - 1 || stateOfPositions[ket].isTopLevel) { // the next top level ket is the counterpart to bra ret[1] = ket; break; } if (ket < piece.length() - 1) { ket = piece.indexOf(")", ket + 1); } else { break; } } return ret; } private String indent(String piece, int callDepth) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < callDepth; ++i) { sb.append(_codeReformatorConfig.getIndent()); } sb.append(piece); return sb.toString(); } private String flatenWhiteSpaces(String in, boolean force) { if (hasCommentEndingWithLineFeed(in) && !force) { // No flaten. We would turn statement parts to comment return in; } StringBuffer ret = new StringBuffer(); int aposCount = 0; for (int i = 0; i < in.length(); ++i) { if ('\'' == in.charAt(i)) { ++aposCount; } boolean dontAppend = false; if (0 != aposCount % 2) { } else { if (Character.isWhitespace(in.charAt(i)) && i + 1 < in.length() && Character.isWhitespace(in.charAt(i + 1))) { dontAppend = true; } } if (false == dontAppend) { char toAppend; if (Character.isWhitespace(in.charAt(i)) && 0 == aposCount % 2) { toAppend = ' '; } else { toAppend = in.charAt(i); } ret.append(toAppend); } } return ret.toString(); } boolean hasCommentEndingWithLineFeed(String in) { CodeReformatorKernel dum = new CodeReformatorKernel( _codeReformatorConfig.getStatementSeparator(), new PieceMarkerSpec[0], _codeReformatorConfig.getCommentSpecs()); StateOfPosition[] sops = dum.getStatesOfPosition(in); boolean inComment = false; for (int i = 0; i < sops.length; ++i) { if (!inComment && -1 < sops[i].commentIndex) { if (-1 < _codeReformatorConfig.getCommentSpecs()[sops[i].commentIndex].commentEnd .indexOf('\n')) { return true; } inComment = true; } if (-1 == sops[i].commentIndex) { inComment = false; } } return false; } private PieceMarkerSpec[] createPieceMarkerSpecIncludeColon() { PieceMarkerSpec[] buf = createPieceMarkerSpecExcludeColon(); ArrayList<PieceMarkerSpec> ret = new ArrayList<PieceMarkerSpec>(); ret.addAll(Arrays.asList(buf)); ret.add(new PieceMarkerSpec(",", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_END)); return ret.toArray(new PieceMarkerSpec[0]); } private PieceMarkerSpec[] createPieceMarkerSpecExcludeColon() { ArrayList<PieceMarkerSpec> ret = new ArrayList<PieceMarkerSpec>(); ret.addAll(Arrays.asList(_codeReformatorConfig.getKeywordPieceMarkerSpecs())); ret.add(new PieceMarkerSpec(_codeReformatorConfig.getStatementSeparator(), PieceMarkerSpec.TYPE_PIECE_MARKER_IN_OWN_PIECE)); return ret.toArray(new PieceMarkerSpec[ret.size()]); } }