/**
* Copyright (c) 2000-present Liferay, Inc. 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.
*/
package com.liferay.portal.tools;
import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader;
import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
import com.liferay.portal.kernel.util.CharPool;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.MapUtil;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.xml.SAXReaderFactory;
import de.hunsicker.io.FileFormat;
import de.hunsicker.jalopy.Jalopy;
import de.hunsicker.jalopy.storage.Convention;
import de.hunsicker.jalopy.storage.ConventionKeys;
import de.hunsicker.jalopy.storage.Environment;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* @author Brian Wing Shun Chan
* @author Charles May
* @author Alexander Chow
* @author Harry Mark
* @author Tariq Dweik
* @author Glenn Powell
* @author Raymond Augé
* @author Prashant Dighe
* @author Shuyang Zhou
* @author James Lefeu
* @author Miguel Pastor
* @author Cody Hoag
* @author James Hinkey
* @author Hugo Huijser
*/
public class ToolsUtil {
public static final String AUTHOR = "Brian Wing Shun Chan";
public static final int PLUGINS_MAX_DIR_LEVEL = 3;
public static final int PORTAL_MAX_DIR_LEVEL = 7;
public static String getContent(String fileName) throws Exception {
Document document = _getContentDocument(fileName);
Element rootElement = document.getRootElement();
Element authorElement = null;
Element namespaceElement = null;
Map<String, Element> entityElements = new TreeMap<>();
Map<String, Element> exceptionElements = new TreeMap<>();
List<Element> elements = rootElement.elements();
for (Element element : elements) {
String elementName = element.getName();
if (elementName.equals("author")) {
element.detach();
if (authorElement != null) {
throw new IllegalArgumentException(
"There can only be one author element");
}
authorElement = element;
}
else if (elementName.equals("namespace")) {
element.detach();
if (namespaceElement != null) {
throw new IllegalArgumentException(
"There can only be one namespace element");
}
namespaceElement = element;
}
else if (elementName.equals("entity")) {
element.detach();
String name = element.attributeValue("name");
entityElements.put(StringUtil.toLowerCase(name), element);
}
else if (elementName.equals("exceptions")) {
element.detach();
List<Element> exceptionElementsList = element.elements(
"exception");
for (Element exceptionElement : exceptionElementsList) {
exceptionElement.detach();
exceptionElements.put(
exceptionElement.getText(), exceptionElement);
}
}
}
if (authorElement != null) {
rootElement.add(authorElement);
}
if (namespaceElement == null) {
throw new IllegalArgumentException(
"The namespace element is required");
}
else {
rootElement.add(namespaceElement);
}
_addElements(rootElement, entityElements);
if (!exceptionElements.isEmpty()) {
Element exceptionsElement = rootElement.addElement("exceptions");
_addElements(exceptionsElement, exceptionElements);
}
return document.asXML();
}
public static String getPackagePath(File file) {
String fileName = StringUtil.replace(
file.toString(), CharPool.BACK_SLASH, CharPool.SLASH);
return getPackagePath(fileName);
}
public static String getPackagePath(String fileName) {
int x = Math.max(
fileName.lastIndexOf("/com/"), fileName.lastIndexOf("/org/"));
int y = fileName.lastIndexOf(CharPool.SLASH);
String packagePath = fileName.substring(x + 1, y);
return StringUtil.replace(packagePath, CharPool.SLASH, CharPool.PERIOD);
}
public static boolean isInsideQuotes(String s, int pos) {
int start = s.lastIndexOf(CharPool.NEW_LINE, pos);
if (start == -1) {
start = 0;
}
int end = s.indexOf(CharPool.NEW_LINE, pos);
if (end == -1) {
end = s.length();
}
String line = s.substring(start, end);
pos -= start;
char delimeter = CharPool.SPACE;
boolean insideQuotes = false;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (insideQuotes) {
if (c == delimeter) {
int precedingBackSlashCount = 0;
for (int j = i - 1; j >= 0; j--) {
if (line.charAt(j) == CharPool.BACK_SLASH) {
precedingBackSlashCount += 1;
}
else {
break;
}
}
if ((precedingBackSlashCount == 0) ||
((precedingBackSlashCount % 2) == 0)) {
insideQuotes = false;
}
}
}
else if ((c == CharPool.APOSTROPHE) || (c == CharPool.QUOTE)) {
delimeter = c;
insideQuotes = true;
}
if (pos == i) {
return insideQuotes;
}
}
return false;
}
/**
* @deprecated As of 7.0.0, replaced by {@link
* #stripFullyQualifiedClassNames(String, String)}
*/
@Deprecated
public static String stripFullyQualifiedClassNames(String content)
throws IOException {
return stripFullyQualifiedClassNames(content, null);
}
public static String stripFullyQualifiedClassNames(
String content, String packagePath)
throws IOException {
String imports = JavaImportsFormatter.getImports(content);
return stripFullyQualifiedClassNames(content, imports, packagePath);
}
public static String stripFullyQualifiedClassNames(
String content, String imports, String packagePath)
throws IOException {
if (Validator.isNull(content) || Validator.isNull(imports)) {
return content;
}
Pattern pattern1 = Pattern.compile(
"\n(.*)" + StringUtil.replace(packagePath, CharPool.PERIOD, "\\.") +
"\\.([A-Z]\\w+)\\W");
outerLoop:
while (true) {
Matcher matcher1 = pattern1.matcher(content);
while (matcher1.find()) {
String lineStart = StringUtil.trimLeading(matcher1.group(1));
if (lineStart.startsWith("import ") ||
lineStart.contains("//") ||
isInsideQuotes(content, matcher1.start(2))) {
continue;
}
String className = matcher1.group(2);
Pattern pattern2 = Pattern.compile(
"import [\\w.]+\\." + className + ";");
Matcher matcher2 = pattern2.matcher(imports);
if (matcher2.find()) {
continue;
}
content = StringUtil.replaceFirst(
content, packagePath + ".", StringPool.BLANK,
matcher1.start());
continue outerLoop;
}
break;
}
UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
new UnsyncStringReader(imports));
String line = null;
while ((line = unsyncBufferedReader.readLine()) != null) {
int x = line.indexOf("import ");
if (x == -1) {
continue;
}
String importPackageAndClassName = line.substring(
x + 7, line.lastIndexOf(StringPool.SEMICOLON));
if (importPackageAndClassName.contains(StringPool.STAR)) {
continue;
}
Pattern pattern3 = Pattern.compile(
"\n(.*)(" +
StringUtil.replace(importPackageAndClassName, ".", "\\.") +
")\\W");
outerLoop:
while (true) {
Matcher matcher3 = pattern3.matcher(content);
while (matcher3.find()) {
String lineStart = StringUtil.trimLeading(
matcher3.group(1));
if (lineStart.startsWith("import ") ||
lineStart.contains("//") ||
isInsideQuotes(content, matcher3.start(2))) {
continue;
}
String importClassName =
importPackageAndClassName.substring(
importPackageAndClassName.lastIndexOf(
StringPool.PERIOD) + 1);
content = StringUtil.replaceFirst(
content, importPackageAndClassName, importClassName,
matcher3.start());
continue outerLoop;
}
break;
}
}
return content;
}
public static void writeFile(
File file, String content, Set<String> modifiedFileNames)
throws IOException {
writeFile(file, content, AUTHOR, modifiedFileNames);
}
public static void writeFile(
File file, String content, String author,
Map<String, Object> jalopySettings, Set<String> modifiedFileNames)
throws IOException {
String packagePath = getPackagePath(file);
String className = file.getName();
className = className.substring(0, className.length() - 5);
ImportsFormatter importsFormatter = new JavaImportsFormatter();
content = importsFormatter.format(content, packagePath, className);
File tempFile = new File(_TMP_DIR, "ServiceBuilder.temp");
_write(tempFile, content);
// Beautify
StringBuffer sb = new StringBuffer();
Jalopy jalopy = new Jalopy();
jalopy.setFileFormat(FileFormat.UNIX);
jalopy.setInput(tempFile);
jalopy.setOutput(sb);
File jalopyXmlFile = new File("tools/jalopy.xml");
if (!jalopyXmlFile.exists()) {
jalopyXmlFile = new File("../tools/jalopy.xml");
}
if (!jalopyXmlFile.exists()) {
jalopyXmlFile = new File("misc/jalopy.xml");
}
if (!jalopyXmlFile.exists()) {
jalopyXmlFile = new File("../misc/jalopy.xml");
}
if (!jalopyXmlFile.exists()) {
jalopyXmlFile = new File("../../misc/jalopy.xml");
}
if (jalopyXmlFile.exists()) {
Jalopy.setConvention(jalopyXmlFile);
}
else {
URL url = _readJalopyXmlFromClassLoader();
Jalopy.setConvention(url);
}
if (jalopySettings == null) {
jalopySettings = new HashMap<>();
}
Environment env = Environment.getInstance();
// Author
author = GetterUtil.getString(
(String)jalopySettings.get("author"), author);
env.set("author", author);
// Fail on format error
boolean failOnFormatError = MapUtil.getBoolean(
jalopySettings, "failOnFormatError");
// File name
env.set("fileName", file.getName());
Convention convention = Convention.getInstance();
String classMask = "/**\n * @author $author$\n*/";
convention.put(
ConventionKeys.COMMENT_JAVADOC_TEMPLATE_CLASS,
env.interpolate(classMask));
convention.put(
ConventionKeys.COMMENT_JAVADOC_TEMPLATE_INTERFACE,
env.interpolate(classMask));
boolean formatSuccess = jalopy.format();
String newContent = sb.toString();
// Remove double blank lines after the package or last import
newContent = newContent.replaceFirst(
"(?m)^[ \t]*((?:package|import) .*;)\\s*^[ \t]*/\\*\\*",
"$1\n\n/**");
/*// Remove blank lines after try {
newContent = StringUtil.replace(newContent, "try {\n\n", "try {\n");
// Remove blank lines after ) {
newContent = StringUtil.replace(newContent, ") {\n\n", ") {\n");
// Remove blank lines empty braces { }
newContent = StringUtil.replace(newContent, "\n\n\t}", "\n\t}");
// Add space to last }
newContent =
newContent.substring(0, newContent.length() - 2) + "\n\n}";*/
writeFileRaw(file, newContent, modifiedFileNames);
tempFile.deleteOnExit();
if (failOnFormatError && !formatSuccess) {
throw new IOException("Unable to beautify " + file);
}
}
public static void writeFile(
File file, String content, String author,
Set<String> modifiedFileNames)
throws IOException {
writeFile(file, content, author, null, modifiedFileNames);
}
public static void writeFileRaw(
File file, String content, Set<String> modifiedFileNames)
throws IOException {
// Write file if and only if the file has changed
if (!file.exists() || !content.equals(_read(file))) {
_write(file, content);
if (modifiedFileNames != null) {
modifiedFileNames.add(file.getAbsolutePath());
}
System.out.println("Writing " + file);
}
}
private static void _addElements(
Element element, Map<String, Element> elements) {
for (Map.Entry<String, Element> entry : elements.entrySet()) {
Element childElement = entry.getValue();
element.add(childElement);
}
}
private static Document _getContentDocument(String fileName)
throws Exception {
SAXReader saxReader = _getSAXReader();
Document document = saxReader.read(new File(fileName));
Element rootElement = document.getRootElement();
List<Element> elements = rootElement.elements();
for (Element element : elements) {
String elementName = element.getName();
if (!elementName.equals("service-builder-import")) {
continue;
}
element.detach();
String dirName = fileName.substring(
0, fileName.lastIndexOf(StringPool.SLASH) + 1);
String serviceBuilderImportFileName = element.attributeValue(
"file");
Document serviceBuilderImportDocument = _getContentDocument(
dirName + serviceBuilderImportFileName);
Element serviceBuilderImportRootElement =
serviceBuilderImportDocument.getRootElement();
List<Element> serviceBuilderImportElements =
serviceBuilderImportRootElement.elements();
for (Element serviceBuilderImportElement :
serviceBuilderImportElements) {
serviceBuilderImportElement.detach();
rootElement.add(serviceBuilderImportElement);
}
}
return document;
}
private static SAXReader _getSAXReader() {
return SAXReaderFactory.getSAXReader(null, false, false);
}
private static String _read(File file) throws IOException {
String s = new String(
Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
return StringUtil.replace(
s, StringPool.RETURN_NEW_LINE, StringPool.NEW_LINE);
}
private static URL _readJalopyXmlFromClassLoader() {
ClassLoader classLoader = ToolsUtil.class.getClassLoader();
URL url = classLoader.getResource("jalopy.xml");
if (url == null) {
throw new RuntimeException(
"Unable to load jalopy.xml from the class loader");
}
return url;
}
private static void _write(File file, String s) throws IOException {
Path path = file.toPath();
Files.createDirectories(path.getParent());
Files.write(path, s.getBytes(StandardCharsets.UTF_8));
}
private static final String _TMP_DIR = System.getProperty("java.io.tmpdir");
}