1// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
2
3package org.xbill.DNS;
4
5import java.io.*;
6import java.util.*;
7
8/**
9 * A representation of a $GENERATE statement in a master file.
10 *
11 * @author Brian Wellington
12 */
13
14public class Generator {
15
16/** The start of the range. */
17public long start;
18
19/** The end of the range. */
20public long end;
21
22/** The step value of the range. */
23public long step;
24
25/** The pattern to use for generating record names. */
26public final String namePattern;
27
28/** The type of the generated records. */
29public final int type;
30
31/** The class of the generated records. */
32public final int dclass;
33
34/** The ttl of the generated records. */
35public final long ttl;
36
37/** The pattern to use for generating record data. */
38public final String rdataPattern;
39
40/** The origin to append to relative names. */
41public final Name origin;
42
43private long current;
44
45/**
46 * Indicates whether generation is supported for this type.
47 * @throws InvalidTypeException The type is out of range.
48 */
49public static boolean
50supportedType(int type) {
51	Type.check(type);
52	return (type == Type.PTR || type == Type.CNAME || type == Type.DNAME ||
53		type == Type.A || type == Type.AAAA || type == Type.NS);
54}
55
56/**
57 * Creates a specification for generating records, as a $GENERATE
58 * statement in a master file.
59 * @param start The start of the range.
60 * @param end The end of the range.
61 * @param step The step value of the range.
62 * @param namePattern The pattern to use for generating record names.
63 * @param type The type of the generated records.  The supported types are
64 * PTR, CNAME, DNAME, A, AAAA, and NS.
65 * @param dclass The class of the generated records.
66 * @param ttl The ttl of the generated records.
67 * @param rdataPattern The pattern to use for generating record data.
68 * @param origin The origin to append to relative names.
69 * @throws IllegalArgumentException The range is invalid.
70 * @throws IllegalArgumentException The type does not support generation.
71 * @throws IllegalArgumentException The dclass is not a valid class.
72 */
73public
74Generator(long start, long end, long step, String namePattern,
75	  int type, int dclass, long ttl, String rdataPattern, Name origin)
76{
77	if (start < 0 || end < 0 || start > end || step <= 0)
78		throw new IllegalArgumentException
79				("invalid range specification");
80	if (!supportedType(type))
81		throw new IllegalArgumentException("unsupported type");
82	DClass.check(dclass);
83
84	this.start = start;
85	this.end = end;
86	this.step = step;
87	this.namePattern = namePattern;
88	this.type = type;
89	this.dclass = dclass;
90	this.ttl = ttl;
91	this.rdataPattern = rdataPattern;
92	this.origin = origin;
93	this.current = start;
94}
95
96private String
97substitute(String spec, long n) throws IOException {
98	boolean escaped = false;
99	byte [] str = spec.getBytes();
100	StringBuffer sb = new StringBuffer();
101
102	for (int i = 0; i < str.length; i++) {
103		char c = (char)(str[i] & 0xFF);
104		if (escaped) {
105			sb.append(c);
106			escaped = false;
107		} else if (c == '\\') {
108			if (i + 1 == str.length)
109				throw new TextParseException
110						("invalid escape character");
111			escaped = true;
112		} else if (c == '$') {
113			boolean negative = false;
114			long offset = 0;
115			long width = 0;
116			long base = 10;
117			boolean wantUpperCase = false;
118			if (i + 1 < str.length && str[i + 1] == '$') {
119				// '$$' == literal '$' for backwards
120				// compatibility with old versions of BIND.
121				c = (char)(str[++i] & 0xFF);
122				sb.append(c);
123				continue;
124			} else if (i + 1 < str.length && str[i + 1] == '{') {
125				// It's a substitution with modifiers.
126				i++;
127				if (i + 1 < str.length && str[i + 1] == '-') {
128					negative = true;
129					i++;
130				}
131				while (i + 1 < str.length) {
132					c = (char)(str[++i] & 0xFF);
133					if (c == ',' || c == '}')
134						break;
135					if (c < '0' || c > '9')
136						throw new TextParseException(
137							"invalid offset");
138					c -= '0';
139					offset *= 10;
140					offset += c;
141				}
142				if (negative)
143					offset = -offset;
144
145				if (c == ',') {
146					while (i + 1 < str.length) {
147						c = (char)(str[++i] & 0xFF);
148						if (c == ',' || c == '}')
149							break;
150						if (c < '0' || c > '9')
151							throw new
152							   TextParseException(
153							   "invalid width");
154						c -= '0';
155						width *= 10;
156						width += c;
157					}
158				}
159
160				if (c == ',') {
161					if  (i + 1 == str.length)
162						throw new TextParseException(
163							   "invalid base");
164					c = (char)(str[++i] & 0xFF);
165					if (c == 'o')
166						base = 8;
167					else if (c == 'x')
168						base = 16;
169					else if (c == 'X') {
170						base = 16;
171						wantUpperCase = true;
172					}
173					else if (c != 'd')
174						throw new TextParseException(
175							   "invalid base");
176				}
177
178				if (i + 1 == str.length || str[i + 1] != '}')
179					throw new TextParseException
180						("invalid modifiers");
181				i++;
182			}
183			long v = n + offset;
184			if (v < 0)
185				throw new TextParseException
186						("invalid offset expansion");
187			String number;
188			if (base == 8)
189				number = Long.toOctalString(v);
190			else if (base == 16)
191				number = Long.toHexString(v);
192			else
193				number = Long.toString(v);
194			if (wantUpperCase)
195				number = number.toUpperCase();
196			if (width != 0 && width > number.length()) {
197				int zeros = (int)width - number.length();
198				while (zeros-- > 0)
199					sb.append('0');
200			}
201			sb.append(number);
202		} else {
203			sb.append(c);
204		}
205	}
206	return sb.toString();
207}
208
209/**
210 * Constructs and returns the next record in the expansion.
211 * @throws IOException The name or rdata was invalid after substitutions were
212 * performed.
213 */
214public Record
215nextRecord() throws IOException {
216	if (current > end)
217		return null;
218	String namestr = substitute(namePattern, current);
219	Name name = Name.fromString(namestr, origin);
220	String rdata = substitute(rdataPattern, current);
221	current += step;
222	return Record.fromString(name, type, dclass, ttl, rdata, origin);
223}
224
225/**
226 * Constructs and returns all records in the expansion.
227 * @throws IOException The name or rdata of a record was invalid after
228 * substitutions were performed.
229 */
230public Record []
231expand() throws IOException {
232	List list = new ArrayList();
233	for (long i = start; i < end; i += step) {
234		String namestr = substitute(namePattern, current);
235		Name name = Name.fromString(namestr, origin);
236		String rdata = substitute(rdataPattern, current);
237		list.add(Record.fromString(name, type, dclass, ttl,
238					   rdata, origin));
239	}
240	return (Record []) list.toArray(new Record[list.size()]);
241}
242
243/**
244 * Converts the generate specification to a string containing the corresponding
245 * $GENERATE statement.
246 */
247public String
248toString() {
249	StringBuffer sb = new StringBuffer();
250	sb.append("$GENERATE ");
251	sb.append(start + "-" + end);
252	if (step > 1)
253		sb.append("/" + step);
254	sb.append(" ");
255	sb.append(namePattern + " ");
256	sb.append(ttl + " ");
257	if (dclass != DClass.IN || !Options.check("noPrintIN"))
258		sb.append(DClass.string(dclass) + " ");
259	sb.append(Type.string(type) + " ");
260	sb.append(rdataPattern + " ");
261	return sb.toString();
262}
263
264}
265