/* * Copyright 2013 Martin Kouba * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.trimou.engine.parser; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.trimou.engine.parser.DefaultParsingHandler.ContainerSegmentBase; import org.trimou.engine.parser.DefaultParsingHandler.PartialSegmentBase; import org.trimou.engine.parser.DefaultParsingHandler.RootSegmentBase; import org.trimou.engine.parser.DefaultParsingHandler.SegmentBase; import org.trimou.engine.segment.SegmentType; import org.trimou.util.Strings; /** * {@link SegmentBase} utils. * * @author Martin Kouba */ final class SegmentBases { private static final Logger LOGGER = LoggerFactory .getLogger(SegmentBases.class); private SegmentBases() { } static void removeStandaloneLines(RootSegmentBase rootSegment) { List<List<SegmentBase>> lines = readSegmentLines(rootSegment); // First identify the segments to remove Set<SegmentBase> segmentsToRemove = new HashSet<>(); int idx = 0; for (List<SegmentBase> line : lines) { idx++; if (isStandaloneLine(line)) { // Extract indentation for standalone partial segment extractIndentationForPartial(line); for (SegmentBase segment : line) { if (segment instanceof ContainerSegmentBase || SegmentType.PARTIAL.equals(segment.getType())) { continue; } segmentsToRemove.add(segment); } LOGGER.trace("Segment line {} is standalone", idx); } } // Then remove segments if (!segmentsToRemove.isEmpty()) { removeSegments(segmentsToRemove, rootSegment); LOGGER.debug("{} segments removed", segmentsToRemove.size()); } } static void removeUnnecessarySegments(ContainerSegmentBase container) { for (Iterator<SegmentBase> iterator = container.iterator(); iterator .hasNext();) { SegmentBase segment = iterator.next(); if (segment instanceof ContainerSegmentBase) { removeUnnecessarySegments((ContainerSegmentBase) segment); } else if (SegmentType.COMMENT.equals(segment.getType()) || SegmentType.DELIMITERS.equals(segment.getType())) { iterator.remove(); } } } static void reuseLineSeparatorSegments(ContainerSegmentBase container) { Map<String, SegmentBase> lineSeparators = new HashMap<>(); for (ListIterator<SegmentBase> iterator = container.listIterator(); iterator .hasNext();) { SegmentBase segment = iterator.next(); if (segment instanceof ContainerSegmentBase) { reuseLineSeparatorSegments((ContainerSegmentBase) segment); } else if (SegmentType.LINE_SEPARATOR.equals(segment.getType())) { SegmentBase lineSeparator = lineSeparators.get(segment .getContent()); if (lineSeparator == null) { lineSeparators.put(segment.getContent(), segment); } else { iterator.set(lineSeparator); } } } } /** * * @param standaloneLine */ private static void extractIndentationForPartial( List<SegmentBase> standaloneLine) { if (SegmentType.TEXT.equals(standaloneLine.get(0).getType())) { // First segment is whitespace - try to find partial PartialSegmentBase partial = null; for (SegmentBase segment : standaloneLine) { if (SegmentType.PARTIAL.equals(segment.getType())) { partial = (PartialSegmentBase) segment; break; } } if (partial != null) { partial.setIndentation(standaloneLine.get(0).getContent()); } } } /** * * @param segmentsToRemove * @param container */ private static void removeSegments(Set<SegmentBase> segmentsToRemove, ContainerSegmentBase container) { for (Iterator<SegmentBase> iterator = container.iterator(); iterator .hasNext();) { SegmentBase segment = iterator.next(); if (segment instanceof ContainerSegmentBase) { removeSegments(segmentsToRemove, (ContainerSegmentBase) segment); } else { if (segmentsToRemove.contains(segment)) { iterator.remove(); } } } } /** * * @param line * @return */ static boolean isStandaloneLine(List<SegmentBase> line) { boolean standaloneCandidate = false; for (SegmentBase segment : line) { if (SegmentType.VALUE.equals(segment.getType())) { // Value segment return false; } else if (SegmentType.TEXT.equals(segment.getType())) { if (!Strings.containsOnlyWhitespace(segment.getContent())) { // Text segment with non-whitespace character return false; } } else if (isStandaloneCandidate(segment.getType())) { standaloneCandidate = true; } } return standaloneCandidate; } private static boolean isStandaloneCandidate(SegmentType type) { return type.equals(SegmentType.COMMENT) || type.equals(SegmentType.SECTION) || type.equals(SegmentType.INVERTED_SECTION) || type.equals(SegmentType.DELIMITERS) || type.equals(SegmentType.PARTIAL); } /** * Read segment lines recursively. * * @param container * @return */ private static List<List<SegmentBase>> readSegmentLines( ContainerSegmentBase container) { List<List<SegmentBase>> lines = new ArrayList<>(); // Add the last line manually - there is no line separator to trigger // flush lines.add(readSegmentLines(lines, null, container)); return lines; } /** * * @param lines * @param currentLine * @param container * @return */ private static List<SegmentBase> readSegmentLines( List<List<SegmentBase>> lines, List<SegmentBase> currentLine, ContainerSegmentBase container) { if (currentLine == null) { currentLine = new ArrayList<>(); } if (!SegmentType.ROOT.equals(container.getType())) { // Simulate the start tag currentLine.add(container); } for (SegmentBase segment : container) { if (segment instanceof ContainerSegmentBase) { currentLine = readSegmentLines(lines, currentLine, (ContainerSegmentBase) segment); } else if (!SegmentType.LINE_SEPARATOR.equals(segment.getType())) { currentLine.add(segment); } else { // New line separator - flush the line currentLine.add(segment); lines.add(currentLine); currentLine = new ArrayList<>(); } } if (!SegmentType.ROOT.equals(container.getType())) { // Simulate the end tag currentLine.add(container); } return currentLine; } }