001/* 002 * This file is part of the JDrupes non-blocking HTTP Codec 003 * Copyright (C) 2016, 2017 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as published 007 * by the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 013 * License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.httpcodec.util; 020 021import java.text.ParseException; 022import java.util.Iterator; 023import java.util.NoSuchElementException; 024 025/** 026 * Splits a list of items. Delimiters are escaped if they are in 027 * double quotes. 028 */ 029public class ListItemizer implements Iterator<String> { 030 031 private String unparsedValue; 032 private int position = 0; 033 private String delimiters; 034 private String pendingItem = null; 035 private ParseException pendingException = null; 036 037 /** 038 * Generates a new itemizer. 039 * 040 * @param list the list to be itemized 041 * @param delimiters the set of delimiter characters 042 */ 043 public ListItemizer(String list, String delimiters) { 044 unparsedValue = list; 045 this.delimiters = delimiters; 046 while (true) { // Skip optional white space 047 char ch = unparsedValue.charAt(position); 048 if (ch != ' ' && ch != '\t') { 049 break; 050 } 051 position += 1; 052 } 053 try { 054 pendingItem = nextItem(); 055 } catch (ParseException e) { 056 pendingException = e; 057 } 058 } 059 060 /** 061 * Returns the next item from the unparsed value. 062 * 063 * @return the next item or {@code null} if no items remain 064 * @throws ParseException if the input violates the field format 065 */ 066 private String nextItem() throws ParseException { 067 // RFC 7230 3.2.6 068 boolean inDquote = false; 069 int startPosition = position; 070 try { 071 while (true) { 072 if (inDquote) { 073 char ch = unparsedValue.charAt(position); 074 switch (ch) { 075 case '\\': 076 position += 2; 077 continue; 078 case '\"': 079 inDquote = false; 080 // fall through 081 default: 082 position += 1; 083 continue; 084 } 085 } 086 if (position == unparsedValue.length()) { 087 if (position == startPosition) { 088 return null; 089 } 090 return unparsedValue.substring(startPosition, position); 091 } 092 char ch = unparsedValue.charAt(position); 093 if (delimiters.indexOf(ch) >= 0) { 094 String result = unparsedValue 095 .substring(startPosition, position).trim(); 096 position += 1; // Skip delimiter 097 while (true) { // Skip optional white space 098 ch = unparsedValue.charAt(position); 099 if (ch != ' ' && ch != '\t') { 100 break; 101 } 102 position += 1; 103 } 104 return result; 105 } 106 switch (ch) { 107 case '\"': 108 inDquote = true; 109 // fall through 110 default: 111 position += 1; 112 continue; 113 } 114 } 115 } catch (IndexOutOfBoundsException e) { 116 throw new ParseException(unparsedValue, position); 117 } 118 } 119 120 /* (non-Javadoc) 121 * @see java.util.Iterator#hasNext() 122 */ 123 @Override 124 public boolean hasNext() { 125 return pendingItem != null; 126 } 127 128 /* (non-Javadoc) 129 * @see java.util.Iterator#next() 130 */ 131 @Override 132 public String next() { 133 if (pendingException != null) { 134 NoSuchElementException exc = new NoSuchElementException(); 135 exc.initCause(pendingException); 136 throw exc; 137 } 138 if (pendingItem == null) { 139 throw new NoSuchElementException("No elements left."); 140 } 141 String result = pendingItem; 142 pendingItem = null; 143 try { 144 pendingItem = nextItem(); 145 } catch (ParseException e) { 146 pendingException = e; 147 NoSuchElementException exc = new NoSuchElementException(); 148 exc.initCause(pendingException); 149 throw exc; 150 } 151 return result; 152 } 153 154}