blob: 4fb507a678f529bd0bab1ff93eb3860e235927e8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.jackrabbit.oak.query;
import java.util.Locale;
import org.jetbrains.annotations.NotNull;
/**
* Formatter for JCR queries in order to make them easier to read. Formatting is
* done on a best-effort basis.
*
* Warning: Care was taken to not add newlines inside string literals and so on,
* but there is still no guarantee that the formatted query is semantically
* equal to the original one. It is NOT recommended to run queries that are
* returned by these methods.
*/
public class QueryFormatter {
/**
* Detect whether the query is an XPath query.
*
* @param query the query
* @param language the language, if known, or null
* @return true if xpath
*/
public static boolean isXPath(String query, String language) {
if (language != null) {
// the language is case sensitive
return "xpath".equals(language);
}
// the query is not, at least SQL is not
query = query.trim().toLowerCase(Locale.ENGLISH);
// explain queries
if (query.startsWith("explain")) {
query = query.substring("explain".length()).trim();
if (query.startsWith("measure")) {
query = query.substring("measure".length()).trim();
}
}
// union queries
while (query.startsWith("(")) {
query = query.substring("(".length()).trim();
}
if (query.startsWith("select")) {
return false;
}
return true;
}
/**
* Format the query into a more human-readable way, by adding newlines before
* keywords such as "where", "and", and "or".
*
* @param query the query (may not be null)
* @param language the query language, or null if unknown
* @return the formatted query
*/
public static String format(@NotNull String query, String language) {
boolean xpath = isXPath(query, language);
if (xpath) {
return formatXPath(query);
} else {
return formatSQL(query);
}
}
private static String formatXPath(String query) {
StringBuilder buff = new StringBuilder(query);
for (int i = 0; i < buff.length(); i++) {
char c = buff.charAt(i);
if (c == '\'' || c == '"') {
while (++i < buff.length() && buff.charAt(i) != c) {
// skip
}
} else if (c =='[') {
if (i + 1 < buff.length() && buff.charAt(i + 1) > ' ') {
buff.insert(i + 1, "\n ");
i += 3;
}
} else if (c == '\n') {
// already formatted
while (++i < buff.length() && buff.charAt(i) == ' ') {
// skip
}
i--;
} else if (c == ' ') {
String sub = buff.substring(i, Math.min(i + 10, buff.length()));
if (sub.startsWith(" and ")
|| sub.startsWith(" or ")
|| sub.startsWith(" order by ")
|| sub.startsWith(" option(")) {
buff.setCharAt(i, '\n');
buff.insert(i + 1, " ");
// just skip over the whitespace - but that's OK
i += 2;
}
}
}
return buff.toString();
}
private static String formatSQL(String query) {
StringBuilder buff = new StringBuilder(query);
for (int i = 0; i < buff.length(); i++) {
char c = buff.charAt(i);
if (c == '\'' || c == '"') {
while (++i < buff.length() && buff.charAt(i) != c) {
// skip
}
} else if (c == '\n') {
// already formatted
while (++i < buff.length() && buff.charAt(i) == ' ') {
// skip
}
i--;
} else if (c == ' ') {
String sub = buff.substring(i, Math.min(i + 10, buff.length()));
if (startsWithIgnoreCase(sub, " and ")
|| startsWithIgnoreCase(sub, " or ")
|| startsWithIgnoreCase(sub, " union ")
|| startsWithIgnoreCase(sub, " from ")
|| startsWithIgnoreCase(sub, " where ")
|| startsWithIgnoreCase(sub, " order by ")
|| startsWithIgnoreCase(sub, " option(")) {
buff.setCharAt(i, '\n');
buff.insert(i + 1, " ");
// just skip over the whitespace - but that's OK
i += 2;
}
}
}
return buff.toString();
}
private static boolean startsWithIgnoreCase(String s, String prefix) {
return s.regionMatches(true, 0, prefix, 0, prefix.length());
}
}