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.sourceformatter;
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.PropertiesUtil;
021    import com.liferay.portal.kernel.util.PropsKeys;
022    import com.liferay.portal.kernel.util.StringBundler;
023    import com.liferay.portal.kernel.util.StringPool;
024    import com.liferay.portal.kernel.util.StringUtil;
025    import com.liferay.portal.kernel.util.Validator;
026    import com.liferay.portal.kernel.xml.Document;
027    import com.liferay.portal.kernel.xml.Element;
028    import com.liferay.portal.tools.ComparableRoute;
029    import com.liferay.util.ContentUtil;
030    
031    import java.io.File;
032    
033    import java.util.ArrayList;
034    import java.util.Arrays;
035    import java.util.Collections;
036    import java.util.Comparator;
037    import java.util.List;
038    import java.util.Map;
039    import java.util.Properties;
040    import java.util.Set;
041    import java.util.TreeMap;
042    import java.util.TreeSet;
043    import java.util.regex.Matcher;
044    import java.util.regex.Pattern;
045    
046    /**
047     * @author Hugo Huijser
048     */
049    public class XMLSourceProcessor extends BaseSourceProcessor {
050    
051            public static String formatXML(String content) {
052                    String newContent = StringUtil.replace(content, "\"/>\n", "\" />\n");
053    
054                    while (true) {
055                            Matcher matcher = _commentPattern1.matcher(newContent);
056    
057                            if (matcher.find()) {
058                                    newContent = StringUtil.replaceFirst(
059                                            newContent, ">\n", ">\n\n", matcher.start());
060    
061                                    continue;
062                            }
063    
064                            matcher = _commentPattern2.matcher(newContent);
065    
066                            if (!matcher.find()) {
067                                    break;
068                            }
069    
070                            newContent = StringUtil.replaceFirst(
071                                    newContent, "-->\n", "-->\n\n", matcher.start());
072                    }
073    
074                    return newContent;
075            }
076    
077            protected void checkPoshiCharactersAfterDefinition(
078                    String fileName, String content) {
079    
080                    if (content.contains("/definition>") &&
081                            !content.endsWith("/definition>")) {
082    
083                            processErrorMessage(
084                                    fileName,
085                                    "Characters found after definition element: " + fileName);
086                    }
087            }
088    
089            protected void checkPoshiCharactersBeforeDefinition(
090                    String fileName, String content) {
091    
092                    if (!content.startsWith("<definition")) {
093                            processErrorMessage(
094                                    fileName,
095                                    "Characters found before definition element: " + fileName);
096                    }
097            }
098    
099            protected void checkServiceXMLExceptions(
100                    String fileName, Element rootElement) {
101    
102                    Element exceptionsElement = rootElement.element("exceptions");
103    
104                    if (exceptionsElement == null) {
105                            return;
106                    }
107    
108                    List<Element> exceptionElements = exceptionsElement.elements(
109                            "exception");
110    
111                    String previousException = StringPool.BLANK;
112    
113                    for (Element exceptionElement : exceptionElements) {
114                            String exception = exceptionElement.getStringValue();
115    
116                            if (Validator.isNotNull(previousException) &&
117                                    (previousException.compareToIgnoreCase(exception) > 0)) {
118    
119                                    processErrorMessage(
120                                            fileName, "sort: " + fileName + " " + exception);
121                            }
122    
123                            previousException = exception;
124                    }
125            }
126    
127            protected void checkServiceXMLFinders(
128                            String fileName, Element entityElement, String entityName)
129                    throws Exception {
130    
131                    _columnNames = getColumnNames(fileName, entityName);
132    
133                    FinderElementComparator finderElementComparator =
134                            new FinderElementComparator();
135    
136                    List<Element> finderElements = entityElement.elements("finder");
137    
138                    for (int i = 1; i < finderElements.size(); i++) {
139                            Element finderElement = finderElements.get(i);
140                            Element previousFinderElement = finderElements.get(i - 1);
141    
142                            if (finderElementComparator.compare(
143                                            previousFinderElement, finderElement) > 0) {
144    
145                                    String finderName = finderElement.attributeValue("name");
146    
147                                    processErrorMessage(
148                                            fileName,
149                                            "order: " + fileName + " " + entityName + " " + finderName);
150                            }
151                    }
152            }
153    
154            protected void checkServiceXMLReferences(
155                    String fileName, Element entityElement, String entityName) {
156    
157                    String previousReferenceEntity = StringPool.BLANK;
158                    String previousReferencePackagePath = StringPool.BLANK;
159    
160                    List<Element> referenceElements = entityElement.elements("reference");
161    
162                    for (Element referenceElement : referenceElements) {
163                            String referenceEntity = referenceElement.attributeValue("entity");
164                            String referencePackagePath = referenceElement.attributeValue(
165                                    "package-path");
166    
167                            if (Validator.isNotNull(previousReferencePackagePath)) {
168                                    if ((previousReferencePackagePath.compareToIgnoreCase(
169                                                    referencePackagePath) > 0) ||
170                                            (previousReferencePackagePath.equals(
171                                                    referencePackagePath) &&
172                                             (previousReferenceEntity.compareToIgnoreCase(
173                                                     referenceEntity) > 0))) {
174    
175                                            processErrorMessage(
176                                                    fileName,
177                                                    "sort: " + fileName + " " + entityName + " " +
178                                                            referenceEntity);
179                                    }
180                            }
181    
182                            previousReferenceEntity = referenceEntity;
183                            previousReferencePackagePath = referencePackagePath;
184                    }
185            }
186    
187            @Override
188            protected String doFormat(
189                            File file, String fileName, String absolutePath, String content)
190                    throws Exception {
191    
192                    if (isExcluded(_xmlExclusions, absolutePath)) {
193                            return content;
194                    }
195    
196                    String newContent = content;
197    
198                    if (!fileName.contains("/build")) {
199                            newContent = trimContent(newContent, false);
200                    }
201    
202                    if (fileName.contains("/build") && !fileName.contains("/tools/")) {
203                            newContent = formatAntXML(fileName, newContent);
204                    }
205                    else if (fileName.contains("/custom-sql/")) {
206                            formatCustomSQLXML(fileName, newContent);
207                    }
208                    else if (fileName.endsWith("structures.xml")) {
209                            newContent = formatDDLStructuresXML(newContent);
210                    }
211                    else if (fileName.endsWith("routes.xml")) {
212                            newContent = formatFriendlyURLRoutesXML(absolutePath, newContent);
213                    }
214                    else if (fileName.endsWith("/liferay-portlet.xml") ||
215                                     (portalSource &&
216                                      fileName.endsWith("/portlet-custom.xml")) ||
217                                     (!portalSource && fileName.endsWith("/portlet.xml"))) {
218    
219                            newContent = formatPortletXML(fileName, absolutePath, newContent);
220                    }
221                    else if (portalSource &&
222                                     (fileName.endsWith(".action") ||
223                                      fileName.endsWith(".function") ||
224                                      fileName.endsWith(".macro") ||
225                                      fileName.endsWith(".testcase") ||
226                                      fileName.endsWith(".testxml"))) {
227    
228                            newContent = formatPoshiXML(fileName, newContent);
229                    }
230                    else if (fileName.endsWith("/service.xml")) {
231                            formatServiceXML(fileName, newContent);
232                    }
233                    else if (portalSource && fileName.endsWith("/struts-config.xml")) {
234                            formatStrutsConfigXML(fileName, newContent);
235                    }
236                    else if (portalSource && fileName.endsWith("/tiles-defs.xml")) {
237                            formatTilesDefsXML(fileName, newContent);
238                    }
239                    else if ((portalSource &&
240                                      fileName.endsWith(
241                                              "portal-web/docroot/WEB-INF/web.xml")) ||
242                                     (!portalSource && fileName.endsWith("/web.xml"))) {
243    
244                            newContent = formatWebXML(fileName, newContent);
245                    }
246    
247                    return formatXML(newContent);
248            }
249    
250            protected String fixAntXMLProjectName(String fileName, String content) {
251                    int x = 0;
252    
253                    if (fileName.endsWith("-ext/build.xml")) {
254                            if (fileName.startsWith("ext/")) {
255                                    x = 4;
256                            }
257                    }
258                    else if (fileName.endsWith("-hook/build.xml")) {
259                            if (fileName.startsWith("hooks/")) {
260                                    x = 6;
261                            }
262                    }
263                    else if (fileName.endsWith("-layouttpl/build.xml")) {
264                            if (fileName.startsWith("layouttpl/")) {
265                                    x = 10;
266                            }
267                    }
268                    else if (fileName.endsWith("-portlet/build.xml")) {
269                            if (fileName.startsWith("portlets/")) {
270                                    x = 9;
271                            }
272                    }
273                    else if (fileName.endsWith("-theme/build.xml")) {
274                            if (fileName.startsWith("themes/")) {
275                                    x = 7;
276                            }
277                    }
278                    else if (fileName.endsWith("-web/build.xml") &&
279                                     !fileName.endsWith("/ext-web/build.xml")) {
280    
281                            if (fileName.startsWith("webs/")) {
282                                    x = 5;
283                            }
284                    }
285                    else {
286                            return content;
287                    }
288    
289                    if (content.contains("<project>")) {
290                            return content;
291                    }
292    
293                    int y = fileName.indexOf("/", x);
294    
295                    String correctProjectElementText =
296                            "<project name=\"" + fileName.substring(x, y) + "\"";
297    
298                    if (!content.contains(correctProjectElementText)) {
299                            x = content.indexOf("<project name=\"");
300    
301                            y = content.indexOf("\"", x) + 1;
302                            y = content.indexOf("\"", y) + 1;
303    
304                            content =
305                                    content.substring(0, x) + correctProjectElementText +
306                                            content.substring(y);
307    
308                            processErrorMessage(
309                                    fileName, fileName + " has an incorrect project name");
310                    }
311    
312                    return content;
313            }
314    
315            protected String fixPoshiXMLElementWithNoChild(String content) {
316                    Matcher matcher = _poshiElementWithNoChildPattern.matcher(content);
317    
318                    while (matcher.find()) {
319                            content = StringUtil.replace(content, matcher.group(), "\" />");
320                    }
321    
322                    return content;
323            }
324    
325            protected String fixPoshiXMLEndLines(String content) {
326                    Matcher matcher = _poshiEndLinesPattern.matcher(content);
327    
328                    while (matcher.find()) {
329                            String statement = matcher.group();
330    
331                            String newStatement = StringUtil.replace(
332                                    statement, matcher.group(), ">\n\n" + matcher.group(1));
333    
334                            content = StringUtil.replace(content, statement, newStatement);
335                    }
336    
337                    return content;
338            }
339    
340            protected String fixPoshiXMLEndLinesAfterClosingElement(String content) {
341                    Matcher matcher = _poshiEndLinesAfterClosingElementPattern.matcher(
342                            content);
343    
344                    while (matcher.find()) {
345                            String statement = matcher.group();
346    
347                            String closingElementName = matcher.group(1);
348    
349                            if (StringUtil.equalsIgnoreCase("</and>", closingElementName) ||
350                                    StringUtil.equalsIgnoreCase("</elseif>", closingElementName) ||
351                                    StringUtil.equalsIgnoreCase("</not>", closingElementName) ||
352                                    StringUtil.equalsIgnoreCase("</or>", closingElementName) ||
353                                    StringUtil.equalsIgnoreCase("</then>", closingElementName)) {
354    
355                                    String newStatement = StringUtil.replace(
356                                            statement, matcher.group(2), "\n");
357    
358                                    content = StringUtil.replace(content, statement, newStatement);
359                            }
360                            else if (!StringUtil.equalsIgnoreCase(
361                                                    "</var>", closingElementName)) {
362    
363                                    String newStatement = StringUtil.replace(
364                                            statement, matcher.group(2), "\n\n");
365    
366                                    content = StringUtil.replace(content, statement, newStatement);
367                            }
368                    }
369    
370                    return content;
371            }
372    
373            protected String fixPoshiXMLEndLinesBeforeClosingElement(String content) {
374                    Matcher matcher = _poshiEndLinesBeforeClosingElementPattern.matcher(
375                            content);
376    
377                    while (matcher.find()) {
378                            String statement = matcher.group();
379    
380                            String newStatement = StringUtil.replace(
381                                    statement, matcher.group(1), "\n");
382    
383                            content = StringUtil.replace(content, statement, newStatement);
384                    }
385    
386                    return content;
387            }
388    
389            protected String fixPoshiXMLNumberOfTabs(String content) {
390                    Matcher matcher = _poshiTabsPattern.matcher(content);
391    
392                    int tabCount = 0;
393    
394                    boolean ignoredCdataBlock = false;
395                    boolean ignoredCommentBlock = false;
396    
397                    while (matcher.find()) {
398                            String statement = matcher.group();
399    
400                            Matcher quoteWithSlashMatcher = _poshiQuoteWithSlashPattern.matcher(
401                                    statement);
402    
403                            String fixedQuoteStatement = statement;
404    
405                            if (quoteWithSlashMatcher.find()) {
406                                    fixedQuoteStatement = StringUtil.replace(
407                                            statement, quoteWithSlashMatcher.group(), "\"\"");
408                            }
409    
410                            Matcher closingTagMatcher = _poshiClosingTagPattern.matcher(
411                                    fixedQuoteStatement);
412                            Matcher openingTagMatcher = _poshiOpeningTagPattern.matcher(
413                                    fixedQuoteStatement);
414                            Matcher wholeTagMatcher = _poshiWholeTagPattern.matcher(
415                                    fixedQuoteStatement);
416    
417                            if (closingTagMatcher.find() && !openingTagMatcher.find() &&
418                                    !wholeTagMatcher.find() && !statement.contains("<!--") &&
419                                    !statement.contains("-->") &&
420                                    !statement.contains("<![CDATA[") &&
421                                    !statement.contains("]]>")) {
422    
423                                    tabCount--;
424                            }
425    
426                            if (statement.contains("]]>")) {
427                                    ignoredCdataBlock = false;
428                            }
429                            else if (statement.contains("<![CDATA[")) {
430                                    ignoredCdataBlock = true;
431                            }
432    
433                            if (statement.contains("-->")) {
434                                    ignoredCommentBlock = false;
435                            }
436                            else if (statement.contains("<!--")) {
437                                    ignoredCommentBlock = true;
438                            }
439    
440                            if (!ignoredCommentBlock && !ignoredCdataBlock) {
441                                    StringBundler sb = new StringBundler(tabCount + 1);
442    
443                                    for (int i = 0; i < tabCount; i++) {
444                                            sb.append(StringPool.TAB);
445                                    }
446    
447                                    sb.append(StringPool.LESS_THAN);
448    
449                                    String replacement = sb.toString();
450    
451                                    if (!replacement.equals(matcher.group(1))) {
452                                            String newStatement = StringUtil.replace(
453                                                    statement, matcher.group(1), replacement);
454    
455                                            return StringUtil.replaceFirst(
456                                                    content, statement, newStatement, matcher.start());
457                                    }
458                            }
459    
460                            if (openingTagMatcher.find() && !closingTagMatcher.find() &&
461                                    !wholeTagMatcher.find() && !statement.contains("<!--") &&
462                                    !statement.contains("-->") &&
463                                    !statement.contains("<![CDATA[") &&
464                                    !statement.contains("]]>")) {
465    
466                                    tabCount++;
467                            }
468                    }
469    
470                    return content;
471            }
472    
473            @Override
474            protected void format() throws Exception {
475                    String[] excludes = new String[] {
476                            "**\\.bnd\\**", "**\\.idea\\**", "**\\.ivy\\**",
477                            "portal-impl\\**\\*.action", "portal-impl\\**\\*.function",
478                            "portal-impl\\**\\*.macro", "portal-impl\\**\\*.testcase"
479                    };
480    
481                    String[] includes = new String[] {
482                            "**\\*.action","**\\*.function","**\\*.macro","**\\*.testcase",
483                            "**\\*.xml"
484                    };
485    
486                    _friendlyUrlRoutesSortExclusions = getPropertyList(
487                            "friendly.url.routes.sort.excludes.files");
488                    _numericalPortletNameElementExclusions = getPropertyList(
489                            "numerical.portlet.name.element.excludes.files");
490                    _xmlExclusions = getPropertyList("xml.excludes.files");
491    
492                    List<String> fileNames = getFileNames(excludes, includes);
493    
494                    for (String fileName : fileNames) {
495                            format(fileName);
496                    }
497            }
498    
499            protected String formatAntXML(String fileName, String content)
500                    throws Exception {
501    
502                    String newContent = trimContent(content, true);
503    
504                    newContent = fixAntXMLProjectName(fileName, newContent);
505    
506                    Document document = saxReaderUtil.read(newContent);
507    
508                    Element rootElement = document.getRootElement();
509    
510                    String previousName = StringPool.BLANK;
511    
512                    List<Element> targetElements = rootElement.elements("target");
513    
514                    for (Element targetElement : targetElements) {
515                            String name = targetElement.attributeValue("name");
516    
517                            if (name.equals("Test")) {
518                                    name = StringUtil.toLowerCase(name);
519                            }
520    
521                            if (name.compareTo(previousName) < -1) {
522                                    processErrorMessage(
523                                            fileName, fileName + " has an unordered target " + name);
524    
525                                    break;
526                            }
527    
528                            previousName = name;
529                    }
530    
531                    return newContent;
532            }
533    
534            protected void formatCustomSQLXML(String fileName, String content) {
535                    Matcher matcher = _whereNotInSQLPattern.matcher(content);
536    
537                    if (!matcher.find()) {
538                            return;
539                    }
540    
541                    int x = content.lastIndexOf("<sql id=", matcher.start());
542    
543                    int y = content.indexOf(CharPool.QUOTE, x);
544    
545                    int z = content.indexOf(CharPool.QUOTE, y + 1);
546    
547                    processErrorMessage(
548                            fileName,
549                                    "LPS-51315 Avoid using WHERE ... NOT IN: " + fileName + " " +
550                                            content.substring(y + 1, z));
551            }
552    
553            protected String formatDDLStructuresXML(String content) throws Exception {
554                    Document document = saxReaderUtil.read(content);
555    
556                    Element rootElement = document.getRootElement();
557    
558                    rootElement.sortAttributes(true);
559    
560                    rootElement.sortElementsByChildElement("structure", "name");
561    
562                    List<Element> structureElements = rootElement.elements("structure");
563    
564                    for (Element structureElement : structureElements) {
565                            Element structureRootElement = structureElement.element("root");
566    
567                            structureRootElement.sortElementsByAttribute(
568                                    "dynamic-element", "name");
569    
570                            List<Element> dynamicElementElements =
571                                    structureRootElement.elements("dynamic-element");
572    
573                            for (Element dynamicElementElement : dynamicElementElements) {
574                                    Element metaDataElement = dynamicElementElement.element(
575                                            "meta-data");
576    
577                                    metaDataElement.sortElementsByAttribute("entry", "name");
578                            }
579                    }
580    
581                    return document.formattedString();
582            }
583    
584            protected String formatFriendlyURLRoutesXML(
585                            String absolutePath, String content)
586                    throws Exception {
587    
588                    if (isExcluded(_friendlyUrlRoutesSortExclusions, absolutePath)) {
589                            return content;
590                    }
591    
592                    Document document = saxReaderUtil.read(content);
593    
594                    Element rootElement = document.getRootElement();
595    
596                    List<ComparableRoute> comparableRoutes =
597                            new ArrayList<ComparableRoute>();
598    
599                    for (Element routeElement : rootElement.elements("route")) {
600                            String pattern = routeElement.elementText("pattern");
601    
602                            ComparableRoute comparableRoute = new ComparableRoute(pattern);
603    
604                            for (Element generatedParameterElement :
605                                            routeElement.elements("generated-parameter")) {
606    
607                                    String name = generatedParameterElement.attributeValue("name");
608                                    String value = generatedParameterElement.getText();
609    
610                                    comparableRoute.addGeneratedParameter(name, value);
611                            }
612    
613                            for (Element ignoredParameterElement :
614                                            routeElement.elements("ignored-parameter")) {
615    
616                                    String name = ignoredParameterElement.attributeValue("name");
617    
618                                    comparableRoute.addIgnoredParameter(name);
619                            }
620    
621                            for (Element implicitParameterElement :
622                                            routeElement.elements("implicit-parameter")) {
623    
624                                    String name = implicitParameterElement.attributeValue("name");
625                                    String value = implicitParameterElement.getText();
626    
627                                    comparableRoute.addImplicitParameter(name, value);
628                            }
629    
630                            for (Element overriddenParameterElement :
631                                            routeElement.elements("overridden-parameter")) {
632    
633                                    String name = overriddenParameterElement.attributeValue("name");
634                                    String value = overriddenParameterElement.getText();
635    
636                                    comparableRoute.addOverriddenParameter(name, value);
637                            }
638    
639                            comparableRoutes.add(comparableRoute);
640                    }
641    
642                    Collections.sort(comparableRoutes);
643    
644                    String mainReleaseVersion = getMainReleaseVersion();
645    
646                    StringBundler sb = new StringBundler();
647    
648                    sb.append("<?xml version=\"1.0\"?>\n");
649                    sb.append("<!DOCTYPE routes PUBLIC \"-//Liferay//DTD Friendly URL ");
650                    sb.append("Routes ");
651                    sb.append(mainReleaseVersion);
652                    sb.append("//EN\" \"http://www.liferay.com/dtd/");
653                    sb.append("liferay-friendly-url-routes_");
654                    sb.append(
655                            StringUtil.replace(
656                                    mainReleaseVersion, StringPool.PERIOD, StringPool.UNDERLINE));
657                    sb.append(".dtd\">\n\n<routes>\n");
658    
659                    for (ComparableRoute comparableRoute : comparableRoutes) {
660                            sb.append("\t<route>\n");
661                            sb.append("\t\t<pattern>");
662                            sb.append(comparableRoute.getPattern());
663                            sb.append("</pattern>\n");
664    
665                            Map<String, String> generatedParameters =
666                                    comparableRoute.getGeneratedParameters();
667    
668                            for (Map.Entry<String, String> entry :
669                                            generatedParameters.entrySet()) {
670    
671                                    sb.append("\t\t<generated-parameter name=\"");
672                                    sb.append(entry.getKey());
673                                    sb.append("\">");
674                                    sb.append(entry.getValue());
675                                    sb.append("</generated-parameter>\n");
676                            }
677    
678                            Set<String> ignoredParameters =
679                                    comparableRoute.getIgnoredParameters();
680    
681                            for (String entry : ignoredParameters) {
682                                    sb.append("\t\t<ignored-parameter name=\"");
683                                    sb.append(entry);
684                                    sb.append("\" />\n");
685                            }
686    
687                            Map<String, String> implicitParameters =
688                                    comparableRoute.getImplicitParameters();
689    
690                            for (Map.Entry<String, String> entry :
691                                            implicitParameters.entrySet()) {
692    
693                                    sb.append("\t\t<implicit-parameter name=\"");
694                                    sb.append(entry.getKey());
695                                    sb.append("\">");
696                                    sb.append(entry.getValue());
697                                    sb.append("</implicit-parameter>\n");
698                            }
699    
700                            Map<String, String> overriddenParameters =
701                                    comparableRoute.getOverriddenParameters();
702    
703                            for (Map.Entry<String, String> entry :
704                                            overriddenParameters.entrySet()) {
705    
706                                    sb.append("\t\t<overridden-parameter name=\"");
707                                    sb.append(entry.getKey());
708                                    sb.append("\">");
709                                    sb.append(entry.getValue());
710                                    sb.append("</overridden-parameter>\n");
711                            }
712    
713                            sb.append("\t</route>\n");
714                    }
715    
716                    sb.append("</routes>");
717    
718                    return sb.toString();
719            }
720    
721            protected String formatPortletXML(
722                            String fileName, String absolutePath, String content)
723                    throws Exception {
724    
725                    Document document = saxReaderUtil.read(content);
726    
727                    Element rootElement = document.getRootElement();
728    
729                    rootElement.sortAttributes(true);
730    
731                    boolean checkNumericalPortletNameElement = !isExcluded(
732                            _numericalPortletNameElementExclusions, absolutePath);
733    
734                    List<Element> portletElements = rootElement.elements("portlet");
735    
736                    for (Element portletElement : portletElements) {
737                            if (checkNumericalPortletNameElement) {
738                                    Element portletNameElement = portletElement.element(
739                                            "portlet-name");
740    
741                                    String portletNameText = portletNameElement.getText();
742    
743                                    if (!Validator.isNumber(portletNameText)) {
744                                            processErrorMessage(
745                                                    fileName,
746                                                    fileName +
747                                                            " contains a nonstandard portlet-name element " +
748                                                                    portletNameText);
749                                    }
750                            }
751    
752                            if (fileName.endsWith("/liferay-portlet.xml")) {
753                                    continue;
754                            }
755    
756                            portletElement.sortElementsByChildElement("init-param", "name");
757    
758                            Element portletPreferencesElement = portletElement.element(
759                                    "portlet-preferences");
760    
761                            if (portletPreferencesElement != null) {
762                                    portletPreferencesElement.sortElementsByChildElement(
763                                            "preference", "name");
764                            }
765                    }
766    
767                    return document.formattedString();
768            }
769    
770            protected String formatPoshiXML(String fileName, String content)
771                    throws Exception {
772    
773                    checkPoshiCharactersAfterDefinition(fileName, content);
774                    checkPoshiCharactersBeforeDefinition(fileName, content);
775    
776                    content = sortPoshiAttributes(fileName, content);
777    
778                    content = sortPoshiCommands(content);
779    
780                    content = sortPoshiVariables(content);
781    
782                    content = fixPoshiXMLElementWithNoChild(content);
783    
784                    content = fixPoshiXMLEndLinesAfterClosingElement(content);
785    
786                    content = fixPoshiXMLEndLinesBeforeClosingElement(content);
787    
788                    content = fixPoshiXMLEndLines(content);
789    
790                    return fixPoshiXMLNumberOfTabs(content);
791            }
792    
793            protected void formatServiceXML(String fileName, String content)
794                    throws Exception {
795    
796                    Document document = saxReaderUtil.read(content);
797    
798                    Element rootElement = document.getRootElement();
799    
800                    List<Element> entityElements = rootElement.elements("entity");
801    
802                    String previousEntityName = StringPool.BLANK;
803    
804                    for (Element entityElement : entityElements) {
805                            String entityName = entityElement.attributeValue("name");
806    
807                            if (Validator.isNotNull(previousEntityName) &&
808                                    (previousEntityName.compareToIgnoreCase(entityName) > 0)) {
809    
810                                    processErrorMessage(
811                                            fileName, "sort: " + fileName + " " + entityName);
812                            }
813    
814                            checkServiceXMLFinders(fileName, entityElement, entityName);
815                            checkServiceXMLReferences(fileName, entityElement, entityName);
816    
817                            previousEntityName = entityName;
818                    }
819    
820                    checkServiceXMLExceptions(fileName, rootElement);
821            }
822    
823            protected void formatStrutsConfigXML(String fileName, String content)
824                    throws Exception {
825    
826                    Document document = saxReaderUtil.read(content);
827    
828                    Element rootElement = document.getRootElement();
829    
830                    Element actionMappingsElement = rootElement.element("action-mappings");
831    
832                    List<Element> actionElements = actionMappingsElement.elements("action");
833    
834                    String previousPath = StringPool.BLANK;
835    
836                    for (Element actionElement : actionElements) {
837                            String path = actionElement.attributeValue("path");
838    
839                            if (Validator.isNotNull(previousPath) &&
840                                    (previousPath.compareTo(path) > 0) &&
841                                    (!previousPath.startsWith("/portal/") ||
842                                     path.startsWith("/portal/"))) {
843    
844                                    processErrorMessage(fileName, "sort: " + fileName + " " + path);
845                            }
846    
847                            previousPath = path;
848                    }
849            }
850    
851            protected void formatTilesDefsXML(String fileName, String content)
852                    throws Exception {
853    
854                    Document document = saxReaderUtil.read(content);
855    
856                    Element rootElement = document.getRootElement();
857    
858                    List<Element> definitionElements = rootElement.elements("definition");
859    
860                    String previousName = StringPool.BLANK;
861    
862                    for (Element definitionElement : definitionElements) {
863                            String name = definitionElement.attributeValue("name");
864    
865                            if (Validator.isNotNull(previousName) &&
866                                    (previousName.compareTo(name) > 0) &&
867                                    !previousName.equals("portlet")) {
868    
869                                    processErrorMessage(fileName, "sort: " + fileName + " " + name);
870                            }
871    
872                            previousName = name;
873                    }
874            }
875    
876            protected String formatWebXML(String fileName, String content)
877                    throws Exception {
878    
879                    if (!portalSource) {
880                            String webXML = ContentUtil.get(
881                                    "com/liferay/portal/deploy/dependencies/web.xml");
882    
883                            if (content.equals(webXML)) {
884                                    processErrorMessage(fileName, fileName);
885                            }
886    
887                            return content;
888                    }
889    
890                    Properties properties = new Properties();
891    
892                    String propertiesContent = fileUtil.read(
893                            BASEDIR + "portal-impl/src/portal.properties");
894    
895                    PropertiesUtil.load(properties, propertiesContent);
896    
897                    String[] locales = StringUtil.split(
898                            properties.getProperty(PropsKeys.LOCALES));
899    
900                    Arrays.sort(locales);
901    
902                    Set<String> urlPatterns = new TreeSet<String>();
903    
904                    for (String locale : locales) {
905                            int pos = locale.indexOf(StringPool.UNDERLINE);
906    
907                            String languageCode = locale.substring(0, pos);
908    
909                            urlPatterns.add(languageCode);
910                            urlPatterns.add(locale);
911                    }
912    
913                    StringBundler sb = new StringBundler(6 * urlPatterns.size());
914    
915                    for (String urlPattern : urlPatterns) {
916                            sb.append("\t<servlet-mapping>\n");
917                            sb.append("\t\t<servlet-name>I18n Servlet</servlet-name>\n");
918                            sb.append("\t\t<url-pattern>/");
919                            sb.append(urlPattern);
920                            sb.append("/*</url-pattern>\n");
921                            sb.append("\t</servlet-mapping>\n");
922                    }
923    
924                    int x = content.indexOf("<servlet-mapping>");
925    
926                    x = content.indexOf("<servlet-name>I18n Servlet</servlet-name>", x);
927    
928                    x = content.lastIndexOf("<servlet-mapping>", x) - 1;
929    
930                    int y = content.lastIndexOf(
931                            "<servlet-name>I18n Servlet</servlet-name>");
932    
933                    y = content.indexOf("</servlet-mapping>", y) + 19;
934    
935                    String newContent =
936                            content.substring(0, x) + sb.toString() + content.substring(y);
937    
938                    x = newContent.indexOf("<security-constraint>");
939    
940                    x = newContent.indexOf(
941                            "<web-resource-name>/c/portal/protected</web-resource-name>", x);
942    
943                    x = newContent.indexOf("<url-pattern>", x) - 3;
944    
945                    y = newContent.indexOf("<http-method>", x);
946    
947                    y = newContent.lastIndexOf("</url-pattern>", y) + 15;
948    
949                    sb = new StringBundler(3 * urlPatterns.size() + 1);
950    
951                    sb.append("\t\t\t<url-pattern>/c/portal/protected</url-pattern>\n");
952    
953                    for (String urlPattern : urlPatterns) {
954                            sb.append("\t\t\t<url-pattern>/");
955                            sb.append(urlPattern);
956                            sb.append("/c/portal/protected</url-pattern>\n");
957                    }
958    
959                    return newContent.substring(0, x) + sb.toString() +
960                            newContent.substring(y);
961            }
962    
963            protected List<String> getColumnNames(String fileName, String entityName)
964                    throws Exception {
965    
966                    List<String> columnNames = new ArrayList<String>();
967    
968                    Pattern pattern = Pattern.compile(
969                            "create table " + entityName + "_? \\(\n([\\s\\S]*?)\n\\);");
970    
971                    Matcher matcher = pattern.matcher(getTablesContent(fileName));
972    
973                    if (!matcher.find()) {
974                            return columnNames;
975                    }
976    
977                    String tableContent = matcher.group(1);
978    
979                    UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
980                            new UnsyncStringReader(tableContent));
981    
982                    String line = null;
983    
984                    while ((line = unsyncBufferedReader.readLine()) != null) {
985                            line = StringUtil.trim(line);
986    
987                            String columnName = line.substring(
988                                    0, line.indexOf(StringPool.SPACE));
989    
990                            columnName = StringUtil.replace(
991                                    columnName, StringPool.UNDERLINE, StringPool.BLANK);
992    
993                            columnNames.add(columnName);
994                    }
995    
996                    return columnNames;
997            }
998    
999            protected String getTablesContent(String fileName) throws Exception {
1000                    if (portalSource) {
1001                            if (_tablesContent == null) {
1002                                    _tablesContent = getContent("sql/portal-tables.sql", 4);
1003                            }
1004    
1005                            return _tablesContent;
1006                    }
1007    
1008                    int pos = fileName.lastIndexOf(StringPool.SLASH);
1009    
1010                    return getContent(fileName.substring(0, pos) + "/sql/tables.sql", 1);
1011            }
1012    
1013            protected String sortPoshiAttributes(String fileName, String content)
1014                    throws Exception {
1015    
1016                    StringBundler sb = new StringBundler();
1017    
1018                    try (UnsyncBufferedReader unsyncBufferedReader =
1019                                    new UnsyncBufferedReader(new UnsyncStringReader(content))) {
1020    
1021                            String line = null;
1022    
1023                            int lineCount = 0;
1024    
1025                            boolean sortAttributes = true;
1026    
1027                            while ((line = unsyncBufferedReader.readLine()) != null) {
1028                                    lineCount++;
1029    
1030                                    String trimmedLine = StringUtil.trimLeading(line);
1031    
1032                                    if (sortAttributes) {
1033                                            if (trimmedLine.startsWith(StringPool.LESS_THAN) &&
1034                                                    trimmedLine.endsWith(StringPool.GREATER_THAN) &&
1035                                                    !trimmedLine.startsWith("<%") &&
1036                                                    !trimmedLine.startsWith("<!")) {
1037    
1038                                                    line = sortAttributes(fileName, line, lineCount, false);
1039                                            }
1040                                            else if (trimmedLine.startsWith("<![CDATA[") &&
1041                                                             !trimmedLine.endsWith("]]>")) {
1042    
1043                                                    sortAttributes = false;
1044                                            }
1045                                    }
1046                                    else if (trimmedLine.endsWith("]]>")) {
1047                                            sortAttributes = true;
1048                                    }
1049    
1050                                    sb.append(line);
1051                                    sb.append("\n");
1052                            }
1053                    }
1054    
1055                    content = sb.toString();
1056    
1057                    if (content.endsWith("\n")) {
1058                            content = content.substring(0, content.length() - 1);
1059                    }
1060    
1061                    return content;
1062            }
1063    
1064            protected String sortPoshiCommands(String content) {
1065                    Matcher matcher = _poshiCommandsPattern.matcher(content);
1066    
1067                    Map<String, String> commandBlocksMap = new TreeMap<String, String>(
1068                            String.CASE_INSENSITIVE_ORDER);
1069    
1070                    String previousName = StringPool.BLANK;
1071    
1072                    boolean hasUnsortedCommands = false;
1073    
1074                    while (matcher.find()) {
1075                            String commandBlock = matcher.group();
1076                            String commandName = matcher.group(1);
1077    
1078                            commandBlocksMap.put(commandName, commandBlock);
1079    
1080                            if (!hasUnsortedCommands &&
1081                                    (commandName.compareToIgnoreCase(previousName) < 0)) {
1082    
1083                                    hasUnsortedCommands = true;
1084                            }
1085    
1086                            previousName = commandName;
1087                    }
1088    
1089                    if (!hasUnsortedCommands) {
1090                            return content;
1091                    }
1092    
1093                    StringBundler sb = new StringBundler();
1094    
1095                    matcher = _poshiSetUpPattern.matcher(content);
1096    
1097                    if (matcher.find()) {
1098                            String setUpBlock = matcher.group();
1099    
1100                            content = content.replace(setUpBlock, "");
1101    
1102                            sb.append(setUpBlock);
1103                    }
1104    
1105                    matcher = _poshiTearDownPattern.matcher(content);
1106    
1107                    if (matcher.find()) {
1108                            String tearDownBlock = matcher.group();
1109    
1110                            content = content.replace(tearDownBlock, "");
1111    
1112                            sb.append(tearDownBlock);
1113                    }
1114    
1115                    for (Map.Entry<String, String> entry : commandBlocksMap.entrySet()) {
1116                            sb.append("\n\t");
1117                            sb.append(entry.getValue());
1118                            sb.append("\n");
1119                    }
1120    
1121                    int x = content.indexOf("<command");
1122                    int y = content.lastIndexOf("</command>");
1123    
1124                    String commandBlock = content.substring(x, y);
1125    
1126                    commandBlock = "\n\t" + commandBlock + "</command>\n";
1127    
1128                    String newCommandBlock = sb.toString();
1129    
1130                    return StringUtil.replaceFirst(content, commandBlock, newCommandBlock);
1131            }
1132    
1133            protected String sortPoshiVariables(String content) {
1134                    Matcher matcher = _poshiVariablesBlockPattern.matcher(content);
1135    
1136                    while (matcher.find()) {
1137                            String previousName = StringPool.BLANK;
1138                            String tabs = StringPool.BLANK;
1139    
1140                            Map<String, String> variableLinesMap = new TreeMap<String, String>(
1141                                    String.CASE_INSENSITIVE_ORDER);
1142    
1143                            String variableBlock = matcher.group(1);
1144    
1145                            variableBlock = variableBlock.trim();
1146    
1147                            Matcher variableLineMatcher = _poshiVariableLinePattern.matcher(
1148                                    variableBlock);
1149    
1150                            boolean hasUnsortedVariables = false;
1151    
1152                            while (variableLineMatcher.find()) {
1153                                    if (tabs.equals(StringPool.BLANK)) {
1154                                            tabs = variableLineMatcher.group(1);
1155                                    }
1156    
1157                                    String variableLine = variableLineMatcher.group(2);
1158                                    String variableName = variableLineMatcher.group(3);
1159    
1160                                    variableLinesMap.put(variableName, variableLine);
1161    
1162                                    if (!hasUnsortedVariables &&
1163                                            (variableName.compareToIgnoreCase(previousName) < 0)) {
1164    
1165                                            hasUnsortedVariables = true;
1166                                    }
1167    
1168                                    previousName = variableName;
1169                            }
1170    
1171                            if (!hasUnsortedVariables) {
1172                                    continue;
1173                            }
1174    
1175                            StringBundler sb = new StringBundler();
1176    
1177                            for (Map.Entry<String, String> entry :
1178                                            variableLinesMap.entrySet()) {
1179    
1180                                    sb.append(tabs);
1181                                    sb.append(entry.getValue());
1182                                    sb.append("\n");
1183                            }
1184    
1185                            String newVariableBlock = sb.toString();
1186    
1187                            newVariableBlock = newVariableBlock.trim();
1188    
1189                            content = StringUtil.replaceFirst(
1190                                    content, variableBlock, newVariableBlock);
1191                    }
1192    
1193                    return content;
1194            }
1195    
1196            private static Pattern _commentPattern1 = Pattern.compile(
1197                    ">\n\t+<!--[\n ]");
1198            private static Pattern _commentPattern2 = Pattern.compile(
1199                    "[\t ]-->\n[\t<]");
1200    
1201            private List<String> _columnNames;
1202            private List<String> _friendlyUrlRoutesSortExclusions;
1203            private List<String> _numericalPortletNameElementExclusions;
1204            private Pattern _poshiClosingTagPattern = Pattern.compile("</[^>/]*>");
1205            private Pattern _poshiCommandsPattern = Pattern.compile(
1206                    "\\<command.*name=\\\"([^\\\"]*)\\\".*\\>[\\s\\S]*?\\</command\\>" +
1207                            "[\\n|\\t]*?(?:[^(?:/\\>)]*?--\\>)*+");
1208            private Pattern _poshiElementWithNoChildPattern = Pattern.compile(
1209                    "\\\"[\\s]*\\>[\\n\\s\\t]*\\</[a-z\\-]+>");
1210            private Pattern _poshiEndLinesAfterClosingElementPattern = Pattern.compile(
1211                    "(\\</[a-z\\-]+>)(\\n+)\\t*\\<[a-z]+");
1212            private Pattern _poshiEndLinesBeforeClosingElementPattern = Pattern.compile(
1213                    "(\\n+)(\\t*</[a-z\\-]+>)");
1214            private Pattern _poshiEndLinesPattern = Pattern.compile(
1215                    "\\>\\n\\n\\n+(\\t*\\<)");
1216            private Pattern _poshiOpeningTagPattern = Pattern.compile(
1217                    "<[^/][^>]*[^/]>");
1218            private Pattern _poshiQuoteWithSlashPattern = Pattern.compile(
1219                    "\"[^\"]*\\>[^\"]*\"");
1220            private Pattern _poshiSetUpPattern = Pattern.compile(
1221                    "\\n[\\t]++\\<set-up\\>([\\s\\S]*?)\\</set-up\\>" +
1222                            "[\\n|\\t]*?(?:[^(?:/\\>)]*?--\\>)*+\\n");
1223            private Pattern _poshiTabsPattern = Pattern.compile("\\n*([ \\t]*<).*");
1224            private Pattern _poshiTearDownPattern = Pattern.compile(
1225                    "\\n[\\t]++\\<tear-down\\>([\\s\\S]*?)\\</tear-down\\>" +
1226                            "[\\n|\\t]*?(?:[^(?:/\\>)]*?--\\>)*+\\n");
1227            private Pattern _poshiVariableLinePattern = Pattern.compile(
1228                    "([\\t]*+)(\\<var name=\\\"([^\\\"]*)\\\".*?/\\>.*+(?:\\</var\\>)??)");
1229            private Pattern _poshiVariablesBlockPattern = Pattern.compile(
1230                    "((?:[\\t]*+\\<var.*?\\>\\n[\\t]*+){2,}?)" +
1231                            "(?:(?:\\n){1,}+|\\</execute\\>)");
1232            private Pattern _poshiWholeTagPattern = Pattern.compile("<[^\\>^/]*\\/>");
1233            private String _tablesContent;
1234            private Pattern _whereNotInSQLPattern = Pattern.compile(
1235                    "WHERE[ \t\n]+\\(*[a-zA-z0-9.]+ NOT IN");
1236            private List<String> _xmlExclusions;
1237    
1238            private class FinderElementComparator implements Comparator<Element> {
1239    
1240                    @Override
1241                    public int compare(Element finderElement1, Element finderElement2) {
1242                            List<Element> finderColumnElements1 = finderElement1.elements(
1243                                    "finder-column");
1244                            List<Element> finderColumnElements2 = finderElement2.elements(
1245                                    "finder-column");
1246    
1247                            int finderColumnCount1 = finderColumnElements1.size();
1248                            int finderColumnCount2 = finderColumnElements2.size();
1249    
1250                            if (finderColumnCount1 != finderColumnCount2) {
1251                                    return finderColumnCount1 - finderColumnCount2;
1252                            }
1253    
1254                            for (int i = 0; i < finderColumnCount1; i++) {
1255                                    Element finderColumnElement1 = finderColumnElements1.get(i);
1256                                    Element finderColumnElement2 = finderColumnElements2.get(i);
1257    
1258                                    String finderColumnName1 = finderColumnElement1.attributeValue(
1259                                            "name");
1260                                    String finderColumnName2 = finderColumnElement2.attributeValue(
1261                                            "name");
1262    
1263                                    int index1 = _columnNames.indexOf(finderColumnName1);
1264                                    int index2 = _columnNames.indexOf(finderColumnName2);
1265    
1266                                    if (index1 != index2) {
1267                                            return index1 - index2;
1268                                    }
1269                            }
1270    
1271                            String finderName1 = finderElement1.attributeValue("name");
1272                            String finderName2 = finderElement2.attributeValue("name");
1273    
1274                            int startsWithWeight = StringUtil.startsWithWeight(
1275                                    finderName1, finderName2);
1276    
1277                            String strippedFinderName1 = finderName1.substring(
1278                                    startsWithWeight);
1279                            String strippedFinderName2 = finderName2.substring(
1280                                    startsWithWeight);
1281    
1282                            if (strippedFinderName1.startsWith("Gt") ||
1283                                    strippedFinderName1.startsWith("Like") ||
1284                                    strippedFinderName1.startsWith("Lt") ||
1285                                    strippedFinderName1.startsWith("Not")) {
1286    
1287                                    if (!strippedFinderName2.startsWith("Gt") &&
1288                                            !strippedFinderName2.startsWith("Like") &&
1289                                            !strippedFinderName2.startsWith("Lt") &&
1290                                            !strippedFinderName2.startsWith("Not")) {
1291    
1292                                            return 1;
1293                                    }
1294                                    else {
1295                                            return strippedFinderName1.compareTo(strippedFinderName2);
1296                                    }
1297                            }
1298    
1299                            return 0;
1300                    }
1301    
1302            }
1303    
1304    }