001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.doxia.macro.snippet; 020 021import java.io.BufferedReader; 022import java.io.IOException; 023import java.io.InputStreamReader; 024import java.net.URL; 025import java.util.ArrayList; 026import java.util.List; 027import java.util.regex.Pattern; 028 029/** 030 * Utility class for reading snippets. 031 */ 032public class SnippetReader { 033 /** System-dependent EOL. */ 034 private static final String EOL = System.getProperty("line.separator"); 035 036 /** The source. */ 037 private URL source; 038 039 /** The encoding of the source. */ 040 private String encoding; 041 042 /** 043 * Constructor. 044 * 045 * @param src The source 046 * @param encoding The file encoding 047 */ 048 public SnippetReader(URL src, String encoding) { 049 this.source = src; 050 this.encoding = encoding; 051 } 052 053 /** 054 * Constructor. 055 * 056 * @param src The source 057 */ 058 public SnippetReader(URL src) { 059 this(src, null); 060 } 061 062 /** 063 * Reads the snippet with given id. 064 * 065 * @param snippetId The id of the snippet. 066 * @return The snippet. 067 * @throws java.io.IOException if something goes wrong. 068 */ 069 public StringBuffer readSnippet(String snippetId) throws IOException { 070 List<String> lines = readLines(snippetId); 071 int minIndent = minIndent(lines); 072 StringBuffer result = new StringBuffer(); 073 for (String line : lines) { 074 result.append(line.substring(minIndent)); 075 result.append(EOL); 076 } 077 return result; 078 } 079 080 /** 081 * Returns the minimal indent of all the lines in the given List. 082 * 083 * @param lines A List of lines. 084 * @return the minimal indent. 085 */ 086 int minIndent(List<String> lines) { 087 int minIndent = Integer.MAX_VALUE; 088 for (String line : lines) { 089 minIndent = Math.min(minIndent, indent(line)); 090 } 091 return minIndent; 092 } 093 094 /** 095 * Returns the indent of the given line. 096 * 097 * @param line A line. 098 * @return the indent. 099 */ 100 int indent(String line) { 101 char[] chars = line.toCharArray(); 102 int indent = 0; 103 for (; indent < chars.length; indent++) { 104 if (chars[indent] != ' ') { 105 break; 106 } 107 } 108 return indent; 109 } 110 111 /** 112 * Reads the snippet and returns the lines in a List. 113 * 114 * @param snippetId The id of the snippet. 115 * @return A List of lines. 116 * @throws IOException if something goes wrong. 117 */ 118 private List<String> readLines(String snippetId) throws IOException { 119 BufferedReader reader; 120 if (encoding == null || "".equals(encoding)) { 121 reader = new BufferedReader(new InputStreamReader(source.openStream())); 122 } else { 123 reader = new BufferedReader(new InputStreamReader(source.openStream(), encoding)); 124 } 125 126 List<String> lines = new ArrayList<>(); 127 try (BufferedReader withReader = reader) { 128 boolean capture = false; 129 String line; 130 boolean foundStart = false; 131 boolean foundEnd = false; 132 boolean hasSnippetId = snippetId != null && !snippetId.isEmpty(); 133 while ((line = withReader.readLine()) != null) { 134 if (!hasSnippetId) { 135 lines.add(line); 136 } else { 137 if (isStart(snippetId, line)) { 138 capture = true; 139 foundStart = true; 140 } else if (isEnd(snippetId, line)) { 141 foundEnd = true; 142 break; 143 } else if (capture) { 144 lines.add(line); 145 } 146 } 147 } 148 149 if (hasSnippetId && !foundStart) { 150 throw new IOException("Failed to find START of snippet " + snippetId + " in file at URL: " + source); 151 } 152 if (hasSnippetId && !foundEnd) { 153 throw new IOException("Failed to find END of snippet " + snippetId + " in file at URL: " + source); 154 } 155 } 156 return lines; 157 } 158 159 /** 160 * Determines if the given line is a start demarcator. 161 * 162 * @param snippetId the id of the snippet. 163 * @param line the line. 164 * @return True, if the line is a start demarcator. 165 */ 166 protected boolean isStart(String snippetId, String line) { 167 return isDemarcator(snippetId, "START", line); 168 } 169 170 /** 171 * Determines if the given line is a demarcator. 172 * 173 * @param snippetId the id of the snippet. 174 * @param what Identifier for the demarcator. 175 * @param line the line. 176 * @return True, if the line is a start demarcator. 177 */ 178 protected static boolean isDemarcator(String snippetId, String what, String line) { 179 // SNIPPET and what are case insensitive 180 // SNIPPET and what can switch order 181 String snippetRegExp = "(^|\\W)(?i:SNIPPET)($|\\W)"; 182 String snippetIdRegExp = "(^|\\W)" + snippetId + "($|\\W)"; 183 String whatRegExp = "(^|\\W)(?i:" + what + ")($|\\W)"; 184 185 return Pattern.compile(snippetRegExp).matcher(line).find() 186 && Pattern.compile(whatRegExp).matcher(line).find() 187 && Pattern.compile(snippetIdRegExp).matcher(line).find(); 188 } 189 190 /** 191 * Determines if the given line is an end demarcator. 192 * 193 * @param snippetId the id of the snippet. 194 * @param line the line. 195 * @return True, if the line is an end demarcator. 196 */ 197 protected boolean isEnd(String snippetId, String line) { 198 return isDemarcator(snippetId, "END", line); 199 } 200}