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