001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.tools;
016    
017    import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader;
018    import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
019    import com.liferay.portal.kernel.util.CharPool;
020    import com.liferay.portal.kernel.util.GetterUtil;
021    import com.liferay.portal.kernel.util.StringPool;
022    import com.liferay.portal.kernel.util.StringUtil;
023    import com.liferay.portal.kernel.util.Validator;
024    import com.liferay.portal.xml.SAXReaderFactory;
025    
026    import de.hunsicker.io.FileFormat;
027    import de.hunsicker.jalopy.Jalopy;
028    import de.hunsicker.jalopy.storage.Convention;
029    import de.hunsicker.jalopy.storage.ConventionKeys;
030    import de.hunsicker.jalopy.storage.Environment;
031    
032    import java.io.File;
033    import java.io.IOException;
034    
035    import java.net.URL;
036    
037    import java.nio.charset.StandardCharsets;
038    import java.nio.file.Files;
039    import java.nio.file.Path;
040    
041    import java.util.HashMap;
042    import java.util.List;
043    import java.util.Map;
044    import java.util.Set;
045    import java.util.TreeMap;
046    import java.util.regex.Matcher;
047    import java.util.regex.Pattern;
048    
049    import org.dom4j.Document;
050    import org.dom4j.Element;
051    import org.dom4j.io.SAXReader;
052    
053    /**
054     * @author Brian Wing Shun Chan
055     * @author Charles May
056     * @author Alexander Chow
057     * @author Harry Mark
058     * @author Tariq Dweik
059     * @author Glenn Powell
060     * @author Raymond Aug??
061     * @author Prashant Dighe
062     * @author Shuyang Zhou
063     * @author James Lefeu
064     * @author Miguel Pastor
065     * @author Cody Hoag
066     * @author James Hinkey
067     * @author Hugo Huijser
068     */
069    public class ToolsUtil {
070    
071            public static final String AUTHOR = "Brian Wing Shun Chan";
072    
073            public static String getContent(String fileName) throws Exception {
074                    Document document = _getContentDocument(fileName);
075    
076                    Element rootElement = document.getRootElement();
077    
078                    Element authorElement = null;
079                    Element namespaceElement = null;
080                    Map<String, Element> entityElements = new TreeMap<>();
081                    Map<String, Element> exceptionElements = new TreeMap<>();
082    
083                    List<Element> elements = rootElement.elements();
084    
085                    for (Element element : elements) {
086                            String elementName = element.getName();
087    
088                            if (elementName.equals("author")) {
089                                    element.detach();
090    
091                                    if (authorElement != null) {
092                                            throw new IllegalArgumentException(
093                                                    "There can only be one author element");
094                                    }
095    
096                                    authorElement = element;
097                            }
098                            else if (elementName.equals("namespace")) {
099                                    element.detach();
100    
101                                    if (namespaceElement != null) {
102                                            throw new IllegalArgumentException(
103                                                    "There can only be one namespace element");
104                                    }
105    
106                                    namespaceElement = element;
107                            }
108                            else if (elementName.equals("entity")) {
109                                    element.detach();
110    
111                                    String name = element.attributeValue("name");
112    
113                                    entityElements.put(StringUtil.toLowerCase(name), element);
114                            }
115                            else if (elementName.equals("exceptions")) {
116                                    element.detach();
117    
118                                    List<Element> exceptionElementsList = element.elements(
119                                            "exception");
120    
121                                    for (Element exceptionElement : exceptionElementsList) {
122                                            exceptionElement.detach();
123    
124                                            exceptionElements.put(
125                                                    exceptionElement.getText(), exceptionElement);
126                                    }
127                            }
128                    }
129    
130                    if (authorElement != null) {
131                            rootElement.add(authorElement);
132                    }
133    
134                    if (namespaceElement == null) {
135                            throw new IllegalArgumentException(
136                                    "The namespace element is required");
137                    }
138                    else {
139                            rootElement.add(namespaceElement);
140                    }
141    
142                    _addElements(rootElement, entityElements);
143    
144                    if (!exceptionElements.isEmpty()) {
145                            Element exceptionsElement = rootElement.addElement("exceptions");
146    
147                            _addElements(exceptionsElement, exceptionElements);
148                    }
149    
150                    return document.asXML();
151            }
152    
153            public static String getPackagePath(File file) {
154                    String fileName = StringUtil.replace(
155                            file.toString(), CharPool.BACK_SLASH, CharPool.SLASH);
156    
157                    return getPackagePath(fileName);
158            }
159    
160            public static String getPackagePath(String fileName) {
161                    int x = Math.max(
162                            fileName.lastIndexOf("/com/"), fileName.lastIndexOf("/org/"));
163                    int y = fileName.lastIndexOf(CharPool.SLASH);
164    
165                    String packagePath = fileName.substring(x + 1, y);
166    
167                    return StringUtil.replace(packagePath, CharPool.SLASH, CharPool.PERIOD);
168            }
169    
170            public static boolean isInsideQuotes(String s, int pos) {
171                    int start = s.lastIndexOf(CharPool.NEW_LINE, pos);
172    
173                    if (start == -1) {
174                            start = 0;
175                    }
176    
177                    int end = s.indexOf(CharPool.NEW_LINE, pos);
178    
179                    if (end == -1) {
180                            end = s.length();
181                    }
182    
183                    String line = s.substring(start, end);
184    
185                    pos -= start;
186    
187                    char delimeter = CharPool.SPACE;
188                    boolean insideQuotes = false;
189    
190                    for (int i = 0; i < line.length(); i++) {
191                            char c = line.charAt(i);
192    
193                            if (insideQuotes) {
194                                    if (c == delimeter) {
195                                            int precedingBackSlashCount = 0;
196    
197                                            for (int j = (i - 1); j >= 0; j--) {
198                                                    if (line.charAt(j) == CharPool.BACK_SLASH) {
199                                                            precedingBackSlashCount += 1;
200                                                    }
201                                                    else {
202                                                            break;
203                                                    }
204                                            }
205    
206                                            if ((precedingBackSlashCount == 0) ||
207                                                    ((precedingBackSlashCount % 2) == 0)) {
208    
209                                                    insideQuotes = false;
210                                            }
211                                    }
212                            }
213                            else if ((c == CharPool.APOSTROPHE) || (c == CharPool.QUOTE)) {
214                                    delimeter = c;
215                                    insideQuotes = true;
216                            }
217    
218                            if (pos == i) {
219                                    return insideQuotes;
220                            }
221                    }
222    
223                    return false;
224            }
225    
226            /**
227             * @deprecated As of 7.0.0, replaced by {@link
228             *             #stripFullyQualifiedClassNames(String, String)}
229             */
230            @Deprecated
231            public static String stripFullyQualifiedClassNames(String content)
232                    throws IOException {
233    
234                    return stripFullyQualifiedClassNames(content, null);
235            }
236    
237            public static String stripFullyQualifiedClassNames(
238                            String content, String packagePath)
239                    throws IOException {
240    
241                    String imports = JavaImportsFormatter.getImports(content);
242    
243                    return stripFullyQualifiedClassNames(content, imports, packagePath);
244            }
245    
246            public static String stripFullyQualifiedClassNames(
247                            String content, String imports, String packagePath)
248                    throws IOException {
249    
250                    if (Validator.isNull(content) || Validator.isNull(imports)) {
251                            return content;
252                    }
253    
254                    Pattern pattern1 = Pattern.compile(
255                            "\n(.*)" + StringUtil.replace(packagePath, CharPool.PERIOD, "\\.") +
256                                    "\\.([A-Z]\\w+)\\W");
257    
258                    outerLoop:
259                    while (true) {
260                            Matcher matcher1 = pattern1.matcher(content);
261    
262                            while (matcher1.find()) {
263                                    String lineStart = StringUtil.trimLeading(matcher1.group(1));
264    
265                                    if (lineStart.startsWith("import ") ||
266                                            lineStart.contains("//") ||
267                                            isInsideQuotes(content, matcher1.start(2))) {
268    
269                                            continue;
270                                    }
271    
272                                    String className = matcher1.group(2);
273    
274                                    Pattern pattern2 = Pattern.compile(
275                                            "import [\\w.]+\\." + className + ";");
276    
277                                    Matcher matcher2 = pattern2.matcher(imports);
278    
279                                    if (matcher2.find()) {
280                                            continue;
281                                    }
282    
283                                    content = StringUtil.replaceFirst(
284                                            content, packagePath + ".", StringPool.BLANK,
285                                            matcher1.start());
286    
287                                    continue outerLoop;
288                            }
289    
290                            break;
291                    }
292    
293                    UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
294                            new UnsyncStringReader(imports));
295    
296                    String line = null;
297    
298                    while ((line = unsyncBufferedReader.readLine()) != null) {
299                            int x = line.indexOf("import ");
300    
301                            if (x == -1) {
302                                    continue;
303                            }
304    
305                            String importPackageAndClassName = line.substring(
306                                    x + 7, line.lastIndexOf(StringPool.SEMICOLON));
307    
308                            if (importPackageAndClassName.contains(StringPool.STAR)) {
309                                    continue;
310                            }
311    
312                            Pattern pattern3 = Pattern.compile(
313                                    "\n(.*)(" +
314                                            StringUtil.replace(importPackageAndClassName, ".", "\\.") +
315                                                    ")\\W");
316    
317                            outerLoop:
318                            while (true) {
319                                    Matcher matcher3 = pattern3.matcher(content);
320    
321                                    while (matcher3.find()) {
322                                            String lineStart = StringUtil.trimLeading(
323                                                    matcher3.group(1));
324    
325                                            if (lineStart.startsWith("import ") ||
326                                                    lineStart.contains("//") ||
327                                                    isInsideQuotes(content, matcher3.start(2))) {
328    
329                                                    continue;
330                                            }
331    
332                                            String importClassName =
333                                                    importPackageAndClassName.substring(
334                                                            importPackageAndClassName.lastIndexOf(
335                                                                    StringPool.PERIOD) + 1);
336    
337                                            content = StringUtil.replaceFirst(
338                                                    content, importPackageAndClassName, importClassName,
339                                                    matcher3.start());
340    
341                                            continue outerLoop;
342                                    }
343    
344                                    break;
345                            }
346                    }
347    
348                    return content;
349            }
350    
351            public static void writeFile(
352                            File file, String content, Set<String> modifiedFileNames)
353                    throws IOException {
354    
355                    writeFile(file, content, AUTHOR, modifiedFileNames);
356            }
357    
358            public static void writeFile(
359                            File file, String content, String author,
360                            Map<String, Object> jalopySettings, Set<String> modifiedFileNames)
361                    throws IOException {
362    
363                    String packagePath = getPackagePath(file);
364    
365                    String className = file.getName();
366    
367                    className = className.substring(0, className.length() - 5);
368    
369                    ImportsFormatter importsFormatter = new JavaImportsFormatter();
370    
371                    content = importsFormatter.format(content, packagePath, className);
372    
373                    File tempFile = new File(_TMP_DIR, "ServiceBuilder.temp");
374    
375                    _write(tempFile, content);
376    
377                    // Beautify
378    
379                    StringBuffer sb = new StringBuffer();
380    
381                    Jalopy jalopy = new Jalopy();
382    
383                    jalopy.setFileFormat(FileFormat.UNIX);
384                    jalopy.setInput(tempFile);
385                    jalopy.setOutput(sb);
386    
387                    File jalopyXmlFile = new File("tools/jalopy.xml");
388    
389                    if (!jalopyXmlFile.exists()) {
390                            jalopyXmlFile = new File("../tools/jalopy.xml");
391                    }
392    
393                    if (!jalopyXmlFile.exists()) {
394                            jalopyXmlFile = new File("misc/jalopy.xml");
395                    }
396    
397                    if (!jalopyXmlFile.exists()) {
398                            jalopyXmlFile = new File("../misc/jalopy.xml");
399                    }
400    
401                    if (!jalopyXmlFile.exists()) {
402                            jalopyXmlFile = new File("../../misc/jalopy.xml");
403                    }
404    
405                    if (jalopyXmlFile.exists()) {
406                            Jalopy.setConvention(jalopyXmlFile);
407                    }
408                    else {
409                            URL url = _readJalopyXmlFromClassLoader();
410    
411                            Jalopy.setConvention(url);
412                    }
413    
414                    if (jalopySettings == null) {
415                            jalopySettings = new HashMap<>();
416                    }
417    
418                    Environment env = Environment.getInstance();
419    
420                    // Author
421    
422                    author = GetterUtil.getString(
423                            (String)jalopySettings.get("author"), author);
424    
425                    env.set("author", author);
426    
427                    // File name
428    
429                    env.set("fileName", file.getName());
430    
431                    Convention convention = Convention.getInstance();
432    
433                    String classMask = "/**\n * @author $author$\n*/";
434    
435                    convention.put(
436                            ConventionKeys.COMMENT_JAVADOC_TEMPLATE_CLASS,
437                            env.interpolate(classMask));
438    
439                    convention.put(
440                            ConventionKeys.COMMENT_JAVADOC_TEMPLATE_INTERFACE,
441                            env.interpolate(classMask));
442    
443                    jalopy.format();
444    
445                    String newContent = sb.toString();
446    
447                    // Remove double blank lines after the package or last import
448    
449                    newContent = newContent.replaceFirst(
450                            "(?m)^[ \t]*((?:package|import) .*;)\\s*^[ \t]*/\\*\\*",
451                            "$1\n\n/**");
452    
453                    /*
454                    // Remove blank lines after try {
455    
456                    newContent = StringUtil.replace(newContent, "try {\n\n", "try {\n");
457    
458                    // Remove blank lines after ) {
459    
460                    newContent = StringUtil.replace(newContent, ") {\n\n", ") {\n");
461    
462                    // Remove blank lines empty braces { }
463    
464                    newContent = StringUtil.replace(newContent, "\n\n\t}", "\n\t}");
465    
466                    // Add space to last }
467    
468                    newContent = newContent.substring(0, newContent.length() - 2) + "\n\n}";
469                    */
470    
471                    writeFileRaw(file, newContent, modifiedFileNames);
472    
473                    tempFile.deleteOnExit();
474            }
475    
476            public static void writeFile(
477                            File file, String content, String author,
478                            Set<String> modifiedFileNames)
479                    throws IOException {
480    
481                    writeFile(file, content, author, null, modifiedFileNames);
482            }
483    
484            public static void writeFileRaw(
485                            File file, String content, Set<String> modifiedFileNames)
486                    throws IOException {
487    
488                    // Write file if and only if the file has changed
489    
490                    if (!file.exists() || !content.equals(_read(file))) {
491                            _write(file, content);
492    
493                            if (modifiedFileNames != null) {
494                                    modifiedFileNames.add(file.getAbsolutePath());
495                            }
496    
497                            System.out.println("Writing " + file);
498                    }
499            }
500    
501            private static void _addElements(
502                    Element element, Map<String, Element> elements) {
503    
504                    for (Map.Entry<String, Element> entry : elements.entrySet()) {
505                            Element childElement = entry.getValue();
506    
507                            element.add(childElement);
508                    }
509            }
510    
511            private static Document _getContentDocument(String fileName)
512                    throws Exception {
513    
514                    SAXReader saxReader = _getSAXReader();
515    
516                    Document document = saxReader.read(new File(fileName));
517    
518                    Element rootElement = document.getRootElement();
519    
520                    List<Element> elements = rootElement.elements();
521    
522                    for (Element element : elements) {
523                            String elementName = element.getName();
524    
525                            if (!elementName.equals("service-builder-import")) {
526                                    continue;
527                            }
528    
529                            element.detach();
530    
531                            String dirName = fileName.substring(
532                                    0, fileName.lastIndexOf(StringPool.SLASH) + 1);
533                            String serviceBuilderImportFileName = element.attributeValue(
534                                    "file");
535    
536                            Document serviceBuilderImportDocument = _getContentDocument(
537                                    dirName + serviceBuilderImportFileName);
538    
539                            Element serviceBuilderImportRootElement =
540                                    serviceBuilderImportDocument.getRootElement();
541    
542                            List<Element> serviceBuilderImportElements =
543                                    serviceBuilderImportRootElement.elements();
544    
545                            for (Element serviceBuilderImportElement :
546                                            serviceBuilderImportElements) {
547    
548                                    serviceBuilderImportElement.detach();
549    
550                                    rootElement.add(serviceBuilderImportElement);
551                            }
552                    }
553    
554                    return document;
555            }
556    
557            private static SAXReader _getSAXReader() {
558                    return SAXReaderFactory.getSAXReader(null, false, false);
559            }
560    
561            private static String _read(File file) throws IOException {
562                    String s = new String(
563                            Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
564    
565                    return StringUtil.replace(
566                            s, StringPool.RETURN_NEW_LINE, StringPool.NEW_LINE);
567            }
568    
569            private static URL _readJalopyXmlFromClassLoader() {
570                    ClassLoader classLoader = ToolsUtil.class.getClassLoader();
571    
572                    URL url = classLoader.getResource("jalopy.xml");
573    
574                    if (url == null) {
575                            throw new RuntimeException(
576                                    "Unable to load jalopy.xml from the class loader");
577                    }
578    
579                    return url;
580            }
581    
582            private static void _write(File file, String s) throws IOException {
583                    Path path = file.toPath();
584    
585                    Files.createDirectories(path.getParent());
586    
587                    Files.write(path, s.getBytes(StandardCharsets.UTF_8));
588            }
589    
590            private static final String _TMP_DIR = System.getProperty("java.io.tmpdir");
591    
592    }