/** * OrbisGIS is a java GIS application dedicated to research in GIScience. * OrbisGIS is developed by the GIS group of the DECIDE team of the * Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>. * * The GIS group of the DECIDE team is located at : * * Laboratoire Lab-STICC – CNRS UMR 6285 * Equipe DECIDE * UNIVERSITÉ DE BRETAGNE-SUD * Institut Universitaire de Technologie de Vannes * 8, Rue Montaigne - BP 561 56017 Vannes Cedex * * OrbisGIS is distributed under GPL 3 license. * * Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488) * Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285) * * This file is part of OrbisGIS. * * OrbisGIS 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 3 of the License, or (at your option) any later * version. * * OrbisGIS 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 * OrbisGIS. If not, see <http://www.gnu.org/licenses/>. * * For more information, please consult: <http://www.orbisgis.org/> * or contact directly: * info_at_ orbisgis.org */ package org.orbisgis.sqlconsole.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 org.orbisgis.commons.utils.TextUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xnap.commons.i18n.I18n; import org.xnap.commons.i18n.I18nFactory; public class CodeReformator implements ICodeReformator { private static final String INDENT = " "; protected final static I18n I18N = I18nFactory.getI18n(CodeReformator.class); private static final int TRY_SPLIT_LINE_LEN = 80; private String _statementSeparator; private CommentSpec[] _commentSpecs; /** Platform-specific line separator string */ private String _lineSep = TextUtils.getEolStr(); private static final Logger s_log = LoggerFactory.getLogger(CodeReformator.class); public CodeReformator(String statementSeparator, CommentSpec[] commentSpecs) { _statementSeparator = statementSeparator; _commentSpecs = commentSpecs; } /** * @param in * @return * @see net.sourceforge.squirrel_sql.fw.codereformat.ICodeReformator#reformat(java.lang.String) */ @Override public String reformat(String in) { in = flatenWhiteSpaces(in, false); PieceMarkerSpec[] markerExcludeComma = createPieceMarkerSpecExcludeColon(); String[] pieces = getReformatedPieces(in, markerExcludeComma).toArray( new String[0]); pieces = doInsertSpecial(pieces); StringBuilder ret = new StringBuilder(); 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()); StringBuilder diffPos = new StringBuilder(); 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(I18N.tr("Reformat failed, normalised strings differ")); 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 = _statementSeparator; // 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( markers, _commentSpecs); String[] pieces = kernel.toPieces(in); ArrayList<String> piecesBuf = new ArrayList<String>(); for (int i = 0; i < pieces.length; ++i) { if (TRY_SPLIT_LINE_LEN < pieces[i].length()) { String[] splitPieces = trySplit(pieces[i], 0, TRY_SPLIT_LINE_LEN); 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 && _statementSeparator.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>(); StringBuilder statementBegin = new StringBuilder(); 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()); StringBuilder insert = new StringBuilder(); StringBuilder values = new StringBuilder(); 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 { StringBuilder sb = new StringBuilder(); 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( new PieceMarkerSpec[0], _commentSpecs); if (hasTopLevelColon(trimmedPiece, dum)) { PieceMarkerSpec[] pms = createPieceMarkerSpecIncludeColon(); CodeReformatorKernel crk = new CodeReformatorKernel( pms, _commentSpecs); 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 * INDENT.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( pms, _commentSpecs); 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 * INDENT.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) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < callDepth; ++i) { sb.append(INDENT); } 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; } StringBuilder ret = new StringBuilder(); 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( new PieceMarkerSpec[0], _commentSpecs); StateOfPosition[] sops = dum.getStatesOfPosition(in); boolean inComment = false; for (int i = 0; i < sops.length; ++i) { if (!inComment && -1 < sops[i].commentIndex) { if (-1 < _commentSpecs[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() { return new PieceMarkerSpec[] { new PieceMarkerSpec("SELECT", PieceMarkerSpec.TYPE_PIECE_MARKER_IN_OWN_PIECE), new PieceMarkerSpec("UNION", PieceMarkerSpec.TYPE_PIECE_MARKER_IN_OWN_PIECE), new PieceMarkerSpec("FROM", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("INNER", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("LEFT", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("RIGHT", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("WHERE", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("AND", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("GROUP", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("ORDER", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("INSERT", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("VALUES", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("UPDATE", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec("DELETE", PieceMarkerSpec.TYPE_PIECE_MARKER_AT_BEGIN), new PieceMarkerSpec(_statementSeparator, PieceMarkerSpec.TYPE_PIECE_MARKER_IN_OWN_PIECE) }; } }