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