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    
047    import org.dom4j.Document;
048    import org.dom4j.Element;
049    import org.dom4j.io.SAXReader;
050    
051    /**
052     * @author Brian Wing Shun Chan
053     * @author Charles May
054     * @author Alexander Chow
055     * @author Harry Mark
056     * @author Tariq Dweik
057     * @author Glenn Powell
058     * @author Raymond Aug??
059     * @author Prashant Dighe
060     * @author Shuyang Zhou
061     * @author James Lefeu
062     * @author Miguel Pastor
063     * @author Cody Hoag
064     * @author James Hinkey
065     * @author Hugo Huijser
066     */
067    public class ToolsUtil {
068    
069            public static final String AUTHOR = "Brian Wing Shun Chan";
070    
071            public static String getContent(String fileName) throws Exception {
072                    Document document = _getContentDocument(fileName);
073    
074                    Element rootElement = document.getRootElement();
075    
076                    Element authorElement = null;
077                    Element namespaceElement = null;
078                    Map<String, Element> entityElements = new TreeMap<>();
079                    Map<String, Element> exceptionElements = new TreeMap<>();
080    
081                    List<Element> elements = rootElement.elements();
082    
083                    for (Element element : elements) {
084                            String elementName = element.getName();
085    
086                            if (elementName.equals("author")) {
087                                    element.detach();
088    
089                                    if (authorElement != null) {
090                                            throw new IllegalArgumentException(
091                                                    "There can only be one author element");
092                                    }
093    
094                                    authorElement = element;
095                            }
096                            else if (elementName.equals("namespace")) {
097                                    element.detach();
098    
099                                    if (namespaceElement != null) {
100                                            throw new IllegalArgumentException(
101                                                    "There can only be one namespace element");
102                                    }
103    
104                                    namespaceElement = element;
105                            }
106                            else if (elementName.equals("entity")) {
107                                    element.detach();
108    
109                                    String name = element.attributeValue("name");
110    
111                                    entityElements.put(StringUtil.toLowerCase(name), element);
112                            }
113                            else if (elementName.equals("exceptions")) {
114                                    element.detach();
115    
116                                    List<Element> exceptionElementsList = element.elements(
117                                            "exception");
118    
119                                    for (Element exceptionElement : exceptionElementsList) {
120                                            exceptionElement.detach();
121    
122                                            exceptionElements.put(
123                                                    exceptionElement.getText(), exceptionElement);
124                                    }
125                            }
126                    }
127    
128                    if (authorElement != null) {
129                            rootElement.add(authorElement);
130                    }
131    
132                    if (namespaceElement == null) {
133                            throw new IllegalArgumentException(
134                                    "The namespace element is required");
135                    }
136                    else {
137                            rootElement.add(namespaceElement);
138                    }
139    
140                    _addElements(rootElement, entityElements);
141    
142                    if (!exceptionElements.isEmpty()) {
143                            Element exceptionsElement = rootElement.addElement("exceptions");
144    
145                            _addElements(exceptionsElement, exceptionElements);
146                    }
147    
148                    return document.asXML();
149            }
150    
151            public static String getPackagePath(File file) {
152                    String fileName = StringUtil.replace(
153                            file.toString(), StringPool.BACK_SLASH, StringPool.SLASH);
154    
155                    int x = Math.max(
156                            fileName.lastIndexOf("/com/"), fileName.lastIndexOf("/org/"));
157                    int y = fileName.lastIndexOf(StringPool.SLASH);
158    
159                    String packagePath = fileName.substring(x + 1, y);
160    
161                    return StringUtil.replace(
162                            packagePath, StringPool.SLASH, StringPool.PERIOD);
163            }
164    
165            public static boolean isInsideQuotes(String s, int pos) {
166                    boolean insideQuotes = false;
167    
168                    for (int i = 0; i < s.length(); i++) {
169                            char c = s.charAt(i);
170    
171                            if (insideQuotes) {
172                                    if (c == CharPool.QUOTE) {
173                                            int precedingBackSlashCount = 0;
174    
175                                            for (int j = (i - 1); j >= 0; j--) {
176                                                    if (s.charAt(j) == CharPool.BACK_SLASH) {
177                                                            precedingBackSlashCount += 1;
178                                                    }
179                                                    else {
180                                                            break;
181                                                    }
182                                            }
183    
184                                            if ((precedingBackSlashCount == 0) ||
185                                                    ((precedingBackSlashCount % 2) == 0)) {
186    
187                                                    insideQuotes = false;
188                                            }
189                                    }
190                            }
191                            else if (c == CharPool.QUOTE) {
192                                    insideQuotes = true;
193                            }
194    
195                            if (pos == i) {
196                                    return insideQuotes;
197                            }
198                    }
199    
200                    return false;
201            }
202    
203            public static String stripFullyQualifiedClassNames(String content)
204                    throws IOException {
205    
206                    String imports = JavaImportsFormatter.getImports(content);
207    
208                    return stripFullyQualifiedClassNames(content, imports);
209            }
210    
211            public static String stripFullyQualifiedClassNames(
212                            String content, String imports)
213                    throws IOException {
214    
215                    if (Validator.isNull(content) || Validator.isNull(imports)) {
216                            return content;
217                    }
218    
219                    UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
220                            new UnsyncStringReader(imports));
221    
222                    String line = null;
223    
224                    while ((line = unsyncBufferedReader.readLine()) != null) {
225                            int x = line.indexOf("import ");
226    
227                            if (x == -1) {
228                                    continue;
229                            }
230    
231                            String importPackageAndClassName = line.substring(
232                                    x + 7, line.lastIndexOf(StringPool.SEMICOLON));
233    
234                            x = -1;
235    
236                            while (true) {
237                                    x = content.indexOf(importPackageAndClassName, x + 1);
238    
239                                    if (x == -1) {
240                                            break;
241                                    }
242    
243                                    if (isInsideQuotes(content, x)) {
244                                            continue;
245                                    }
246    
247                                    if (content.length() >
248                                                    (x + importPackageAndClassName.length())) {
249    
250                                            char nextChar = content.charAt(
251                                                    x + importPackageAndClassName.length());
252    
253                                            if (Character.isAlphabetic(nextChar) ||
254                                                    Character.isDigit(nextChar) ||
255                                                    (nextChar == CharPool.PERIOD) ||
256                                                    (nextChar == CharPool.SEMICOLON) ||
257                                                    (nextChar == CharPool.UNDERLINE)) {
258    
259                                                    continue;
260                                            }
261    
262                                            if (x > 0) {
263                                                    char previousChar = content.charAt(x - 1);
264    
265                                                    if ((previousChar == CharPool.QUOTE) &&
266                                                            (nextChar == CharPool.QUOTE)) {
267    
268                                                            continue;
269                                                    }
270                                            }
271                                    }
272    
273                                    String importClassName = importPackageAndClassName.substring(
274                                            importPackageAndClassName.lastIndexOf(StringPool.PERIOD) +
275                                                    1);
276    
277                                    content = StringUtil.replaceFirst(
278                                            content, importPackageAndClassName, importClassName, x);
279                            }
280                    }
281    
282                    return content;
283            }
284    
285            public static void writeFile(
286                            File file, String content, Set<String> modifiedFileNames)
287                    throws IOException {
288    
289                    writeFile(file, content, AUTHOR, modifiedFileNames);
290            }
291    
292            public static void writeFile(
293                            File file, String content, String author,
294                            Map<String, Object> jalopySettings, Set<String> modifiedFileNames)
295                    throws IOException {
296    
297                    String packagePath = getPackagePath(file);
298    
299                    String className = file.getName();
300    
301                    className = className.substring(0, className.length() - 5);
302    
303                    content = JavaImportsFormatter.stripJavaImports(
304                            content, packagePath, className);
305    
306                    content = stripFullyQualifiedClassNames(content);
307    
308                    File tempFile = new File(_TMP_DIR, "ServiceBuilder.temp");
309    
310                    _write(tempFile, content);
311    
312                    // Beautify
313    
314                    StringBuffer sb = new StringBuffer();
315    
316                    Jalopy jalopy = new Jalopy();
317    
318                    jalopy.setFileFormat(FileFormat.UNIX);
319                    jalopy.setInput(tempFile);
320                    jalopy.setOutput(sb);
321    
322                    File jalopyXmlFile = new File("tools/jalopy.xml");
323    
324                    if (!jalopyXmlFile.exists()) {
325                            jalopyXmlFile = new File("../tools/jalopy.xml");
326                    }
327    
328                    if (!jalopyXmlFile.exists()) {
329                            jalopyXmlFile = new File("misc/jalopy.xml");
330                    }
331    
332                    if (!jalopyXmlFile.exists()) {
333                            jalopyXmlFile = new File("../misc/jalopy.xml");
334                    }
335    
336                    if (!jalopyXmlFile.exists()) {
337                            jalopyXmlFile = new File("../../misc/jalopy.xml");
338                    }
339    
340                    if (jalopyXmlFile.exists()) {
341                            Jalopy.setConvention(jalopyXmlFile);
342                    }
343                    else {
344                            URL url = _readJalopyXmlFromClassLoader();
345    
346                            Jalopy.setConvention(url);
347                    }
348    
349                    if (jalopySettings == null) {
350                            jalopySettings = new HashMap<>();
351                    }
352    
353                    Environment env = Environment.getInstance();
354    
355                    // Author
356    
357                    author = GetterUtil.getString(
358                            (String)jalopySettings.get("author"), author);
359    
360                    env.set("author", author);
361    
362                    // File name
363    
364                    env.set("fileName", file.getName());
365    
366                    Convention convention = Convention.getInstance();
367    
368                    String classMask = "/**\n * @author $author$\n*/";
369    
370                    convention.put(
371                            ConventionKeys.COMMENT_JAVADOC_TEMPLATE_CLASS,
372                            env.interpolate(classMask));
373    
374                    convention.put(
375                            ConventionKeys.COMMENT_JAVADOC_TEMPLATE_INTERFACE,
376                            env.interpolate(classMask));
377    
378                    jalopy.format();
379    
380                    String newContent = sb.toString();
381    
382                    // Remove double blank lines after the package or last import
383    
384                    newContent = newContent.replaceFirst(
385                            "(?m)^[ \t]*((?:package|import) .*;)\\s*^[ \t]*/\\*\\*",
386                            "$1\n\n/**");
387    
388                    /*
389                    // Remove blank lines after try {
390    
391                    newContent = StringUtil.replace(newContent, "try {\n\n", "try {\n");
392    
393                    // Remove blank lines after ) {
394    
395                    newContent = StringUtil.replace(newContent, ") {\n\n", ") {\n");
396    
397                    // Remove blank lines empty braces { }
398    
399                    newContent = StringUtil.replace(newContent, "\n\n\t}", "\n\t}");
400    
401                    // Add space to last }
402    
403                    newContent = newContent.substring(0, newContent.length() - 2) + "\n\n}";
404                    */
405    
406                    writeFileRaw(file, newContent, modifiedFileNames);
407    
408                    tempFile.deleteOnExit();
409            }
410    
411            public static void writeFile(
412                            File file, String content, String author,
413                            Set<String> modifiedFileNames)
414                    throws IOException {
415    
416                    writeFile(file, content, author, null, modifiedFileNames);
417            }
418    
419            public static void writeFileRaw(
420                            File file, String content, Set<String> modifiedFileNames)
421                    throws IOException {
422    
423                    // Write file if and only if the file has changed
424    
425                    if (!file.exists() || !content.equals(_read(file))) {
426                            _write(file, content);
427    
428                            if (modifiedFileNames != null) {
429                                    modifiedFileNames.add(file.getAbsolutePath());
430                            }
431    
432                            System.out.println("Writing " + file);
433                    }
434            }
435    
436            private static void _addElements(
437                    Element element, Map<String, Element> elements) {
438    
439                    for (Map.Entry<String, Element> entry : elements.entrySet()) {
440                            Element childElement = entry.getValue();
441    
442                            element.add(childElement);
443                    }
444            }
445    
446            private static Document _getContentDocument(String fileName)
447                    throws Exception {
448    
449                    SAXReader saxReader = _getSAXReader();
450    
451                    Document document = saxReader.read(new File(fileName));
452    
453                    Element rootElement = document.getRootElement();
454    
455                    List<Element> elements = rootElement.elements();
456    
457                    for (Element element : elements) {
458                            String elementName = element.getName();
459    
460                            if (!elementName.equals("service-builder-import")) {
461                                    continue;
462                            }
463    
464                            element.detach();
465    
466                            String dirName = fileName.substring(
467                                    0, fileName.lastIndexOf(StringPool.SLASH) + 1);
468                            String serviceBuilderImportFileName = element.attributeValue(
469                                    "file");
470    
471                            Document serviceBuilderImportDocument = _getContentDocument(
472                                    dirName + serviceBuilderImportFileName);
473    
474                            Element serviceBuilderImportRootElement =
475                                    serviceBuilderImportDocument.getRootElement();
476    
477                            List<Element> serviceBuilderImportElements =
478                                    serviceBuilderImportRootElement.elements();
479    
480                            for (Element serviceBuilderImportElement :
481                                            serviceBuilderImportElements) {
482    
483                                    serviceBuilderImportElement.detach();
484    
485                                    rootElement.add(serviceBuilderImportElement);
486                            }
487                    }
488    
489                    return document;
490            }
491    
492            private static SAXReader _getSAXReader() {
493                    return SAXReaderFactory.getSAXReader(null, false, false);
494            }
495    
496            private static String _read(File file) throws IOException {
497                    String s = new String(
498                            Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
499    
500                    return StringUtil.replace(
501                            s, StringPool.RETURN_NEW_LINE, StringPool.NEW_LINE);
502            }
503    
504            private static URL _readJalopyXmlFromClassLoader() {
505                    ClassLoader classLoader = ToolsUtil.class.getClassLoader();
506    
507                    URL url = classLoader.getResource("jalopy.xml");
508    
509                    if (url == null) {
510                            throw new RuntimeException(
511                                    "Unable to load jalopy.xml from the class loader");
512                    }
513    
514                    return url;
515            }
516    
517            private static void _write(File file, String s) throws IOException {
518                    Path path = file.toPath();
519    
520                    Files.createDirectories(path.getParent());
521    
522                    Files.write(path, s.getBytes(StandardCharsets.UTF_8));
523            }
524    
525            private static final String _TMP_DIR = System.getProperty("java.io.tmpdir");
526    
527    }