001: // SampleLabel.java
002: // $Id: SampleLabel.java,v 1.7 2000/08/16 21:37:43 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.jigsaw.pics;
007:
008: import java.io.BufferedInputStream;
009: import java.io.File;
010: import java.io.FileInputStream;
011: import java.io.IOException;
012: import java.io.InputStream;
013: import java.io.LineNumberInputStream;
014: import java.io.PrintStream;
015: import java.io.StringBufferInputStream;
016:
017: // I like writing these silly kind of parsers !
018: // (its probably the X)
019:
020: class LabelParser {
021: File file = null;
022: LineNumberInputStream in = null;
023: int ch = -1;
024: byte buffer[] = null;
025: int bufptr = 0;
026:
027: /**
028: * Append the given char in the internal buffer
029: */
030:
031: public void append(int ch) {
032: if (bufptr + 1 >= buffer.length) {
033: // resize buffer
034: byte nbuf[] = new byte[buffer.length * 2];
035: System.arraycopy(buffer, 0, nbuf, 0, bufptr);
036: buffer = nbuf;
037: }
038: buffer[bufptr++] = (byte) ch;
039: }
040:
041: /**
042: * Get the token from our internale buffer.
043: */
044:
045: public String getToken(boolean clear) {
046: String tok = new String(buffer, 0, 0, bufptr);
047: if (clear)
048: bufptr = 0;
049: return tok;
050: }
051:
052: /**
053: * Parser expects given character.
054: */
055:
056: void expect(int c) throws InvalidLabelException {
057: if (ch == c)
058: return;
059: String msg = ("expected "
060: + (new Character((char) c)).toString() + "[" + c + "]"
061: + " got " + (new Character((char) ch)).toString() + "["
062: + ch + "]");
063: if (file == null)
064: throw new InvalidLabelException(in.getLineNumber(), msg);
065: else
066: throw new InvalidLabelFileException(file, in
067: .getLineNumber(), msg);
068: }
069:
070: String parseVariableName() throws IOException {
071: while ((ch != '=') && (ch != '\n') && (ch != -1)) {
072: append(Character.toLowerCase((char) ch));
073: ch = in.read();
074: }
075: return getToken(true);
076: }
077:
078: String parseVariableValue() throws IOException {
079: while ((ch != -1) && (ch != '\n')) {
080: append(ch);
081: ch = in.read();
082: }
083: return getToken(true);
084: }
085:
086: SampleLabel parse(SampleLabel into) throws InvalidLabelException,
087: InvalidLabelFileException {
088: try {
089: while (true) {
090: switch (ch) {
091: case -1:
092: // we are done.
093: return into;
094: case ' ':
095: case '\t':
096: case '\n':
097: ch = in.read();
098: continue;
099: case '#':
100: while (((ch = in.read()) != '\n') && (ch != -1))
101: ;
102: continue;
103: default:
104: String name = parseVariableName();
105: expect('=');
106: ch = in.read();
107: String value = parseVariableValue();
108: if (ch != -1) { // Pb -1 instead of \n
109: expect('\n');
110: ch = in.read();
111: }
112: into.addBinding(name, value);
113: continue;
114: }
115: }
116: } catch (IOException e) {
117: if (file == null)
118: throw new InvalidLabelException("IO exception.");
119: else
120: throw new InvalidLabelFileException(file
121: .getAbsolutePath()
122: + ": IO exception.");
123: }
124: }
125:
126: LabelParser(File file) throws InvalidLabelFileException {
127: this .file = file;
128: try {
129: this .in = (new LineNumberInputStream(
130: new BufferedInputStream(new FileInputStream(file))));
131: this .buffer = new byte[32];
132: this .ch = in.read();
133: } catch (IOException e) {
134: throw new InvalidLabelFileException(file.getAbsolutePath()
135: + ": IO exception.");
136: }
137: }
138:
139: LabelParser(String string) throws InvalidLabelException {
140: try {
141: this .in = (new LineNumberInputStream(
142: new BufferedInputStream(
143: new StringBufferInputStream(string))));
144:
145: this .buffer = new byte[32];
146: this .ch = in.read();
147: } catch (IOException e) {
148: throw new InvalidLabelException("IO exception.");
149: }
150: }
151:
152: }
153:
154: /**
155: * Label internal representation.
156: * The server has to know something about labels. In this implementation, I try
157: * to reduce this knowledge as fas as I could. Here, a label is a set of
158: * assignements to variables (options in PICS terms), and a rating.
159: * The syntax for writing label files is the following:
160: * <variable>=<value>\n
161: * <p>Comments are allowed through the '#' at beginning of line.
162: * With the special variable 'ratings' being mandatory.
163: */
164:
165: public class SampleLabel implements LabelInterface {
166: // Default variables array size
167: private static final int VARSIZE = 8;
168:
169: String vars[] = null;
170: String vals[] = null;
171:
172: int varptr = 0;
173:
174: void addBinding(String var, String val) {
175: if (varptr + 1 >= vars.length) {
176: // resize arrays
177: String nvars[] = new String[vars.length * 2];
178: String nvals[] = new String[vals.length * 2];
179: System.arraycopy(vars, 0, nvars, 0, vars.length);
180: System.arraycopy(vals, 0, nvals, 0, vals.length);
181: vars = nvars;
182: vals = nvals;
183: }
184: vars[varptr] = var;
185: vals[varptr] = val;
186: varptr++;
187: }
188:
189: /**
190: * Debugging: print the given options of this label.
191: * @param out The PrintStream to print to.
192: */
193:
194: public void print(PrintStream out) {
195: for (int i = 0; i < varptr; i++) {
196: System.out.println("\t" + vars[i] + " = " + vals[i]);
197: }
198: }
199:
200: /**
201: * Does this label defines the given option ?
202: * @param option The option to look for.
203: * @return <strong>true</strong> if the label defines the given option.
204: */
205:
206: public boolean hasOption(String option) {
207: for (int i = 0; i < varptr; i++) {
208: if (vars[i].equals(option))
209: return true;
210: }
211: return false;
212: }
213:
214: /**
215: * Get an option index.
216: * This allows for fast acces to label options. There is no guarantee that
217: * the same option will get the same index across labels.
218: * @param option The option to look for.
219: * @return An integer, which is the option index if the option was found
220: * or <strong>-1</strong> if the option isn't defined for this label.
221: */
222:
223: public int getOptionIndex(String option) {
224: for (int i = 0; i < varptr; i++) {
225: if (vars[i].equals(option))
226: return i;
227: }
228: return -1;
229: }
230:
231: /**
232: * Get an option value, by index.
233: * This, along with the <strong>getOptionIndex</strong> method provides
234: * a fast access to option values.
235: * @param idx The index of the option, as gotten from
236: * <strong>getOptionIndex</strong>.
237: * @return A String instance, providing the option value.
238: */
239:
240: public String getOptionValue(int idx) {
241: if ((idx < 0) || (idx >= varptr))
242: throw new RuntimeException(this .getClass().getName()
243: + "[getOptionValue]: " + " invalid index.");
244: return vals[idx];
245: }
246:
247: /**
248: * Get an option value, by option name.
249: * @param option The option that you want the value of.
250: * @return A String instance giving the option value, or
251: * <strong>null</strong>, if the option isn't defined for this label.
252: */
253:
254: public String getOptionValue(String option) {
255: for (int i = 0; i < varptr; i++) {
256: if (vars[i].equals(option))
257: return vals[i];
258: }
259: return null;
260: }
261:
262: /**
263: * Is this label generic ?
264: * @return <strong>true</strong> if the label if generic.
265: */
266:
267: public boolean isGeneric() {
268: int idx = getOptionIndex("generic");
269: if (idx >= 0)
270: return (new Boolean(getOptionValue(idx))).booleanValue();
271: return false;
272: }
273:
274: /**
275: * Emit the given option to the output.
276: * If the option is not defined for this label, skip it (don't emit). If
277: * possible, and according to the format, emit the short option name.
278: * @param space Should we emit a leading space.
279: * @param option The option name.
280: * @return <strong>true</strong> if the option was successfully emited,
281: * <strong>false</strong> otherwise.
282: */
283:
284: private boolean emit(boolean space, StringBuffer into,
285: String option, int format) {
286: String value = getOptionValue(option);
287: if (value != null) {
288: switch (format) {
289: case LabelBureauInterface.FMT_MINIMAL:
290: case LabelBureauInterface.FMT_SHORT:
291: // emit short option name
292: if (space)
293: into.append(" ");
294: into.append(option + " " + value);
295: break;
296: default:
297: // emit full option name
298: if (space)
299: into.append(" ");
300: into.append(option + " " + value);
301: break;
302: }
303: return true;
304: }
305: return false;
306: }
307:
308: /**
309: * Emit the given option to the output.
310: * If possible, and according to the format, emit the short option name.
311: * @param space Should we emit a leading space.
312: * @param into The StringBuffer to dump this label to.
313: * @param option The option name.
314: */
315:
316: private void emit(boolean space, StringBuffer into, int idx,
317: int format) {
318: switch (format) {
319: case LabelBureauInterface.FMT_MINIMAL:
320: case LabelBureauInterface.FMT_SHORT:
321: // emit short option name
322: if (space)
323: into.append(" ");
324: into.append(vars[idx] + " " + vals[idx]);
325: break;
326: default:
327: if (space)
328: into.append(" ");
329: into.append(vars[idx] + " " + vals[idx]);
330: }
331: }
332:
333: /**
334: * Dump this label according to the given format.
335: * The dump should take place <em>after</em> the service has dump itself.
336: * @param into A StringBuffer to dump the labels to.
337: * @param format The format in which the dump should be done. This can
338: * be one of the four format described in the PICS specification.
339: */
340:
341: public void dump(StringBuffer into, int format) {
342: boolean space = false;
343: ;
344: switch (format) {
345: case LabelBureauInterface.FMT_MINIMAL:
346: // emit just the ratings, except if generic
347: if (isGeneric()) {
348: // emit the generic and for options (FIXME errors ?)
349: emit(space, into, "generic",
350: LabelBureauInterface.FMT_MINIMAL);
351: space = true;
352: emit(space, into, "for",
353: LabelBureauInterface.FMT_MINIMAL);
354: emit(space, into, "ratings",
355: LabelBureauInterface.FMT_MINIMAL);
356: } else {
357: emit(space, into, "ratings",
358: LabelBureauInterface.FMT_MINIMAL);
359: }
360: break;
361: case LabelBureauInterface.FMT_SHORT:
362: // emit ratings, and full, which I deem appropriate
363: if (isGeneric()) {
364: // emit the generic and for options:
365: emit(space, into, "generic",
366: LabelBureauInterface.FMT_SHORT);
367: space = true;
368: emit(space, into, "for", LabelBureauInterface.FMT_SHORT);
369: emit(space, into, "full",
370: LabelBureauInterface.FMT_SHORT);
371: emit(space, into, "ratings",
372: LabelBureauInterface.FMT_SHORT);
373: } else {
374: emit(space, into, "full",
375: LabelBureauInterface.FMT_SHORT);
376: space = true;
377: emit(space, into, "ratings",
378: LabelBureauInterface.FMT_SHORT);
379: }
380: break;
381: case LabelBureauInterface.FMT_FULL:
382: // Emit all options, rating at the end
383: for (int i = 0; i < varptr; i++) {
384: if (vars[i].equals("ratings"))
385: continue;
386: emit(space, into, i, LabelBureauInterface.FMT_FULL);
387: space = true;
388: }
389: emit(space, into, "ratings", LabelBureauInterface.FMT_FULL);
390: break;
391: case LabelBureauInterface.FMT_SIGNED:
392: throw new RuntimeException(this .getClass().getName()
393: + "[dump]: " + "SIGNED format unsupported.");
394:
395: // not reached
396: default:
397: throw new RuntimeException(this .getClass().getName()
398: + "[dump]: " + " invalid format " + "\"" + format
399: + "\"");
400: }
401: }
402:
403: /**
404: * Create a new label from the given stream.
405: * @param file The file inwhich the label options are described.
406: */
407:
408: SampleLabel(File file) throws InvalidLabelException {
409: try {
410: this .vars = new String[VARSIZE];
411: this .vals = new String[VARSIZE];
412: this .varptr = 0;
413: LabelParser p = new LabelParser(file);
414: p.parse(this );
415: } catch (InvalidLabelException e) {
416: throw new InvalidLabelFileException(e.getMessage());
417: }
418: }
419:
420: SampleLabel(String string) throws InvalidLabelException {
421: this .vars = new String[VARSIZE];
422: this .vals = new String[VARSIZE];
423: this .varptr = 0;
424: LabelParser p = new LabelParser(string);
425: p.parse(this );
426: }
427:
428: /**
429: * Create a new label from a set of options.
430: * This constructor takes two arrays, one of option names, and one
431: * of option values. It builds out of them the internal
432: * representation for this label.
433: * <p>The given arrays are used as is (no copy), and might side-effected
434: * by the new instance.
435: * @param optnames Names of option for this label.
436: * @param optvalues Values of option for this label.
437: */
438:
439: public SampleLabel(String optnames[], String optvals[]) {
440: if (optnames.length != optvals.length)
441: throw new RuntimeException(this .getClass().getName()
442: + " invalid constructor params:" + " bad length.");
443: this .vars = optnames;
444: this .vals = optvals;
445: this .varptr = optnames.length;
446: }
447:
448: // Testing only
449:
450: public static void main(String args[]) {
451: if (args.length != 1) {
452: System.out.println("Label <file>");
453: System.exit(1);
454: }
455: try {
456: SampleLabel label = new SampleLabel(new File(args[0]));
457: StringBuffer sb = new StringBuffer();
458: label.dump(sb, LabelBureauInterface.FMT_MINIMAL);
459: System.out.println(sb.toString());
460: } catch (InvalidLabelException e) {
461: System.out.println(e.getMessage());
462: System.exit(1);
463: }
464: System.exit(0);
465: }
466:
467: }
|