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