001: /*
002: * $RCSfile: ConfigSexpression.java,v $
003: *
004: * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * - Redistribution of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * - Redistribution in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * Neither the name of Sun Microsystems, Inc. or the names of
019: * contributors may be used to endorse or promote products derived
020: * from this software without specific prior written permission.
021: *
022: * This software is provided "AS IS," without a warranty of any
023: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
024: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
025: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
026: * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
027: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
028: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
029: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
030: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
031: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
032: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
033: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
034: * POSSIBILITY OF SUCH DAMAGES.
035: *
036: * You acknowledge that this software is not designed, licensed or
037: * intended for use in the design, construction, operation or
038: * maintenance of any nuclear facility.
039: *
040: * $Revision: 1.5 $
041: * $Date: 2007/02/09 17:20:44 $
042: * $State: Exp $
043: */
044:
045: package com.sun.j3d.utils.universe;
046:
047: import java.awt.event.*;
048: import java.io.*;
049: import java.lang.Integer;
050: import java.lang.Boolean;
051: import java.util.*;
052: import javax.vecmath.*;
053: import javax.media.j3d.*;
054:
055: class ConfigSexpression {
056:
057: private ArrayList elements = new ArrayList();
058:
059: private void syntaxError(StreamTokenizer st, String file, String s) {
060: System.out.println(s + ":\nat line " + st.lineno() + " in "
061: + file);
062: print();
063: System.out.print("\n\n");
064: }
065:
066: private int myNextToken(StreamTokenizer st, String file) {
067: int tok = 0;
068: try {
069: tok = st.nextToken();
070: } catch (IOException e) {
071: throw new RuntimeException(e + "\nwhile reading " + file);
072: }
073: return tok;
074: }
075:
076: Object parseAndEval(ConfigContainer configContainer,
077: StreamTokenizer st, int level) {
078: int tok;
079: String s;
080: String file = configContainer.currentFileName;
081:
082: //
083: // First tokenize the character stream and add the tokens to the
084: // elements array.
085: //
086: elements.clear();
087:
088: // Look for an open paren to start this sexp.
089: while (true) {
090: tok = myNextToken(st, file);
091:
092: if (tok == StreamTokenizer.TT_EOF)
093: return Boolean.FALSE;
094:
095: if (tok == ')')
096: syntaxError(st, file, "Premature closing parenthesis");
097:
098: if (tok == '(')
099: break;
100: }
101:
102: // Add elements until a close paren for this sexp is found.
103: for (int i = 0; true; i++) {
104: tok = myNextToken(st, file);
105:
106: if (tok == StreamTokenizer.TT_EOF) {
107: syntaxError(st, file, "Missing closing parenthesis");
108: break;
109: }
110:
111: // An open paren starts a new embedded sexp. Put the paren back,
112: // evaluate the sexp, and add the result to the elements list.
113: if (tok == '(') {
114: st.pushBack();
115: ConfigSexpression cs = new ConfigSexpression();
116: elements.add(cs.parseAndEval(configContainer, st,
117: level + 1));
118: continue;
119: }
120:
121: // A close paren finishes the scan.
122: if (tok == ')')
123: break;
124:
125: // Check for too many arguments.
126: if (i >= 20)
127: syntaxError(st, file, "Too many arguments");
128:
129: // Check for numeric argument.
130: if (tok == StreamTokenizer.TT_NUMBER) {
131: elements.add(new Double(st.nval));
132: continue;
133: }
134:
135: // Anything other than a word or a quoted string is an error.
136: if (tok != StreamTokenizer.TT_WORD && tok != '"'
137: && tok != '\'') {
138: String badToken = String.valueOf((char) tok);
139: elements.add(badToken); // so bad token prints out
140: syntaxError(st, file, "Invalid token \"" + badToken
141: + "\" must be enclosed in quotes");
142: continue;
143: }
144:
145: // Scan the token for Java property substitution syntax ${...}.
146: s = scanJavaProperties(st, file, st.sval);
147: if (s == null)
148: continue;
149:
150: if (s.equalsIgnoreCase("true"))
151: // Replace "true" or "True" with the Boolean equivalent.
152: elements.add(new Boolean(true));
153:
154: else if (s.equalsIgnoreCase("false"))
155: // Replace "false" or "False" with the Boolean equivalent.
156: elements.add(new Boolean(false));
157:
158: else
159: // Add the token as a string element.
160: elements.add(s);
161: }
162:
163: //
164: // Now evaluate elements.
165: //
166: if (elements.size() == 0)
167: syntaxError(st, file, "Null command");
168:
169: // If the first argument is a string, then this sexp must be
170: // a top-level command or a built-in, and needs to be evaluated.
171: if (elements.get(0) instanceof String) {
172: try {
173: if (level == 0) {
174: configContainer.evaluateCommand(elements, st
175: .lineno());
176:
177: // Continue parsing top-level commands.
178: return Boolean.TRUE;
179: } else {
180: // Evaluate built-in and return result to next level up.
181: return evaluateBuiltIn(configContainer, elements,
182: st.lineno());
183: }
184: } catch (IllegalArgumentException e) {
185: syntaxError(st, file, e.getMessage());
186: if (level == 0)
187: // Command ignored: continue parsing.
188: return Boolean.TRUE;
189: else
190: // Function ignored: return sexp to next level up so error
191: // processing can print it out in context of command.
192: return this ;
193: }
194: }
195:
196: // If the first argument isn't a string, and we are at level 0,
197: // this is a syntax error.
198: if (level == 0)
199: syntaxError(st, file, "Malformed top-level command name");
200:
201: // If the first argument is a number, then we must have
202: // either a 2D, 3D, or 4D numeric vector.
203: if (elements.get(0) instanceof Double) {
204: if (elements.size() == 1)
205: syntaxError(st, file,
206: "Can't have single-element vector");
207:
208: // Point2D
209: if (elements.size() == 2) {
210: if (!(elements.get(1) instanceof Double))
211: syntaxError(st, file,
212: "Both elements must be numbers");
213:
214: return new Point2d(((Double) elements.get(0))
215: .doubleValue(), ((Double) elements.get(1))
216: .doubleValue());
217: }
218:
219: // Point3d
220: if (elements.size() == 3) {
221: if (!(elements.get(1) instanceof Double)
222: || !(elements.get(2) instanceof Double))
223: syntaxError(st, file,
224: "All elements must be numbers");
225:
226: return new Point3d(((Double) elements.get(0))
227: .doubleValue(), ((Double) elements.get(1))
228: .doubleValue(), ((Double) elements.get(2))
229: .doubleValue());
230: }
231:
232: // Point4D
233: if (elements.size() == 4) {
234: if (!(elements.get(1) instanceof Double)
235: || !(elements.get(2) instanceof Double)
236: || !(elements.get(3) instanceof Double))
237: syntaxError(st, file,
238: "All elements must be numbers");
239:
240: return new Point4d(((Double) elements.get(0))
241: .doubleValue(), ((Double) elements.get(1))
242: .doubleValue(), ((Double) elements.get(2))
243: .doubleValue(), ((Double) elements.get(3))
244: .doubleValue());
245: }
246:
247: // Anything else is an error.
248: syntaxError(st, file, "Too many vector elements");
249: }
250:
251: // If the first argument is a Point3d, then we should be a Matrix3d.
252: if (elements.get(0) instanceof Point3d) {
253: if (elements.size() != 3)
254: syntaxError(st, file, "Matrix must have three rows");
255:
256: if (!(elements.get(1) instanceof Point3d)
257: || !(elements.get(2) instanceof Point3d))
258: syntaxError(st, file,
259: "All rows must have three elements");
260:
261: return new Matrix3d(((Point3d) elements.get(0)).x,
262: ((Point3d) elements.get(0)).y, ((Point3d) elements
263: .get(0)).z, ((Point3d) elements.get(1)).x,
264: ((Point3d) elements.get(1)).y, ((Point3d) elements
265: .get(1)).z, ((Point3d) elements.get(2)).x,
266: ((Point3d) elements.get(2)).y, ((Point3d) elements
267: .get(2)).z);
268: }
269:
270: // If the first argument is a Point4d, then we should be a Matrix4d.
271: if (elements.get(0) instanceof Point4d) {
272: if (elements.size() == 3) {
273: if (!(elements.get(1) instanceof Point4d)
274: || !(elements.get(2) instanceof Point4d))
275: syntaxError(st, file,
276: "All rows must have four elements");
277:
278: return new Matrix4d(((Point4d) elements.get(0)).x,
279: ((Point4d) elements.get(0)).y,
280: ((Point4d) elements.get(0)).z,
281: ((Point4d) elements.get(0)).w,
282: ((Point4d) elements.get(1)).x,
283: ((Point4d) elements.get(1)).y,
284: ((Point4d) elements.get(1)).z,
285: ((Point4d) elements.get(1)).w,
286: ((Point4d) elements.get(2)).x,
287: ((Point4d) elements.get(2)).y,
288: ((Point4d) elements.get(2)).z,
289: ((Point4d) elements.get(2)).w, 0.0, 0.0, 0.0,
290: 1.0);
291: } else if (elements.size() != 4)
292: syntaxError(st, file,
293: "Matrix must have three or four rows");
294:
295: if (!(elements.get(1) instanceof Point4d)
296: || !(elements.get(2) instanceof Point4d)
297: || !(elements.get(3) instanceof Point4d))
298: syntaxError(st, file,
299: "All rows must have four elements");
300:
301: return new Matrix4d(((Point4d) elements.get(0)).x,
302: ((Point4d) elements.get(0)).y, ((Point4d) elements
303: .get(0)).z, ((Point4d) elements.get(0)).w,
304: ((Point4d) elements.get(1)).x, ((Point4d) elements
305: .get(1)).y, ((Point4d) elements.get(1)).z,
306: ((Point4d) elements.get(1)).w, ((Point4d) elements
307: .get(2)).x, ((Point4d) elements.get(2)).y,
308: ((Point4d) elements.get(2)).z, ((Point4d) elements
309: .get(2)).w, ((Point4d) elements.get(3)).x,
310: ((Point4d) elements.get(3)).y, ((Point4d) elements
311: .get(3)).z, ((Point4d) elements.get(3)).w);
312: }
313:
314: // Anything else is an error.
315: syntaxError(st, file, "Syntax error");
316: return null;
317: }
318:
319: /**
320: * Scan for Java properties in the specified string. Nested properties are
321: * not supported.
322: *
323: * @param st stream tokenizer in use
324: * @param f current file name
325: * @param s string containing non-nested Java properties possibly
326: * interspersed with arbitrary text.
327: * @return scanned string with Java properties replaced with values
328: */
329: private String scanJavaProperties(StreamTokenizer st, String f,
330: String s) {
331: int open = s.indexOf("${");
332: if (open == -1)
333: return s;
334:
335: int close = 0;
336: StringBuffer buf = new StringBuffer();
337: while (open != -1) {
338: buf.append(s.substring(close, open));
339: close = s.indexOf('}', open);
340: if (close == -1) {
341: elements.add(s); // so that the bad element prints out
342: syntaxError(st, f,
343: "Java property substitution syntax error");
344: return null;
345: }
346:
347: String property = s.substring(open + 2, close);
348: String value = ConfigCommand.evaluateJavaProperty(property);
349: if (value == null) {
350: elements.add(s); // so that the bad element prints out
351: syntaxError(st, f, "Java property \"" + property
352: + "\" has a null value");
353: return null;
354: }
355:
356: buf.append(value);
357: open = s.indexOf("${", close);
358: close++;
359: }
360:
361: buf.append(s.substring(close));
362: return buf.toString();
363: }
364:
365: /**
366: * This method gets called from the s-expression parser to evaluate a
367: * built-in command.
368: *
369: * @param elements tokenized list of sexp elements
370: * @return object representing result of evaluation
371: */
372: private Object evaluateBuiltIn(ConfigContainer configContainer,
373: ArrayList elements, int lineNumber) {
374: int argc;
375: String functionName;
376:
377: argc = elements.size();
378: functionName = (String) elements.get(0);
379:
380: if (functionName.equals("Rotate")) {
381: return makeRotate(elements);
382: } else if (functionName.equals("Translate")) {
383: return makeTranslate(elements);
384: } else if (functionName.equals("RotateTranslate")
385: || functionName.equals("TranslateRotate")
386: || functionName.equals("Concatenate")) {
387:
388: return concatenate(elements);
389: } else if (functionName.equals("BoundingSphere")) {
390: return makeBoundingSphere(elements);
391: } else {
392: // This built-in can't be evaluated immediately or contains an
393: // unknown command. Create a ConfigCommand for later evaluation.
394: return new ConfigCommand(elements,
395: configContainer.currentFileName, lineNumber);
396: }
397: }
398:
399: /**
400: * Processes the built-in command (Translate x y z).
401: *
402: * @param elements ArrayList containing Doubles wrapping x, y, and z
403: * translation components at indices 1, 2, and 3 respectively
404: *
405: * @return matrix that translates by the given x, y, and z components
406: */
407: private Matrix4d makeTranslate(ArrayList elements) {
408: if (elements.size() != 4) {
409: throw new IllegalArgumentException(
410: "Incorrect number of arguments to Translate");
411: }
412:
413: if (!(elements.get(1) instanceof Double)
414: || !(elements.get(2) instanceof Double)
415: || !(elements.get(3) instanceof Double)) {
416: throw new IllegalArgumentException(
417: "All arguments to Translate must be numbers");
418: }
419:
420: Matrix4d m4d = new Matrix4d();
421: m4d.set(new Vector3d(((Double) elements.get(1)).doubleValue(),
422: ((Double) elements.get(2)).doubleValue(),
423: ((Double) elements.get(3)).doubleValue()));
424:
425: return m4d;
426: }
427:
428: /**
429: * Processes the (Rotate x y z) built-in command.
430: *
431: * @param elements ArrayList containing Doubles wrapping x, y, and z Euler
432: * angles at indices 1, 2, and 3 respectively
433: *
434: * @return matrix that rotates by the given Euler angles around static X,
435: * Y, and Z basis vectors: first about X, then Y, and then Z
436: *
437: * @see Transform3D#setEuler()
438: */
439: private Matrix4d makeRotate(ArrayList elements) {
440: if (elements.size() != 4) {
441: throw new IllegalArgumentException(
442: "Incorrect number of arguments to Rotate");
443: }
444:
445: if (!(elements.get(1) instanceof Double)
446: || !(elements.get(2) instanceof Double)
447: || !(elements.get(3) instanceof Double)) {
448: throw new IllegalArgumentException(
449: "All arguments to Rotate must be numbers");
450: }
451:
452: double x = Math.toRadians(((Double) elements.get(1))
453: .doubleValue());
454: double y = Math.toRadians(((Double) elements.get(2))
455: .doubleValue());
456: double z = Math.toRadians(((Double) elements.get(3))
457: .doubleValue());
458:
459: Transform3D t3d = new Transform3D();
460: t3d.setEuler(new Vector3d(x, y, z));
461:
462: Matrix4d m4d = new Matrix4d();
463: t3d.get(m4d);
464:
465: return m4d;
466: }
467:
468: /**
469: * Processes the (RotateTranslate m1 m2), (TranslateRotate m1 m2), and
470: * (Concatenate m1 m2) built-in commands. Although these do exactly the
471: * same thing, using the appropriate command is recommended in order to
472: * explicitly describe the sequence of transforms and their intent.
473: *
474: * @param elements ArrayList containing Matrix4d objects m1 and m2 at
475: * indices 1 and 2 respectively
476: *
477: * @return matrix that concatenates m1 and m2 in that order: if a point is
478: * transformed by the resulting matrix, then in effect the points are
479: * first transformed by m1 and then m2
480: */
481: private Matrix4d concatenate(ArrayList elements) {
482: String functionName = (String) elements.get(0);
483:
484: if (elements.size() != 3) {
485: throw new IllegalArgumentException(
486: "Incorrect number of arguments to " + functionName);
487: }
488:
489: if (!(elements.get(1) instanceof Matrix4d)
490: || !(elements.get(2) instanceof Matrix4d)) {
491: throw new IllegalArgumentException("Both arguments to "
492: + functionName + " must be Matrix4d");
493: }
494:
495: // Multiply the matrices in the order such that the result, when
496: // transforming a 3D point, will apply the transform represented by
497: // the 1st matrix and then apply the transform represented by the 2nd
498: // matrix.
499: Matrix4d m4d = new Matrix4d((Matrix4d) elements.get(2));
500: m4d.mul((Matrix4d) elements.get(1));
501:
502: return m4d;
503: }
504:
505: /**
506: * Processes the built-in command (BoundingSphere center radius).
507: * This is used when configuring behaviors.
508: *
509: * @param elements ArrayList containing Point3d at index 1 for the sphere
510: * center and Double at index 2 wrapping the sphere radius, or the String
511: * "infinite" at index 2.
512: *
513: * @return BoundingSphere with the given center and radius
514: */
515: private BoundingSphere makeBoundingSphere(ArrayList elements) {
516: if (elements.size() != 3) {
517: throw new IllegalArgumentException(
518: "Incorrect number of arguments to BoundingSphere");
519: }
520:
521: if (!(elements.get(1) instanceof Point3d)
522: || !(elements.get(2) instanceof Double || elements
523: .get(2) instanceof String))
524: throw new IllegalArgumentException(
525: "BoundingSphere needs a Point3d center "
526: + "followed by a Double radius or the String \"infinite\"");
527:
528: double r;
529: if (elements.get(2) instanceof Double)
530: r = ((Double) elements.get(2)).doubleValue();
531: else
532: r = Double.POSITIVE_INFINITY;
533:
534: return new BoundingSphere((Point3d) elements.get(1), r);
535: }
536:
537: void print() {
538: System.out.print("(");
539: int argc = elements.size();
540: for (int i = 0; i < argc; i++) {
541: if (elements.get(i) instanceof Matrix3d) {
542: String[] rows = ConfigCommand
543: .formatMatrixRows((Matrix3d) elements.get(i));
544: System.out.println("\n ((" + rows[0] + ")");
545: System.out.println(" (" + rows[1] + ")");
546: System.out.print(" (" + rows[2] + "))");
547: if (i != (argc - 1))
548: System.out.println();
549: } else if (elements.get(i) instanceof Matrix4d) {
550: String[] rows = ConfigCommand
551: .formatMatrixRows((Matrix4d) elements.get(i));
552: System.out.println("\n ((" + rows[0] + ")");
553: System.out.println(" (" + rows[1] + ")");
554: System.out.println(" (" + rows[2] + ")");
555: System.out.print(" (" + rows[3] + "))");
556: if (i != (argc - 1))
557: System.out.println();
558: } else if (elements.get(i) instanceof ConfigSexpression) {
559: if (i > 0)
560: System.out.print(" ");
561: ((ConfigSexpression) elements.get(i)).print();
562: if (i != (argc - 1))
563: System.out.println();
564: } else if (elements.get(i) instanceof ConfigCommand) {
565: if (i > 0)
566: System.out.print(" ");
567: System.out.print(elements.get(i).toString());
568: if (i != (argc - 1))
569: System.out.println();
570: } else {
571: if (i > 0)
572: System.out.print(" ");
573: System.out.print(elements.get(i).toString());
574: }
575: }
576: System.out.print(")");
577: }
578: }
|