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 * 036 * @version $Id$ 037 */ 038public class SnippetReader 039{ 040 /** System-dependent EOL. */ 041 private static final String EOL = System.getProperty( "line.separator" ); 042 043 /** The source. */ 044 private URL source; 045 046 /** The encoding of the source. */ 047 private String encoding; 048 049 /** 050 * Constructor. 051 * 052 * @param src The source 053 * @param encoding The file encoding 054 */ 055 public SnippetReader( URL src, String encoding ) 056 { 057 this.source = src; 058 this.encoding = encoding; 059 } 060 061 /** 062 * Constructor. 063 * 064 * @param src The source 065 */ 066 public SnippetReader( URL src ) 067 { 068 this( src, null ) ; 069 } 070 071 /** 072 * Reads the snippet with given id. 073 * 074 * @param snippetId The id of the snippet. 075 * @return The snippet. 076 * @throws java.io.IOException if something goes wrong. 077 */ 078 public StringBuffer readSnippet( String snippetId ) 079 throws IOException 080 { 081 List<String> lines = readLines( snippetId ); 082 int minIndent = minIndent( lines ); 083 StringBuffer result = new StringBuffer(); 084 for ( String line : lines ) 085 { 086 result.append( line.substring( minIndent ) ); 087 result.append( EOL ); 088 } 089 return result; 090 } 091 092 /** 093 * Returns the minimal indent of all the lines in the given List. 094 * 095 * @param lines A List of lines. 096 * @return the minimal indent. 097 */ 098 int minIndent( List<String> lines ) 099 { 100 int minIndent = Integer.MAX_VALUE; 101 for ( String line : lines ) 102 { 103 minIndent = Math.min( minIndent, indent( line ) ); 104 } 105 return minIndent; 106 } 107 108 /** 109 * Returns the indent of the given line. 110 * 111 * @param line A line. 112 * @return the indent. 113 */ 114 int indent( String line ) 115 { 116 char[] chars = line.toCharArray(); 117 int indent = 0; 118 for ( ; indent < chars.length; indent++ ) 119 { 120 if ( chars[indent] != ' ' ) 121 { 122 break; 123 } 124 } 125 return indent; 126 } 127 128 /** 129 * Reads the snippet and returns the lines in a List. 130 * 131 * @param snippetId The id of the snippet. 132 * @return A List of lines. 133 * @throws IOException if something goes wrong. 134 */ 135 private List<String> readLines( String snippetId ) 136 throws IOException 137 { 138 BufferedReader reader; 139 if ( encoding == null || "".equals( encoding ) ) 140 { 141 reader = new BufferedReader( new InputStreamReader( source.openStream() ) ); 142 } 143 else 144 { 145 reader = new BufferedReader( new InputStreamReader( source.openStream(), encoding ) ); 146 } 147 148 List<String> lines = new ArrayList<String>(); 149 try 150 { 151 boolean capture = false; 152 String line; 153 boolean foundStart = false; 154 boolean foundEnd = false; 155 boolean hasSnippetId = StringUtils.isNotEmpty( snippetId ); 156 while ( ( line = reader.readLine() ) != null ) 157 { 158 if ( !hasSnippetId ) 159 { 160 lines.add( line ); 161 } 162 else 163 { 164 if ( isStart( snippetId, line ) ) 165 { 166 capture = true; 167 foundStart = true; 168 } 169 else if ( isEnd( snippetId, line ) ) 170 { 171 foundEnd = true; 172 break; 173 } 174 else if ( capture ) 175 { 176 lines.add( line ); 177 } 178 } 179 } 180 181 if ( hasSnippetId && !foundStart ) 182 { 183 throw new IOException( "Failed to find START of snippet " + snippetId + " in file at URL: " + source ); 184 } 185 if ( hasSnippetId && !foundEnd ) 186 { 187 throw new IOException( "Failed to find END of snippet " + snippetId + " in file at URL: " + source ); 188 } 189 } 190 finally 191 { 192 IOUtil.close( reader ); 193 } 194 return lines; 195 } 196 197 /** 198 * Determines if the given line is a start demarcator. 199 * 200 * @param snippetId the id of the snippet. 201 * @param line the line. 202 * @return True, if the line is a start demarcator. 203 */ 204 protected boolean isStart( String snippetId, String line ) 205 { 206 return isDemarcator( snippetId, "START", line ); 207 } 208 209 /** 210 * Determines if the given line is a demarcator. 211 * 212 * @param snippetId the id of the snippet. 213 * @param what Identifier for the demarcator. 214 * @param line the line. 215 * @return True, if the line is a start demarcator. 216 */ 217 protected static boolean isDemarcator( String snippetId, String what, String line ) 218 { 219 // SNIPPET and what are case insensitive 220 // SNIPPET and what can switch order 221 String snippetRegExp = "(^|\\W)(?i:SNIPPET)($|\\W)"; 222 String snippetIdRegExp = "(^|\\W)" + snippetId + "($|\\W)"; 223 String whatRegExp = "(^|\\W)(?i:" + what + ")($|\\W)"; 224 225 return Pattern.compile( snippetRegExp ).matcher( line ).find() 226 && Pattern.compile( whatRegExp ).matcher( line ).find() 227 && Pattern.compile( snippetIdRegExp ).matcher( line ).find(); 228 } 229 230 /** 231 * Determines if the given line is an end demarcator. 232 * 233 * @param snippetId the id of the snippet. 234 * @param line the line. 235 * @return True, if the line is an end demarcator. 236 */ 237 protected boolean isEnd( String snippetId, String line ) 238 { 239 return isDemarcator( snippetId, "END", line ); 240 } 241}