Java tutorial
// Copyright 2011 The PageObjectGenerator Authors. // Copyright 2011 Google Inc. // // 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 com.google.testing.pogen.parser.template; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Range; import com.google.common.collect.RangeSet; import com.google.common.collect.TreeRangeSet; /** * A class to parse a template to retrieve the {@link TemplateInfo} instance. * * @author Kazunori Sakamoto */ public abstract class TemplateParser { /** * A name of the attribute to be assigned for tags containing template * variables. */ protected final String attributeName; /** * Constructs the instance of {@link TemplateParser} with the specified * attribute name. * * @param attributeName * the name of the attribute to be assigned for tags containing * template variables */ public TemplateParser(String attributeName) { this.attributeName = attributeName; } /** * Retrieves the information of the specified template by parsing it. * * @param template * the string of the template to be parsed * @return the information of the specified template * @throws TemplateParseException * if the specified template is in bad format */ public TemplateInfo parse(String template) throws TemplateParseException { Preconditions.checkNotNull(template); RangeSet<Integer> repeatedParts = parseRepeatedPart(template); List<HtmlTagInfo> htmlTagInfos = parseTagsContainingVariables(template, repeatedParts); return new TemplateInfo(template, htmlTagInfos); } /** * Retrieves an information list of html tags which contain template * variables by parsing the specified template. * * @param template * the string of the template to be parsed * @param repeatedParts * @return the information list of html tags which contain template * variables * @throws TemplateParseException * if the specified template is in bad format */ protected abstract List<HtmlTagInfo> parseTagsContainingVariables(String template, RangeSet<Integer> repeatedParts) throws TemplateParseException; /** * Retrieves a {@link RangeSet} of indexes where the repeated part are * located in the specified template. Note that repeated part is inner part * of for/foreach tags and of template tags called from another repeated * part. For example, {template .t1}repeated part{/template}{foreach * ...}repeated part{call .t1}{/foreach}. * * @param template * the string of the template to be parsed * @return the {@link RangeSet} of the indexes where repeated part are * located * @throws TemplateParseException * if the specified template is in bad format */ protected abstract RangeSet<Integer> parseRepeatedPart(String template) throws TemplateParseException; /** * Returns start indexes of strings that match the indicated regular * expression. * * @param text * the string to be parsed * @param pattern * the regular expression to find * @return the start indexes of matched strings with the indicated pattern */ protected static List<Integer> getMatchedIndexes(String text, Pattern pattern) { List<Integer> results = Lists.newArrayList(); Matcher m = pattern.matcher(text); while (m.find()) { results.add(m.start()); } return results; } /** * Returns a list of pairs containing matched strings and their start index * for the specified regular expression and the given group index. * * @param text * the string to be parsed * @param pattern * the regular expression to find * @param groupIndex * the index of the group whose matched string we want to * retrieve * @return the start indexes of matched strings with the indicated pattern */ protected static List<StringWithIndex> getMatchedStringAndIndexes(String text, Pattern pattern, int groupIndex) { List<StringWithIndex> results = Lists.newArrayList(); Matcher m = pattern.matcher(text); while (m.find()) { results.add(new StringWithIndex(m.group(groupIndex), m.start())); } return results; } /** * Returns a {@link RangeSet} of indexes between the start and the end tags * for non-nested tags. * * @param text * the string to be parsed * @param startPattern * the regular expression of start tags to find * @param endPattern * the regular expression of end tags to find * @return the {@link RangeSet} of the indexes between the start and the end * tags * @throws TemplateParseException * if the specified template is in bad format where broken pairs * of start and end tags appear */ protected static RangeSet<Integer> getIndexRangesOfNonNestedTags(String text, Pattern startPattern, Pattern endPattern) throws TemplateParseException { RangeSet<Integer> rangeSet = TreeRangeSet.create(); List<Integer> startIndexes = getMatchedIndexes(text, startPattern); List<Integer> endIndexes = getMatchedIndexes(text, endPattern); // Check whether all start tags and end tags are paired correctly if (startIndexes.size() != endIndexes.size()) { throw new TemplateParseException( String.format("There are broken pairs of start and end tags (#start tags: %d, #end tags: %d)", startIndexes.size(), endIndexes.size())); } for (int i = 0; i < startIndexes.size(); i++) { rangeSet.add(Range.closedOpen(startIndexes.get(i), endIndexes.get(i))); } return rangeSet; } /** * Returns a {@link RangeSet} of indexes between the start and the end tags * for nested tags. If there're nested tags, returns the outermost ranges. * * @param text * the string to be parsed * @param startPattern * the regular expression of start tags to find * @param endPattern * the regular expression of end tags to find * @return the {@link RangeSet} of the indexes between the start and the end * tags * @throws TemplateParseException * if the specified template is in bad format where broken pairs * of start and end tags appear */ protected static RangeSet<Integer> getIndexRangesOfNestedTags(String text, Pattern startPattern, Pattern endPattern) throws TemplateParseException { RangeSet<Integer> rangeSet = TreeRangeSet.create(); List<Integer> startIndexes = getMatchedIndexes(text, startPattern); List<Integer> endIndexes = getMatchedIndexes(text, endPattern); int startIndex = 0, endIndex = 0; // Check whether the sizes of start tags and end tags are equal if (startIndexes.size() != endIndexes.size()) { throw new TemplateParseException(String.format( "The sizes of start tags and end tags are not equal (#start tags: %d, #end tags: %d).", startIndexes.size(), endIndexes.size())); } int depth = 0, lastStartIndex = 0; while (endIndex < endIndexes.size()) { boolean consumedStartIndexes = startIndex == startIndexes.size(); // Process a previous index before a next index if (consumedStartIndexes || endIndexes.get(endIndex) < startIndexes.get(startIndex)) { if (--depth <= 0) { if (depth < 0) { // Check whether all start tags and end tags are paired // correctly throw new TemplateParseException( String.format("Broken pairs of start and end tags are found.")); } rangeSet.add(Range.closedOpen(lastStartIndex, endIndexes.get(endIndex))); } endIndex++; } else { if (depth++ == 0) { lastStartIndex = startIndexes.get(startIndex); } startIndex++; } } return rangeSet; } /** * Returns a {@link Map} of names and indexes between the start and the end * tags for non-nested tags. * * @param text * the string to be parsed * @param startPattern * the regular expression of start tags to find * @param endPattern * the regular expression of end tags to find * @param groupIndex * the index of the group in matched parts to retrieve a name * @return the {@link Map} of the names and the indexes between the start * and the end tags * @throws TemplateParseException * if the specified template is in bad format where broken pairs * of start and end tags appear */ protected static Map<String, Range<Integer>> getNamedIndexRangesOfNonNestedTags(String text, Pattern startPattern, Pattern endPattern, int groupIndex) throws TemplateParseException { Map<String, Range<Integer>> templates = Maps.newHashMap(); List<StringWithIndex> starts = getMatchedStringAndIndexes(text, startPattern, groupIndex); List<Integer> ends = getMatchedIndexes(text, endPattern); // Check whether the sizes of start tags and end tags are equal if (starts.size() != ends.size()) { throw new TemplateParseException( String.format("There are broken pairs of start and end tags (#start tags: %d, #end tags: %d)", starts.size(), ends.size())); } int endIndexesIndex = 0; for (StringWithIndex nameAndIndex : starts) { int endIndex = ends.get(endIndexesIndex++); // Check whether all start tags and end tags are paired correctly if (nameAndIndex.getIndex() >= endIndex) { throw new TemplateParseException(String.format("Broken pairs of start and end tags are found.")); } templates.put(nameAndIndex.getString(), Range.closedOpen(nameAndIndex.getIndex(), endIndex)); } return templates; } }