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                    int start = s.lastIndexOf(CharPool.NEW_LINE, pos);
167    
168                    if (start == -1) {
169                            start = 0;
170                    }
171    
172                    int end = s.indexOf(CharPool.NEW_LINE, pos);
173    
174                    if (end == -1) {
175                            end = s.length();
176                    }
177    
178                    String line = s.substring(start, end);
179    
180                    pos -= start;
181    
182                    char delimeter = CharPool.SPACE;
183                    boolean insideQuotes = false;
184    
185                    for (int i = 0; i < line.length(); i++) {
186                            char c = line.charAt(i);
187    
188                            if (insideQuotes) {
189                                    if (c == delimeter) {
190                                            int precedingBackSlashCount = 0;
191    
192                                            for (int j = (i - 1); j >= 0; j--) {
193                                                    if (line.charAt(j) == CharPool.BACK_SLASH) {
194                                                            precedingBackSlashCount += 1;
195                                                    }
196                                                    else {
197                                                            break;
198                                                    }
199                                            }
200    
201                                            if ((precedingBackSlashCount == 0) ||
202                                                    ((precedingBackSlashCount % 2) == 0)) {
203    
204                                                    insideQuotes = false;
205                                            }
206                                    }
207                            }
208                            else if ((c == CharPool.APOSTROPHE) || (c == CharPool.QUOTE)) {
209                                    delimeter = c;
210                                    insideQuotes = true;
211                            }
212    
213                            if (pos == i) {
214                                    return insideQuotes;
215                            }
216                    }
217    
218                    return false;
219            }
220    
221            public static String stripFullyQualifiedClassNames(String content)
222                    throws IOException {
223    
224                    String imports = JavaImportsFormatter.getImports(content);
225    
226                    return stripFullyQualifiedClassNames(content, imports);
227            }
228    
229            public static String stripFullyQualifiedClassNames(
230                            String content, String imports)
231                    throws IOException {
232    
233                    if (Validator.isNull(content) || Validator.isNull(imports)) {
234                            return content;
235                    }
236    
237                    UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
238                            new UnsyncStringReader(imports));
239    
240                    String line = null;
241    
242                    while ((line = unsyncBufferedReader.readLine()) != null) {
243                            int x = line.indexOf("import ");
244    
245                            if (x == -1) {
246                                    continue;
247                            }
248    
249                            String importPackageAndClassName = line.substring(
250                                    x + 7, line.lastIndexOf(StringPool.SEMICOLON));
251    
252                            x = -1;
253    
254                            while (true) {
255                                    x = content.indexOf(importPackageAndClassName, x + 1);
256    
257                                    if (x == -1) {
258                                            break;
259                                    }
260    
261                                    if (isInsideQuotes(content, x)) {
262                                            continue;
263                                    }
264    
265                                    if (content.length() >
266                                                    (x + importPackageAndClassName.length())) {
267    
268                                            char nextChar = content.charAt(
269                                                    x + importPackageAndClassName.length());
270    
271                                            if (Character.isAlphabetic(nextChar) ||
272                                                    Character.isDigit(nextChar) ||
273                                                    (nextChar == CharPool.PERIOD) ||
274                                                    (nextChar == CharPool.SEMICOLON) ||
275                                                    (nextChar == CharPool.UNDERLINE)) {
276    
277                                                    continue;
278                                            }
279    
280                                            if (x > 0) {
281                                                    char previousChar = content.charAt(x - 1);
282    
283                                                    if ((previousChar == CharPool.QUOTE) &&
284                                                            (nextChar == CharPool.QUOTE)) {
285    
286                                                            continue;
287                                                    }
288                                            }
289                                    }
290    
291                                    String importClassName = importPackageAndClassName.substring(
292                                            importPackageAndClassName.lastIndexOf(StringPool.PERIOD) +
293                                                    1);
294    
295                                    content = StringUtil.replaceFirst(
296                                            content, importPackageAndClassName, importClassName, x);
297                            }
298                    }
299    
300                    return content;
301            }
302    
303            public static void writeFile(
304                            File file, String content, Set<String> modifiedFileNames)
305                    throws IOException {
306    
307                    writeFile(file, content, AUTHOR, modifiedFileNames);
308            }
309    
310            public static void writeFile(
311                            File file, String content, String author,
312                            Map<String, Object> jalopySettings, Set<String> modifiedFileNames)
313                    throws IOException {
314    
315                    String packagePath = getPackagePath(file);
316    
317                    String className = file.getName();
318    
319                    className = className.substring(0, className.length() - 5);
320    
321                    content = JavaImportsFormatter.stripJavaImports(
322                            content, packagePath, className);
323    
324                    content = stripFullyQualifiedClassNames(content);
325    
326                    File tempFile = new File(_TMP_DIR, "ServiceBuilder.temp");
327    
328                    _write(tempFile, content);
329    
330                    // Beautify
331    
332                    StringBuffer sb = new StringBuffer();
333    
334                    Jalopy jalopy = new Jalopy();
335    
336                    jalopy.setFileFormat(FileFormat.UNIX);
337                    jalopy.setInput(tempFile);
338                    jalopy.setOutput(sb);
339    
340                    File jalopyXmlFile = new File("tools/jalopy.xml");
341    
342                    if (!jalopyXmlFile.exists()) {
343                            jalopyXmlFile = new File("../tools/jalopy.xml");
344                    }
345    
346                    if (!jalopyXmlFile.exists()) {
347                            jalopyXmlFile = new File("misc/jalopy.xml");
348                    }
349    
350                    if (!jalopyXmlFile.exists()) {
351                            jalopyXmlFile = new File("../misc/jalopy.xml");
352                    }
353    
354                    if (!jalopyXmlFile.exists()) {
355                            jalopyXmlFile = new File("../../misc/jalopy.xml");
356                    }
357    
358                    if (jalopyXmlFile.exists()) {
359                            Jalopy.setConvention(jalopyXmlFile);
360                    }
361                    else {
362                            URL url = _readJalopyXmlFromClassLoader();
363    
364                            Jalopy.setConvention(url);
365                    }
366    
367                    if (jalopySettings == null) {
368                            jalopySettings = new HashMap<>();
369                    }
370    
371                    Environment env = Environment.getInstance();
372    
373                    // Author
374    
375                    author = GetterUtil.getString(
376                            (String)jalopySettings.get("author"), author);
377    
378                    env.set("author", author);
379    
380                    // File name
381    
382                    env.set("fileName", file.getName());
383    
384                    Convention convention = Convention.getInstance();
385    
386                    String classMask = "/**\n * @author $author$\n*/";
387    
388                    convention.put(
389                            ConventionKeys.COMMENT_JAVADOC_TEMPLATE_CLASS,
390                            env.interpolate(classMask));
391    
392                    convention.put(
393                            ConventionKeys.COMMENT_JAVADOC_TEMPLATE_INTERFACE,
394                            env.interpolate(classMask));
395    
396                    jalopy.format();
397    
398                    String newContent = sb.toString();
399    
400                    // Remove double blank lines after the package or last import
401    
402                    newContent = newContent.replaceFirst(
403                            "(?m)^[ \t]*((?:package|import) .*;)\\s*^[ \t]*/\\*\\*",
404                            "$1\n\n/**");
405    
406                    /*
407                    // Remove blank lines after try {
408    
409                    newContent = StringUtil.replace(newContent, "try {\n\n", "try {\n");
410    
411                    // Remove blank lines after ) {
412    
413                    newContent = StringUtil.replace(newContent, ") {\n\n", ") {\n");
414    
415                    // Remove blank lines empty braces { }
416    
417                    newContent = StringUtil.replace(newContent, "\n\n\t}", "\n\t}");
418    
419                    // Add space to last }
420    
421                    newContent = newContent.substring(0, newContent.length() - 2) + "\n\n}";
422                    */
423    
424                    writeFileRaw(file, newContent, modifiedFileNames);
425    
426                    tempFile.deleteOnExit();
427            }
428    
429            public static void writeFile(
430                            File file, String content, String author,
431                            Set<String> modifiedFileNames)
432                    throws IOException {
433    
434                    writeFile(file, content, author, null, modifiedFileNames);
435            }
436    
437            public static void writeFileRaw(
438                            File file, String content, Set<String> modifiedFileNames)
439                    throws IOException {
440    
441                    // Write file if and only if the file has changed
442    
443                    if (!file.exists() || !content.equals(_read(file))) {
444                            _write(file, content);
445    
446                            if (modifiedFileNames != null) {
447                                    modifiedFileNames.add(file.getAbsolutePath());
448                            }
449    
450                            System.out.println("Writing " + file);
451                    }
452            }
453    
454            private static void _addElements(
455                    Element element, Map<String, Element> elements) {
456    
457                    for (Map.Entry<String, Element> entry : elements.entrySet()) {
458                            Element childElement = entry.getValue();
459    
460                            element.add(childElement);
461                    }
462            }
463    
464            private static Document _getContentDocument(String fileName)
465                    throws Exception {
466    
467                    SAXReader saxReader = _getSAXReader();
468    
469                    Document document = saxReader.read(new File(fileName));
470    
471                    Element rootElement = document.getRootElement();
472    
473                    List<Element> elements = rootElement.elements();
474    
475                    for (Element element : elements) {
476                            String elementName = element.getName();
477    
478                            if (!elementName.equals("service-builder-import")) {
479                                    continue;
480                            }
481    
482                            element.detach();
483    
484                            String dirName = fileName.substring(
485                                    0, fileName.lastIndexOf(StringPool.SLASH) + 1);
486                            String serviceBuilderImportFileName = element.attributeValue(
487                                    "file");
488    
489                            Document serviceBuilderImportDocument = _getContentDocument(
490                                    dirName + serviceBuilderImportFileName);
491    
492                            Element serviceBuilderImportRootElement =
493                                    serviceBuilderImportDocument.getRootElement();
494    
495                            List<Element> serviceBuilderImportElements =
496                                    serviceBuilderImportRootElement.elements();
497    
498                            for (Element serviceBuilderImportElement :
499                                            serviceBuilderImportElements) {
500    
501                                    serviceBuilderImportElement.detach();
502    
503                                    rootElement.add(serviceBuilderImportElement);
504                            }
505                    }
506    
507                    return document;
508            }
509    
510            private static SAXReader _getSAXReader() {
511                    return SAXReaderFactory.getSAXReader(null, false, false);
512            }
513    
514            private static String _read(File file) throws IOException {
515                    String s = new String(
516                            Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
517    
518                    return StringUtil.replace(
519                            s, StringPool.RETURN_NEW_LINE, StringPool.NEW_LINE);
520            }
521    
522            private static URL _readJalopyXmlFromClassLoader() {
523                    ClassLoader classLoader = ToolsUtil.class.getClassLoader();
524    
525                    URL url = classLoader.getResource("jalopy.xml");
526    
527                    if (url == null) {
528                            throw new RuntimeException(
529                                    "Unable to load jalopy.xml from the class loader");
530                    }
531    
532                    return url;
533            }
534    
535            private static void _write(File file, String s) throws IOException {
536                    Path path = file.toPath();
537    
538                    Files.createDirectories(path.getParent());
539    
540                    Files.write(path, s.getBytes(StandardCharsets.UTF_8));
541            }
542    
543            private static final String _TMP_DIR = System.getProperty("java.io.tmpdir");
544    
545    }