001package org.unix4j.unix.sed; 002 003import java.util.Arrays; 004import java.util.regex.Matcher; 005 006import org.unix4j.line.Line; 007import org.unix4j.line.SimpleLine; 008import org.unix4j.processor.LineProcessor; 009import org.unix4j.util.StringUtil; 010 011final class SubstituteProcessor extends AbstractRegexpProcessor { 012 013 private static final int[] EMPTY_OCCURRENCE = new int[0]; 014 015 private final String replacement; 016 private final int[] occurrences; 017 018 public SubstituteProcessor(Command command, SedArguments args, LineProcessor output) { 019 super(command, args, output); 020 this.replacement = getReplacement(args); 021 this.occurrences = args.isOccurrenceSet() ? args.getOccurrence() : EMPTY_OCCURRENCE; 022 for (int i = 0; i < occurrences.length; i++) { 023 if (occurrences[i] <= 0) { 024 throw new IllegalArgumentException("invalid occurrence index " + occurrences[i] + " in sed " + command + " command"); 025 } 026 } 027 Arrays.sort(occurrences); 028 } 029 public SubstituteProcessor(Command command, String script, SedArguments args, LineProcessor output) { 030 this(command, deriveArgs(command, script, args), output); 031 } 032 033 private static SedArguments deriveArgs(Command command, String script, SedArguments args) { 034 final int start = StringUtil.findStartTrimWhitespace(script) + 1; 035 final int mid = indexOfNextDelimiter(script, start); 036 final int end = indexOfNextDelimiter(script, mid); 037 if (mid < 0 || end < 0) { 038 throw new IllegalArgumentException("invalid script for sed " + command + " command: " + script); 039 } 040 args = parseSubstituteFlags(command, args, script, end + 1); 041 args.setRegexp(script.substring(start + 1, mid)); 042 args.setReplacement(script.substring(mid + 1, end)); 043 return args; 044 } 045 046 private static SedArguments parseSubstituteFlags(Command command, SedArguments args, String script, int start) { 047 final int end = StringUtil.findWhitespace(script, start); 048 if (end < StringUtil.findEndTrimWhitespace(script)) { 049 throw new IllegalArgumentException("extra non-whitespace characters found after substitute command in sed script: " + script); 050 } 051 if (start < end) { 052 final SedOptions.Default options = new SedOptions.Default(args.getOptions()); 053 //g, p, I flags 054 int index; 055 for (index = end - 1; index >= start; index--) { 056 final char flag = script.charAt(index); 057 if (flag == 'g') { 058 options.set(SedOption.global); 059 } else if (flag == 'p') { 060 options.set(SedOption.print); 061 } else if (flag == 'I') { 062 options.set(SedOption.ignoreCase); 063 } else { 064 break; 065 } 066 } 067 args = new SedArguments(options); 068 //occurrence index 069 if (index >= start) { 070 final String occurrenceStr = script.substring(start, index + 1); 071 final int occurrence; 072 try { 073 occurrence = Integer.parseInt(occurrenceStr); 074 } catch (NumberFormatException e) { 075 throw new IllegalArgumentException("invalid substitute flags in sed script: " + script, e); 076 } 077 if (occurrence <= 0) { 078 throw new IllegalArgumentException("invalid occurrence index " + occurrence + " in sed script: " + script); 079 } 080 args.setOccurrence(occurrence); 081 } 082 } 083 return args; 084 } 085 @Override 086 public boolean processLine(Line line) { 087 final Matcher matcher = regexp.matcher(line.getContent()); 088 if (matcher.find()) { 089 boolean matches = true; 090 final StringBuffer changed = new StringBuffer();//cannot use StringBuilder here since matcher does not support it 091 if (occurrences.length > 0) { 092 int current = 1; 093 for (int i = 0; i < occurrences.length; i++) { 094 final int occurrence = occurrences[i]; 095 while (matches && current < occurrence) { 096 matches = matcher.find(); 097 current++;//one too much after unsuccessful find, but does not matter anymore 098 } 099 if (matches) { 100 matcher.appendReplacement(changed, replacement); 101 } else { 102 break; 103 } 104 } 105 if (matches && occurrences.length == 1 && args.isGlobal()) { 106 //replace all subsequent occurrences in this case 107 matches = matcher.find(); 108 while (matches) { 109 matcher.appendReplacement(changed, replacement); 110 matches = matcher.find(); 111 } 112 } 113 } else { 114 while (matches) { 115 matcher.appendReplacement(changed, replacement); 116 matches = args.isGlobal() && matcher.find(); 117 } 118 } 119 matcher.appendTail(changed); 120 return output.processLine(new SimpleLine(changed, line.getLineEnding())); 121 } else { 122 if (args.isQuiet()) { 123 return true; 124 } 125 return output.processLine(line); 126 } 127 } 128}