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